ptechcore_ui 0.0.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.
Files changed (57) hide show
  1. package/eslint.config.js +28 -0
  2. package/index.html +78 -0
  3. package/package.json +42 -0
  4. package/postcss.config.js +6 -0
  5. package/src/App.tsx +156 -0
  6. package/src/assets/imgs/login_illustration.png +0 -0
  7. package/src/components/common/Buttons.tsx +39 -0
  8. package/src/components/common/Cards.tsx +18 -0
  9. package/src/components/common/FDrawer.tsx +2448 -0
  10. package/src/components/common/FDrawer.types.ts +191 -0
  11. package/src/components/common/Inputs.tsx +409 -0
  12. package/src/components/common/Modals.tsx +41 -0
  13. package/src/components/common/Navigations.tsx +0 -0
  14. package/src/components/common/Toast.tsx +0 -0
  15. package/src/components/demo/ToastDemo.tsx +73 -0
  16. package/src/components/layout/Header.tsx +202 -0
  17. package/src/components/layout/ModernDoubleSidebarLayout.tsx +727 -0
  18. package/src/components/layout/PrivateLayout.tsx +52 -0
  19. package/src/components/layout/Sidebar.tsx +182 -0
  20. package/src/components/ui/Toast.tsx +93 -0
  21. package/src/contexts/SessionContext.tsx +77 -0
  22. package/src/contexts/ThemeContext.tsx +58 -0
  23. package/src/contexts/ToastContext.tsx +94 -0
  24. package/src/index.css +3 -0
  25. package/src/main.tsx +10 -0
  26. package/src/models/Organization.ts +47 -0
  27. package/src/models/Plan.ts +42 -0
  28. package/src/models/User.ts +23 -0
  29. package/src/pages/Analytics.tsx +101 -0
  30. package/src/pages/CreateOrganization.tsx +215 -0
  31. package/src/pages/Dashboard.tsx +15 -0
  32. package/src/pages/Home.tsx +12 -0
  33. package/src/pages/Profile.tsx +313 -0
  34. package/src/pages/Settings.tsx +382 -0
  35. package/src/pages/Team.tsx +180 -0
  36. package/src/pages/auth/Login.tsx +140 -0
  37. package/src/pages/auth/Register.tsx +302 -0
  38. package/src/pages/organizations/DetailEntity.tsx +1002 -0
  39. package/src/pages/organizations/DetailOrganizations.tsx +1629 -0
  40. package/src/pages/organizations/ListOrganizations.tsx +270 -0
  41. package/src/pages/pricings/CartPlan.tsx +486 -0
  42. package/src/pages/pricings/ListPricing.tsx +321 -0
  43. package/src/pages/users/CreateUser.tsx +450 -0
  44. package/src/pages/users/ListUsers.tsx +0 -0
  45. package/src/services/AuthServices.ts +94 -0
  46. package/src/services/OrganizationServices.ts +61 -0
  47. package/src/services/PlanSubscriptionServices.tsx +137 -0
  48. package/src/services/UserServices.ts +36 -0
  49. package/src/services/api.ts +64 -0
  50. package/src/styles/theme.ts +383 -0
  51. package/src/utils/utils.ts +48 -0
  52. package/src/vite-env.d.ts +1 -0
  53. package/tailwind.config.js +158 -0
  54. package/tsconfig.app.json +24 -0
  55. package/tsconfig.json +7 -0
  56. package/tsconfig.node.json +22 -0
  57. package/vite.config.ts +10 -0
@@ -0,0 +1,486 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useNavigate, useSearchParams } from 'react-router-dom';
3
+ import {
4
+ Check,
5
+ X,
6
+ Plus,
7
+ Minus,
8
+ ArrowLeft,
9
+ CreditCard,
10
+ Shield,
11
+ Clock,
12
+ Users,
13
+ Building2,
14
+ Zap,
15
+ Trash2
16
+ } from 'lucide-react';
17
+ import { useToast } from '../../contexts/ToastContext';
18
+ import { useSession } from '../../contexts/SessionContext';
19
+ import { ModuleServices, SubscriptionServices } from '../../services/PlanSubscriptionServices';
20
+ import { Module, Plan } from '../../models/Plan';
21
+ import { OrganizationServices } from '../../services/OrganizationServices';
22
+ import PrimaryButton, { SecondaryButton } from '../../components/common/Buttons';
23
+
24
+ interface CartItem {
25
+ type: 'plan' | 'module';
26
+ id: number;
27
+ name: string;
28
+ description: string;
29
+ price: number;
30
+ currency: string;
31
+ period: string;
32
+ quantity?: number;
33
+ features?: string[];
34
+ }
35
+
36
+ interface BillingInfo {
37
+ firstName: string;
38
+ lastName: string;
39
+ email: string;
40
+ phone: string;
41
+ company: string;
42
+ address: string;
43
+ city: string;
44
+ country: string;
45
+ billingPeriod: 'monthly' | 'yearly';
46
+ }
47
+
48
+ const CartPlan: React.FC = () => {
49
+ const [searchParams] = useSearchParams();
50
+ const [cartItems, setCartItems] = useState<CartItem[]>([]);
51
+ const [additionalModules, setAdditionalModules] = useState<Module[]>([]);
52
+ const [loading, setLoading] = useState(false);
53
+ const [modulesLoading, setModulesLoading] = useState(true);
54
+ const [processingPayment, setProcessingPayment] = useState(false);
55
+ const [showBillingForm, setShowBillingForm] = useState(false);
56
+ const [billingInfo, setBillingInfo] = useState<BillingInfo>({
57
+ firstName: '',
58
+ lastName: '',
59
+ email: '',
60
+ phone: '',
61
+ company: '',
62
+ address: '',
63
+ city: '',
64
+ country: 'CI',
65
+ billingPeriod: 'monthly'
66
+ });
67
+
68
+ const navigate = useNavigate();
69
+ const { success, error: showError } = useToast();
70
+ const { loggedUser, token } = useSession();
71
+ const organizationId = Number(searchParams.get('org'));
72
+
73
+ useEffect(() => {
74
+ // Récupérer le plan sélectionné depuis les paramètres URL
75
+
76
+ const planId = searchParams.get('plan');
77
+ const planCode = searchParams.get('code');
78
+
79
+ if (planId && planCode) {
80
+ // Simuler la récupération des détails du plan sélectionné
81
+ const selectedPlan = getSelectedPlan(planId, planCode);
82
+ if (selectedPlan) {
83
+ setCartItems([selectedPlan]);
84
+ }
85
+ }
86
+
87
+ loadAdditionalModules();
88
+ }, [searchParams]);
89
+
90
+ const getSelectedPlan = (planId: string, planCode: string): CartItem | null => {
91
+ // Simuler la récupération du plan depuis les données existantes
92
+ const planData = {
93
+ 'pro': {
94
+ id: 1,
95
+ name: 'Rewise Pro',
96
+ description: 'Améliorez la productivité et l\'apprentissage avec un accès supplémentaire.',
97
+ price: 100000,
98
+ currency: 'FCFA',
99
+ period: '/ mois',
100
+ features: ['1 entité', 'jusqu\'à 5 utilisateurs', 'CRM, Comptabilité et Facturation']
101
+ },
102
+ 'max': {
103
+ id: 2,
104
+ name: 'Rewise Max',
105
+ description: 'Débloquez toutes les capacités avec un accès anticipé aux nouveaux produits.',
106
+ price: 200000,
107
+ currency: 'FCFA',
108
+ period: '/ mois',
109
+ features: ['jusqu\'à 3 entités', 'jusqu\'à 15 utilisateurs', 'CRM, Comptabilité et Facturation', '+1 module au choix']
110
+ }
111
+ };
112
+
113
+ const plan = planData[planId as keyof typeof planData];
114
+ return plan ? { type: 'plan', ...plan } : null;
115
+ };
116
+
117
+ const loadAdditionalModules = async () => {
118
+ if (!token) return;
119
+
120
+ try {
121
+ setModulesLoading(true);
122
+ const result = await ModuleServices.getActiveModules(token) as { data: Module[] };
123
+ setAdditionalModules(result.data || []);
124
+ } catch (error) {
125
+ showError('Erreur lors du chargement des modules');
126
+ } finally {
127
+ setModulesLoading(false);
128
+ }
129
+ };
130
+
131
+ const addModuleToCart = (module: Module) => {
132
+ const existingModule = cartItems.find(item => item.id === module.id && item.type === 'module');
133
+
134
+ if (existingModule) {
135
+ // Augmenter la quantité si le module existe déjà
136
+ setCartItems(prev => prev.map(item =>
137
+ item.id === module.id && item.type === 'module'
138
+ ? { ...item, quantity: (item.quantity || 1) + 1 }
139
+ : item
140
+ ));
141
+ } else {
142
+ // Ajouter le nouveau module
143
+ const moduleItem: CartItem = {
144
+ type: 'module',
145
+ id: module.id,
146
+ name: module.name,
147
+ description: module.description || '',
148
+ price: billingInfo.billingPeriod === 'monthly' ? module.price_monthly : module.price_yearly,
149
+ currency: 'FCFA',
150
+ period: billingInfo.billingPeriod === 'monthly' ? '/ mois' : '/ an',
151
+ quantity: 1
152
+ };
153
+ setCartItems(prev => [...prev, moduleItem]);
154
+ }
155
+ };
156
+
157
+ const removeModuleFromCart = (moduleId: number) => {
158
+ setCartItems(prev => prev.filter(item => !(item.id === moduleId && item.type === 'module')));
159
+ };
160
+
161
+ const updateModuleQuantity = (moduleId: number, quantity: number) => {
162
+ if (quantity <= 0) {
163
+ removeModuleFromCart(moduleId);
164
+ return;
165
+ }
166
+
167
+ setCartItems(prev => prev.map(item =>
168
+ item.id === moduleId && item.type === 'module'
169
+ ? { ...item, quantity }
170
+ : item
171
+ ));
172
+ };
173
+
174
+ const calculateTotal = () => {
175
+ return cartItems.reduce((total, item) => {
176
+ const quantity = item.quantity || 1;
177
+ return total + (item.price * quantity);
178
+ }, 0);
179
+ };
180
+
181
+ const calculateTax = (subtotal: number) => {
182
+ return subtotal * 0.18; // TVA 18%
183
+ };
184
+
185
+ const handleBillingInfoChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
186
+ const { name, value } = e.target;
187
+ setBillingInfo(prev => ({ ...prev, [name]: value }));
188
+ };
189
+
190
+ const toggleBillingPeriod = () => {
191
+ const newPeriod = billingInfo.billingPeriod === 'monthly' ? 'yearly' : 'monthly';
192
+ setBillingInfo(prev => ({ ...prev, billingPeriod: newPeriod }));
193
+
194
+ // Mettre à jour les prix des modules dans le panier
195
+ setCartItems(prev => prev.map(item => {
196
+ if (item.type === 'module') {
197
+ const module = additionalModules.find(m => m.id === item.id);
198
+ if (module) {
199
+ return {
200
+ ...item,
201
+ price: newPeriod === 'monthly' ? module.price_monthly : module.price_yearly,
202
+ period: newPeriod === 'monthly' ? '/ mois' : '/ an'
203
+ };
204
+ }
205
+ }
206
+ return item;
207
+ }));
208
+ };
209
+
210
+ const handleProcessPayment = async () => {
211
+ if (!token) {
212
+ showError('Vous devez être connecté pour effectuer un paiement');
213
+ return;
214
+ }
215
+
216
+ setProcessingPayment(true);
217
+ try {
218
+ // Simuler le traitement du paiement
219
+ await new Promise(resolve => setTimeout(resolve, 3000));
220
+ await OrganizationServices.subscribe(organizationId, {
221
+ plan: planItem!.id,
222
+ billing_cycle: billingInfo.billingPeriod,
223
+ extra_modules: moduleItems.map(item => item.id)
224
+ }, token);
225
+
226
+ success('Paiement effectué avec succès ! Votre plan sera activé sous peu.');
227
+ navigate('/organizations');
228
+ } catch (error) {
229
+ showError('Erreur lors du traitement du paiement');
230
+ } finally {
231
+ setProcessingPayment(false);
232
+ }
233
+ };
234
+
235
+ const subtotal = calculateTotal();
236
+ const tax = calculateTax(subtotal);
237
+ const total = subtotal + tax;
238
+
239
+ const planItem = cartItems.find(item => item.type === 'plan');
240
+ const moduleItems = cartItems.filter(item => item.type === 'module');
241
+
242
+ return (
243
+ <div className="min-h-screen bg-gray-50 py-8">
244
+ <div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
245
+ {/* Header */}
246
+ <div className="flex items-center mb-8">
247
+ <button
248
+ onClick={() => navigate('/pricing')}
249
+ className="flex items-center text-gray-600 hover:text-gray-900 transition-colors mr-4"
250
+ >
251
+ <ArrowLeft className="w-5 h-5 mr-2" />
252
+ Retour aux plans
253
+ </button>
254
+ <h1 className="text-3xl font-bold text-gray-900">Votre commande</h1>
255
+ </div>
256
+
257
+ <div className="grid lg:grid-cols-3 gap-8">
258
+ {/* Panier principal */}
259
+ <div className="lg:col-span-2 space-y-6">
260
+ {/* Plan s�lectionn� */}
261
+ {planItem && (
262
+ <div className="bg-white rounded-lg shadow-sm border p-6">
263
+ <h2 className="text-xl font-semibold mb-4 flex items-center">
264
+ <Zap className="w-5 h-5 mr-2 text-blue-600" />
265
+ Plan sélectionné
266
+ </h2>
267
+
268
+ <div className="border rounded-lg p-4">
269
+ <div className="flex justify-between items-start">
270
+ <div className="flex-1">
271
+ <h3 className="font-semibold text-lg">{planItem.name}</h3>
272
+ <p className="text-gray-600 text-sm mt-1">{planItem.description}</p>
273
+ {planItem.features && (
274
+ <ul className="mt-3 space-y-1">
275
+ {planItem.features.map((feature, index) => (
276
+ <li key={index} className="flex items-center text-sm text-gray-700">
277
+ <Check className="w-4 h-4 text-green-500 mr-2 flex-shrink-0" />
278
+ {feature}
279
+ </li>
280
+ ))}
281
+ </ul>
282
+ )}
283
+ </div>
284
+ <div className="text-right ml-4">
285
+ <div className="text-xl font-bold">
286
+ {planItem.price.toLocaleString()} {planItem.currency}
287
+ </div>
288
+ <div className="text-sm text-gray-500">{planItem.period}</div>
289
+ </div>
290
+ </div>
291
+ </div>
292
+ </div>
293
+ )}
294
+
295
+ {/* Toggle p�riode de facturation */}
296
+ <div className="bg-white rounded-lg shadow-sm border p-6">
297
+ <h3 className="font-semibold mb-4">Période de facturation</h3>
298
+ <div className="flex items-center space-x-4">
299
+ <button
300
+ onClick={toggleBillingPeriod}
301
+ className={`px-4 py-2 rounded-lg transition-colors ${billingInfo.billingPeriod === 'monthly'
302
+ ? 'bg-[#6A8A82] text-white'
303
+ : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
304
+ }`}
305
+ >
306
+ Mensuel
307
+ </button>
308
+ <button
309
+ onClick={toggleBillingPeriod}
310
+ className={`px-4 py-2 rounded-lg transition-colors ${billingInfo.billingPeriod === 'yearly'
311
+ ? 'bg-[#6A8A82] text-white'
312
+ : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
313
+ }`}
314
+ >
315
+ Annuel
316
+ <span className="ml-2 text-xs bg-green-100 text-green-800 px-2 py-1 rounded-full">
317
+ -17%
318
+ </span>
319
+ </button>
320
+ </div>
321
+ </div>
322
+
323
+ {/* Modules dans le panier */}
324
+ {moduleItems.length > 0 && (
325
+ <div className="bg-white rounded-lg shadow-sm border p-6">
326
+ <h2 className="text-xl font-semibold mb-4 flex items-center">
327
+ <Plus className="w-5 h-5 mr-2 text-green-600" />
328
+ Modules supplémentaires
329
+ </h2>
330
+
331
+ <div className="space-y-3">
332
+ {moduleItems.map((module) => (
333
+ <div key={module.id} className="flex items-center justify-between p-3 border rounded-lg">
334
+ <div className="flex-1">
335
+ <h4 className="font-medium">{module.name}</h4>
336
+ <p className="text-sm text-gray-600">{module.description}</p>
337
+ </div>
338
+
339
+ <div className="flex items-center space-x-3">
340
+
341
+
342
+ <div className="text-right">
343
+ <div className="font-semibold">
344
+ {(module.price * (module.quantity || 1)).toLocaleString()} {module.currency}
345
+ </div>
346
+ <div className="text-sm text-gray-500">{module.period}</div>
347
+ </div>
348
+
349
+ <button
350
+ onClick={() => removeModuleFromCart(module.id)}
351
+ className="p-1 text-red-500 hover:text-red-700"
352
+ >
353
+ <Trash2 className="w-4 h-4" />
354
+ </button>
355
+ </div>
356
+ </div>
357
+ ))}
358
+ </div>
359
+ </div>
360
+ )}
361
+
362
+ {/* Modules disponibles */}
363
+ <div className="bg-white rounded-lg shadow-sm border p-6">
364
+ <h2 className="text-xl font-semibold mb-4">Ajouter des modules</h2>
365
+
366
+ {modulesLoading ? (
367
+ <div className="text-center py-8">
368
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4"></div>
369
+ <p className="text-gray-600">Chargement des modules...</p>
370
+ </div>
371
+ ) : (
372
+ <div className=" gap-4">
373
+ {additionalModules.map((module) => {
374
+ const isInCart = cartItems.some(item => item.id === module.id && item.type === 'module');
375
+ const price = billingInfo.billingPeriod === 'monthly' ? module.price_monthly : module.price_yearly;
376
+
377
+ return (
378
+ <div key={module.id} className="border rounded-lg mb-2 p-4 hover:shadow-md transition-shadow">
379
+ <div className="flex justify-between items-start mb-2">
380
+ <h3 className="font-medium">{module.name}</h3>
381
+ <div className="text-right">
382
+ <div className="font-semibold">
383
+ {price.toLocaleString()} FCFA {billingInfo.billingPeriod === 'monthly' ? '/ mois' : '/ an'}
384
+ </div>
385
+
386
+
387
+ </div>
388
+ </div>
389
+
390
+ <p className="text-sm text-gray-600 mb-3">{module.description}</p>
391
+ <div className="flex justify-end items-start mb-2">
392
+ <SecondaryButton onClick={() => addModuleToCart(module)} disabled={isInCart || !module.is_active} classname='py-2 px-4 rounded-lg font-medium transition-colors'>
393
+ {isInCart ? 'Ajouté' : 'Ajouter au panier'}
394
+ </SecondaryButton>
395
+
396
+ </div>
397
+ </div>
398
+ );
399
+ })}
400
+ </div>
401
+ )}
402
+ </div>
403
+ </div>
404
+
405
+ {/* R�sum� de commande */}
406
+ <div className="lg:col-span-1">
407
+ <div className="bg-white rounded-lg shadow-sm border p-6 sticky top-6">
408
+ <h2 className="text-xl font-semibold mb-4">Résumé de la commande</h2>
409
+
410
+ {/* Items */}
411
+ <div className="space-y-3 mb-4">
412
+ {cartItems.map((item) => (
413
+ <div key={`${item.type}-${item.id}`} className="flex justify-between text-sm">
414
+ <span>
415
+ {item.name}
416
+ {item.quantity && item.quantity > 1 && ` (x${item.quantity})`}
417
+ </span>
418
+ <span>{(item.price * (item.quantity || 1)).toLocaleString()} FCFA</span>
419
+ </div>
420
+ ))}
421
+ </div>
422
+
423
+ <div className="border-t pt-4 space-y-2">
424
+ <div className="flex justify-between text-sm">
425
+ <span>Sous-total</span>
426
+ <span>{subtotal.toLocaleString()} FCFA</span>
427
+ </div>
428
+ <div className="flex justify-between text-sm">
429
+ <span>TVA (18%)</span>
430
+ <span>{tax.toLocaleString()} FCFA</span>
431
+ </div>
432
+ <div className="flex justify-between font-semibold text-lg border-t pt-2">
433
+ <span>Total</span>
434
+ <span>{total.toLocaleString()} FCFA</span>
435
+ </div>
436
+ </div>
437
+
438
+ {/* Garanties */}
439
+ <div className="mt-6 pt-6 border-t">
440
+ <div className="space-y-3 text-sm text-gray-600">
441
+ <div className="flex items-center">
442
+ <Shield className="w-4 h-4 mr-2 " />
443
+ Paiement 100% sécurisé
444
+ </div>
445
+ <div className="flex items-center">
446
+ <Clock className="w-4 h-4 mr-2" />
447
+ Activation immédiate
448
+ </div>
449
+ <div className="flex items-center">
450
+ <Users className="w-4 h-4 mr-2" />
451
+ Support client 24/7
452
+ </div>
453
+ </div>
454
+ </div>
455
+
456
+ {/* Bouton de paiement */}
457
+
458
+ <PrimaryButton onClick={handleProcessPayment} classname='w-full mt-6 py-3 center' disabled={processingPayment || cartItems.length === 0}>
459
+
460
+ {processingPayment ? (
461
+ <>
462
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
463
+ Traitement...
464
+ </>
465
+ ) : (
466
+ <>
467
+ <CreditCard className="w-5 h-5 mr-2" />
468
+ Procéder au paiement
469
+ </>
470
+ )}
471
+
472
+ </PrimaryButton>
473
+
474
+
475
+ <p className="text-xs text-gray-500 mt-3 text-center">
476
+ En procédant au paiement, vous acceptez nos conditions d'utilisation et notre politique de confidentialité.
477
+ </p>
478
+ </div>
479
+ </div>
480
+ </div>
481
+ </div>
482
+ </div>
483
+ );
484
+ };
485
+
486
+ export default CartPlan;