From 3cb93bb51ea828b521535149fba7a49958a900f3 Mon Sep 17 00:00:00 2001 From: Xomkalol Date: Sun, 7 Sep 2025 22:53:28 +0500 Subject: [PATCH 01/13] feat: add firebase config --- package.json | 1 + src/config/firebase.js | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/config/firebase.js diff --git a/package.json b/package.json index 25e4a42..51e30d2 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "compression": "^1.8.1", "cross-env": "^10.0.0", "express": "^5.1.0", + "firebase": "^12.2.1", "react": "^19.1.1", "react-dom": "^19.1.1", "react-redux": "^9.2.0", diff --git a/src/config/firebase.js b/src/config/firebase.js new file mode 100644 index 0000000..edc3aad --- /dev/null +++ b/src/config/firebase.js @@ -0,0 +1,21 @@ +// Import the functions you need from the SDKs you need +import { initializeApp } from 'firebase/app'; +import { getAnalytics } from 'firebase/analytics'; +// TODO: Add SDKs for Firebase products that you want to use +// https://firebase.google.com/docs/web/setup#available-libraries + +// Your web app's Firebase configuration +// For Firebase JS SDK v7.20.0 and later, measurementId is optional +const firebaseConfig = { + apiKey: 'AIzaSyBPGKPIQ4QHwhM5VZVU2NY42orf7Ai88Ds', + authDomain: 'rss-project-postman.firebaseapp.com', + projectId: 'rss-project-postman', + storageBucket: 'rss-project-postman.firebasestorage.app', + messagingSenderId: '571102150844', + appId: '1:571102150844:web:16a5f2f963d8bf8ca1d0b9', + measurementId: 'G-HFH1H34D2C', +}; + +// Initialize Firebase +const app = initializeApp(firebaseConfig); +const analytics = getAnalytics(app); From 8304dcbf4a9c3c8db21af890a7d79fc5bebc1d4f Mon Sep 17 00:00:00 2001 From: Xomkalol Date: Sun, 7 Sep 2025 23:40:01 +0500 Subject: [PATCH 02/13] feat: react hook form --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 51e30d2..b8992f8 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "firebase": "^12.2.1", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-hook-form": "^7.62.0", "react-redux": "^9.2.0", "react-router": "7.7.0" }, From 3d3d47bd230cbf76a63cc0964cd00ab6a23d8190 Mon Sep 17 00:00:00 2001 From: Xomkalol Date: Sun, 7 Sep 2025 23:59:06 +0500 Subject: [PATCH 03/13] feat: first sign up implement --- src/routes/login/route.tsx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/routes/login/route.tsx b/src/routes/login/route.tsx index a488fb6..53ef604 100644 --- a/src/routes/login/route.tsx +++ b/src/routes/login/route.tsx @@ -1,10 +1,36 @@ +'use client'; +import { useForm, type SubmitHandler } from 'react-hook-form'; + +interface IFormInput { + email: string; + password: string; +} export default function Login() { + const { register, handleSubmit } = useForm(); + const onSubmit: SubmitHandler = (data) => console.log(data); + return (

Login Page

This is the Login Page of our application.

+
+ + + + + + +
From b67b2f87f0148b7005717e84c597884187a38d9d Mon Sep 17 00:00:00 2001 From: Xomkalol Date: Mon, 8 Sep 2025 00:19:56 +0500 Subject: [PATCH 04/13] feat: firebase auth first implement --- src/config/{firebase.js => firebase.ts} | 44 +++++++++++++------------ src/routes/login/route.tsx | 15 +++++++-- 2 files changed, 36 insertions(+), 23 deletions(-) rename src/config/{firebase.js => firebase.ts} (87%) diff --git a/src/config/firebase.js b/src/config/firebase.ts similarity index 87% rename from src/config/firebase.js rename to src/config/firebase.ts index edc3aad..fe4135a 100644 --- a/src/config/firebase.js +++ b/src/config/firebase.ts @@ -1,21 +1,23 @@ -// Import the functions you need from the SDKs you need -import { initializeApp } from 'firebase/app'; -import { getAnalytics } from 'firebase/analytics'; -// TODO: Add SDKs for Firebase products that you want to use -// https://firebase.google.com/docs/web/setup#available-libraries - -// Your web app's Firebase configuration -// For Firebase JS SDK v7.20.0 and later, measurementId is optional -const firebaseConfig = { - apiKey: 'AIzaSyBPGKPIQ4QHwhM5VZVU2NY42orf7Ai88Ds', - authDomain: 'rss-project-postman.firebaseapp.com', - projectId: 'rss-project-postman', - storageBucket: 'rss-project-postman.firebasestorage.app', - messagingSenderId: '571102150844', - appId: '1:571102150844:web:16a5f2f963d8bf8ca1d0b9', - measurementId: 'G-HFH1H34D2C', -}; - -// Initialize Firebase -const app = initializeApp(firebaseConfig); -const analytics = getAnalytics(app); +// Import the functions you need from the SDKs you need +import { initializeApp } from 'firebase/app'; +import { getAnalytics } from 'firebase/analytics'; +import { getAuth } from 'firebase/auth'; +// TODO: Add SDKs for Firebase products that you want to use +// https://firebase.google.com/docs/web/setup#available-libraries + +// Your web app's Firebase configuration +// For Firebase JS SDK v7.20.0 and later, measurementId is optional +const firebaseConfig = { + apiKey: 'AIzaSyBPGKPIQ4QHwhM5VZVU2NY42orf7Ai88Ds', + authDomain: 'rss-project-postman.firebaseapp.com', + projectId: 'rss-project-postman', + storageBucket: 'rss-project-postman.firebasestorage.app', + messagingSenderId: '571102150844', + appId: '1:571102150844:web:16a5f2f963d8bf8ca1d0b9', + measurementId: 'G-HFH1H34D2C', +}; + +// Initialize Firebase +const app = initializeApp(firebaseConfig); +export const auth = getAuth(app); +export const analytics = getAnalytics(app); diff --git a/src/routes/login/route.tsx b/src/routes/login/route.tsx index 53ef604..d44fc99 100644 --- a/src/routes/login/route.tsx +++ b/src/routes/login/route.tsx @@ -1,5 +1,7 @@ 'use client'; +import { createUserWithEmailAndPassword } from 'firebase/auth'; import { useForm, type SubmitHandler } from 'react-hook-form'; +import { auth } from '../../config/firebase.ts'; interface IFormInput { email: string; @@ -7,7 +9,16 @@ interface IFormInput { } export default function Login() { const { register, handleSubmit } = useForm(); - const onSubmit: SubmitHandler = (data) => console.log(data); + + const handleSignIn: SubmitHandler = async (data) => { + const { email, password } = data; + try { + await createUserWithEmailAndPassword(auth, email, password); + alert('Signed in successfully'); + } catch (error) { + console.error(error); + } + }; return (
@@ -15,7 +26,7 @@ export default function Login() {

Login Page

This is the Login Page of our application.

-
+ Date: Thu, 11 Sep 2025 21:57:42 +0300 Subject: [PATCH 05/13] refact: create header with Mantine --- package-lock.json | 27 ++++++ src/components/Header/Header.module.css | 57 +++++++++++ src/components/Header/Header.module.sass | 10 -- src/components/Header/Header.tsx | 118 +++++++++++++++++------ src/routes/root/client.tsx | 2 +- 5 files changed, 172 insertions(+), 42 deletions(-) create mode 100644 src/components/Header/Header.module.css delete mode 100644 src/components/Header/Header.module.sass diff --git a/package-lock.json b/package-lock.json index 613ef5b..c95b2f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@mantine/hooks": "^8.3.0", "@reduxjs/toolkit": "^2.9.0", "@remix-run/node-fetch-server": "^0.8.0", + "@tabler/icons-react": "^3.34.1", "compression": "^1.8.1", "cross-env": "^10.0.0", "express": "^5.1.0", @@ -2356,6 +2357,32 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@tabler/icons": { + "version": "3.34.1", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.34.1.tgz", + "integrity": "sha512-9gTnUvd7Fd/DmQgr3MKY+oJLa1RfNsQo8c/ir3TJAWghOuZXodbtbVp0QBY2DxWuuvrSZFys0HEbv1CoiI5y6A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "node_modules/@tabler/icons-react": { + "version": "3.34.1", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.34.1.tgz", + "integrity": "sha512-Ld6g0NqOO05kyyHsfU8h787PdHBm7cFmOycQSIrGp45XcXYDuOK2Bs0VC4T2FWSKZ6bx5g04imfzazf/nqtk1A==", + "license": "MIT", + "dependencies": { + "@tabler/icons": "3.34.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + }, + "peerDependencies": { + "react": ">= 16" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", diff --git a/src/components/Header/Header.module.css b/src/components/Header/Header.module.css new file mode 100644 index 0000000..3ab6fe2 --- /dev/null +++ b/src/components/Header/Header.module.css @@ -0,0 +1,57 @@ +.header { + height: 60px; + padding-left: var(--mantine-spacing-md); + padding-right: var(--mantine-spacing-md); + border-bottom: 1px solid + light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4)); +} + +.link { + display: flex; + align-items: center; + height: 100%; + padding-left: var(--mantine-spacing-md); + padding-right: var(--mantine-spacing-md); + text-decoration: none; + color: light-dark(var(--mantine-color-black), var(--mantine-color-white)); + font-weight: 500; + font-size: var(--mantine-font-size-sm); + + @media (max-width: mantine-breakpoint-sm) { + height: 42px; + width: 100%; + } + + &:hover { + background-color: light-dark( + var(--mantine-color-gray-0), + var(--mantine-color-dark-6) + ); + } +} + +.subLink { + width: 100%; + padding: var(--mantine-spacing-xs) var(--mantine-spacing-md); + border-radius: var(--mantine-radius-md); + + &:hover { + background-color: light-dark( + var(--mantine-color-gray-0), + var(--mantine-color-dark-7) + ); + } +} + +.dropdownFooter { + background-color: light-dark( + var(--mantine-color-gray-0), + var(--mantine-color-dark-7) + ); + margin: calc(var(--mantine-spacing-md) * -1); + margin-top: var(--mantine-spacing-sm); + padding: var(--mantine-spacing-md) calc(var(--mantine-spacing-md) * 2); + padding-bottom: var(--mantine-spacing-xl); + border-top: 1px solid + light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5)); +} diff --git a/src/components/Header/Header.module.sass b/src/components/Header/Header.module.sass deleted file mode 100644 index 4d73225..0000000 --- a/src/components/Header/Header.module.sass +++ /dev/null @@ -1,10 +0,0 @@ - -.headerContainer - display: flex - justify-content: start - align-items: center - column-gap: 30px - -.links - display: flex - column-gap: 20px diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 5e9f3af..e81fcc1 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,46 +1,102 @@ 'use client'; -import s from './Header.module.sass'; +import classes from './Header.module.css'; import { type JSX } from 'react'; -import classNames from 'classnames'; import { NavLink } from 'react-router'; import { useAppState } from '../../redux/useAppSelector'; import { useActions } from '../../redux/useActions'; +import { + Box, + Burger, + Button, + Divider, + Drawer, + Group, + ScrollArea, +} from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; export function Header(): JSX.Element { - const headerStyles = classNames('header ', s.header); - const headerContainerStyles = classNames('container ', s.headerContainer); + const [drawerOpened, { toggle: toggleDrawer, close: closeDrawer }] = + useDisclosure(false); const { isLogin } = useAppState(); const { login, logout } = useActions(); return ( -
-
- - - -
-
+ +
+ + + + Home + + + Rest + + + Variables + + + History + + + + + + + + + + +
+ + + + + + Home + + + Rest + + + Variables + + + History + + + + + + + + + + +
); } diff --git a/src/routes/root/client.tsx b/src/routes/root/client.tsx index 553269c..9fc284d 100644 --- a/src/routes/root/client.tsx +++ b/src/routes/root/client.tsx @@ -5,9 +5,9 @@ import '@mantine/core/styles.css'; import { MantineProvider } from '@mantine/core'; import { theme } from '../../theme/theme'; import { Footer } from '../../components/Footer/Footer'; -import { Header } from '../../components/Header/Header'; import { Provider } from 'react-redux'; import store from '../../redux/store'; +import { Header } from '../../components/Header/Header'; export function Layout({ children }: { children: React.ReactNode }) { return ( From 07a2ea7d10644acb67020b9065876466312afab2 Mon Sep 17 00:00:00 2001 From: Xomkalol Date: Fri, 12 Sep 2025 00:04:13 +0500 Subject: [PATCH 06/13] feat: sign in and sign up --- src/components/Header/Header.tsx | 37 ++++++++++++++--- src/config/firebase.ts | 9 ----- src/routes/config.ts | 5 +++ src/routes/login/login.sass | 7 ++++ src/routes/login/route.tsx | 15 +++++-- src/routes/root/route.tsx | 2 +- src/routes/signIn/route.tsx | 69 ++++++++++++++++++++++++++++++++ 7 files changed, 125 insertions(+), 19 deletions(-) create mode 100644 src/routes/login/login.sass create mode 100644 src/routes/signIn/route.tsx diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 5e9f3af..a2dacc7 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,19 +1,39 @@ 'use client'; import s from './Header.module.sass'; -import { type JSX } from 'react'; +import { useEffect, useState, type JSX } from 'react'; import classNames from 'classnames'; import { NavLink } from 'react-router'; import { useAppState } from '../../redux/useAppSelector'; import { useActions } from '../../redux/useActions'; +import { auth } from '../../config/firebase'; +import { signOut } from 'firebase/auth'; export function Header(): JSX.Element { const headerStyles = classNames('header ', s.header); const headerContainerStyles = classNames('container ', s.headerContainer); + const [name, setName] = useState('unknown'); const { isLogin } = useAppState(); const { login, logout } = useActions(); + auth.onAuthStateChanged((user) => { + if (user) { + if (!isLogin) { + login(); + } + setName(user?.email ? user.email : 'unknown'); + } else { + setName('unknown'); + logout(); + } + }); + + const handleLogout = () => { + signOut(auth); + console.log('user signOut'); + }; + return (
@@ -32,14 +52,19 @@ export function Header(): JSX.Element { History
  • - Sign In / Sign Up + Sign Up +
  • +
  • + Sign In
  • +
    Logged as {name}
    - - + {isLogin && ( + + )}
    ); diff --git a/src/config/firebase.ts b/src/config/firebase.ts index fe4135a..e5740f7 100644 --- a/src/config/firebase.ts +++ b/src/config/firebase.ts @@ -1,12 +1,5 @@ -// Import the functions you need from the SDKs you need import { initializeApp } from 'firebase/app'; -import { getAnalytics } from 'firebase/analytics'; import { getAuth } from 'firebase/auth'; -// TODO: Add SDKs for Firebase products that you want to use -// https://firebase.google.com/docs/web/setup#available-libraries - -// Your web app's Firebase configuration -// For Firebase JS SDK v7.20.0 and later, measurementId is optional const firebaseConfig = { apiKey: 'AIzaSyBPGKPIQ4QHwhM5VZVU2NY42orf7Ai88Ds', authDomain: 'rss-project-postman.firebaseapp.com', @@ -17,7 +10,5 @@ const firebaseConfig = { measurementId: 'G-HFH1H34D2C', }; -// Initialize Firebase const app = initializeApp(firebaseConfig); export const auth = getAuth(app); -export const analytics = getAnalytics(app); diff --git a/src/routes/config.ts b/src/routes/config.ts index a024987..0616167 100644 --- a/src/routes/config.ts +++ b/src/routes/config.ts @@ -32,6 +32,11 @@ export function routes() { path: 'rest', lazy: () => import('./rest/route'), }, + { + id: 'signin', + path: 'signIn', + lazy: () => import('./signIn/route'), + }, ], }, ] satisfies RSCRouteConfig; diff --git a/src/routes/login/login.sass b/src/routes/login/login.sass new file mode 100644 index 0000000..5486ad2 --- /dev/null +++ b/src/routes/login/login.sass @@ -0,0 +1,7 @@ +.form + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + gap: 15px; + diff --git a/src/routes/login/route.tsx b/src/routes/login/route.tsx index d44fc99..3a05c99 100644 --- a/src/routes/login/route.tsx +++ b/src/routes/login/route.tsx @@ -1,7 +1,13 @@ 'use client'; -import { createUserWithEmailAndPassword } from 'firebase/auth'; +import './login.sass'; +import { + createUserWithEmailAndPassword, + signInWithEmailAndPassword, +} from 'firebase/auth'; import { useForm, type SubmitHandler } from 'react-hook-form'; import { auth } from '../../config/firebase.ts'; +import { useAppState } from '../../redux/useAppSelector.ts'; +import { useActions } from '../../redux/useActions.ts'; interface IFormInput { email: string; @@ -9,12 +15,15 @@ interface IFormInput { } export default function Login() { const { register, handleSubmit } = useForm(); + const { isLogin } = useAppState(); + const { login, logout } = useActions(); const handleSignIn: SubmitHandler = async (data) => { const { email, password } = data; try { await createUserWithEmailAndPassword(auth, email, password); - alert('Signed in successfully'); + login(); + console.log('logged'); } catch (error) { console.error(error); } @@ -26,7 +35,7 @@ export default function Login() {

    Login Page

    This is the Login Page of our application.

    - + {children}; diff --git a/src/routes/signIn/route.tsx b/src/routes/signIn/route.tsx new file mode 100644 index 0000000..5fe084b --- /dev/null +++ b/src/routes/signIn/route.tsx @@ -0,0 +1,69 @@ +'use client'; +import { + createUserWithEmailAndPassword, + signInWithEmailAndPassword, +} from 'firebase/auth'; +import { useForm, type SubmitHandler } from 'react-hook-form'; +import { auth } from '../../config/firebase.ts'; +import { useAppState } from '../../redux/useAppSelector.ts'; +import { useActions } from '../../redux/useActions.ts'; + +interface IFormInput { + email: string; + password: string; +} +export default function signIn() { + const { register, handleSubmit } = useForm(); + const { isLogin } = useAppState(); + const { login, logout } = useActions(); + + const handleSignIn: SubmitHandler = async (data) => { + const { email, password } = data; + try { + await createUserWithEmailAndPassword(auth, email, password); + login(); + console.log('logged'); + } catch (error) { + console.error(error); + } + }; + + const handleLogin: SubmitHandler = async (data) => { + const { email, password } = data; + try { + await signInWithEmailAndPassword(auth, email, password); + login(); + console.log('logged'); + } catch (error) { + console.error(error); + } + }; + + return ( +
    +
    +
    +

    Sign Page

    +

    This is the sign Page of our application.

    + +

    ITS LOGIN IN

    + + + + + + + +
    +
    +
    + ); +} From 3807c531a296768727cd55c4b7d4e3a58dbd55e4 Mon Sep 17 00:00:00 2001 From: Xomkalol Date: Fri, 12 Sep 2025 00:42:05 +0500 Subject: [PATCH 07/13] feat: add yup validation --- package.json | 4 +++- src/components/Header/Header.tsx | 2 +- src/routes/login/route.tsx | 34 ++++++++++++++++++++++-------- src/routes/root/route.tsx | 4 ++-- src/routes/signIn/route.tsx | 36 +++++++++++++------------------- src/yup/yup.ts | 20 ++++++++++++++++++ src/yup/yupSignin.ts | 16 ++++++++++++++ 7 files changed, 82 insertions(+), 34 deletions(-) create mode 100644 src/yup/yup.ts create mode 100644 src/yup/yupSignin.ts diff --git a/package.json b/package.json index b8992f8..80519a1 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "test:coverage": "vitest run --coverage" }, "dependencies": { + "@hookform/resolvers": "^5.2.1", "@reduxjs/toolkit": "^2.9.0", "@remix-run/node-fetch-server": "^0.8.0", "compression": "^1.8.1", @@ -25,7 +26,8 @@ "react-dom": "^19.1.1", "react-hook-form": "^7.62.0", "react-redux": "^9.2.0", - "react-router": "7.7.0" + "react-router": "7.7.0", + "yup": "^1.7.0" }, "devDependencies": { "@eslint/js": "^9.33.0", diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index a2dacc7..b932a77 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,7 +1,7 @@ 'use client'; import s from './Header.module.sass'; -import { useEffect, useState, type JSX } from 'react'; +import { useState, type JSX } from 'react'; import classNames from 'classnames'; import { NavLink } from 'react-router'; import { useAppState } from '../../redux/useAppSelector'; diff --git a/src/routes/login/route.tsx b/src/routes/login/route.tsx index 3a05c99..f291bfa 100644 --- a/src/routes/login/route.tsx +++ b/src/routes/login/route.tsx @@ -1,22 +1,26 @@ 'use client'; import './login.sass'; -import { - createUserWithEmailAndPassword, - signInWithEmailAndPassword, -} from 'firebase/auth'; +import { createUserWithEmailAndPassword } from 'firebase/auth'; import { useForm, type SubmitHandler } from 'react-hook-form'; import { auth } from '../../config/firebase.ts'; -import { useAppState } from '../../redux/useAppSelector.ts'; import { useActions } from '../../redux/useActions.ts'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { schema } from '../../yup/yup.ts'; interface IFormInput { email: string; password: string; + confirmPassword: string; } export default function Login() { - const { register, handleSubmit } = useForm(); - const { isLogin } = useAppState(); - const { login, logout } = useActions(); + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + const { login } = useActions(); const handleSignIn: SubmitHandler = async (data) => { const { email, password } = data; @@ -41,13 +45,25 @@ export default function Login() { placeholder="Enter email" type="text" {...register('email')} + required /> +

    {errors.email?.message}

    +

    {errors.password?.message}

    + + +

    {errors.confirmPassword?.message}

    diff --git a/src/routes/root/route.tsx b/src/routes/root/route.tsx index 8b6ca4e..8d7269f 100644 --- a/src/routes/root/route.tsx +++ b/src/routes/root/route.tsx @@ -3,12 +3,12 @@ import { Outlet } from 'react-router'; import { Layout as ClientLayout } from './client'; import './styles.sass'; import './app.sass'; -import { fetchUsers } from './server-function'; +// import { fetchUsers } from './server-function'; export { ErrorBoundary } from './client'; export async function Layout({ children }: { children: React.ReactNode }) { - const users = await fetchUsers(); + // const users = await fetchUsers(); // console.log('Fetched users: ', users); // This is necessary for the bundler to inject the needed CSS assets. diff --git a/src/routes/signIn/route.tsx b/src/routes/signIn/route.tsx index 5fe084b..941e0bc 100644 --- a/src/routes/signIn/route.tsx +++ b/src/routes/signIn/route.tsx @@ -1,32 +1,24 @@ 'use client'; -import { - createUserWithEmailAndPassword, - signInWithEmailAndPassword, -} from 'firebase/auth'; +import { signInWithEmailAndPassword } from 'firebase/auth'; import { useForm, type SubmitHandler } from 'react-hook-form'; import { auth } from '../../config/firebase.ts'; -import { useAppState } from '../../redux/useAppSelector.ts'; import { useActions } from '../../redux/useActions.ts'; +import { schema } from '../../yup/yupSignin.ts'; +import { yupResolver } from '@hookform/resolvers/yup'; interface IFormInput { email: string; password: string; } -export default function signIn() { - const { register, handleSubmit } = useForm(); - const { isLogin } = useAppState(); - const { login, logout } = useActions(); - - const handleSignIn: SubmitHandler = async (data) => { - const { email, password } = data; - try { - await createUserWithEmailAndPassword(auth, email, password); - login(); - console.log('logged'); - } catch (error) { - console.error(error); - } - }; +export default function SignIn() { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + const { login } = useActions(); const handleLogin: SubmitHandler = async (data) => { const { email, password } = data; @@ -53,12 +45,14 @@ export default function signIn() { type="text" {...register('email')} /> +

    {errors.email?.message}

    +

    {errors.password?.message}

    diff --git a/src/yup/yup.ts b/src/yup/yup.ts new file mode 100644 index 0000000..cc4e579 --- /dev/null +++ b/src/yup/yup.ts @@ -0,0 +1,20 @@ +import * as yup from 'yup'; + +export const schema = yup.object().shape({ + email: yup.string().email().required(), + password: yup + .string() + .required('Password is required') + .min(8, 'Password must be at least 8 characters long') + .matches(/[0-9]/, 'Password must contain at least one number') + .matches(/[a-z]/, 'Password must contain at least one lowercase letter') + .matches(/[A-Z]/, 'Password must contain at least one uppercase letter') + .matches( + /[!@#$%^&*(),.?":{}|<>]/, + 'Password must contain at least one special character' + ), + confirmPassword: yup + .string() + .oneOf([yup.ref('password')], 'Passwords must match') + .required('Confirm Password is required'), +}); diff --git a/src/yup/yupSignin.ts b/src/yup/yupSignin.ts new file mode 100644 index 0000000..61a448e --- /dev/null +++ b/src/yup/yupSignin.ts @@ -0,0 +1,16 @@ +import * as yup from 'yup'; + +export const schema = yup.object().shape({ + email: yup.string().email().required(), + password: yup + .string() + .required('Password is required') + .min(8, 'Password must be at least 8 characters long') + .matches(/[0-9]/, 'Password must contain at least one number') + .matches(/[a-z]/, 'Password must contain at least one lowercase letter') + .matches(/[A-Z]/, 'Password must contain at least one uppercase letter') + .matches( + /[!@#$%^&*(),.?":{}|<>]/, + 'Password must contain at least one special character' + ), +}); From 07e380d38c3bf3781ac6d4898ce09db3ea72a8aa Mon Sep 17 00:00:00 2001 From: Xomkalol Date: Fri, 12 Sep 2025 01:02:49 +0500 Subject: [PATCH 08/13] feat: add password str bar --- src/components/passwordStr/passwordStr.ts | 35 +++++++++++++++++++ src/routes/login/route.tsx | 41 +++++++++++++++++++++-- 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/components/passwordStr/passwordStr.ts diff --git a/src/components/passwordStr/passwordStr.ts b/src/components/passwordStr/passwordStr.ts new file mode 100644 index 0000000..8f8e778 --- /dev/null +++ b/src/components/passwordStr/passwordStr.ts @@ -0,0 +1,35 @@ +export interface passwordStrInterface { + score: number; + level: 'empty' | 'weak' | 'medium' | 'strong'; + color: 'red' | 'orange' | 'yellow' | 'green'; +} + +export const getPasswordStrength = (password: string): passwordStrInterface => { + if (!password) + return { + score: 0, + level: 'empty', + color: 'red', + }; + + let score = 0; + const checks = [ + password.length >= 8, + /\d/.test(password), + /[a-z]/.test(password), + /[A-Z]/.test(password), + /[!@#$%^&*(),.?":{}|<>]/.test(password), + ]; + + score = checks.filter(Boolean).length; + console.log(score); + if (score === 0) { + return { score, level: 'empty', color: 'red' }; + } else if (score <= 2) { + return { score, level: 'weak', color: 'red' }; + } else if (score === 3 || score === 4) { + return { score, level: 'medium', color: 'orange' }; + } else { + return { score, level: 'strong', color: 'green' }; + } +}; diff --git a/src/routes/login/route.tsx b/src/routes/login/route.tsx index f291bfa..452f742 100644 --- a/src/routes/login/route.tsx +++ b/src/routes/login/route.tsx @@ -6,6 +6,11 @@ import { auth } from '../../config/firebase.ts'; import { useActions } from '../../redux/useActions.ts'; import { yupResolver } from '@hookform/resolvers/yup'; import { schema } from '../../yup/yup.ts'; +import { useState } from 'react'; +import { + getPasswordStrength, + type passwordStrInterface, +} from '../../components/passwordStr/passwordStr.ts'; interface IFormInput { email: string; @@ -21,7 +26,11 @@ export default function Login() { resolver: yupResolver(schema), }); const { login } = useActions(); - + const [passwordStr, setPasswordStr] = useState({ + score: 0, + level: 'empty', + color: 'red', + }); const handleSignIn: SubmitHandler = async (data) => { const { email, password } = data; try { @@ -52,10 +61,38 @@ export default function Login() { { + setPasswordStr(getPasswordStrength(e.target.value)); + }, + })} required />

    {errors.password?.message}

    +
    +
    +
    +
    +
    Date: Thu, 11 Sep 2025 23:50:20 +0300 Subject: [PATCH 09/13] feat: create footer with Mantine --- src/components/Footer/Footer.module.css | 8 +++++ src/components/Footer/Footer.tsx | 42 +++++++++++++++++++++++-- src/components/Header/Header.module.css | 2 +- src/components/Header/Header.tsx | 2 +- src/routes/root/app.sass | 6 +--- src/routes/root/client.tsx | 9 ++++-- src/routes/root/styles.sass | 6 ++++ 7 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 src/components/Footer/Footer.module.css diff --git a/src/components/Footer/Footer.module.css b/src/components/Footer/Footer.module.css new file mode 100644 index 0000000..625dff8 --- /dev/null +++ b/src/components/Footer/Footer.module.css @@ -0,0 +1,8 @@ +.footer { + /* height: 60px; */ + padding-left: var(--mantine-spacing-md); + padding-right: var(--mantine-spacing-md); + padding-top: var(--mantine-spacing-md); + border-top: 1px solid + light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4)); +} diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx index 766e4ca..889fb7b 100644 --- a/src/components/Footer/Footer.tsx +++ b/src/components/Footer/Footer.tsx @@ -1,9 +1,45 @@ +import { Anchor, Box, Group, Image, Stack } from '@mantine/core'; import type { JSX } from 'react'; +import classes from './Footer.module.css'; +import { IconBrandGithub } from '@tabler/icons-react'; export function Footer(): JSX.Element { return ( -
    -
    Footer
    -
    + +
    + + + + + + aliquickc + + + + + + xomkalol + + + + + + sashawl + + + + ©2025 + + + rs-logo + + + +
    +
    ); } diff --git a/src/components/Header/Header.module.css b/src/components/Header/Header.module.css index 3ab6fe2..e6a3568 100644 --- a/src/components/Header/Header.module.css +++ b/src/components/Header/Header.module.css @@ -1,5 +1,5 @@ .header { - height: 60px; + height: 90px; padding-left: var(--mantine-spacing-md); padding-right: var(--mantine-spacing-md); border-bottom: 1px solid diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index e81fcc1..a44eedf 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -24,7 +24,7 @@ export function Header(): JSX.Element { const { login, logout } = useActions(); return ( - +
    diff --git a/src/routes/root/app.sass b/src/routes/root/app.sass index 02eb0d9..d1764da 100644 --- a/src/routes/root/app.sass +++ b/src/routes/root/app.sass @@ -7,12 +7,8 @@ main flex: 1 0 auto flex: 1 1 auto padding: 20px 0 - background-color: #92D8F9 + .header, .footer min-height: 50px - background-color: #c6f7b0 - -.main - background-color: #92D8F9 diff --git a/src/routes/root/client.tsx b/src/routes/root/client.tsx index 9fc284d..fab5931 100644 --- a/src/routes/root/client.tsx +++ b/src/routes/root/client.tsx @@ -3,6 +3,7 @@ import { isRouteErrorResponse, useRouteError } from 'react-router'; import '@mantine/core/styles.css'; import { MantineProvider } from '@mantine/core'; +import './styles.sass'; import { theme } from '../../theme/theme'; import { Footer } from '../../components/Footer/Footer'; import { Provider } from 'react-redux'; @@ -26,9 +27,11 @@ export function Layout({ children }: { children: React.ReactNode }) { -
    - {children} -