Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add login page draft #13

Merged
merged 20 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e4e39c0
feat: add basic logic for sing up/sing in. Draft. Saving imtermediate…
Tedzury Dec 11, 2023
b2094f1
feat: separating forms to own files. draft. Saving intermediate results
Tedzury Dec 11, 2023
09e9124
fix: pull update from develop branch. Resolve merge conflicts
Tedzury Dec 11, 2023
15ea0c1
feat: add proper routing for auth. Add errors notification during log…
Tedzury Dec 11, 2023
78663b4
feat: add logic for privatge routes and redirection. Separate router …
Tedzury Dec 12, 2023
4c251b3
refactor: isolated login and logout logic into auth context component
Tedzury Dec 12, 2023
eff4459
fix: pull changes from develop to be up to date. Resolve merge conflict
Tedzury Dec 12, 2023
e806562
feat: add layout and translation for login page
Tedzury Dec 13, 2023
0f034c0
feat: add layout for sign up page
Tedzury Dec 14, 2023
8e3612f
feat: add tests for helpers functions
Tedzury Dec 14, 2023
8a22978
feat: add test for login page and private routes
Tedzury Dec 15, 2023
991f2cb
feat: add test for sign up page. Refactor: move web comp mocks to set…
Tedzury Dec 15, 2023
67149be
fix: remove screen.debug
Tedzury Dec 15, 2023
a702473
feat: add logic for localizing en/ru for error messages at login and …
Tedzury Dec 15, 2023
fad8ac2
fix: fix back def lang to en
Tedzury Dec 15, 2023
e5208d3
add logic and UI for toggling show/hide password
Tedzury Dec 16, 2023
9b9338b
fix: fix tests setup and icon mock
Tedzury Dec 16, 2023
2dba41c
feat: add some styling and localized toastify notations
Tedzury Dec 16, 2023
b5c301d
feat: add variativiry for test error msgs. Centered toastify msg
Tedzury Dec 16, 2023
1557b26
fix:remove commented code
Tedzury Dec 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Quiddlee marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 3 additions & 4 deletions src/layouts/MainLayout.tsx
Tedzury marked this conversation as resolved.
Show resolved Hide resolved
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
Tedzury marked this conversation as resolved.
Show resolved Hide resolved
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