Skip to content

Commit 094f2de

Browse files
feat: Require campaign before Shopify connection
The user needs to create a campaign before connecting a Shopify store. The Shopify app should then prompt the user to select which campaign to connect to.
1 parent b850cb4 commit 094f2de

5 files changed

Lines changed: 99 additions & 146 deletions

File tree

src/components/ShopifyIntegrationDialog.tsx

Lines changed: 86 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import {
99
import { Button } from '@/components/ui/button';
1010
import { Input } from '@/components/ui/input';
1111
import { Label } from '@/components/ui/label';
12+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
1213
import { ShoppingBag, ArrowRight } from 'lucide-react';
1314
import { ShopifyPrivateAppDialog } from '@/components/ShopifyPrivateAppDialog';
14-
import { supabase } from '@/integrations/supabase/client';
15+
import { useAuth } from '@/contexts/AuthContext';
16+
import { useCampaignData } from '@/hooks/useCampaignData';
1517
import { toast } from '@/hooks/use-toast';
1618

1719
interface ShopifyIntegrationDialogProps {
@@ -28,65 +30,36 @@ export const ShopifyIntegrationDialog: React.FC<ShopifyIntegrationDialogProps> =
2830
isLoading
2931
}) => {
3032
const [shopName, setShopName] = useState('');
33+
const [selectedCampaignId, setSelectedCampaignId] = useState('');
3134
const [showPrivateAppDialog, setShowPrivateAppDialog] = useState(false);
35+
36+
// Récupération des campagnes de l'utilisateur
37+
const { user, loading: authLoading } = useAuth();
38+
const { campaigns, loading: campaignsLoading } = useCampaignData(user?.uid || null, authLoading);
3239

3340
const handleSubmit = (e: React.FormEvent) => {
3441
e.preventDefault();
35-
if (shopName.trim()) {
42+
if (shopName.trim() && selectedCampaignId) {
3643
setShowPrivateAppDialog(true);
44+
} else if (!selectedCampaignId) {
45+
toast({
46+
title: "Campagne requise",
47+
description: "Veuillez sélectionner une campagne avant de continuer",
48+
variant: "destructive"
49+
});
3750
}
3851
};
3952

4053
const handleClose = () => {
4154
if (!isLoading) {
4255
setShopName('');
56+
setSelectedCampaignId('');
4357
onOpenChange(false);
4458
}
4559
};
4660

47-
const handleConnect = async (accessToken: string, shopDomain: string) => {
48-
// Sauvegarder l'intégration via l'edge function
49-
try {
50-
const { data: { user } } = await supabase.auth.getUser();
51-
if (!user) {
52-
toast({
53-
title: "Erreur",
54-
description: "Vous devez être connecté",
55-
variant: "destructive"
56-
});
57-
return;
58-
}
59-
60-
const response = await supabase.functions.invoke('shopify-private-app', {
61-
body: {
62-
shopDomain,
63-
accessToken,
64-
campaignId: 'test-campaign-123', // TODO: récupérer le vrai campaign ID
65-
userId: user.id
66-
}
67-
});
68-
69-
if (response.error) {
70-
throw new Error(response.error.message);
71-
}
72-
73-
toast({
74-
title: "Shopify connecté !",
75-
description: `Boutique ${response.data.shopInfo.name} connectée avec succès`,
76-
});
77-
78-
setShowPrivateAppDialog(false);
79-
handleClose();
80-
81-
} catch (error) {
82-
console.error('Erreur connexion Shopify:', error);
83-
toast({
84-
title: "Erreur",
85-
description: error instanceof Error ? error.message : "Erreur lors de la connexion",
86-
variant: "destructive"
87-
});
88-
}
89-
};
61+
// Vérification si aucune campagne disponible
62+
const noCampaignsAvailable = !campaignsLoading && campaigns.length === 0;
9063

9164
return (
9265
<>
@@ -106,22 +79,54 @@ export const ShopifyIntegrationDialog: React.FC<ShopifyIntegrationDialogProps> =
10679
</div>
10780
</DialogHeader>
10881

109-
<form onSubmit={handleSubmit} className="space-y-4">
110-
<div className="space-y-2">
111-
<Label htmlFor="shopName">Nom de votre boutique Shopify</Label>
112-
<Input
113-
id="shopName"
114-
type="text"
115-
placeholder="ma-boutique"
116-
value={shopName}
117-
onChange={(e) => setShopName(e.target.value)}
118-
disabled={isLoading}
119-
className="w-full"
120-
/>
121-
<p className="text-xs text-muted-foreground">
122-
Entrez uniquement le nom de votre boutique (sans .myshopify.com)
123-
</p>
82+
{noCampaignsAvailable ? (
83+
<div className="text-center py-6 space-y-4">
84+
<div className="p-3 bg-orange-100 rounded-lg">
85+
<h4 className="font-medium text-orange-900 mb-2">Aucune campagne disponible</h4>
86+
<p className="text-sm text-orange-800">
87+
Vous devez créer au moins une campagne avec un moyen de paiement configuré avant de pouvoir connecter Shopify.
88+
</p>
89+
</div>
90+
<Button variant="outline" onClick={handleClose}>
91+
Fermer
92+
</Button>
12493
</div>
94+
) : (
95+
<form onSubmit={handleSubmit} className="space-y-4">
96+
<div className="space-y-2">
97+
<Label htmlFor="campaign">Campagne à connecter</Label>
98+
<Select value={selectedCampaignId} onValueChange={setSelectedCampaignId}>
99+
<SelectTrigger>
100+
<SelectValue placeholder="Sélectionnez une campagne" />
101+
</SelectTrigger>
102+
<SelectContent>
103+
{campaigns.map((campaign) => (
104+
<SelectItem key={campaign.id} value={campaign.id}>
105+
{campaign.name}
106+
</SelectItem>
107+
))}
108+
</SelectContent>
109+
</Select>
110+
<p className="text-xs text-muted-foreground">
111+
Cette boutique Shopify sera liée à la campagne sélectionnée
112+
</p>
113+
</div>
114+
115+
<div className="space-y-2">
116+
<Label htmlFor="shopName">Nom de votre boutique Shopify</Label>
117+
<Input
118+
id="shopName"
119+
type="text"
120+
placeholder="ma-boutique"
121+
value={shopName}
122+
onChange={(e) => setShopName(e.target.value)}
123+
disabled={isLoading || campaignsLoading}
124+
className="w-full"
125+
/>
126+
<p className="text-xs text-muted-foreground">
127+
Entrez uniquement le nom de votre boutique (sans .myshopify.com)
128+
</p>
129+
</div>
125130

126131
<div className="bg-green-50 p-4 rounded-lg space-y-2">
127132
<h4 className="font-medium text-green-900">Nouvelle méthode simplifiée !</h4>
@@ -133,32 +138,34 @@ export const ShopifyIntegrationDialog: React.FC<ShopifyIntegrationDialogProps> =
133138
</ul>
134139
</div>
135140

136-
<div className="flex justify-between">
137-
<Button
138-
type="button"
139-
variant="outline"
140-
onClick={handleClose}
141-
disabled={isLoading}
142-
>
143-
Annuler
144-
</Button>
145-
<Button
146-
type="submit"
147-
disabled={!shopName.trim() || isLoading}
148-
className="flex items-center space-x-2"
149-
>
150-
<ArrowRight className="h-4 w-4" />
151-
<span>Continuer</span>
152-
</Button>
153-
</div>
154-
</form>
141+
<div className="flex justify-between">
142+
<Button
143+
type="button"
144+
variant="outline"
145+
onClick={handleClose}
146+
disabled={isLoading || campaignsLoading}
147+
>
148+
Annuler
149+
</Button>
150+
<Button
151+
type="submit"
152+
disabled={!shopName.trim() || !selectedCampaignId || isLoading || campaignsLoading}
153+
className="flex items-center space-x-2"
154+
>
155+
<ArrowRight className="h-4 w-4" />
156+
<span>Continuer</span>
157+
</Button>
158+
</div>
159+
</form>
160+
)}
155161
</DialogContent>
156162
</Dialog>
157163

158164
<ShopifyPrivateAppDialog
159165
open={showPrivateAppDialog}
160166
onOpenChange={setShowPrivateAppDialog}
161167
shopName={shopName}
168+
campaignId={selectedCampaignId}
162169
onSuccess={() => {
163170
toast({
164171
title: "Configuration terminée !",

src/components/ShopifyPrivateAppDialog.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ interface ShopifyPrivateAppDialogProps {
1212
open: boolean;
1313
onOpenChange: (open: boolean) => void;
1414
shopName: string;
15-
onSuccess: () => void; // Changé de onConnect vers onSuccess
15+
campaignId: string;
16+
onSuccess: () => void;
1617
}
1718

1819
export const ShopifyPrivateAppDialog = ({
1920
open,
2021
onOpenChange,
2122
shopName,
23+
campaignId,
2224
onSuccess
2325
}: ShopifyPrivateAppDialogProps) => {
2426
const [accessToken, setAccessToken] = useState('');
@@ -69,7 +71,7 @@ export const ShopifyPrivateAppDialog = ({
6971
body: {
7072
shopDomain,
7173
accessToken: accessToken.trim(),
72-
campaignId: 'test-campaign-123', // TODO: récupérer le vrai campaign ID
74+
campaignId,
7375
userId: user.id
7476
}
7577
});

src/components/ShopifyTestComponent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const ShopifyTestComponent: React.FC<ShopifyTestComponentProps> = ({ camp
1818
fetchIntegrations,
1919
initiateShopifyInstall,
2020
removeIntegration
21-
} = useShopifySupabase(campaignId);
21+
} = useShopifySupabase();
2222

2323
const [showDialog, setShowDialog] = useState(false);
2424

src/hooks/useShopifySupabase.ts

Lines changed: 6 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,19 @@ export interface ShopifyIntegration {
2323
created_at: string;
2424
}
2525

26-
export const useShopifySupabase = (campaignId: string) => {
26+
export const useShopifySupabase = () => {
2727
const [integrations, setIntegrations] = useState<ShopifyIntegration[]>([]);
2828
const [isLoading, setIsLoading] = useState(false);
2929
const { user } = useAuth();
3030

31-
// Récupérer les intégrations existantes
31+
// Récupérer les intégrations existantes pour un utilisateur
3232
const fetchIntegrations = useCallback(async () => {
3333
try {
3434
if (!user) throw new Error('Utilisateur non connecté');
3535

3636
const { data, error } = await supabase
3737
.from('shopify_integrations')
3838
.select('*')
39-
.eq('campaign_id', campaignId)
4039
.eq('user_id', user.uid)
4140
.eq('active', true)
4241
.order('created_at', { ascending: false });
@@ -62,66 +61,12 @@ export const useShopifySupabase = (campaignId: string) => {
6261
variant: "destructive"
6362
});
6463
}
65-
}, [campaignId, user]);
64+
}, [user]);
6665

67-
// Initier l'installation Shopify
66+
// Initier l'installation Shopify (supprimé car maintenant géré par ShopifyPrivateAppDialog)
6867
const initiateShopifyInstall = useCallback(async (shopName: string) => {
69-
if (!shopName.trim()) {
70-
toast({
71-
title: "Erreur",
72-
description: "Veuillez entrer le nom de votre boutique Shopify",
73-
variant: "destructive"
74-
});
75-
return;
76-
}
77-
78-
setIsLoading(true);
79-
80-
try {
81-
if (!user) throw new Error('Utilisateur non connecté');
82-
83-
// Appeler l'Edge Function pour générer l'URL d'autorisation
84-
const { data, error } = await supabase.functions.invoke('shopify-auth', {
85-
body: {
86-
shop: shopName.includes('.myshopify.com') ? shopName : `${shopName}.myshopify.com`,
87-
campaignId,
88-
userId: user.uid
89-
}
90-
});
91-
92-
if (error) throw error;
93-
94-
if (!data?.authUrl) {
95-
throw new Error('URL d\'autorisation non reçue');
96-
}
97-
98-
// Rediriger vers Shopify dans une nouvelle fenêtre
99-
const popup = window.open(data.authUrl, 'shopify-auth', 'width=600,height=700,scrollbars=yes,resizable=yes');
100-
101-
if (!popup) {
102-
throw new Error('Le popup a été bloqué. Veuillez autoriser les popups pour ce site.');
103-
}
104-
105-
// Surveiller la fermeture du popup
106-
const checkClosed = setInterval(() => {
107-
if (popup.closed) {
108-
clearInterval(checkClosed);
109-
setIsLoading(false);
110-
// Rafraîchir les intégrations au cas où l'autorisation aurait réussi
111-
fetchIntegrations();
112-
}
113-
}, 1000);
114-
115-
} catch (error) {
116-
console.error('Erreur initiation OAuth:', error);
117-
toast({
118-
title: "Erreur",
119-
description: error.message || "Impossible d'initier l'installation Shopify",
120-
variant: "destructive"
121-
});
122-
setIsLoading(false);
123-
}
124-
}, [campaignId]);
68+
console.log('Cette méthode est maintenant gérée par ShopifyPrivateAppDialog');
69+
}, []);
12570

12671
// Finaliser l'installation après le callback
12772
const finalizeShopifyInstall = useCallback(async (code: string, state: string, shop: string) => {

src/pages/ShopifyCallbackSupabasePage.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ export const ShopifyCallbackSupabasePage: React.FC = () => {
1313
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
1414
const [message, setMessage] = useState('');
1515

16-
// Récupérer campaignId depuis sessionStorage ou URL
17-
const campaignId = sessionStorage.getItem('shopify_campaign_id') || 'default';
18-
const { finalizeShopifyInstall } = useShopifySupabase(campaignId);
16+
// Récupérer campaignId depuis sessionStorage ou URL (plus nécessaire avec Private Apps)
17+
const { finalizeShopifyInstall } = useShopifySupabase();
1918

2019
useEffect(() => {
2120
const handleCallback = async () => {

0 commit comments

Comments
 (0)