Completing the functionalization of the login page

This commit is contained in:
Alihaghighattalab
2024-08-05 17:44:52 +03:30
parent a581dcce7e
commit ba0f7cd71a
7 changed files with 143 additions and 12 deletions
+71 -1
View File
@@ -9,6 +9,7 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@headlessui/react": "^2.1.2", "@headlessui/react": "^2.1.2",
"@hookform/resolvers": "^3.9.0",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
@@ -20,11 +21,13 @@
"iconsax-react": "^0.0.8", "iconsax-react": "^0.0.8",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.52.2",
"react-otp-input": "^3.1.1", "react-otp-input": "^3.1.1",
"react-router-dom": "^6.26.0", "react-router-dom": "^6.26.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4",
"yup": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
"tailwindcss": "^3.4.7" "tailwindcss": "^3.4.7"
@@ -2622,6 +2625,15 @@
"react-dom": "^18" "react-dom": "^18"
} }
}, },
"node_modules/@hookform/resolvers": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.0.tgz",
"integrity": "sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==",
"license": "MIT",
"peerDependencies": {
"react-hook-form": "^7.0.0"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.14", "version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@@ -15767,6 +15779,12 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/property-expr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
"integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==",
"license": "MIT"
},
"node_modules/proxy-addr": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -16160,6 +16178,22 @@
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-hook-form": {
"version": "7.52.2",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.52.2.tgz",
"integrity": "sha512-pqfPEbERnxxiNMPd0bzmt1tuaPcVccywFDpyk2uV5xCIBphHV5T8SVnX9/o3kplPE1zzKt77+YIoq+EMwJp56A==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -18254,6 +18288,12 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/tiny-case": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
"integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==",
"license": "MIT"
},
"node_modules/tmpl": { "node_modules/tmpl": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -18290,6 +18330,12 @@
"node": ">=0.6" "node": ">=0.6"
} }
}, },
"node_modules/toposort": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==",
"license": "MIT"
},
"node_modules/tough-cookie": { "node_modules/tough-cookie": {
"version": "4.1.4", "version": "4.1.4",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
@@ -19784,6 +19830,30 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/yup": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz",
"integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==",
"license": "MIT",
"dependencies": {
"property-expr": "^2.0.5",
"tiny-case": "^1.0.3",
"toposort": "^2.0.2",
"type-fest": "^2.19.0"
}
},
"node_modules/yup/node_modules/type-fest": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
} }
} }
} }
+4 -1
View File
@@ -4,6 +4,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@headlessui/react": "^2.1.2", "@headlessui/react": "^2.1.2",
"@hookform/resolvers": "^3.9.0",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
@@ -15,11 +16,13 @@
"iconsax-react": "^0.0.8", "iconsax-react": "^0.0.8",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.52.2",
"react-otp-input": "^3.1.1", "react-otp-input": "^3.1.1",
"react-router-dom": "^6.26.0", "react-router-dom": "^6.26.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4",
"yup": "^1.4.0"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
+17
View File
@@ -0,0 +1,17 @@
import { FC } from "react";
type Props = {
message: any;
show: any;
};
export const ErrorComponent: FC<Props> = ({ message, show }) => {
return (
show && (
<p className="w-full text-right text-xs text-red-500 font-medium -mt-5">
* {message}
</p>
)
);
};
+4 -5
View File
@@ -6,22 +6,21 @@ type Props = {
style?: string, style?: string,
type?: React.HTMLInputTypeAttribute, type?: React.HTMLInputTypeAttribute,
placeholder: string, placeholder: string,
icon?: React.ReactNode icon?: React.ReactNode,
other?: any
} }
export const Input: FC<Props> = ({ placeholder, type = "text", icon }) => { export const Input: FC<Props> = ({ placeholder, type = "text", icon, other }) => {
const [hidden, setHidden] = useState<boolean>(false) const [hidden, setHidden] = useState<boolean>(false)
const handleHidden = () => setHidden(prev => !prev) const handleHidden = () => setHidden(prev => !prev)
return ( return (
<div className="relative w-full min-w-[250px] sm:min-w-[400px]"> <div className="relative w-full min-w-[250px] sm:min-w-[400px]">
<div className="absolute top-[18.5px] left-5 grid h-5 w-5 place-items-center text-blue-gray-500"> <div className="absolute top-[18.5px] left-5 grid h-5 w-5 place-items-center text-blue-gray-500">
{type === "password" ? {type === "password" ?
!hidden ? <Eye size="23" color="#777577" onClick={handleHidden} /> : <EyeSlash size="23" color="#777577" onClick={handleHidden} /> !hidden ? <Eye size="23" color="#777577" onClick={handleHidden} /> : <EyeSlash size="23" color="#777577" onClick={handleHidden} />
: icon} : icon}
</div> </div>
<InputComponent type={type === "password" && hidden ? "text" : type} placeholder={placeholder} className="input-style" /> <InputComponent {...other} type={type === "password" && hidden ? "text" : type} placeholder={placeholder} className="input-style" />
</div> </div>
) )
} }
+31 -5
View File
@@ -1,9 +1,25 @@
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import { Input } from "../common/input" import { Input } from "../common/input"
import { Button } from "@headlessui/react"
import { Controller, SubmitHandler, useForm } from "react-hook-form"
import { LoginFormInterface } from "../../types"
import { loginSchema } from "../../schema"
import { ErrorComponent } from "../common/error"
import { yupResolver } from '@hookform/resolvers/yup'
export const LoginForm = () => { export const LoginForm = () => {
const {
handleSubmit,
formState: { errors },
control,
} = useForm<LoginFormInterface>({
resolver: yupResolver(loginSchema)
})
const handleLoginSubmit: SubmitHandler<any> = async (data) => {
console.log("data=>", data)
}
return ( return (
<div <form onSubmit={handleSubmit(handleLoginSubmit)}
className="w-fit sm:min-w-[500px] xl:w-1/2 xl:p-0 p-10 flex flex-col bg-auth-form justify-center items-center gap-y-[42px] rounded-3xl"> className="w-fit sm:min-w-[500px] xl:w-1/2 xl:p-0 p-10 flex flex-col bg-auth-form justify-center items-center gap-y-[42px] rounded-3xl">
<img src="/svgs/logo/logo.svg" alt="logo" className="auth-logo-size" /> <img src="/svgs/logo/logo.svg" alt="logo" className="auth-logo-size" />
<div className="flex flex-col xl:-mr-24"> <div className="flex flex-col xl:-mr-24">
@@ -17,15 +33,25 @@ export const LoginForm = () => {
</div> </div>
</div> </div>
<div className="flex flex-col gap-y-[25px] mt-9 mb-[42px]"> <div className="flex flex-col gap-y-[25px] mt-9 mb-[42px]">
<Input type="number" placeholder="شماره تماس" /> <Controller control={control} name="phoneNumber" render={({ field }) => (
<Input type="password" placeholder="رمز عبور" /> <>
<Input type="number" placeholder="شماره تماس" other={field} />
<ErrorComponent show={errors.phoneNumber} message={errors.phoneNumber?.message} />
</>
)} />
<Controller control={control} name="password" render={({ field }) => (
<>
<Input type="password" placeholder="رمز عبور" other={field} />
<ErrorComponent show={errors.password} message={errors.password?.message} />
</>
)} />
<Link to="/auth/forgot-password"><p className="font-medium text-sm text-secondary-text-color cursor-pointer">فراموشی رمز عبور</p></Link> <Link to="/auth/forgot-password"><p className="font-medium text-sm text-secondary-text-color cursor-pointer">فراموشی رمز عبور</p></Link>
</div> </div>
<div className="flex flex-col gap-y-[27px] items-center"> <div className="flex flex-col gap-y-[27px] items-center">
<button>ثبت</button> <Button type="submit">ثبت</Button>
<Link to="/auth/login-with-code"><p className="font-medium text-base text-primary-color cursor-pointer">ورود با رمز یکبار مصرف</p></Link> <Link to="/auth/login-with-code"><p className="font-medium text-base text-primary-color cursor-pointer">ورود با رمز یکبار مصرف</p></Link>
</div> </div>
</div> </div>
</div> </form>
) )
} }
+12
View File
@@ -0,0 +1,12 @@
import * as yup from 'yup'
export const loginSchema = yup.object({
phoneNumber: yup.string()
.required("وارد کردن شماره تماس الزامی میباشد")
.min(11, "شماره تماس نمیتوند کمتر از 11 رقم باشد")
.max(11, "شماره تماس نمیتواند بیشتر از 11 رقم باشد")
.matches(/^[0-9]{11}$/, "شماره تماس نامعتبر"),
password: yup.string()
.required("وارد کردن رمزعبور الزامی میباشد")
.min(8, "رمزعبور نمیتواند کمتر از 8 کاراکتر باشد")
})
+4
View File
@@ -0,0 +1,4 @@
export interface LoginFormInterface {
phoneNumber: string,
password: string
}