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.
- package/eslint.config.js +28 -0
- package/index.html +78 -0
- package/package.json +42 -0
- package/postcss.config.js +6 -0
- package/src/App.tsx +156 -0
- package/src/assets/imgs/login_illustration.png +0 -0
- package/src/components/common/Buttons.tsx +39 -0
- package/src/components/common/Cards.tsx +18 -0
- package/src/components/common/FDrawer.tsx +2448 -0
- package/src/components/common/FDrawer.types.ts +191 -0
- package/src/components/common/Inputs.tsx +409 -0
- package/src/components/common/Modals.tsx +41 -0
- package/src/components/common/Navigations.tsx +0 -0
- package/src/components/common/Toast.tsx +0 -0
- package/src/components/demo/ToastDemo.tsx +73 -0
- package/src/components/layout/Header.tsx +202 -0
- package/src/components/layout/ModernDoubleSidebarLayout.tsx +727 -0
- package/src/components/layout/PrivateLayout.tsx +52 -0
- package/src/components/layout/Sidebar.tsx +182 -0
- package/src/components/ui/Toast.tsx +93 -0
- package/src/contexts/SessionContext.tsx +77 -0
- package/src/contexts/ThemeContext.tsx +58 -0
- package/src/contexts/ToastContext.tsx +94 -0
- package/src/index.css +3 -0
- package/src/main.tsx +10 -0
- package/src/models/Organization.ts +47 -0
- package/src/models/Plan.ts +42 -0
- package/src/models/User.ts +23 -0
- package/src/pages/Analytics.tsx +101 -0
- package/src/pages/CreateOrganization.tsx +215 -0
- package/src/pages/Dashboard.tsx +15 -0
- package/src/pages/Home.tsx +12 -0
- package/src/pages/Profile.tsx +313 -0
- package/src/pages/Settings.tsx +382 -0
- package/src/pages/Team.tsx +180 -0
- package/src/pages/auth/Login.tsx +140 -0
- package/src/pages/auth/Register.tsx +302 -0
- package/src/pages/organizations/DetailEntity.tsx +1002 -0
- package/src/pages/organizations/DetailOrganizations.tsx +1629 -0
- package/src/pages/organizations/ListOrganizations.tsx +270 -0
- package/src/pages/pricings/CartPlan.tsx +486 -0
- package/src/pages/pricings/ListPricing.tsx +321 -0
- package/src/pages/users/CreateUser.tsx +450 -0
- package/src/pages/users/ListUsers.tsx +0 -0
- package/src/services/AuthServices.ts +94 -0
- package/src/services/OrganizationServices.ts +61 -0
- package/src/services/PlanSubscriptionServices.tsx +137 -0
- package/src/services/UserServices.ts +36 -0
- package/src/services/api.ts +64 -0
- package/src/styles/theme.ts +383 -0
- package/src/utils/utils.ts +48 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.js +158 -0
- package/tsconfig.app.json +24 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +22 -0
- 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;
|