From 3cb93bb51ea828b521535149fba7a49958a900f3 Mon Sep 17 00:00:00 2001 From: Xomkalol Date: Sun, 7 Sep 2025 22:53:28 +0500 Subject: [PATCH 01/10] 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/10] 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/10] 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/10] 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: Fri, 12 Sep 2025 00:04:13 +0500 Subject: [PATCH 05/10] 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 06/10] 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 07/10] 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: Wed, 17 Sep 2025 10:19:53 +0300 Subject: [PATCH 08/10] feat: create body editor --- src/Types/Types.ts | 7 ++ src/components/BodyEditor/BodyEditor.tsx | 99 +++++++++++++++++++ .../FormDataEditor/FormDataEditor.tsx | 65 ++++++++++++ src/routes/rest/route.tsx | 2 + 4 files changed, 173 insertions(+) create mode 100644 src/components/BodyEditor/BodyEditor.tsx create mode 100644 src/components/FormDataEditor/FormDataEditor.tsx diff --git a/src/Types/Types.ts b/src/Types/Types.ts index 2444689..58c9220 100644 --- a/src/Types/Types.ts +++ b/src/Types/Types.ts @@ -1 +1,8 @@ export type Lang = 'en' | 'ru'; + +export interface FormDataItem { + key: string; + value: string; +} + +export type BodyValue = null | string | FormData; diff --git a/src/components/BodyEditor/BodyEditor.tsx b/src/components/BodyEditor/BodyEditor.tsx new file mode 100644 index 0000000..afbe4e9 --- /dev/null +++ b/src/components/BodyEditor/BodyEditor.tsx @@ -0,0 +1,99 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import FormDataEditor from '../FormDataEditor/FormDataEditor'; +import type { BodyValue, FormDataItem } from '../../Types/Types'; + +type BodyType = 'none' | 'raw' | 'form-data'; + +interface BodyEditorProps { + value: BodyValue; + onChange: (val: BodyValue) => void; + readOnly?: boolean; +} + +export default function BodyEditor({ + value, + onChange, + readOnly = false, +}: BodyEditorProps) { + const [error, setError] = useState(null); + const [bodyType, setBodyType] = useState('none'); + const [rawValue, setRawValue] = useState(''); + const [formDataValue, setFormDataValue] = useState([]); + + useEffect(() => { + if (typeof value === 'string') { + setBodyType('raw'); + setRawValue(value); + } else if (value instanceof FormData) { + setBodyType('form-data'); + + const arr: FormDataItem[] = []; + value.forEach((val, key) => arr.push({ key, value: String(val) })); + setFormDataValue(arr); + } else { + setBodyType('none'); + } + }, [value]); + + useEffect(() => { + if (bodyType === 'raw') { + try { + JSON.parse(rawValue); + setError(null); + onChange(rawValue); + } catch { + setError('Invalid JSON'); + } + } + }, [rawValue, bodyType, onChange]); + + useEffect(() => { + if (bodyType === 'form-data') { + const fd = new FormData(); + formDataValue.forEach((item) => { + if (item.key) fd.append(item.key, item.value); + }); + onChange(fd); + } + }, [formDataValue, bodyType, onChange]); + + return ( +
    + + + {bodyType === 'raw' && ( +