refactoring
This commit is contained in:
44
src/app/consent/components/ConsentButton/ConsentButton.tsx
Normal file
44
src/app/consent/components/ConsentButton/ConsentButton.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
"use client";
|
||||
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Button, Text } from "@mantine/core";
|
||||
import SERVICES from "@/constants/services";
|
||||
import { ServiceCode } from "@/enums/ServiceCode";
|
||||
import { RootState } from "@/lib/store";
|
||||
import ServiceData from "@/types/ServiceData";
|
||||
|
||||
const ConsentButton: FC = () => {
|
||||
const serviceCode = useSelector(
|
||||
(state: RootState) => state.targetService.serviceCode
|
||||
);
|
||||
const [serviceData, setServiceData] = useState<ServiceData>();
|
||||
|
||||
useEffect(() => {
|
||||
if (serviceCode === ServiceCode.UNDEFINED) {
|
||||
redirect("services");
|
||||
}
|
||||
setServiceData(SERVICES[serviceCode]);
|
||||
}, [serviceCode]);
|
||||
|
||||
const confirmAccess = () => {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => confirmAccess()}
|
||||
variant={"filled"}>
|
||||
Войти
|
||||
</Button>
|
||||
<Text
|
||||
fz={"h4"}
|
||||
ta="center">
|
||||
Сервис {serviceData?.name} получит{" "}
|
||||
{serviceData?.requiredAccesses}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConsentButton;
|
||||
@ -0,0 +1,14 @@
|
||||
.container {
|
||||
@media (min-width: 48em) {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.gray-text {
|
||||
@mixin dark {
|
||||
color: #807e7e;
|
||||
}
|
||||
@mixin light {
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
22
src/app/consent/components/ConsentForm/ConsentForm.tsx
Normal file
22
src/app/consent/components/ConsentForm/ConsentForm.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { FC } from "react";
|
||||
import { Stack, Text } from "@mantine/core";
|
||||
import ConfirmAccessButton from "@/app/consent/components/ConsentButton/ConsentButton";
|
||||
import styles from "./ConsentForm.module.css";
|
||||
|
||||
const ConsentForm: FC = () => {
|
||||
return (
|
||||
<Stack
|
||||
align={"center"}
|
||||
className={styles.container}>
|
||||
<ConfirmAccessButton />
|
||||
<Text
|
||||
className={styles["gray-text"]}
|
||||
ta="center">
|
||||
Данные из LogiDex ID передаются в другой сервис и обрабатываются
|
||||
в соответствии с правилами этого сервиса
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConsentForm;
|
||||
16
src/app/consent/page.tsx
Normal file
16
src/app/consent/page.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import PageContainer from "@/components/PageContainer/PageContainer";
|
||||
import PageItem from "@/components/PageBlock/PageItem";
|
||||
import Logo from "@/components/Logo/Logo";
|
||||
import ConsentForm from "@/app/consent/components/ConsentForm/ConsentForm";
|
||||
|
||||
|
||||
export default function ConfirmAccessPage() {
|
||||
return (
|
||||
<PageContainer center>
|
||||
<PageItem fullScreenMobile>
|
||||
<Logo title={"Вход с помощью LogiDex ID"} />
|
||||
<ConsentForm />
|
||||
</PageItem>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
15
src/app/create-id/page.tsx
Normal file
15
src/app/create-id/page.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import LoginForm from "@/components/LoginForm/LoginForm";
|
||||
import Logo from "@/components/Logo/Logo";
|
||||
import PageItem from "@/components/PageBlock/PageItem";
|
||||
import PageContainer from "@/components/PageContainer/PageContainer";
|
||||
|
||||
export default function CreateIdPage() {
|
||||
return (
|
||||
<PageContainer center>
|
||||
<PageItem fullScreenMobile>
|
||||
<Logo title={"Создание аккаунта"} />
|
||||
<LoginForm isCreatingId />
|
||||
</PageItem>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
5
src/app/global.css
Normal file
5
src/app/global.css
Normal file
@ -0,0 +1,5 @@
|
||||
body {
|
||||
@mixin light {
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
}
|
||||
57
src/app/layout.tsx
Normal file
57
src/app/layout.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import "@mantine/core/styles.css";
|
||||
import React, { ReactNode } from "react";
|
||||
import {
|
||||
ColorSchemeScript,
|
||||
mantineHtmlProps,
|
||||
MantineProvider,
|
||||
} from "@mantine/core";
|
||||
import Header from "@/components/Header/Header";
|
||||
import { theme } from "@/theme";
|
||||
import "@/app/global.css";
|
||||
import Footer from "@/components/Footer/Footer";
|
||||
import ReduxProvider from "@/providers/ReduxProvider";
|
||||
|
||||
export const metadata = {
|
||||
title: "LogiDex ID",
|
||||
description: "LogiDex ID",
|
||||
};
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: Props) {
|
||||
return (
|
||||
<html
|
||||
lang="ru"
|
||||
{...mantineHtmlProps}>
|
||||
<head>
|
||||
<ColorSchemeScript defaultColorScheme={"auto"} />
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
href="/favicon.svg"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="global.css"
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="minimum-scale=1, initial-scale=1, width=device-width, user-scalable=no"
|
||||
/>
|
||||
<title />
|
||||
</head>
|
||||
<body>
|
||||
<MantineProvider
|
||||
theme={theme}
|
||||
defaultColorScheme={"auto"}>
|
||||
<ReduxProvider>
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
</ReduxProvider>
|
||||
</MantineProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
15
src/app/page.tsx
Normal file
15
src/app/page.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import LoginForm from "@/components/LoginForm/LoginForm";
|
||||
import Logo from "@/components/Logo/Logo";
|
||||
import PageItem from "@/components/PageBlock/PageItem";
|
||||
import PageContainer from "@/components/PageContainer/PageContainer";
|
||||
|
||||
export default function MainPage() {
|
||||
return (
|
||||
<PageContainer center>
|
||||
<PageItem fullScreenMobile>
|
||||
<Logo title={"Вход"} />
|
||||
<LoginForm />
|
||||
</PageItem>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
.container {
|
||||
width: 400px;
|
||||
|
||||
@media (max-width: 48em) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
66
src/app/services/components/ServicesList/ServicesList.tsx
Normal file
66
src/app/services/components/ServicesList/ServicesList.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { Button, Stack, Title } from "@mantine/core";
|
||||
import styles from "@/app/services/components/ServicesList/ServicesList.module.css";
|
||||
import TitleWithLines from "@/components/TitleWithLines/TitleWithLines";
|
||||
import SERVICES from "@/constants/services";
|
||||
import { ServiceCode } from "@/enums/ServiceCode";
|
||||
import { setTargetService } from "@/lib/features/targetService/targetServiceSlice";
|
||||
import { useAppDispatch } from "@/lib/store";
|
||||
import ServiceData from "@/types/ServiceData";
|
||||
|
||||
const ServicesList = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const services = useMemo(
|
||||
() =>
|
||||
Object.entries(SERVICES)
|
||||
.filter(([key]) => key !== ServiceCode.UNDEFINED)
|
||||
.map(([, value]) => value),
|
||||
[SERVICES]
|
||||
);
|
||||
|
||||
const onServiceClick = (service: ServiceData) => {
|
||||
dispatch(setTargetService(service.code));
|
||||
redirect("consent");
|
||||
};
|
||||
|
||||
const getServiceButton = (service: ServiceData, key: number) => {
|
||||
return (
|
||||
<Button
|
||||
key={key}
|
||||
size={"xl"}
|
||||
onClick={() => onServiceClick(service)}>
|
||||
<Stack gap={0}>
|
||||
<Title order={4}>{service.name}</Title>
|
||||
</Stack>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const getServiceInDevelopment = (title: string) => {
|
||||
return (
|
||||
<Button
|
||||
size={"xl"}
|
||||
onClick={() => {}}
|
||||
disabled>
|
||||
<Stack gap={0}>
|
||||
<Title order={4}>{title}</Title>
|
||||
</Stack>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
className={styles.container}
|
||||
gap={"lg"}>
|
||||
{services.map((service, i) => getServiceButton(service, i))}
|
||||
<TitleWithLines title="Скоро будет" />
|
||||
{getServiceInDevelopment("Analytics")}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicesList;
|
||||
15
src/app/services/page.tsx
Normal file
15
src/app/services/page.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import Logo from "@/components/Logo/Logo";
|
||||
import PageItem from "@/components/PageBlock/PageItem";
|
||||
import PageContainer from "@/components/PageContainer/PageContainer";
|
||||
import ServicesList from "@/app/services/components/ServicesList/ServicesList";
|
||||
|
||||
export default function ServicesPage() {
|
||||
return (
|
||||
<PageContainer center>
|
||||
<PageItem fullScreenMobile>
|
||||
<Logo title={"Сервисы LogiDex"} />
|
||||
<ServicesList />
|
||||
</PageItem>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
"use client";
|
||||
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Button } from "@mantine/core";
|
||||
import { setLastSendTime } from "@/lib/features/verification/verificationSlice";
|
||||
import { RootState } from "@/lib/store";
|
||||
import { MAX_COUNTDOWN } from "@/constants/verification";
|
||||
|
||||
const ResendVerificationCode: FC = () => {
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
const dispatch = useDispatch();
|
||||
const lastSendTime = useSelector(
|
||||
(state: RootState) => state.verification.lastSendTime
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setLastSendTime(Date.now()));
|
||||
setCountdown(MAX_COUNTDOWN);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastSendTime) {
|
||||
const elapsed = Math.floor((Date.now() - lastSendTime) / 1000);
|
||||
const remaining = Math.max(0, MAX_COUNTDOWN - elapsed);
|
||||
setCountdown(remaining);
|
||||
}
|
||||
}, [lastSendTime]);
|
||||
|
||||
useEffect(() => {
|
||||
if (countdown > 0) {
|
||||
const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [countdown]);
|
||||
|
||||
const sendCode = () => {
|
||||
if (countdown > 0) return;
|
||||
};
|
||||
|
||||
const handleResend = () => {
|
||||
sendCode();
|
||||
dispatch(setLastSendTime(Date.now()));
|
||||
setCountdown(MAX_COUNTDOWN);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant={"outline"}
|
||||
disabled={countdown > 0}
|
||||
onClick={handleResend}>
|
||||
{countdown > 0
|
||||
? `Отправить код через ${countdown}с`
|
||||
: "Отправить код"}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResendVerificationCode;
|
||||
@ -0,0 +1,11 @@
|
||||
.pin-input-root {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.pin-input {
|
||||
font-size: 17px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { Button, PinInput, Stack } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import ResendVerificationCode from "@/app/verify-phone/components/ResendVerificationCode/ResendVerificationCode";
|
||||
import style from "@/app/verify-phone/components/VerifyPhoneForm/VerifyPhone.module.css";
|
||||
|
||||
type VerifyNumberForm = {
|
||||
code: string;
|
||||
};
|
||||
|
||||
const VerifyPhoneForm: FC = () => {
|
||||
const form = useForm<VerifyNumberForm>({
|
||||
initialValues: {
|
||||
code: "",
|
||||
},
|
||||
validate: {
|
||||
code: code => code.length !== 6 && "Введите весь код",
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (values: VerifyNumberForm) => {
|
||||
console.log(values);
|
||||
|
||||
redirect("/services");
|
||||
};
|
||||
|
||||
const navigateToLogin = () => redirect("/");
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack>
|
||||
<PinInput
|
||||
length={6}
|
||||
placeholder="_"
|
||||
oneTimeCode
|
||||
size={"md"}
|
||||
classNames={{
|
||||
root: style["pin-input-root"],
|
||||
input: style["pin-input"],
|
||||
}}
|
||||
{...form.getInputProps("code")}
|
||||
/>
|
||||
<Button
|
||||
variant={"filled"}
|
||||
type={"submit"}
|
||||
disabled={form.values.code.length !== 6}>
|
||||
Подтвердить
|
||||
</Button>
|
||||
<ResendVerificationCode />
|
||||
<Button
|
||||
variant={"outline"}
|
||||
onClick={navigateToLogin}>
|
||||
Назад
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyPhoneForm;
|
||||
15
src/app/verify-phone/page.tsx
Normal file
15
src/app/verify-phone/page.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import Logo from "@/components/Logo/Logo";
|
||||
import PageItem from "@/components/PageBlock/PageItem";
|
||||
import PageContainer from "@/components/PageContainer/PageContainer";
|
||||
import VerifyPhoneForm from "@/app/verify-phone/components/VerifyPhoneForm/VerifyPhoneForm";
|
||||
|
||||
export default function CreateIdPage() {
|
||||
return (
|
||||
<PageContainer center>
|
||||
<PageItem fullScreenMobile>
|
||||
<Logo title={"Введите код, отправленный на номер"} />
|
||||
<VerifyPhoneForm />
|
||||
</PageItem>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user