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,321 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import {
4
+ Check,
5
+ Star,
6
+ Sparkles,
7
+ X
8
+ } from 'lucide-react';
9
+ import { useToast } from '../../contexts/ToastContext';
10
+ import { useSession } from '../../contexts/SessionContext';
11
+
12
+ // Types pour les plans de pricing
13
+ interface PricingFeature {
14
+ name: string;
15
+ included: boolean;
16
+ limit?: string;
17
+ special?: boolean;
18
+ }
19
+
20
+ interface PricingPlan {
21
+ id: string;
22
+ code?: string;
23
+ name: string;
24
+ description: string;
25
+ price: number;
26
+ originalPrice?: number;
27
+ currency: string;
28
+ period: string;
29
+ popular?: boolean;
30
+ features: PricingFeature[];
31
+ buttonText: string;
32
+ buttonVariant: 'primary' | 'secondary' | 'dark';
33
+ }
34
+
35
+ interface PricingCategory {
36
+ id: string;
37
+ name: string;
38
+ plans: PricingPlan[];
39
+ }
40
+
41
+ const ListPricing: React.FC<{ organizationId: number }> = ({ organizationId }) => {
42
+ const [selectedPlan, setSelectedPlan] = useState<string | null>(null);
43
+ const [loading, setLoading] = useState(false);
44
+ const [activeCategory, setActiveCategory] = useState<string>('personnel');
45
+
46
+ const navigate = useNavigate();
47
+ const { success, error: showError } = useToast();
48
+ const { loggedUser , token } = useSession();
49
+
50
+ // Plans de pricing par catégorie
51
+ const pricingCategories: PricingCategory[] = [
52
+ {
53
+ id: 'personnel',
54
+ name: 'Personnel',
55
+ plans: [
56
+ {
57
+ id: 'pro',
58
+ code: 'rewise_pro',
59
+ name: 'Pro',
60
+ description: 'Améliorez la productivité et l\'apprentissage avec un accès supplémentaire.',
61
+ price: 100000,
62
+ originalPrice: 83333,
63
+ currency: 'FCFA',
64
+ period: '/ mois',
65
+ features: [
66
+ { name: '1 entité', included: true },
67
+ { name: 'jusqu\'à 5 utilisateurs', included: true },
68
+ { name: 'CRM, Comptabilité et Facturation', included: true },
69
+ { name: '+1 module au choix', included: false },
70
+
71
+
72
+ ],
73
+ buttonText: 'Obtenez Pro',
74
+ buttonVariant: 'primary'
75
+ },
76
+ {
77
+ id: 'max',
78
+ code: 'rewise_max',
79
+ name: 'Max',
80
+ description: 'Débloquez toutes les capacités de Perplexity avec un accès anticipé aux nouveaux produits.',
81
+ price: 200000,
82
+ originalPrice: 166667,
83
+ currency: 'FCFA',
84
+ period: '/ mois',
85
+ popular: true,
86
+ features: [
87
+ { name: 'jusqu\'à 3 entités', included: true },
88
+ { name: 'jusqu\'à 15 utilisateurs', included: true },
89
+ { name: 'CRM, Comptabilité et Facturation', included: true },
90
+ { name: '+1 module au choix', included: true },
91
+ ],
92
+ buttonText: 'Obtenez Max',
93
+ buttonVariant: 'dark'
94
+ }
95
+ ]
96
+ },
97
+ {
98
+ id: 'education',
99
+ name: 'Éducation',
100
+ plans: [
101
+ {
102
+ id: 'student',
103
+ name: 'Étudiant',
104
+ description: 'Plan spécial pour les étudiants et enseignants.',
105
+ price: 10.00,
106
+ currency: '$US',
107
+ period: '/ mois',
108
+ features: [
109
+ { name: 'Accès complet à la recherche académique', included: true },
110
+ { name: 'Citations et références automatiques', included: true },
111
+ { name: 'Collaboration en temps réel', included: true },
112
+ { name: 'Support pédagogique', included: true }
113
+ ],
114
+ buttonText: 'Plan Étudiant',
115
+ buttonVariant: 'primary'
116
+ }
117
+ ]
118
+ },
119
+ {
120
+ id: 'affaires',
121
+ name: 'Affaires',
122
+ plans: [
123
+ {
124
+ id: 'business',
125
+ name: 'Business',
126
+ description: 'Solution complète pour les entreprises.',
127
+ price: 50.00,
128
+ currency: '$US',
129
+ period: '/ mois',
130
+ features: [
131
+ { name: 'Gestion d\'équipe avancée', included: true },
132
+ { name: 'API d\'entreprise', included: true },
133
+ { name: 'Sécurité renforcée', included: true },
134
+ { name: 'Support prioritaire 24/7', included: true },
135
+ { name: 'Rapports et analytics', included: true }
136
+ ],
137
+ buttonText: 'Plan Business',
138
+ buttonVariant: 'primary'
139
+ }
140
+ ]
141
+ }
142
+ ];
143
+
144
+ const handleSelectPlan = async (planId: string) => {
145
+ if (!token) {
146
+ showError('Vous devez être connecté pour sélectionner un plan');
147
+ navigate('/auth/login');
148
+ return;
149
+ }
150
+
151
+ setLoading(true);
152
+ setSelectedPlan(planId);
153
+
154
+ try {
155
+ // Simuler l'appel API pour sélectionner le plan
156
+ await new Promise(resolve => setTimeout(resolve, 1000));
157
+
158
+ // Trouver le plan sélectionné
159
+ const selectedPlanData = getCurrentPlans().find(plan => plan.id === planId);
160
+ if (selectedPlanData) {
161
+ // Redirection vers le panier avec les paramètres du plan
162
+ navigate(`/pricing/cart?org=${organizationId}&plan=${planId}&code=${selectedPlanData.code}`);
163
+ } else {
164
+ showError('Plan non trouvé');
165
+ }
166
+ } catch (error) {
167
+ showError('Erreur lors de la sélection du plan');
168
+ console.error(error);
169
+ } finally {
170
+ setLoading(false);
171
+ setSelectedPlan(null);
172
+ }
173
+ };
174
+
175
+ const getCurrentPlans = (): PricingPlan[] => {
176
+ const category = pricingCategories.find(cat => cat.id === activeCategory);
177
+ return category ? category.plans : [];
178
+ };
179
+
180
+ const getButtonStyles = (variant: string, isSelected: boolean) => {
181
+ const baseStyles = "w-full py-3 px-6 rounded-lg font-medium transition-all duration-200 flex items-center justify-center gap-2";
182
+
183
+ if (isSelected) {
184
+ return `${baseStyles} bg-gray-400 text-white cursor-not-allowed`;
185
+ }
186
+
187
+ switch (variant) {
188
+ case 'primary':
189
+ return `${baseStyles} bg-teal-600 text-white hover:bg-teal-700`;
190
+ case 'dark':
191
+ return `${baseStyles} bg-gray-800 text-white hover:bg-gray-900`;
192
+ default:
193
+ return `${baseStyles} bg-gray-100 text-gray-900 hover:bg-gray-200 border border-gray-300`;
194
+ }
195
+ };
196
+
197
+ return (
198
+ <div className="min-h-screen bg-gray-50 py-8">
199
+ <div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
200
+ {/* Category Tabs */}
201
+ {/* <div className="flex justify-center mb-12">
202
+ <div className="bg-gray-100 p-1 rounded-lg">
203
+ {pricingCategories.map((category) => (
204
+ <button
205
+ key={category.id}
206
+ onClick={() => setActiveCategory(category.id)}
207
+ className={`px-6 py-2 rounded-md font-medium transition-all duration-200 ${
208
+ activeCategory === category.id
209
+ ? 'bg-white text-teal-600 shadow-sm'
210
+ : 'text-gray-600 hover:text-gray-900'
211
+ }`}
212
+ >
213
+ {category.name}
214
+ </button>
215
+ ))}
216
+ </div>
217
+ </div> */}
218
+
219
+ <h1 className='text-center text-3xl font-bold mb-8'>Choisissez votre plan</h1>
220
+ <p className='text-center text-gray-600 mb-12'>Sélectionnez le plan qui convient le mieux à vos besoins.</p>
221
+
222
+ {/* Pricing Cards */}
223
+ <div className="flex justify-center gap-8 mb-16">
224
+ {getCurrentPlans().map((plan) => (
225
+ <div
226
+ key={plan.id}
227
+ className={`relative bg-white rounded-2xl shadow-sm border transition-all duration-300 hover:shadow-md w-full max-w-md ${plan.popular
228
+ ? 'border-gray-300'
229
+ : 'border-gray-200'
230
+ }`}
231
+ >
232
+ {/* Popular badge */}
233
+ {plan.popular && (
234
+ <div className="absolute -top-3 left-1/2 transform -translate-x-1/2">
235
+ <span className="bg-teal-100 text-teal-700 px-3 py-1 rounded-full text-sm font-medium">
236
+ Populaire
237
+ </span>
238
+ </div>
239
+ )}
240
+
241
+ <div className="p-8">
242
+ {/* Plan header */}
243
+ <div className="mb-8">
244
+ <h3 className="text-2xl font-bold text-gray-900 mb-3">{plan.name}</h3>
245
+ <div className="mb-4">
246
+ <span className="text-3xl font-bold text-gray-900">
247
+ {plan.price.toFixed(2)} {plan.currency}
248
+ </span>
249
+ <span className="text-gray-600 ml-1">{plan.period}</span>
250
+ {plan.originalPrice && (
251
+ <div className="text-sm text-gray-500">
252
+ {plan.originalPrice.toFixed(2)} {plan.currency} lorsque facturé annuellement
253
+ </div>
254
+ )}
255
+ </div>
256
+ <p className="text-gray-600 text-sm leading-relaxed">{plan.description}</p>
257
+ </div>
258
+
259
+ {/* CTA Button */}
260
+ <div className="mb-8">
261
+ <button
262
+ onClick={() => handleSelectPlan(plan.id)}
263
+ disabled={loading && selectedPlan === plan.id}
264
+ className={getButtonStyles(plan.buttonVariant, loading && selectedPlan === plan.id)}
265
+ >
266
+ {loading && selectedPlan === plan.id ? (
267
+ <>
268
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
269
+ Traitement...
270
+ </>
271
+ ) : (
272
+ plan.buttonText
273
+ )}
274
+ </button>
275
+ </div>
276
+
277
+ {/* Features list */}
278
+ <ul className="space-y-3">
279
+ {plan.features.map((feature, index) => (
280
+ <li key={index} className="flex items-start gap-3">
281
+ {feature.included ? (
282
+ <Check className="w-4 h-4 text-green-500 mt-0.5 flex-shrink-0" />
283
+ ) : (
284
+ <X className="w-4 h-4 text-gray-500 mt-0.5 flex-shrink-0" />
285
+ )}
286
+ <span className="text-sm text-gray-700 leading-relaxed">
287
+ {feature.name}
288
+ {feature.limit && (
289
+ <span className="text-gray-500"> ({feature.limit})</span>
290
+ )}
291
+ </span>
292
+ </li>
293
+ ))}
294
+ </ul>
295
+
296
+ {/* Footer note for specific plans */}
297
+ {plan.id === 'pro' && (
298
+ <div className="mt-6 pt-6 border-t border-gray-100">
299
+ <p className="text-xs text-gray-500">
300
+ Abonné existant ? <a href="#" className="text-teal-600 hover:underline">Voir aide à la facturation</a>
301
+ </p>
302
+ </div>
303
+ )}
304
+ {plan.id === 'max' && (
305
+ <div className="mt-6 pt-6 border-t border-gray-100">
306
+ <p className="text-xs text-gray-500">
307
+ Pour un usage personnel uniquement, et sous réserve de notre <a href="#" className="text-gray-700 underline">politique</a>
308
+ </p>
309
+ </div>
310
+ )}
311
+ </div>
312
+ </div>
313
+ ))}
314
+ </div>
315
+
316
+ </div>
317
+ </div>
318
+ );
319
+ };
320
+
321
+ export default ListPricing;