Skip to content

Commit

Permalink
Merge pull request #13 from Quiddlee/add_login_page_draft
Browse files Browse the repository at this point in the history
Add login page draft
  • Loading branch information
Tedzury authored Dec 18, 2023
2 parents 757e639 + 1557b26 commit adc131e
Show file tree
Hide file tree
Showing 41 changed files with 1,783 additions and 76 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = {
rules: {
"import/no-extraneous-dependencies": 0,
"react/function-component-definition": 0,
"react/jsx-props-no-spreading": 0,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'sort-imports': ['error', {ignoreCase: true, ignoreDeclarationSort: true}],
'import/order': [
Expand Down
903 changes: 897 additions & 6 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"@hookform/resolvers": "^3.3.2",
"@lit/react": "^1.0.2",
"@material/web": "^1.0.1",
"@types/react-test-renderer": "^18.0.7",
"firebase": "^10.7.1",
"overlayscrollbars": "^2.4.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand All @@ -35,6 +37,7 @@
"@types/node": "^20.10.3",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@types/toastify-js": "^1.12.3",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"@vitejs/plugin-react": "^4.2.0",
Expand Down
39 changes: 11 additions & 28 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,18 @@
import { createHashRouter, RouterProvider } from 'react-router-dom';
import { RouterProvider } from 'react-router-dom';

import MainLayout from '@/layouts/MainLayout';
import LoginPage from '@pages/LoginPage';
import MainPage from '@pages/MainPage';
import WelcomePage from '@pages/WelcomePage';
import router from '@/router/router';

import ROUTES from './shared/constatns/routes';

const router = createHashRouter([
{
path: '/',
element: <MainLayout />,
children: [
{
path: ROUTES.WELCOME_PAGE,
element: <WelcomePage />,
},
{
path: ROUTES.LOGIN,
element: <LoginPage />,
},
{
path: ROUTES.MAIN,
element: <MainPage />,
},
],
},
]);
import AuthProvider from './shared/Context/AuthContext';
import LanguageProvider from './shared/Context/LanguageContext';

const App = () => {
return <RouterProvider router={router} />;
return (
<AuthProvider>
<LanguageProvider>
<RouterProvider router={router} />
</LanguageProvider>
</AuthProvider>
);
};

export default App;
Empty file removed src/components/.gitkeep
Empty file.
12 changes: 12 additions & 0 deletions src/components/loginReg/FormInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

import { createComponent } from '@lit/react';
import { MdOutlinedTextField } from '@material/web/textfield/outlined-text-field';

const FormInput = createComponent({
react: React,
tagName: 'md-outlined-text-field',
elementClass: MdOutlinedTextField,
});

export default FormInput;
28 changes: 28 additions & 0 deletions src/components/loginReg/PassVisibilityIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';

import { createComponent } from '@lit/react';
import { MdIcon } from '@material/web/icon/icon';
import { MdIconButton } from '@material/web/iconbutton/icon-button';

const Icon = createComponent({
react: React,
tagName: 'md-icon',
elementClass: MdIcon,
});

const IconSlot = createComponent({
react: React,
tagName: 'md-icon-button',
elementClass: MdIconButton,
});

const PassVisibilityIcon = ({ onClick }: { onClick: () => void }) => {
return (
<IconSlot toggle slot="trailing-icon" onClick={onClick}>
<Icon>visibility</Icon>
<Icon slot="selected">visibility_off</Icon>
</IconSlot>
);
};

export default PassVisibilityIcon;
12 changes: 12 additions & 0 deletions src/components/loginReg/SubmitBtn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

import { createComponent } from '@lit/react';
import { MdFilledTonalButton } from '@material/web/button/filled-tonal-button';

const SubmitBtn = createComponent({
react: React,
tagName: 'md-filled-tonal-button',
elementClass: MdFilledTonalButton,
});

export default SubmitBtn;
14 changes: 14 additions & 0 deletions src/firebase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { initializeApp } from 'firebase/app';

const firebaseConfig = {
apiKey: 'AIzaSyAijnqgoeQIQJu_lKQ6fgNC7a0fIEuND2c',
authDomain: 'graphiql-app-47127.firebaseapp.com',
projectId: 'graphiql-app-47127',
storageBucket: 'graphiql-app-47127.appspot.com',
messagingSenderId: '1033759243197',
appId: '1:1033759243197:web:3757214e041ae1ab0bf0d4',
};

const initFirebaseApp = () => initializeApp(firebaseConfig);

export default initFirebaseApp;
7 changes: 3 additions & 4 deletions src/layouts/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import { Outlet } from 'react-router-dom';

const MainLayout = () => {
return (
<main>
<header>Here will be header</header>
<section>
<main className="mx-auto flex min-h-[100dvh] max-w-[1440px] flex-col">
<section className="flex h-full w-full grow items-center justify-center">
<Outlet />
</section>
<footer>Here will be footer</footer>
<footer className="grow-0">Here will be footer</footer>
</main>
);
};
Expand Down
19 changes: 19 additions & 0 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
const en = {
loginPage: {
title: 'Log in',
subtitle: 'to continue to GraphiQL 🚀',
emailPlaceHold: 'Email',
passPlaceHold: 'Password',
btnTitle: 'Log in',
linkClue: "Don't have an account yet?",
linkTitle: 'Sign up',
},
signUpPage: {
title: 'Sign in',
subtitle: 'to continue to GraphiQL 🚀',
emailPlaceHold: 'Email',
passPlaceHold: 'Password',
confPassPlaceHold: 'Confirm Password',
btnTitle: 'Sign up',
linkClue: 'Already have an account?',
linkTitle: 'Log in',
},
header: { h1: { h2: 'english' } },
button: 'Press me',
};
Expand Down
19 changes: 19 additions & 0 deletions src/locales/ru.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
const ru = {
loginPage: {
title: 'Войти',
subtitle: 'чтобы воспользоваться GraphiQL 🚀',
emailPlaceHold: 'Эл. почта',
passPlaceHold: 'Пароль',
btnTitle: 'Войти',
linkClue: 'Ещё нет аккаунта?',
linkTitle: 'Зарегистрироваться',
},
signUpPage: {
title: 'Зарегистрироваться',
subtitle: 'чтобы воспользоваться GraphiQL 🚀',
emailPlaceHold: 'Эл. почта',
passPlaceHold: 'Пароль',
confPassPlaceHold: 'Подтв. пароль',
btnTitle: 'Зарегистрироваться',
linkClue: 'Уже есть аккаунт?',
linkTitle: 'Войти',
},
header: { h1: { h2: 'русский' } },
button: 'Нажми',
};
Expand Down
9 changes: 4 additions & 5 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import ReactDOM from 'react-dom/client';

import App from '@/App';

import LanguageProvider from './shared/Context/LanguageContext';

import initFirebaseApp from './firebase';
import '@/styles/index.css';

initFirebaseApp();

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<LanguageProvider>
<App />
</LanguageProvider>
<App />
</React.StrictMode>,
);
106 changes: 102 additions & 4 deletions src/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,103 @@
const LoginPage = () => {
return <section>Here is my fancy login page!</section>;
};
import { useState } from 'react';

export default LoginPage;
import { yupResolver } from '@hookform/resolvers/yup';
import { TextFieldType } from '@material/web/textfield/outlined-text-field';
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
import { useForm } from 'react-hook-form';
import { Link, useNavigate } from 'react-router-dom';

import PassVisibilityIcon from '@/components/loginReg/PassVisibilityIcon';
import AUTH_ERRORS from '@/shared/constants/authErrors';
import ROUTES from '@/shared/constants/routes';
import { loginValidationSchema } from '@/shared/constants/validationSchema';
import useAuth from '@/shared/Context/authHook';
import useLanguage from '@/shared/Context/hooks';
import notationLocalizer from '@/shared/helpers/notationLocalizer';
import switchPassType from '@/shared/helpers/switchPassType';
import toastifyNotation from '@/shared/helpers/toastifyNotation';
import { ErrorType, TextInputProps } from '@/shared/types';
import FormInput from '@components/loginReg/FormInput';
import SubmitBtn from '@components/loginReg/SubmitBtn';

export default function LoginPage() {
const navigate = useNavigate();
const [passType, setPassType] = useState('password');
const { translation, language } = useLanguage();
const { title, subtitle, emailPlaceHold, passPlaceHold, btnTitle, linkClue, linkTitle } = translation.loginPage;
const { logInAuth } = useAuth();

const {
register,
handleSubmit,
formState: { errors, isValid },
reset,
} = useForm({
resolver: yupResolver(loginValidationSchema),
mode: 'all',
});

async function onSubmit({ email, password }: { email: string; password: string }) {
const auth = getAuth();
try {
const { user } = await signInWithEmailAndPassword(auth, email, password);
if (user) {
logInAuth(user.email as string);
reset();
navigate(`/${ROUTES.MAIN}`);
}
return null;
} catch (e) {
if ((e as ErrorType).code === AUTH_ERRORS.INVALID_EMAIL)
return toastifyNotation(notationLocalizer(language, 'code8'));
if ((e as ErrorType).code === AUTH_ERRORS.INVALID_PASS)
return toastifyNotation(notationLocalizer(language, 'code9'));
return toastifyNotation(notationLocalizer(language, 'code11'));
}
}

return (
<section className="mx-5 flex items-center justify-center">
<article className="w-[560px] rounded-[30px] bg-surface-container px-7 py-[60px] sm:px-20">
<h1 className="text-center text-2xl font-[400] text-on-surface">{title}</h1>
<h2 className="mt-3 text-center text-base font-[400] text-on-surface-variant">{subtitle}</h2>
<form noValidate className="mt-8" onSubmit={handleSubmit(onSubmit)}>
<div className="relative">
<FormInput
style={{ width: '100%' }}
{...(register('email') as TextInputProps)}
type="email"
placeholder={emailPlaceHold}
label={emailPlaceHold}
/>
<p className="absolute left-4 top-[62px] text-sm font-[400] text-on-surface">
{notationLocalizer(language, errors.email?.message)}
</p>
</div>
<div className="relative mt-12">
<FormInput
className="w-full"
{...(register('password') as TextInputProps)}
type={passType as TextFieldType}
placeholder={passPlaceHold}
label={passPlaceHold}
>
<PassVisibilityIcon onClick={() => setPassType((prev) => switchPassType(prev))} />
</FormInput>
<p className="absolute left-4 top-[62px] text-sm font-[400] text-on-surface">
{notationLocalizer(language, errors.password?.message)}
</p>
</div>
<SubmitBtn className="mt-[52px] w-full" disabled={!isValid}>
{btnTitle}
</SubmitBtn>
</form>
<p className="mt-8 text-center text-sm font-[400] text-on-surface-variant">
{linkClue}{' '}
<Link className="text-primary" to={`/${ROUTES.AUTH}/${ROUTES.SIGNUP}`}>
{linkTitle}
</Link>
</p>
</article>
</section>
);
}
Loading

0 comments on commit adc131e

Please sign in to comment.