Localization | registration

This commit is contained in:
Max 2024-11-20 21:30:01 +03:00
parent 184a47c481
commit 418947022b
10 changed files with 390 additions and 85 deletions

View File

@ -1,28 +1,43 @@
import * as Form from "@radix-ui/react-form"; import * as Form from "@radix-ui/react-form";
import { CrossCircledIcon } from "@radix-ui/react-icons"; import { CrossCircledIcon } from "@radix-ui/react-icons";
import { Button, Card, Heading, Text, TextField } from "@radix-ui/themes"; import { Button, Card, Heading, Text, TextField } from "@radix-ui/themes";
import { useEffect, useState } from "react"; import { useMutation } from "@tanstack/react-query";
import { t } from "i18next";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { axiosLocalhost } from "../../../api/axios/axios";
import UseCapsLock from "../../../hooks/useCapsLock";
import ShowPasswordButton from "../ShowPasswordButton/ShowPasswordButton"; import ShowPasswordButton from "../ShowPasswordButton/ShowPasswordButton";
type TLoginData = {
username: string;
password: string;
};
export default function LoginElement() { export default function LoginElement() {
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [isCapsLockOn, setIsCapsLockOn] = useState(false); const { isCapsLockOn } = UseCapsLock();
const [isError, setIsError] = useState(false);
useEffect(() => { const navigate = useNavigate()
const f = (e: KeyboardEvent) => {
if (e.getModifierState("CapsLock")) {
setIsCapsLockOn(true);
} else {
setIsCapsLockOn(false);
}
};
document.addEventListener("keydown", f); const logInMutation = useMutation({
mutationFn: async (data: TLoginData) => {
await axiosLocalhost.post(
"/login",
JSON.stringify(data)
);
},
return () => { onError: (error, _variables, _context) => {
document.removeEventListener("keydown", f); console.log(error);
}; setIsError(true);
}, []); },
onSuccess: () => {
navigate("/")
},
});
return ( return (
<Card <Card
@ -32,16 +47,30 @@ export default function LoginElement() {
translate-x-[-50%] translate-y-[-50%]" translate-x-[-50%] translate-y-[-50%]"
> >
<Heading weight={"medium"} className="mb-4 text-center"> <Heading weight={"medium"} className="mb-4 text-center">
Log in form {t("loginForm")}
</Heading> </Heading>
<Form.Root> <Form.Root
onSubmit={(e) => {
e.preventDefault();
let formData = new FormData(
document.querySelector("form") as HTMLFormElement
);
let loginData: TLoginData = {
password: (formData.get("password") as string) || "",
username: (formData.get("username") as string) || "",
};
logInMutation.mutate(loginData);
}}
>
<Form.Field className="mb-2.5 gap-0.5 grid" name="username"> <Form.Field className="mb-2.5 gap-0.5 grid" name="username">
<div className="flex items-baseline justify-between gap-2"> <div className="flex items-baseline justify-between gap-2">
<Form.Label> <Form.Label>
<Heading size={"3"}>Username</Heading> <Text size={"4"}>{t("username")}</Text>
</Form.Label> </Form.Label>
<Form.Message match="valueMissing"> <Form.Message match="valueMissing">
<Text color="red">Please enter your username</Text> <Text color="red">{t("errors.enterUsername")}</Text>
</Form.Message> </Form.Message>
</div> </div>
<Form.Control asChild> <Form.Control asChild>
@ -70,10 +99,10 @@ export default function LoginElement() {
<Form.Field className="mb-2.5 gap-0.5 grid" name="password"> <Form.Field className="mb-2.5 gap-0.5 grid" name="password">
<div className="flex items-baseline justify-between gap-2"> <div className="flex items-baseline justify-between gap-2">
<Form.Label> <Form.Label>
<Heading size={"3"}>Password</Heading> <Text size={"4"}>{t("password")}</Text>
</Form.Label> </Form.Label>
<Form.Message match="valueMissing"> <Form.Message match="valueMissing">
<Text color="red">Please enter your password</Text> <Text color="red">{t("errors.enterPassword")}</Text>
</Form.Message> </Form.Message>
</div> </div>
<Form.Control asChild> <Form.Control asChild>
@ -103,60 +132,17 @@ export default function LoginElement() {
</TextField.Root> </TextField.Root>
</Form.Control> </Form.Control>
<Text size={"1"} hidden={!isCapsLockOn}> <Text size={"1"} hidden={!isCapsLockOn}>
Caps lock is on {t("capsLogWarning")}
</Text> </Text>
</Form.Field> </Form.Field>
{/* <Form.Field <Text color="red" hidden={!isError}>
className="mb-2.5 gap-0.5 grid" {t("errors.invalidLoginData")}
name="conf-password" </Text>
>
<div className="flex items-baseline justify-between">
<Form.Label>
<Heading size={"3"}>Confirm password</Heading>
</Form.Label>
<Form.Message match="valueMissing">
<Text color="red">Please enter your password</Text>
</Form.Message>
<Form.Message
match={(value, formData) =>
value !== formData.get("password")
}
>
<Text color="red">Passwords must be the same</Text>
</Form.Message>
</div>
<Form.Control asChild>
<TextField.Root
type={showConfPassword ? "text" : "password"}
required
>
<Form.ValidityState>
{(validity) => (
<TextField.Slot
side="right"
color={
validity
? validity.valid
? undefined
: "red"
: undefined
}
>
<ShowPasswordButton
isShown={showConfPassword}
setIsShown={setShowConfPassword}
/>
</TextField.Slot>
)}
</Form.ValidityState>
</TextField.Root>
</Form.Control>
</Form.Field> */}
<Form.Submit className="flex justify-center w-full"> <Form.Submit className="flex justify-center" asChild>
<Button type="submit" className="w-1/3"> <Button type="submit" className="w-1/3 m-auto">
<Text size={"3"}>Submit</Text> <Text size={"3"}>{t("submit")}</Text>
</Button> </Button>
</Form.Submit> </Form.Submit>
</Form.Root> </Form.Root>

View File

@ -1,10 +1,16 @@
import { useState } from "react";
import LoginElement from "./LoginElement/LoginElement"; import LoginElement from "./LoginElement/LoginElement";
import RegisterElement from "./RegisterElement/RegisterElement";
export default function LoginRegisterPage() { export function LoginPage() {
const [isRegister, setIsRegister] = useState(false)
return ( return (
<LoginElement /> <LoginElement />
) )
} }
export function RegisterPage() {
return (
<RegisterElement />
)
}

View File

@ -0,0 +1,241 @@
import * as Form from "@radix-ui/react-form";
import { CrossCircledIcon } from "@radix-ui/react-icons";
import { Button, Card, Heading, Text, TextField } from "@radix-ui/themes";
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { axiosLocalhost } from "../../../api/axios/axios";
import UseCapsLock from "../../../hooks/useCapsLock";
import ShowPasswordButton from "../ShowPasswordButton/ShowPasswordButton";
type TRegisterData = {
username: string;
password: string;
email: string;
};
export default function RegisterElement() {
const [showPassword, setShowPassword] = useState(false);
const [showConfPassword, setShowConfPassword] = useState(false);
const { isCapsLockOn } = UseCapsLock();
const { t } = useTranslation();
const [isError, setIsError] = useState(false);
const navigate = useNavigate();
const registerMutation = useMutation({
mutationFn: async (data: TRegisterData) => {
await axiosLocalhost.post("/users", JSON.stringify(data));
},
onError: (error, _variables, _context) => {
console.log(error);
setIsError(true);
},
onSuccess: () => {
navigate("/");
},
});
return (
<Card
size={"2"}
className="absolute w-[25rem] min-w-[20rem]
left-[50%] top-[50%]
translate-x-[-50%] translate-y-[-50%]"
>
<Heading weight={"medium"} className="mb-4 text-center">
{t("registerForm")}
</Heading>
<Form.Root
onSubmit={(e) => {
e.preventDefault();
let formData = new FormData(
document.querySelector("form") as HTMLFormElement
);
let registerData: TRegisterData = {
password: (formData.get("password") as string) || "",
username: (formData.get("username") as string) || "",
email: (formData.get("email") as string) || "",
};
registerMutation.mutate(registerData);
}}
>
<Form.Field className="mb-2.5 gap-0.5 grid" name="username">
<div className="flex items-baseline justify-between gap-2">
<Form.Label>
<Text size={"4"}>{t("username")}</Text>
</Form.Label>
<Form.Message match="valueMissing">
<Text color="red">{t("errors.enterUsername")}</Text>
</Form.Message>
</div>
<Form.Control asChild>
<TextField.Root type="text" required>
<Form.ValidityState>
{(validity) => (
<TextField.Slot
side="right"
color="red"
className={
validity
? validity.valid
? "hidden"
: "mr-0.5"
: "hidden"
}
>
<CrossCircledIcon />
</TextField.Slot>
)}
</Form.ValidityState>
</TextField.Root>
</Form.Control>
</Form.Field>
<Form.Field className="mb-2.5 gap-0.5 grid" name="email">
<div className="flex items-baseline justify-between gap-2">
<Form.Label>
<Text size={"4"}>{t("email")}</Text>
</Form.Label>
<Form.Message match="valueMissing">
<Text color="red">{t("errors.enterEmail")}</Text>
</Form.Message>
<Form.Message match="typeMismatch">
<Text color="red">{t("errors.invalidEmail")}</Text>
</Form.Message>
</div>
<Form.Control asChild>
<TextField.Root type="email" required>
<Form.ValidityState>
{(validity) => (
<TextField.Slot
side="right"
color="red"
className={
validity
? validity.valid
? "hidden"
: "mr-0.5"
: "hidden"
}
>
<CrossCircledIcon />
</TextField.Slot>
)}
</Form.ValidityState>
</TextField.Root>
</Form.Control>
</Form.Field>
<Form.Field className="mb-2.5 gap-0.5 grid" name="password">
<div className="flex items-baseline justify-between gap-2">
<Form.Label>
<Text size={"4"}>{t("password")}</Text>
</Form.Label>
<Form.Message match="valueMissing">
<Text color="red">{t("errors.enterPassword")}</Text>
</Form.Message>
</div>
<Form.Control asChild>
<TextField.Root
type={showPassword ? "text" : "password"}
required
>
<Form.ValidityState>
{(validity) => (
<TextField.Slot
side="right"
color={
validity
? validity.valid
? undefined
: "red"
: undefined
}
>
<ShowPasswordButton
isShown={showPassword}
setIsShown={setShowPassword}
/>
</TextField.Slot>
)}
</Form.ValidityState>
</TextField.Root>
</Form.Control>
<Text size={"1"} hidden={!isCapsLockOn}>
{t("capsLogWarning")}
</Text>
</Form.Field>
<Form.Field
className="mb-2.5 gap-0.5 grid"
name="conf-password"
>
<div className="flex items-baseline justify-between">
<Form.Label>
<Text size={"4"}>{t("confirmPassword")}</Text>
</Form.Label>
<Form.Message match="valueMissing">
<Text color="red">{t("errors.enterPassword")}</Text>
</Form.Message>
<Form.Message
match={(value, formData) =>
value !== formData.get("password")
}
>
<Text color="red">
{t("errors.passwordsMismatch")}
</Text>
</Form.Message>
</div>
<Form.Control asChild>
<TextField.Root
type={showConfPassword ? "text" : "password"}
required
>
<Form.ValidityState>
{(validity) => (
<TextField.Slot
side="right"
color={
validity
? validity.valid
? undefined
: "red"
: undefined
}
>
<ShowPasswordButton
isShown={showConfPassword}
setIsShown={setShowConfPassword}
/>
</TextField.Slot>
)}
</Form.ValidityState>
</TextField.Root>
</Form.Control>
<Text size={"1"} hidden={!isCapsLockOn}>
{t("capsLogWarning")}
</Text>
</Form.Field>
<Text color="red" hidden={!isError}>
{t("errors.invalidRegisterData")}
</Text>
<Form.Submit className="flex justify-center" asChild>
<Button type="submit" className="w-1/3 m-auto">
<Text size={"3"}>{t("submit")}</Text>
</Button>
</Form.Submit>
</Form.Root>
</Card>
);
}

View File

@ -2,7 +2,7 @@ import axios from "axios";
export const axiosLocalhost = axios.create( export const axiosLocalhost = axios.create(
{ {
baseURL: `http://localhost:9876/`, baseURL: `http://127.0.0.1:9876/`,
withCredentials: true, withCredentials: true,
headers: { headers: {

View File

@ -0,0 +1,27 @@
import { useEffect, useState } from "react";
export default function UseCapsLock() {
const [isCapsLockOn, setIsCapsLockOn] = useState(false);
useEffect(() => {
const f = (e: KeyboardEvent) => {
if (e.getModifierState("CapsLock")) {
setIsCapsLockOn(true);
} else {
setIsCapsLockOn(false);
}
};
document.addEventListener("keydown", f);
return () => {
document.removeEventListener("keydown", f);
};
}, []);
return {
isCapsLockOn
}
}

View File

@ -1,6 +1,26 @@
const en = { const en = {
hello: "hello!", hello: "hello!",
search: "Search...", search: "Search...",
} username: "Username",
email: "Email",
password: "Password",
confirmPassword: "Confirm password",
submit: "Submit",
capsLogWarning: "CapsLock is on",
registerForm: "Register",
loginForm: "Log in",
errors: {
enterUsername: "Please enter your username",
enterEmail: "Please enter your email",
invalidEmail: "Please enter correct email",
enterPassword: "Please enter your password",
passwordsMismatch: "Passwords must be the same",
invalidLoginData: "Invalid username or password",
invalidRegisterData: "Invalid register data",
},
};
export default en; export default en;

View File

@ -1,6 +1,27 @@
const ru = { const ru = {
hello: "Привет!", hello: "Привет!",
search: "Поиск...", search: "Поиск...",
} username: "Имя пользователя",
email: "Электронная почта",
password: "Пароль",
confirmPassword: "Подтвердите пароль",
submit: "Подтвердить",
capsLogWarning: "Включён CapsLock",
registerForm: "Регистрация",
loginForm: "Вход",
errors: {
enterUsername: "Пожалуйста, введите ваше имя пользователя",
enterEmail: "Пожалуйста, введите свой адрес электронной почты",
invalidEmail: "Пожалуйста, введите корректный адрес электронной почты",
enterPassword: "Пожалуйста, введите пароль",
passwordsMismatch: "Пароли должны быть одинаковыми",
invalidLoginData: "Неверное имя пользователя или пароль",
invalidRegisterData:
"Пользователь с таким адресом электронной почты или именем пользователя уже существует",
},
};
export default ru; export default ru;

View File

@ -1,6 +1,6 @@
import { Text } from "@radix-ui/themes"; import { Text } from "@radix-ui/themes";
import { createRoutesFromElements, Route, useRouteError } from "react-router-dom"; import { createRoutesFromElements, Route, useRouteError } from "react-router-dom";
import LoginRegisterPage from "../Pages/LoginRegisterPage/LoginRegisterPage"; import { LoginPage, RegisterPage } from "../Pages/LoginRegisterPage/LoginRegisterPage";
import MainPage from "../Pages/MainPage/MainPage"; import MainPage from "../Pages/MainPage/MainPage";
@ -28,9 +28,13 @@ export const routes = createRoutesFromElements(
<Route <Route
path="/login" path="/login"
errorElement={<ErrorBoundary />} errorElement={<ErrorBoundary />}
element={<LoginRegisterPage />} element={<LoginPage />}
> />
</Route> <Route
path="/register"
errorElement={<ErrorBoundary />}
element={<RegisterPage />}
/>
</> </>
) )

View File

@ -1,7 +1,7 @@
package global package global
const PathForCookies = "/" const PathForCookies = "/"
const DomainForCookies = "localhost" const DomainForCookies = "127.0.0.1"
const SecureForCookies = false const SecureForCookies = false
const HttpOnlyForCookies = false const HttpOnlyForCookies = false

View File

@ -4,7 +4,7 @@ import "github.com/gin-gonic/gin"
func CORSMiddleware() gin.HandlerFunc { func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173") c.Writer.Header().Set("Access-Control-Allow-Origin", "http://127.0.0.1:5173")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set( c.Writer.Header().Set(
"Access-Control-Allow-Headers", "Access-Control-Allow-Headers",