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,140 @@
1
+ import React, { useState } from "react";
2
+ import login_illustration from "../../assets/imgs/login_illustration.png";
3
+ import { useToast } from "../../contexts/ToastContext";
4
+
5
+ const Login: React.FC = () => {
6
+ const [formData, setFormData] = useState({ email: "", password: "" });
7
+ const [loading, setLoading] = useState(false);
8
+ const { success, error: showError } = useToast();
9
+
10
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
11
+ const { name, value } = e.target;
12
+ setFormData(prev => ({
13
+ ...prev,
14
+ [name]: value,
15
+ }));
16
+ };
17
+
18
+ const handleSubmit = async (e: React.FormEvent) => {
19
+ e.preventDefault();
20
+ const { email, password } = formData;
21
+ if (!email || !password) {
22
+ showError("Veuillez entrer votre email et votre mot de passe.");
23
+ return;
24
+ }
25
+
26
+ setLoading(true);
27
+
28
+ try {
29
+ // Simuler un appel API
30
+ await new Promise((resolve, reject) => {
31
+ setTimeout(() => {
32
+ // Simulation d'une connexion réussie
33
+ if (email === "test@example.com" && password === "password") {
34
+ resolve(true);
35
+ } else {
36
+ reject(new Error("Email ou mot de passe incorrect"));
37
+ }
38
+ }, 1000);
39
+ });
40
+
41
+ success("Connexion réussie ! Bienvenue !");
42
+ // Rediriger vers le dashboard ou page d'accueil
43
+
44
+ } catch (error) {
45
+ showError(error instanceof Error ? error.message : "Erreur de connexion");
46
+ } finally {
47
+ setLoading(false);
48
+ }
49
+ };
50
+
51
+ return (
52
+ <div className="min-h-screen flex items-center justify-center bg-gray-100">
53
+ <div className="bg-white rounded-3xl shadow-2xl overflow-hidden max-w-4xl w-full mx-4">
54
+ <div className="flex flex-col lg:flex-row">
55
+ {/* Côté gauche - Illustration */}
56
+ <div className="lg:w-1/2 p-8 lg:p-12 bg-white flex flex-col justify-center">
57
+ <div className="flex justify-center items-center h-full mb-8">
58
+ <img src={login_illustration} alt="Illustration de connexion" className="object-contain" />
59
+ </div>
60
+ </div>
61
+ {/* Côté droit - Formulaire de connexion */}
62
+ <div className="lg:w-1/2 p-8 lg:p-12 bg-gray-50">
63
+ <div className="max-w-sm mx-auto">
64
+ <h2 className="text-3xl font-bold text-gray-800 mb-2">Bienvenue sur Rewise</h2>
65
+ <p className="text-gray-600 mb-8">Débloquez la performance de votre équipe</p>
66
+
67
+
68
+ <form onSubmit={handleSubmit} className="space-y-6">
69
+ <div>
70
+ <label htmlFor="email" className="block text-gray-700 text-sm font-medium mb-2">
71
+ Adresse e-mail
72
+ </label>
73
+ <input
74
+ id="email"
75
+ name="email"
76
+ type="email"
77
+ placeholder="exemple@email.com"
78
+ className="w-full px-4 py-3 border border-teal-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent placeholder-gray-400"
79
+ value={formData.email}
80
+ onChange={handleChange}
81
+ autoComplete="email"
82
+ required
83
+ />
84
+ </div>
85
+
86
+ <div>
87
+ <label htmlFor="password" className="block text-gray-700 text-sm font-medium mb-2">
88
+ Mot de passe
89
+ </label>
90
+ <input
91
+ id="password"
92
+ name="password"
93
+ type="password"
94
+ placeholder="Entrez votre mot de passe"
95
+ className="w-full px-4 py-3 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent placeholder-gray-400"
96
+ value={formData.password}
97
+ onChange={handleChange}
98
+ autoComplete="current-password"
99
+ required
100
+ />
101
+ </div>
102
+
103
+ <div className="text-right">
104
+ <a href="#" className="text-gray-600 text-sm hover:text-teal-600 transition-colors">
105
+ Mot de passe oublié ?
106
+ </a>
107
+ </div>
108
+
109
+ <button
110
+ type="submit"
111
+ disabled={loading}
112
+ className="w-full bg-[#8290A9] text-white py-3 rounded-lg font-medium hover:bg-[#6B7C92] transition-colors shadow-lg disabled:opacity-50 disabled:cursor-not-allowed"
113
+ >
114
+ {loading ? "Connexion en cours..." : "Se connecter"}
115
+ </button>
116
+ </form>
117
+
118
+ <div className="mt-6 text-center">
119
+ <span className="text-gray-600 text-sm">
120
+ Vous n'avez pas de compte ?{" "}
121
+ <a href="/auth/register" className="text-[#8290A9] font-medium hover:text-[#6B7C92] transition-colors">
122
+ Inscrivez-vous
123
+ </a>
124
+ </span>
125
+ </div>
126
+ </div>
127
+
128
+ <div className="text-center mt-12">
129
+ <p className="text-gray-400 text-xs">
130
+ ©2021 Tous droits réservés
131
+ </p>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ );
138
+ };
139
+
140
+ export default Login;
@@ -0,0 +1,302 @@
1
+ import React, { useState } from "react";
2
+ import login_illustration from "../../assets/imgs/login_illustration.png";
3
+ import { useToast } from "../../contexts/ToastContext";
4
+ import { AuthServices } from "../../services/AuthServices";
5
+ import { useNavigate } from "react-router-dom";
6
+
7
+ const Register: React.FC = () => {
8
+ const [formData, setFormData] = useState({
9
+ email: "",
10
+ first_name: "",
11
+ last_name: "",
12
+ otp: "",
13
+ password: "",
14
+ confirm_password: "",
15
+ });
16
+ const [step, setStep] = useState<"register" | "otp" | "password">("register");
17
+ const [loading, setLoading] = useState(false);
18
+ const { success, error: showError, info } = useToast();
19
+
20
+ const navigate = useNavigate();
21
+
22
+ // Appels API réels
23
+ const sendOtp = async (email: string) => {
24
+ setLoading(true);
25
+ try {
26
+ const res = await AuthServices.sendOtp({ email }) as { success: boolean; message?: string; data?: any };
27
+ if (res.success) {
28
+ setStep("otp");
29
+ info(`Code de vérification envoyé à ${email}`);
30
+ } else {
31
+ showError(res.message || "Erreur lors de l'envoi du code de vérification");
32
+ }
33
+ } catch (error: any) {
34
+ showError(error?.message || "Erreur lors de l'envoi du code de vérification");
35
+ } finally {
36
+ setLoading(false);
37
+ }
38
+ };
39
+
40
+ const verifyOtp = async (otp: string) => {
41
+ setLoading(true);
42
+ try {
43
+ const res = await AuthServices.verifyOtp({ email: formData.email, otp }) as { success: boolean; message?: string; data?: any };
44
+ if (res.success) {
45
+ setStep("password");
46
+ success("Code vérifié avec succès !");
47
+ } else {
48
+ showError(res.message || "Code de vérification invalide");
49
+ }
50
+ } catch (error: any) {
51
+ showError(error?.message || "Code de vérification invalide");
52
+ } finally {
53
+ setLoading(false);
54
+ }
55
+ };
56
+
57
+ const handleChange = (
58
+ e: React.ChangeEvent<HTMLInputElement>
59
+ ) => {
60
+ setFormData({ ...formData, [e.target.name]: e.target.value });
61
+ };
62
+
63
+ const handleRegister = (e: React.FormEvent) => {
64
+ e.preventDefault();
65
+ if (!formData.email || !formData.first_name || !formData.last_name) {
66
+ showError("Tous les champs sont obligatoires");
67
+ return;
68
+ }
69
+ sendOtp(formData.email);
70
+ };
71
+
72
+ const handleOtpSubmit = (e: React.FormEvent) => {
73
+ e.preventDefault();
74
+ if (!formData.otp) {
75
+ showError("Le code de vérification est obligatoire");
76
+ return;
77
+ }
78
+ verifyOtp(formData.otp);
79
+ };
80
+
81
+ const handlePasswordSubmit = async (e: React.FormEvent) => {
82
+ e.preventDefault();
83
+ if (!formData.password) {
84
+ showError("Le mot de passe est obligatoire");
85
+ return;
86
+ }
87
+ if (formData.password !== formData.confirm_password) {
88
+ showError("Les mots de passe ne correspondent pas");
89
+ return;
90
+ }
91
+ setLoading(true);
92
+ try {
93
+ const res = await AuthServices.completeRegistration({
94
+ email: formData.email,
95
+ password: formData.password,
96
+ confirmed_password: formData.confirm_password,
97
+ firstname: formData.first_name,
98
+ lastname: formData.last_name,
99
+ }) as { success: boolean; message?: string; data?: any };
100
+ if (res.success) {
101
+ success("Inscription réussie ! Bienvenue sur Rewise !");
102
+ setStep("register");
103
+ localStorage.setItem("token", res.data.token);
104
+ navigate("/home");
105
+ setFormData({ email: "", first_name: "", last_name: "", otp: "", password: "", confirm_password: "" });
106
+ } else {
107
+ showError(res.message || "Erreur lors de l'inscription");
108
+ }
109
+ } catch (error: any) {
110
+ showError(error?.message || "Erreur lors de l'inscription");
111
+ } finally {
112
+ setLoading(false);
113
+ }
114
+ };
115
+
116
+ const getTitle = () => {
117
+ return step === "register" ? "Créer un compte" : step === "otp" ? "Vérifier l'e-mail" : "Définir le mot de passe";
118
+ };
119
+
120
+ return (
121
+ <div className="min-h-screen flex items-center justify-center bg-gray-100 py-12 px-4 sm:px-6 lg:px-8">
122
+ <div className="bg-white rounded-3xl shadow-2xl overflow-hidden max-w-4xl w-full mx-4">
123
+ <div className="flex flex-col lg:flex-row">
124
+ {/* Left side - Branding and Illustration */}
125
+ <div className="lg:w-1/2 p-8 lg:p-12 bg-white flex flex-col justify-center">
126
+ {/* Team illustration */}
127
+ <div className="flex justify-center items-center h-full mb-8">
128
+ <img src={login_illustration} alt="Register Illustration" className="object-contain" />
129
+ </div>
130
+ </div>
131
+
132
+ {/* Right side - Register form */}
133
+ <div className="lg:w-1/2 p-8 lg:p-12 bg-gray-50">
134
+ <div className="max-w-sm mx-auto">
135
+ <h2 className="text-3xl font-bold text-gray-800 mb-2">Bienvenue sur Rewise</h2>
136
+ <p className="text-gray-600 mb-8">{getTitle()}</p>
137
+
138
+
139
+ {step === "register" && (
140
+ <form onSubmit={handleRegister} className="space-y-6">
141
+ <div>
142
+ <label htmlFor="first_name" className="block text-gray-700 text-sm font-medium mb-2">
143
+ Prénom
144
+ </label>
145
+ <input
146
+ id="first_name"
147
+ type="text"
148
+ name="first_name"
149
+ placeholder="Entrez votre prénom"
150
+ value={formData.first_name}
151
+ onChange={handleChange}
152
+ className="w-full px-4 py-3 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#6B7C92] focus:border-transparent placeholder-gray-400"
153
+ required
154
+ />
155
+ </div>
156
+
157
+ <div>
158
+ <label htmlFor="last_name" className="block text-gray-700 text-sm font-medium mb-2">
159
+ Nom
160
+ </label>
161
+ <input
162
+ id="last_name"
163
+ type="text"
164
+ name="last_name"
165
+ placeholder="Entrez votre nom"
166
+ value={formData.last_name}
167
+ onChange={handleChange}
168
+ className="w-full px-4 py-3 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#6B7C92] focus:border-transparent placeholder-gray-400"
169
+ required
170
+ />
171
+ </div>
172
+
173
+ <div>
174
+ <label htmlFor="email" className="block text-gray-700 text-sm font-medium mb-2">
175
+ Adresse e-mail
176
+ </label>
177
+ <input
178
+ id="email"
179
+ type="email"
180
+ name="email"
181
+ placeholder="Entrez votre e-mail"
182
+ value={formData.email}
183
+ onChange={handleChange}
184
+ className="w-full px-4 py-3 border border-[#6B7C92] rounded-lg focus:outline-none focus:ring-2 focus:ring-[#6B7C92] focus:border-transparent placeholder-gray-400"
185
+ required
186
+ />
187
+ </div>
188
+
189
+ <button
190
+ type="submit"
191
+ className="w-full bg-[#8290A9] text-white py-3 rounded-lg font-medium hover:bg-[#6B7C92] transition-colors shadow-lg"
192
+ disabled={loading}
193
+ >
194
+ {loading ? "Envoi du code..." : "Créer un compte"}
195
+ </button>
196
+ </form>
197
+ )}
198
+
199
+ {step === "otp" && (
200
+ <form onSubmit={handleOtpSubmit} className="space-y-6">
201
+ <div>
202
+ <label htmlFor="otp" className="block text-gray-700 text-sm font-medium mb-2">
203
+ Code de vérification
204
+ </label>
205
+ <input
206
+ id="otp"
207
+ type="text"
208
+ name="otp"
209
+ placeholder="Entrez le code de vérification"
210
+ value={formData.otp}
211
+ onChange={handleChange}
212
+ className="w-full px-4 py-3 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent placeholder-gray-400"
213
+ required
214
+ />
215
+ </div>
216
+
217
+ <button
218
+ type="submit"
219
+ className="w-full bg-teal-500 text-white py-3 rounded-lg font-medium hover:bg-teal-600 transition-colors shadow-lg"
220
+ disabled={loading}
221
+ >
222
+ {loading ? "Vérification..." : "Vérifier le code"}
223
+ </button>
224
+
225
+ <button
226
+ type="button"
227
+ className="w-full text-teal-600 text-sm hover:text-teal-700 transition-colors"
228
+ onClick={() => sendOtp(formData.email)}
229
+ disabled={loading}
230
+ >
231
+ Renvoyer le code
232
+ </button>
233
+ </form>
234
+ )}
235
+
236
+ {step === "password" && (
237
+ <form onSubmit={handlePasswordSubmit} className="space-y-6">
238
+ <div>
239
+ <label htmlFor="password" className="block text-gray-700 text-sm font-medium mb-2">
240
+ Nouveau mot de passe
241
+ </label>
242
+ <input
243
+ id="password"
244
+ type="password"
245
+ name="password"
246
+ placeholder="Entrez votre mot de passe"
247
+ value={formData.password}
248
+ onChange={handleChange}
249
+ className="w-full px-4 py-3 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent placeholder-gray-400"
250
+ required
251
+ />
252
+ </div>
253
+
254
+ <div>
255
+ <label htmlFor="confirm_password" className="block text-gray-700 text-sm font-medium mb-2">
256
+ Confirmer le mot de passe
257
+ </label>
258
+ <input
259
+ id="confirm_password"
260
+ type="password"
261
+ name="confirm_password"
262
+ placeholder="Confirmez votre mot de passe"
263
+ value={formData.confirm_password}
264
+ onChange={handleChange}
265
+ className="w-full px-4 py-3 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent placeholder-gray-400"
266
+ required
267
+ />
268
+ </div>
269
+
270
+ <button
271
+ type="submit"
272
+ className="w-full bg-teal-500 text-white py-3 rounded-lg font-medium hover:bg-teal-600 transition-colors shadow-lg"
273
+ disabled={loading}
274
+ >
275
+ {loading ? "Création du compte..." : "Terminer l'inscription"}
276
+ </button>
277
+ </form>
278
+ )}
279
+
280
+ <div className="mt-6 text-center">
281
+ <span className="text-gray-600 text-sm">
282
+ Vous avez déjà un compte ?{" "}
283
+ <a href="/auth/login" className="text-[#8290A9] font-medium hover:text-teal-600 transition-colors">
284
+ Se connecter
285
+ </a>
286
+ </span>
287
+ </div>
288
+ </div>
289
+
290
+ <div className="text-center mt-12">
291
+ <p className="text-gray-400 text-xs">
292
+ ©2021 Tous droits réservés
293
+ </p>
294
+ </div>
295
+ </div>
296
+ </div>
297
+ </div>
298
+ </div>
299
+ );
300
+ };
301
+
302
+ export default Register;