@mostajs/licensing-ui-react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,270 @@
1
+ "use client";
2
+ import { useState } from 'react';
3
+ import { Shield, CheckCircle, Clock, AlertTriangle, Loader2, Key, Server, Calendar, Users, Zap, XCircle } from 'lucide-react';
4
+
5
+ interface PendingLicenseActivationProps {
6
+ licenseInfo: any;
7
+ verificationInfo?: any;
8
+ activationCode: string;
9
+ licenseData: any;
10
+ onActivate?: (result: any) => void;
11
+ onCancel?: () => void;
12
+ }
13
+
14
+ export default function PendingLicenseActivation({
15
+ licenseInfo,
16
+ verificationInfo,
17
+ activationCode,
18
+ licenseData,
19
+ onActivate,
20
+ onCancel
21
+ }: PendingLicenseActivationProps) {
22
+ const [isActivating, setIsActivating] = useState(false);
23
+ const [error, setError] = useState<string | null>(null);
24
+ const [activationResult, setActivationResult] = useState<any>(null);
25
+
26
+ const handleStartActivation = async () => {
27
+ setIsActivating(true);
28
+ setError(null);
29
+
30
+ try {
31
+ // Appeler l'API pour démarrer l'activation et le comptage
32
+ const response = await fetch('/api/license/start-activation', {
33
+ method: 'POST',
34
+ headers: {
35
+ 'Content-Type': 'application/json',
36
+ },
37
+ body: JSON.stringify({
38
+ activationCode,
39
+ licenseData
40
+ }),
41
+ });
42
+
43
+ if (!response.ok) {
44
+ const errorData = await response.json();
45
+ throw new Error(errorData.error || 'Échec de l\'activation');
46
+ }
47
+
48
+ const result = await response.json();
49
+ setActivationResult(result);
50
+
51
+ // Mettre à jour le localStorage
52
+ localStorage.setItem('license_cache', JSON.stringify({
53
+ licenseInfo: {
54
+ valid: true,
55
+ license: {
56
+ ...licenseInfo,
57
+ status: 'ACTIVE',
58
+ activated_at: result.activationInfo?.started_at,
59
+ expires_at: result.activationInfo?.expires_at
60
+ }
61
+ },
62
+ timestamp: new Date().toISOString(),
63
+ valid: true
64
+ }));
65
+
66
+ // Supprimer la licence en attente
67
+ localStorage.removeItem('pending_license');
68
+
69
+ // Notifier le parent après un court délai
70
+ setTimeout(() => {
71
+ onActivate?.(result);
72
+ }, 1500);
73
+
74
+ } catch (err: any) {
75
+ console.error('Erreur activation:', err);
76
+ setError(err.message || 'Erreur lors de l\'activation');
77
+ setIsActivating(false);
78
+ }
79
+ };
80
+
81
+ // Calculer les jours restants
82
+ const calculateRemainingDays = () => {
83
+ if (verificationInfo?.code_info?.expires_at) {
84
+ const expires = new Date(verificationInfo.code_info.expires_at);
85
+ const now = new Date();
86
+ const diff = Math.floor((expires.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
87
+ return diff;
88
+ }
89
+ return 365; // Par défaut
90
+ };
91
+
92
+ const remainingDays = calculateRemainingDays();
93
+ const activationsRemaining = verificationInfo?.code_info?.activations_remaining ||
94
+ (verificationInfo?.code_info?.max_activations - verificationInfo?.code_info?.activations_used) ||
95
+ 4;
96
+
97
+ if (activationResult) {
98
+ // Afficher le succès de l'activation
99
+ return (
100
+ <div className="max-w-2xl mx-auto p-8 bg-white rounded-xl shadow-lg">
101
+ <div className="text-center">
102
+ <CheckCircle className="w-20 h-20 text-green-500 mx-auto mb-6" />
103
+ <h2 className="text-3xl font-bold text-gray-800 mb-4">
104
+ Licence Activée avec Succès !
105
+ </h2>
106
+ <p className="text-lg text-gray-600 mb-6">
107
+ Le comptage de votre licence a commencé.
108
+ </p>
109
+
110
+ <div className="bg-green-50 border border-green-200 rounded-lg p-6 text-left">
111
+ <h3 className="font-semibold text-green-800 mb-3">Détails de l'activation :</h3>
112
+ <div className="space-y-2 text-sm text-green-700">
113
+ <p><strong>Date d'activation :</strong> {new Date().toLocaleDateString()}</p>
114
+ <p><strong>Expire le :</strong> {new Date(activationResult.activationInfo?.expires_at).toLocaleDateString()}</p>
115
+ <p><strong>Jours restants :</strong> {activationResult.activationInfo?.remaining_days} jours</p>
116
+ <p><strong>Activations utilisées :</strong> {activationResult.activationInfo?.activations_used}/{activationResult.activationInfo?.max_activations}</p>
117
+ </div>
118
+ </div>
119
+
120
+ <p className="text-sm text-gray-500 mt-6">
121
+ L'application va redémarrer dans quelques instants...
122
+ </p>
123
+ </div>
124
+ </div>
125
+ );
126
+ }
127
+
128
+ return (
129
+ <div className="max-w-2xl mx-auto p-8 bg-white rounded-xl shadow-lg">
130
+ {/* En-tête */}
131
+ <div className="text-center mb-8">
132
+ <div className="relative inline-block">
133
+ <Shield className="w-20 h-20 text-blue-500 mx-auto" />
134
+ <Clock className="w-8 h-8 text-orange-500 absolute -bottom-1 -right-1 bg-white rounded-full p-1" />
135
+ </div>
136
+ <h2 className="text-3xl font-bold text-gray-800 mt-4 mb-2">
137
+ Licence Prête pour Activation
138
+ </h2>
139
+ <p className="text-gray-600">
140
+ Votre licence a été téléchargée et vérifiée avec succès
141
+ </p>
142
+ </div>
143
+
144
+ {/* Informations de la licence */}
145
+ <div className="bg-blue-50 border border-blue-200 rounded-lg p-6 mb-6">
146
+ <h3 className="font-semibold text-blue-800 mb-4 flex items-center">
147
+ <Key className="w-5 h-5 mr-2" />
148
+ Détails de la licence
149
+ </h3>
150
+
151
+ <div className="grid grid-cols-2 gap-4 text-sm">
152
+ <div>
153
+ <p className="text-blue-600 font-medium">Code d'activation</p>
154
+ <p className="text-blue-800 font-mono">{activationCode}</p>
155
+ </div>
156
+
157
+ <div>
158
+ <p className="text-blue-600 font-medium">Projet</p>
159
+ <p className="text-blue-800">{licenseInfo?.project || 'app'}</p>
160
+ </div>
161
+
162
+ <div>
163
+ <p className="text-blue-600 font-medium">Email</p>
164
+ <p className="text-blue-800">{licenseInfo?.email}</p>
165
+ </div>
166
+
167
+ <div>
168
+ <p className="text-blue-600 font-medium">Durée de validité</p>
169
+ <p className="text-blue-800">{remainingDays} jours</p>
170
+ </div>
171
+ </div>
172
+ </div>
173
+
174
+ {/* Statut des activations */}
175
+ <div className="bg-orange-50 border border-orange-200 rounded-lg p-6 mb-6">
176
+ <h3 className="font-semibold text-orange-800 mb-3 flex items-center">
177
+ <Users className="w-5 h-5 mr-2" />
178
+ Statut des activations
179
+ </h3>
180
+
181
+ <div className="flex items-center justify-between">
182
+ <div className="text-sm text-orange-700">
183
+ <p className="mb-1">Activations disponibles :</p>
184
+ <p className="text-2xl font-bold text-orange-800">
185
+ {activationsRemaining} / {verificationInfo?.code_info?.max_activations || 4}
186
+ </p>
187
+ </div>
188
+
189
+ {activationsRemaining <= 1 && (
190
+ <div className="flex items-center text-orange-600">
191
+ <AlertTriangle className="w-5 h-5 mr-2" />
192
+ <span className="text-sm">Dernière activation disponible</span>
193
+ </div>
194
+ )}
195
+ </div>
196
+ </div>
197
+
198
+ {/* Avertissement important */}
199
+ <div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
200
+ <div className="flex items-start">
201
+ <AlertTriangle className="w-5 h-5 text-yellow-600 mr-3 mt-0.5 flex-shrink-0" />
202
+ <div className="text-sm">
203
+ <p className="text-yellow-800 font-medium mb-1">Information importante</p>
204
+ <p className="text-yellow-700">
205
+ En cliquant sur "Activer maintenant", le comptage de votre licence démarrera immédiatement
206
+ et sera comptabilisé sur le serveur de licences. Cette action est irréversible.
207
+ </p>
208
+ </div>
209
+ </div>
210
+ </div>
211
+
212
+ {/* Message d'erreur */}
213
+ {error && (
214
+ <div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
215
+ <div className="flex items-center">
216
+ <XCircle className="w-5 h-5 text-red-500 mr-2 flex-shrink-0" />
217
+ <p className="text-red-700 text-sm">{error}</p>
218
+ </div>
219
+ </div>
220
+ )}
221
+
222
+ {/* Boutons d'action */}
223
+ <div className="flex space-x-4">
224
+ <button
225
+ onClick={handleStartActivation}
226
+ disabled={isActivating}
227
+ className="flex-1 px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 font-medium disabled:opacity-50 disabled:cursor-not-allowed transition-all transform hover:scale-105 flex items-center justify-center"
228
+ >
229
+ {isActivating ? (
230
+ <>
231
+ <Loader2 className="w-5 h-5 animate-spin mr-2" />
232
+ Activation en cours...
233
+ </>
234
+ ) : (
235
+ <>
236
+ <Zap className="w-5 h-5 mr-2" />
237
+ Activer Maintenant
238
+ </>
239
+ )}
240
+ </button>
241
+
242
+ {onCancel && (
243
+ <button
244
+ onClick={onCancel}
245
+ disabled={isActivating}
246
+ className="px-6 py-3 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 transition-colors"
247
+ >
248
+ Annuler
249
+ </button>
250
+ )}
251
+ </div>
252
+
253
+ {/* Footer informatif */}
254
+ <div className="mt-8 pt-6 border-t border-gray-200">
255
+ <div className="flex items-center justify-center text-xs text-gray-500">
256
+ <Server className="w-4 h-4 mr-2" />
257
+ <span>
258
+ Connexion au serveur de licences :
259
+ <span className="text-green-600 font-medium ml-1">Active</span>
260
+ </span>
261
+ <span className="mx-2">•</span>
262
+ <Calendar className="w-4 h-4 mr-2" />
263
+ <span>
264
+ Date : {new Date().toLocaleDateString()}
265
+ </span>
266
+ </div>
267
+ </div>
268
+ </div>
269
+ );
270
+ }
@@ -0,0 +1,273 @@
1
+ "use client";
2
+ import { useState } from 'react';
3
+ import { Shield, CheckCircle, XCircle, AlertTriangle, Loader2, Key, Download } from 'lucide-react';
4
+ import { useLicenseUi } from './LicenseProvider';
5
+
6
+ interface SimpleLicenseActivationProps {
7
+ onActivationSuccess?: (licenseInfo: any) => void;
8
+ onBack?: () => void;
9
+ onSwitchToManual?: () => void;
10
+ }
11
+
12
+ export default function SimpleLicenseActivation({
13
+ onActivationSuccess,
14
+ onBack,
15
+ onSwitchToManual
16
+ }: SimpleLicenseActivationProps) {
17
+ const { serverUrl } = useLicenseUi(); // config .env
18
+ const [activationCode, setActivationCode] = useState('');
19
+ const [loading, setLoading] = useState(false);
20
+ const [error, setError] = useState<string | null>(null);
21
+ const [success, setSuccess] = useState<string | null>(null);
22
+ const [progress, setProgress] = useState<string | null>(null);
23
+
24
+ const validateActivationCode = (code: string): boolean => {
25
+ // Format attendu: XXXX-XXXX-XXXX-XXXX (16 caractères + 3 tirets)
26
+ const codeRegex = /^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/;
27
+ return codeRegex.test(code.toUpperCase());
28
+ };
29
+
30
+ const handleActivation = async () => {
31
+ if (!activationCode.trim()) {
32
+ setError('Veuillez entrer votre code d\'activation');
33
+ return;
34
+ }
35
+
36
+ if (!validateActivationCode(activationCode)) {
37
+ setError('Format de code d\'activation invalide (attendu: XXXX-XXXX-XXXX-XXXX)');
38
+ return;
39
+ }
40
+
41
+ setLoading(true);
42
+ setError(null);
43
+ setSuccess(null);
44
+ setProgress('Connexion au serveur de licences...');
45
+
46
+ try {
47
+ // Étape 1: Télécharger la licence depuis le serveur
48
+ setProgress('Téléchargement de la licence et de la clé publique...');
49
+
50
+ const downloadResponse = await fetch('/api/license/download-by-code', {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/json',
54
+ },
55
+ body: JSON.stringify({
56
+ activationCode: activationCode.toUpperCase(),
57
+ downloadPublicKey: true // Télécharger automatiquement le public.pem
58
+ }),
59
+ });
60
+
61
+ if (!downloadResponse.ok) {
62
+ const errorData = await downloadResponse.json();
63
+ throw new Error(errorData.error || 'Impossible de télécharger la licence');
64
+ }
65
+
66
+ const { licenseData, licenseInfo, publicKeyDownloaded, verificationInfo } = await downloadResponse.json();
67
+
68
+ setProgress('Vérification de la licence...');
69
+
70
+ // La licence est maintenant en état PENDING
71
+ // Sauvegarder les données pour l'activation ultérieure
72
+ const pendingLicense = {
73
+ licenseData,
74
+ licenseInfo: {
75
+ ...licenseInfo,
76
+ status: 'PENDING'
77
+ },
78
+ activationCode: activationCode.toUpperCase(),
79
+ verificationInfo,
80
+ publicKeyDownloaded,
81
+ downloadedAt: new Date().toISOString()
82
+ };
83
+
84
+ // Sauvegarder dans le localStorage comme licence en attente
85
+ localStorage.setItem('pending_license', JSON.stringify(pendingLicense));
86
+
87
+ setSuccess('Licence téléchargée et vérifiée avec succès !');
88
+ setProgress(null);
89
+
90
+ // Informer le parent que la licence est prête pour activation
91
+ setTimeout(() => {
92
+ onActivationSuccess?.({
93
+ ...licenseInfo,
94
+ status: 'PENDING',
95
+ pendingData: pendingLicense
96
+ });
97
+ }, 1500);
98
+
99
+ } catch (err: any) {
100
+ console.error('Erreur d\'activation:', err);
101
+ setProgress(null);
102
+
103
+ if (err.message.includes('fetch')) {
104
+ setError('Impossible de se connecter au serveur. Vérifiez votre connexion internet.');
105
+ } else if (err.message.includes('404')) {
106
+ setError('Code d\'activation non trouvé. Vérifiez votre code.');
107
+ } else if (err.message.includes('expired')) {
108
+ setError('Ce code d\'activation a expiré.');
109
+ } else if (err.message.includes('used')) {
110
+ setError('Ce code a déjà été utilisé le nombre maximum de fois.');
111
+ } else {
112
+ setError(err.message || 'Erreur lors de l\'activation de la licence');
113
+ }
114
+ } finally {
115
+ setLoading(false);
116
+ }
117
+ };
118
+
119
+ const clearForm = () => {
120
+ setActivationCode('');
121
+ setError(null);
122
+ setSuccess(null);
123
+ setProgress(null);
124
+ };
125
+
126
+ return (
127
+ <div className="max-w-md mx-auto p-8 bg-white rounded-xl shadow-lg">
128
+ <div className="text-center mb-8">
129
+ <Shield className="w-16 h-16 text-blue-500 mx-auto mb-4" />
130
+ <h2 className="text-2xl font-bold text-gray-800 mb-2">Activation Rapide</h2>
131
+ <p className="text-gray-600">
132
+ Entrez votre code d'activation pour télécharger et activer automatiquement votre licence
133
+ </p>
134
+ </div>
135
+
136
+ {/* Messages d'état */}
137
+ {error && (
138
+ <div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
139
+ <div className="flex items-center">
140
+ <XCircle className="w-5 h-5 text-red-500 mr-2 flex-shrink-0" />
141
+ <p className="text-red-700 text-sm">{error}</p>
142
+ </div>
143
+ </div>
144
+ )}
145
+
146
+ {success && (
147
+ <div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-6">
148
+ <div className="flex items-center">
149
+ <CheckCircle className="w-5 h-5 text-green-500 mr-2 flex-shrink-0" />
150
+ <p className="text-green-700 text-sm">{success}</p>
151
+ </div>
152
+ </div>
153
+ )}
154
+
155
+ {progress && (
156
+ <div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
157
+ <div className="flex items-center">
158
+ <Loader2 className="w-5 h-5 text-blue-500 mr-2 animate-spin flex-shrink-0" />
159
+ <p className="text-blue-700 text-sm">{progress}</p>
160
+ </div>
161
+ </div>
162
+ )}
163
+
164
+ {/* Code d'activation */}
165
+ <div className="mb-6">
166
+ <label className="block text-sm font-medium text-gray-700 mb-3">
167
+ <Key className="w-4 h-4 inline mr-2" />
168
+ Code d'activation
169
+ </label>
170
+ <input
171
+ type="text"
172
+ value={activationCode}
173
+ onChange={(e) => {
174
+ // Auto-format with dashes
175
+ let value = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
176
+ if (value.length > 4) value = value.slice(0, 4) + '-' + value.slice(4);
177
+ if (value.length > 9) value = value.slice(0, 9) + '-' + value.slice(9);
178
+ if (value.length > 14) value = value.slice(0, 14) + '-' + value.slice(14);
179
+ if (value.length > 19) value = value.slice(0, 19);
180
+ setActivationCode(value);
181
+ }}
182
+ placeholder="XXXX-XXXX-XXXX-XXXX"
183
+ maxLength={19}
184
+ className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent font-mono text-center tracking-widest text-lg"
185
+ disabled={loading}
186
+ />
187
+ <p className="text-xs text-gray-500 mt-2">
188
+ Entrez le code d'activation fourni lors de l'achat
189
+ </p>
190
+ </div>
191
+
192
+ {/* Info box */}
193
+ <div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
194
+ <div className="flex items-start">
195
+ <Download className="w-5 h-5 text-blue-500 mr-2 mt-0.5 flex-shrink-0" />
196
+ <div>
197
+ <h4 className="text-sm font-medium text-blue-800 mb-1">Activation automatique</h4>
198
+ <p className="text-xs text-blue-700">
199
+ La licence sera automatiquement téléchargée depuis nos serveurs et activée sur cette machine.
200
+ </p>
201
+ </div>
202
+ </div>
203
+ </div>
204
+
205
+ {/* Boutons d'action */}
206
+ <div className="space-y-3">
207
+ <button
208
+ onClick={handleActivation}
209
+ disabled={!activationCode.trim() || loading}
210
+ className="w-full px-4 py-3 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 font-medium disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center justify-center"
211
+ >
212
+ {loading ? (
213
+ <>
214
+ <Loader2 className="w-5 h-5 animate-spin mr-2" />
215
+ Activation en cours...
216
+ </>
217
+ ) : (
218
+ <>
219
+ <Download className="w-5 h-5 mr-2" />
220
+ Télécharger et Activer
221
+ </>
222
+ )}
223
+ </button>
224
+
225
+ <div className="flex space-x-3">
226
+ <button
227
+ onClick={clearForm}
228
+ disabled={loading}
229
+ className="flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 transition-colors"
230
+ >
231
+ Effacer
232
+ </button>
233
+
234
+ {onBack && (
235
+ <button
236
+ onClick={onBack}
237
+ disabled={loading}
238
+ className="flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 transition-colors"
239
+ >
240
+ Retour
241
+ </button>
242
+ )}
243
+ </div>
244
+
245
+ {/* Lien vers activation manuelle */}
246
+ {onSwitchToManual && (
247
+ <button
248
+ onClick={onSwitchToManual}
249
+ disabled={loading}
250
+ className="w-full text-sm text-blue-500 hover:text-blue-700 font-medium transition-colors"
251
+ >
252
+ Activation manuelle avec fichier de licence →
253
+ </button>
254
+ )}
255
+ </div>
256
+
257
+ {/* Footer */}
258
+ <div className="mt-6 pt-6 border-t border-gray-200 text-center">
259
+ <p className="text-xs text-gray-500">
260
+ Vous n'avez pas de code d'activation ?
261
+ </p>
262
+ <a
263
+ href={serverUrl}
264
+ target="_blank"
265
+ rel="noopener noreferrer"
266
+ className="text-sm text-blue-500 hover:text-blue-700 font-medium"
267
+ >
268
+ Obtenir une licence →
269
+ </a>
270
+ </div>
271
+ </div>
272
+ );
273
+ }