Add ProfilePage, update RegisterForm and ToastBox components
Added new ProfilePage component with necessary UI layout and buttons. Updated RegisterForm component with form validation using Zod schema and improved error handling. Updated icons in ToastBox component for proper display. Added necessary dependencies for form handling and validation.
This commit is contained in:
parent
db6d838796
commit
f6959724b0
29
.idea/workspace.xml
generated
29
.idea/workspace.xml
generated
@ -5,16 +5,12 @@
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="42d2b9e8-8d19-46be-8636-69ddba4519e4" name="Changes" comment="">
|
||||
<change afterPath="$PROJECT_DIR$/src/app/threads/page.tsx" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/components/legals.tsx" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/app/profile/page.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/pnpm-lock.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/pnpm-lock.yaml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/app/layout.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/app/layout.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/login-form.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/login-form.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/register-form.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/register-form.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/ui/toast.tsx" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/ui/toaster.tsx" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/ui/use-toast.ts" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/ui/toast-box.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/ui/toast-box.tsx" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@ -24,13 +20,14 @@
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="TypeScript JSX File" />
|
||||
<option value="TypeScript File" />
|
||||
<option value="TypeScript JSX File" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
<option name="UPDATE_TYPE" value="REBASE" />
|
||||
</component>
|
||||
<component name="PackageJsonUpdateNotifier">
|
||||
<dismissed value="$PROJECT_DIR$/package.json" />
|
||||
@ -48,7 +45,7 @@
|
||||
"ASKED_ADD_EXTERNAL_FILES": "true",
|
||||
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"last_opened_file_path": "/home/avnyr/workspace/Simplon/brief-06-front",
|
||||
"last_opened_file_path": "/home/avnyr/workspace/Simplon/brief-06-back",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
@ -91,10 +88,24 @@
|
||||
<workItem from="1715776267209" duration="3260000" />
|
||||
<workItem from="1715781823049" duration="3317000" />
|
||||
<workItem from="1715794057604" duration="8757000" />
|
||||
<workItem from="1716275453494" duration="11394000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="Remove toast components and added register form The toast related components (toast.tsx, toaster.tsx, use-toast.ts) were removed from the code base. On the other hand, the register-form.tsx file was updated with a form for users to create an account. A new file toast-box.tsx was also added to display a custom message box.">
|
||||
<option name="closed" value="true" />
|
||||
<created>1715869879024</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1715869879024</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="2" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="Remove toast components and added register form The toast related components (toast.tsx, toaster.tsx, use-toast.ts) were removed from the code base. On the other hand, the register-form.tsx file was updated with a form for users to create an account. A new file toast-box.tsx was also added to display a custom message box." />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="Remove toast components and added register form The toast related components (toast.tsx, toaster.tsx, use-toast.ts) were removed from the code base. On the other hand, the register-form.tsx file was updated with a form for users to create an account. A new file toast-box.tsx was also added to display a custom message box." />
|
||||
</component>
|
||||
</project>
|
@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"@fontsource-variable/kode-mono": "^5.0.3",
|
||||
"@fontsource/ubuntu": "^5.0.13",
|
||||
"@hookform/resolvers": "^3.4.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
@ -29,7 +30,8 @@
|
||||
"react-hook-form": "^7.51.4",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
|
18
pnpm-lock.yaml
generated
18
pnpm-lock.yaml
generated
@ -11,6 +11,9 @@ dependencies:
|
||||
'@fontsource/ubuntu':
|
||||
specifier: ^5.0.13
|
||||
version: 5.0.13
|
||||
'@hookform/resolvers':
|
||||
specifier: ^3.4.2
|
||||
version: 3.4.2(react-hook-form@7.51.4)
|
||||
'@radix-ui/react-dropdown-menu':
|
||||
specifier: ^2.0.6
|
||||
version: 2.0.6(@types/react-dom@18.3.0)(@types/react@18.3.2)(react-dom@18.3.1)(react@18.3.1)
|
||||
@ -68,6 +71,9 @@ dependencies:
|
||||
tailwindcss-animate:
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7(tailwindcss@3.4.3)
|
||||
zod:
|
||||
specifier: ^3.23.8
|
||||
version: 3.23.8
|
||||
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
@ -138,6 +144,14 @@ packages:
|
||||
resolution: {integrity: sha512-uCnfknLk6ERUUgGPHqvtLGkHkW/0oyxEJy6CZLaHbh6d1om1jvqcnc5nSPel1UCX5frLxRSTkRKU/vAbL9QtJw==}
|
||||
dev: false
|
||||
|
||||
/@hookform/resolvers@3.4.2(react-hook-form@7.51.4):
|
||||
resolution: {integrity: sha512-1m9uAVIO8wVf7VCDAGsuGA0t6Z3m6jVGAN50HkV9vYLl0yixKK/Z1lr01vaRvYCkIKGoy1noVRxMzQYb4y/j1Q==}
|
||||
peerDependencies:
|
||||
react-hook-form: ^7.0.0
|
||||
dependencies:
|
||||
react-hook-form: 7.51.4(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@isaacs/cliui@8.0.2:
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
@ -1796,3 +1810,7 @@ packages:
|
||||
resolution: {integrity: sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==}
|
||||
engines: {node: '>= 14'}
|
||||
hasBin: true
|
||||
|
||||
/zod@3.23.8:
|
||||
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
|
||||
dev: false
|
||||
|
19
src/app/profile/page.tsx
Normal file
19
src/app/profile/page.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {KeyRound, Rss, UserRoundPlus} from "lucide-react";
|
||||
|
||||
export default function ProfilePage() {
|
||||
return (
|
||||
<main className="flex flex-col justify-evenly items-center antialiased h-full w-full">
|
||||
<section className={"flex flex-col justify-evenly items-center md:h-1/3 mt-4 p-2 w-full h-full gap-1"}>
|
||||
<h1 className={"text-4xl md:text-6xl font-bold"}>Only<em className={"text-primary opacity-65"}>Devs</em></h1>
|
||||
<h2 className={"text-xl md:text-3xl text-center mt-1"}>A social network for you the <em className={"text-primary opacity-65"}>nerdy</em> developer !</h2>
|
||||
<div className={"flex flex-col md:flex-row justify-evenly max-w-64 w-full md:max-w-full mt-2 gap-1"}>
|
||||
<Button className={"mt-3 md:scale-125 gap-2 bg-purple-600"}><KeyRound />Login</Button>
|
||||
<Button className={"mt-3 md:scale-125 gap-2 bg-purple-800"}><UserRoundPlus />Register</Button>
|
||||
<Button className={"mt-3 md:scale-125 gap-2"}><Rss />Go see the posts</Button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
@ -3,83 +3,97 @@ import React from "react";
|
||||
import {Label} from "@/components/ui/label";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {cn} from "@/lib/utils";
|
||||
import {OctagonX, User} from "lucide-react";
|
||||
import {BadgeAlert, OctagonX, User} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {Resolver, useForm} from "react-hook-form";
|
||||
import {SubmitHandler, useForm} from "react-hook-form";
|
||||
import toast, {ToastBar} from "react-hot-toast";
|
||||
import {ToastBox, toastType} from "@/components/ui/toast-box";
|
||||
import {zodResolver} from "@hookform/resolvers/zod"
|
||||
import {z} from 'zod'
|
||||
|
||||
interface UserInsert {
|
||||
username: string,
|
||||
displayName?: string,
|
||||
email: string,
|
||||
password: string,
|
||||
rePassword: string
|
||||
error?: Record<string, { type: string; message: string }>
|
||||
}
|
||||
const Schema = z.object({
|
||||
username:
|
||||
z.string()
|
||||
.min(3, {
|
||||
message: 'The username should be at least 3 character(s).'
|
||||
})
|
||||
.max(14, {
|
||||
message: 'The username should be at most 14 character(s).'
|
||||
}),
|
||||
displayName:
|
||||
z.string()
|
||||
.min(3, {
|
||||
message: 'The display name should be at least 3 character(s).'
|
||||
})
|
||||
.max(20, {
|
||||
message: 'The display name should be at most 20 character(s).'
|
||||
})
|
||||
.optional(),
|
||||
email:
|
||||
z.string()
|
||||
.email({
|
||||
message: 'Please enter a valid email address.'
|
||||
})
|
||||
.max(32, {
|
||||
message: 'The email should be less than 32 character(s).'
|
||||
}),
|
||||
password:
|
||||
z.string()
|
||||
.min(8, {
|
||||
message: 'The password should be at least 8 character(s).'
|
||||
})
|
||||
.max(32, {
|
||||
message: 'The password should be less than 32 character(s).'
|
||||
}),
|
||||
rePassword:
|
||||
z.string()
|
||||
}).superRefine(({ rePassword, password }, ctx) => {
|
||||
if (rePassword !== password) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: "The passwords did not match",
|
||||
path: ['rePassword']
|
||||
});
|
||||
}});
|
||||
|
||||
export const resolver: Resolver<UserInsert> = async (values) => {
|
||||
const errors: Record<string, { type: string; message: string }> = {}
|
||||
type UserInsert = z.infer<typeof Schema>
|
||||
|
||||
if (!values.username) {
|
||||
errors.name = {
|
||||
type: 'required',
|
||||
message: 'The username is required.',
|
||||
}
|
||||
}
|
||||
function ErrorLabel({message}: {message: string}) {
|
||||
return (
|
||||
<div className={'flex flex-row items-center gap-1 text-sm'}>
|
||||
<BadgeAlert className={'text-red-700'}/>
|
||||
<p className={'text-red-700'}>{message}</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
if (!values.displayName) {
|
||||
errors.description = {
|
||||
type: 'required',
|
||||
message: 'The displayname is required.',
|
||||
}
|
||||
}
|
||||
|
||||
if (!values.email) {
|
||||
errors.start = {
|
||||
type: 'required',
|
||||
message: 'The email is required.',
|
||||
}
|
||||
}
|
||||
|
||||
if (!values.password) {
|
||||
errors.promoId = {
|
||||
type: 'required',
|
||||
message: 'The password is required.',
|
||||
}
|
||||
}
|
||||
|
||||
if (!values.rePassword) {
|
||||
errors.promoId = {
|
||||
type: 'required',
|
||||
message: 'You should retype the password.',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
values: Object.keys(errors).length === 0 ? values : {},
|
||||
errors,
|
||||
}
|
||||
}
|
||||
|
||||
export function RegisterForm() {
|
||||
const handleSubbmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
console.log("Form submitted");
|
||||
};
|
||||
|
||||
const { register, handleSubmit, getValues } = useForm();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
getValues,
|
||||
setError,
|
||||
formState: {errors,
|
||||
isSubmitting} } = useForm<UserInsert>({
|
||||
resolver: zodResolver(Schema)
|
||||
});
|
||||
|
||||
const onSubmit = (data: UserInsert) => {
|
||||
console.log(data);
|
||||
if (!data.error) {
|
||||
const onSubmit: SubmitHandler<UserInsert> = async (data) => {
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1_000))
|
||||
toast.custom(<ToastBox
|
||||
message={'Hello world !'}
|
||||
title={`Verify your email`}
|
||||
message={`Please verify you mail inbox to activate your account.`}
|
||||
type={toastType.success}
|
||||
/>)
|
||||
console.log(data);
|
||||
} catch (err) {
|
||||
setError('root', {message: 'An error occurred.'})
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<div className="max-w-md w-full mx-auto rounded-none md:rounded-2xl p-4 md:p-8 shadow-input md:scale-125">
|
||||
<h2 className="font-bold text-xl text-neutral-800 dark:text-neutral-200">
|
||||
@ -90,14 +104,14 @@ export function RegisterForm() {
|
||||
</p>
|
||||
|
||||
<form className="my-8" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-2 mb-4">
|
||||
<div className="flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-2 mb-1">
|
||||
<LabelInputContainer className="mb-4">
|
||||
<Label htmlFor="username">Username<em className={"opacity-50"}>*</em></Label>
|
||||
<Input
|
||||
id="username"
|
||||
placeholder="john.doe68"
|
||||
type="text"
|
||||
{...register("username", { required: true })}
|
||||
{...register("username")}
|
||||
/>
|
||||
</LabelInputContainer>
|
||||
<LabelInputContainer className="mb-4">
|
||||
@ -106,10 +120,11 @@ export function RegisterForm() {
|
||||
id="displayName"
|
||||
placeholder="Johny"
|
||||
type="text"
|
||||
{...register("displayName")}
|
||||
{...register("displayName", {required: false})}
|
||||
/>
|
||||
</LabelInputContainer>
|
||||
</div>
|
||||
{errors.username && <ErrorLabel message={errors.username?.message || ''} />}
|
||||
<div
|
||||
className="bg-gradient-to-r from-transparent via-neutral-300 dark:via-neutral-700 to-transparent my-4 h-[1px] w-full"/>
|
||||
<LabelInputContainer className="mb-4">
|
||||
@ -118,8 +133,9 @@ export function RegisterForm() {
|
||||
id="email"
|
||||
placeholder="john.doe@foo.bar"
|
||||
type="email"
|
||||
{...register("email", { required: true })}
|
||||
{...register("email")}
|
||||
/>
|
||||
{errors.email && <ErrorLabel message={errors.email?.message || ''} />}
|
||||
</LabelInputContainer>
|
||||
<LabelInputContainer className="mb-4">
|
||||
<Label htmlFor="password">Password<em className={"opacity-50"}>*</em></Label>
|
||||
@ -127,24 +143,28 @@ export function RegisterForm() {
|
||||
id="password"
|
||||
placeholder="••••••••"
|
||||
type="password"
|
||||
{...register("password", { required: true })}
|
||||
{...register("password")}
|
||||
/>
|
||||
{errors.password && <ErrorLabel message={errors.password?.message || ''} />}
|
||||
</LabelInputContainer>
|
||||
<LabelInputContainer className="mb-8">
|
||||
<Label htmlFor="re-password">Re-type password<em className={"opacity-50"}>*</em></Label>
|
||||
<Input
|
||||
id="re-password"
|
||||
id="rePassword"
|
||||
placeholder="••••••••"
|
||||
type="password"
|
||||
{...register("re-password", { validate: (value) => value === getValues('password') || "The passwords do not match" })}
|
||||
{...register("rePassword", {required: 'You need fill again our password here.', validate: (value) => value === getValues('password') || "The passwords do not match." })}
|
||||
/>
|
||||
{errors.rePassword && <ErrorLabel message={errors.rePassword?.message || ''} />}
|
||||
</LabelInputContainer>
|
||||
|
||||
<div className={'flex flex-row justify-center items-center'}>
|
||||
{errors.root && <ErrorLabel message={errors.root?.message || ''} />}
|
||||
</div>
|
||||
<button
|
||||
className="bg-gradient-to-br relative group/btn from-black dark:from-zinc-900 dark:to-zinc-900 to-neutral-600 block dark:bg-zinc-800 w-full text-white rounded-md h-10 font-medium shadow-[0px_1px_0px_0px_#ffffff40_inset,0px_-1px_0px_0px_#ffffff40_inset] dark:shadow-[0px_1px_0px_0px_var(--zinc-800)_inset,0px_-1px_0px_0px_var(--zinc-800)_inset]"
|
||||
type="submit"
|
||||
>
|
||||
Register →
|
||||
{!isSubmitting ? 'Register' : 'Loading...'}
|
||||
<BottomGradient/>
|
||||
</button>
|
||||
<div
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {CircleCheckBig, MessageSquareText, OctagonX} from "lucide-react";
|
||||
import {Bug, CircleAlert, CircleCheckBig, CircleHelp, MessageSquareText, Minus, OctagonX, Plus} from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
|
||||
@ -22,23 +22,23 @@ export function ToastBox({title, message, type}: {title?: string, message: strin
|
||||
bgColor = 'bg-accent'
|
||||
break
|
||||
case toastType.add:
|
||||
icon = '<Plus />'
|
||||
icon = <Plus />
|
||||
bgColor = 'bg-accent'
|
||||
break
|
||||
case toastType.del:
|
||||
icon = '<Minus />'
|
||||
icon = <Minus />
|
||||
bgColor = 'bg-accent'
|
||||
break
|
||||
case toastType.info:
|
||||
icon = '<CircleHelp />'
|
||||
icon = <CircleHelp />
|
||||
bgColor = 'bg-accent'
|
||||
break
|
||||
case toastType.warn:
|
||||
icon = '<CircleAlert />'
|
||||
icon = <CircleAlert />
|
||||
bgColor = 'bg-orange-500'
|
||||
break
|
||||
case toastType.error:
|
||||
icon = '<Bug />'
|
||||
icon = <Bug />
|
||||
bgColor = 'bg-red-500'
|
||||
break
|
||||
case toastType.success:
|
||||
@ -46,7 +46,7 @@ export function ToastBox({title, message, type}: {title?: string, message: strin
|
||||
bgColor = 'bg-green-500'
|
||||
break
|
||||
case toastType.refused:
|
||||
icon = '<OctagonX />'
|
||||
icon = <OctagonX />
|
||||
bgColor = 'bg-red-700'
|
||||
break
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user