diff options
| -rw-r--r-- | src/components/header/authdialog/LoginForm.tsx | 12 | ||||
| -rw-r--r-- | src/components/header/authdialog/RegisterForm.tsx | 12 | ||||
| -rw-r--r-- | src/components/settings/ChangePassword.tsx | 79 | ||||
| -rw-r--r-- | src/components/settings/SecurityPage.tsx | 26 | ||||
| -rw-r--r-- | src/components/settings/SetPasswordField.tsx | 76 | ||||
| -rw-r--r-- | src/lib/api/RefreshUser.ts | 37 | ||||
| -rw-r--r-- | src/lib/api/SetPassword.ts | 52 |
7 files changed, 231 insertions, 63 deletions
diff --git a/src/components/header/authdialog/LoginForm.tsx b/src/components/header/authdialog/LoginForm.tsx index b4d90f1..3a42979 100644 --- a/src/components/header/authdialog/LoginForm.tsx +++ b/src/components/header/authdialog/LoginForm.tsx @@ -6,6 +6,10 @@ import { InputField } from '@/components/ui/inputfield'; import { useAuthContext } from '@/lib/contexts/Auth.context'; import { useRouter } from 'next/navigation'; export default function LoginForm({ redirectTo }: { redirectTo?: string }) { + const handleGoogleLogin = () => { + window.location.href = `${API_URL}/api/auth/google/login`; + }; + const API_URL = process.env.NEXT_PUBLIC_API_URL; const [errors, setErrors] = useState<any>({}); const [loading, setLoading] = useState(false); @@ -70,9 +74,13 @@ export default function LoginForm({ redirectTo }: { redirectTo?: string }) { onSubmit={handleSubmit} className="flex flex-col gap-5 min-w-[310px] w-fit" > - <Button className="w-full bg-white hover:bg-white hover:text-black"> + <Button + type="button" + onClick={handleGoogleLogin} + className="w-full bg-white hover:bg-white hover:text-black" + > <GoogleIcon /> - <span className="text-black text-sm">Войти через Google</span> + <span className="text-black">Войти через Google</span> </Button> <div className="flex flex-col gap-2.5"> diff --git a/src/components/header/authdialog/RegisterForm.tsx b/src/components/header/authdialog/RegisterForm.tsx index ef22433..604b26c 100644 --- a/src/components/header/authdialog/RegisterForm.tsx +++ b/src/components/header/authdialog/RegisterForm.tsx @@ -8,6 +8,12 @@ import { useAuthContext } from '@/lib/contexts/Auth.context'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; export default function RegisterForm({ redirectTo }: { redirectTo?: string }) { + const API_URL = process.env.NEXT_PUBLIC_API_URL; + + const handleGoogleLogin = () => { + window.location.href = `${API_URL}/api/auth/google/login`; + }; + const [errors, setErrors] = useState<any>({}); const [loading, setLoading] = useState(false); const router = useRouter(); @@ -53,7 +59,11 @@ export default function RegisterForm({ redirectTo }: { redirectTo?: string }) { onSubmit={handleSubmit} className="flex flex-col gap-5 min-w-[310px] w-fit" > - <Button className="w-full bg-white hover:bg-white hover:text-black"> + <Button + type="button" + onClick={handleGoogleLogin} + className="w-full bg-white hover:bg-white hover:text-black" + > <GoogleIcon /> <span className="text-black">Войти через Google</span> </Button> diff --git a/src/components/settings/ChangePassword.tsx b/src/components/settings/ChangePassword.tsx index d437b5e..7721c85 100644 --- a/src/components/settings/ChangePassword.tsx +++ b/src/components/settings/ChangePassword.tsx @@ -21,8 +21,8 @@ export const ChangePasswordField = () => { return; } - if (newPassword.length < 6) { - setError('Пароль должен быть минимум 6 символов'); + if (newPassword.length < 8) { + setError('Пароль должен быть минимум 8 символов'); return; } @@ -47,45 +47,50 @@ export const ChangePasswordField = () => { }; return ( - <div className="flex flex-col gap-[20px] w-[310px]"> - <p className="text-light-violet font-medium">СМЕНА ПАРОЛЯ</p> + <> + <div className="flex flex-col gap-[20px] w-[310px]"> + <p className="text-light-violet font-medium">СМЕНА ПАРОЛЯ</p> - <div className="flex flex-col gap-[10px]"> - <InputField - placeholder="Текущий пароль" - isPassword - type="password" - name="currentPassword" - value={currentPassword} - onChange={(e: any) => setCurrentPassword(e.target.value)} - /> + <div className="flex flex-col gap-[10px]"> + <InputField + placeholder="Текущий пароль" + isPassword + type="password" + name="currentPassword" + value={currentPassword} + onChange={(e: any) => + setCurrentPassword(e.target.value) + } + /> - <InputField - placeholder="Новый пароль" - isPassword - type="password" - name="newPassword" - value={newPassword} - onChange={(e: any) => setNewPassword(e.target.value)} - /> + <InputField + placeholder="Новый пароль" + isPassword + type="password" + name="newPassword" + value={newPassword} + onChange={(e: any) => setNewPassword(e.target.value)} + /> - <InputField - placeholder="Повторите пароль" - isPassword - type="password" - name="confirmPassword" - value={confirmPassword} - onChange={(e: any) => setConfirmPassword(e.target.value)} - /> - </div> - - {error && <p className="text-red text-sm">{error}</p>} + <InputField + placeholder="Повторите пароль" + isPassword + type="password" + name="confirmPassword" + value={confirmPassword} + onChange={(e: any) => + setConfirmPassword(e.target.value) + } + /> + </div> - <Button onClick={handleSubmit} disabled={loading}> - {loading ? 'Смена...' : 'Сменить'} - </Button> + {error && <p className="text-red text-sm">{error}</p>} - <Separator className="bg-violet/30 h-[1px]" /> - </div> + <Button onClick={handleSubmit} disabled={loading}> + {loading ? 'Смена...' : 'Сменить'} + </Button> + </div> + <Separator className="bg-violet/30 h-[1px] w-[900px]" /> + </> ); }; diff --git a/src/components/settings/SecurityPage.tsx b/src/components/settings/SecurityPage.tsx index dd650c4..9c498aa 100644 --- a/src/components/settings/SecurityPage.tsx +++ b/src/components/settings/SecurityPage.tsx @@ -6,6 +6,7 @@ import LogoutButton from './LogoutButton'; import { changeEmail } from '@/lib/api/ChangeEmail'; import { useState } from 'react'; import { ChangePasswordField } from './ChangePassword'; +import { SetPasswordField } from './SetPasswordField'; export default function SecurityPage() { const { user } = useAuthContext(); @@ -42,28 +43,7 @@ export default function SecurityPage() { <> <div className="flex flex-col flex-start gap-[40px] w-[900px] h-[816px]"> <div className="flex flex-col gap-[20px] w-[900px] "> - {showSetPassword && ( - <div className="flex flex-col flex-start gap-[20px] w-[310px] h-[241px]"> - <p className="text-light-violet font-medium"> - ЗАДАТЬ ПАРОЛЬ - </p> - <div className="flex flex-col flex-start gap-[10px] w-[310px] h-[137px]"> - <InputField - placeholder="Введите пароль" - type="password" - name="password" - /> - <InputField - isPassword - placeholder="Повторите пароль" - type="password" - name="repeat_password" - /> - </div> - <Button>Сменить</Button> - <Separator className="bg-violet/30 h-[1px]" /> - </div> - )} + {showSetPassword && <SetPasswordField />} <div className="flex flex-col gap-[20px] w-[310px]"> <p className="text-light-violet font-medium"> @@ -91,7 +71,7 @@ export default function SecurityPage() { <Button onClick={handleChangeEmail}>Сменить</Button> </div> - <Separator className="bg-violet/30 h-[1px]" /> + <Separator className="bg-violet/30 h-[1px] w-[900px]" /> {hasPassword && <ChangePasswordField />} <LogoutButton /> </div> diff --git a/src/components/settings/SetPasswordField.tsx b/src/components/settings/SetPasswordField.tsx new file mode 100644 index 0000000..6030c8f --- /dev/null +++ b/src/components/settings/SetPasswordField.tsx @@ -0,0 +1,76 @@ +'use client'; + +import { useState } from 'react'; +import { InputField } from '@/components/ui/inputfield'; +import { Button } from '@/components/ui/button'; +import { Separator } from '@/components/ui/separator'; +import { setPassword } from '@/lib/api/SetPassword'; + +export const SetPasswordField = () => { + const [newPassword, setNewPassword] = useState(''); + const [repeatPassword, setRepeatPassword] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState<string | null>(null); + + const handleSubmit = async () => { + setError(null); + + if (newPassword !== repeatPassword) { + setError('Пароли не совпадают'); + return; + } + + if (newPassword.length < 8) { + setError('Пароль должен быть минимум 8 символов'); + return; + } + + setLoading(true); + + const res = await setPassword(newPassword, repeatPassword); + + setLoading(false); + + if (res.error) { + setError(res.error.general); + return; + } + + setNewPassword(''); + setRepeatPassword(''); + }; + + return ( + <div className="flex flex-col gap-[20px] w-[310px]"> + <p className="text-light-violet font-medium">ЗАДАТЬ ПАРОЛЬ</p> + + <div className="flex flex-col gap-[10px]"> + <InputField + placeholder="Введите пароль" + isPassword + type="password" + name="new_password" + value={newPassword} + onChange={(e: any) => setNewPassword(e.target.value)} + /> + + <InputField + placeholder="Повторите пароль" + isPassword + type="password" + name="repeat_password" + value={repeatPassword} + onChange={(e: any) => setRepeatPassword(e.target.value)} + /> + </div> + + {error && <p className="text-red text-sm">{error}</p>} + + <Button onClick={handleSubmit} disabled={loading}> + {loading ? 'Сохранение...' : 'Сменить'} + </Button> + + <Separator className="bg-violet/30 h-[1px] w-[900px]" /> + </div> + ); +}; diff --git a/src/lib/api/RefreshUser.ts b/src/lib/api/RefreshUser.ts new file mode 100644 index 0000000..d93d310 --- /dev/null +++ b/src/lib/api/RefreshUser.ts @@ -0,0 +1,37 @@ +const API_URL = process.env.NEXT_PUBLIC_API_URL; + +export type RefreshUserResponse = { + authenticated: boolean; + user: any | null; +}; + +export const refreshUser = async (): Promise<RefreshUserResponse> => { + try { + const res = await fetch(`${API_URL}/api/me`, { + method: 'GET', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + }); + + const data = await res.json().catch(() => null); + + if (!res.ok || !data) { + return { + authenticated: false, + user: null, + }; + } + + return { + authenticated: data.authenticated, + user: data.authenticated ? data.user : null, + }; + } catch (err) { + return { + authenticated: false, + user: null, + }; + } +}; diff --git a/src/lib/api/SetPassword.ts b/src/lib/api/SetPassword.ts new file mode 100644 index 0000000..ee445fc --- /dev/null +++ b/src/lib/api/SetPassword.ts @@ -0,0 +1,52 @@ +const API_URL = process.env.NEXT_PUBLIC_API_URL; + +export type SetPasswordResponse = + | { data: { success: true }; error: null } + | { data: null; error: { general: string } }; + +export const setPassword = async ( + newPassword: string, + repeatPassword: string, +): Promise<SetPasswordResponse> => { + try { + const res = await fetch(`${API_URL}/api/users/password/set`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + new_password: newPassword, + repeat_password: repeatPassword, + }), + }); + + const data = await res.json().catch(() => null); + + if (!res.ok) { + const detail = data?.detail; + + return { + data: null, + error: { + general: + typeof detail === 'string' + ? detail + : detail?.msg || 'Ошибка установки пароля', + }, + }; + } + + return { + data: { success: true }, + error: null, + }; + } catch (err: any) { + return { + data: null, + error: { + general: err?.message || 'Network error', + }, + }; + } +}; |
