Skip to content

Commit

Permalink
Merge pull request #646 from hngprojects/Vxrcel-clean
Browse files Browse the repository at this point in the history
  • Loading branch information
Prudent Bird authored Jul 24, 2024
2 parents c27bd71 + fd37368 commit 44ed535
Show file tree
Hide file tree
Showing 53 changed files with 1,794 additions and 244 deletions.
10 changes: 10 additions & 0 deletions public/signup/icons/bi_facebook.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions public/signup/icons/flat-color-icons_google.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BellRing } from "lucide-react";
import { FC } from "react";

import CustomButton from "~/components/common/button/button";
import CustomButton from "~/components/common/common-button/common-button";
import {
Card,
CardContent,
Expand Down
2 changes: 1 addition & 1 deletion src/app/(admin)/admin/email/generate-with-html/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FC, ReactNode } from "react";

import { Breadcrumb } from "~/components/common/breadcrumb/breadcrumb";
import { Breadcrumb } from "~/components/common/breadcrumb";

interface IProperties {
children: ReactNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useState } from "react";

import { Breadcrumb } from "~/components/common/breadcrumb";
import { Button } from "~/components/common/button";
import { Button } from "~/components/common/common-button";
import PageHeader from "../../../_components/page-header";
import { templateOne } from "./template-example";
import TemplateViewer from "./TemplateViewer";
Expand Down
39 changes: 39 additions & 0 deletions src/app/(auth-routes)/reset-password/verify-otp/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";

import CustomButton from "~/components/common/common-button/common-button";
import { InputOtp } from "~/components/common/Input-otp";

export default function VerifyCodePage() {
return (
<>
<div className="m-auto mb-[120px] mt-[73px] flex flex-col items-center justify-center gap-5 px-6 text-center md:gap-6 lg:mb-[133px] lg:mt-20 lg:w-[551px]">
<div className="flex flex-col items-center gap-2 md:gap-4">
<h3 className="text-2xl font-semibold md:text-[28px]">
Verification Code
</h3>
<p className="text-[13px] font-normal text-[#525252] md:text-lg">
Confirm the OTP sent to{" "}
<span className="font-semibold">[email protected]</span> and
enter the Verification code that was sent. Code expires in{" "}
<span className="font-bold text-[#F97316]">00:59</span>
</p>
</div>

<div>
<InputOtp />
</div>
<div className="flex w-full flex-col gap-2 md:gap-4">
<CustomButton variant="subtle" size="lg" isDisabled={true}>
Verify{" "}
</CustomButton>
<p className="text-[13px]">
Didn&apos;t recieve any code?{" "}
<span className="ml-2 text-base font-semibold text-[#F97316]">
Resend OTP
</span>
</p>
</div>
</div>
</>
);
}
211 changes: 211 additions & 0 deletions src/app/(auth-routes)/sign-up/_component/SignupForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
"use client";

import { Eye, EyeOff } from "lucide-react";
import { useState } from "react";
import { z, ZodType } from "zod";

import CustomButton from "~/components/common/common-button/common-button";

interface IData {
fullName: string;
email: string;
password: string;
}

interface TestFormProperties {
onSubmit: (data: IData) => void;
}

const schemaRegister = z.object({
fullName: z.string().regex(/^['A-Za-z-]+(?: ['A-Za-z-]+)*$/, {
message: "Enter your full name",
}),
email: z.string().email({
message: "Enter a valid email format",
}),
password: z
.string()
.min(8, {
message: "Password must be at least 8 characters long.",
})
.max(20, {
message: "Password should not be more than 20 characters long.",
}),
});

type SchemaRegisterType = z.infer<typeof schemaRegister>;

type ErrorsType = Partial<Record<keyof SchemaRegisterType, string>>;

const SignupForm = ({ onSubmit }: TestFormProperties) => {
const [formData, setFormData] = useState<SchemaRegisterType>({
fullName: "",
email: "",
password: "",
});
const [errors, setErrors] = useState<ErrorsType>({});
const [passwordVisible, setPasswordVisible] = useState<boolean>(false);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;

if (name in schemaRegister.shape) {
setFormData({ ...formData, [name]: value });

const fieldSchema = schemaRegister.shape[
name as keyof typeof schemaRegister.shape
] as ZodType<string>;
const result = fieldSchema.safeParse(value);

if (result.success) {
setErrors((previousErrors) => {
const remainingErrors = { ...previousErrors };
delete remainingErrors[name as keyof SchemaRegisterType];
return remainingErrors;
});
} else {
setErrors((previousErrors) => ({
...previousErrors,
[name]: result.error.errors[0].message,
}));
}
}
};

const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const result = schemaRegister.safeParse(formData);

if (result.success) {
setErrors({});
onSubmit(formData);
} else {
const newErrors: ErrorsType = {};
for (const error of result.error.errors) {
const fieldName = error.path[0] as keyof SchemaRegisterType;
newErrors[fieldName] = error.message;
}
setErrors(newErrors);
}
};
return (
<form
className="mt-5 flex flex-col space-y-5 text-left lg:space-y-6"
onSubmit={handleSubmit}
>
<div className="flex flex-col">
<div className="flex flex-col space-y-2 lg:space-y-3">
<label
htmlFor="fullName"
className="text-xl font-medium lg:font-normal"
>
Full Name
</label>
<input
type="text"
id="fullName"
name="fullName"
value={formData.fullName}
onChange={handleChange}
className={`text-neutralColor-dark-2 rounded-lg border px-4 py-3 text-lg outline-none placeholder:text-[hsl(var(--neutralColor-dark-1))] focus:border-primary lg:py-5 lg:pl-4 lg:pr-6 ${
errors.fullName
? "border-[#E80D0D]"
: formData.fullName
? "border-primary"
: "border-stroke-colors-stroke"
}`}
placeholder="Enter your full name"
/>
</div>
{errors.fullName && (
<span className="text-[13px] leading-[180%] text-[#E80D0D]">
{errors.fullName}
</span>
)}
</div>
<div className="flex flex-col">
<div className="flex flex-col space-y-2 lg:space-y-3">
<label
htmlFor="emailAddress"
className="text-xl font-medium lg:font-normal"
>
Email Address
</label>
<input
type="email"
id="emailAddress"
name="email"
value={formData.email}
onChange={handleChange}
className={`text-neutralColor-dark-2 rounded-lg border px-4 py-3 text-lg outline-none placeholder:text-[hsl(var(--neutralColor-dark-1))] focus:border-primary lg:py-5 lg:pl-4 lg:pr-6 ${
errors.email
? "border-[#E80D0D]"
: formData.email
? "border-primary"
: "border-stroke-colors-stroke"
}`}
placeholder="Enter your email address"
/>
</div>
{errors.email && (
<span className="text-[13px] leading-[180%] text-[#E80D0D]">
{errors.email}
</span>
)}
</div>
<div className="flex flex-col">
<div className="relative flex flex-col space-y-2 lg:space-y-3">
<label
htmlFor="password"
className="text-xl font-medium lg:font-normal"
>
Password
</label>
<input
type={passwordVisible ? "text" : "password"}
id="password"
name="password"
value={formData.password}
onChange={handleChange}
className={`text-neutralColor-dark-2 rounded-lg border px-4 py-3 text-lg outline-none placeholder:text-[hsl(var(--neutralColor-dark-1))] focus:border-primary lg:py-5 lg:pl-4 lg:pr-6 ${
errors.password
? "border-[#E80D0D]"
: formData.password
? "border-primary"
: "border-stroke-colors-stroke"
}`}
placeholder="Create your password"
/>
{passwordVisible ? (
<Eye
size={24}
color="#939393"
className="absolute right-6 top-[50%] cursor-pointer"
onClick={() => setPasswordVisible(!passwordVisible)}
/>
) : (
<EyeOff
size={24}
color="#939393"
className="absolute right-6 top-[50%] cursor-pointer"
onClick={() => setPasswordVisible(!passwordVisible)}
/>
)}
</div>
{errors.password && (
<span className="text-[13px] leading-[180%] text-[#E80D0D]">
{errors.password}
</span>
)}
</div>
<CustomButton
variant="primary"
className="px-4 py-2 text-base font-bold lg:h-16"
>
Create Account
</CustomButton>
</form>
);
};

export default SignupForm;
63 changes: 63 additions & 0 deletions src/app/(auth-routes)/sign-up/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import SignupForm from "./_component/SignupForm";
import SignUp from "./page";

describe("form", () => {
const handleSubmit = vi.fn();

it("should render the form fields", () => {
expect.hasAssertions();
render(<SignUp />);

expect(screen.getByLabelText("Full Name")).toBeInTheDocument();
expect(screen.getByLabelText("Email Address")).toBeInTheDocument();
expect(screen.getByLabelText("Password")).toBeInTheDocument();
expect(
screen.getByRole("button", { name: "Create Account" }),
).toBeInTheDocument();

expect(screen.getByLabelText("Full Name")).toHaveAttribute("type", "text");
expect(screen.getByLabelText("Email Address")).toHaveAttribute(
"type",
"email",
);
expect(screen.getByLabelText("Password")).toHaveAttribute(
"type",
"password",
);
});

it("should not submit the form if required fields are empty", async () => {
expect.hasAssertions();
render(<SignupForm onSubmit={handleSubmit} />);

await userEvent.click(
screen.getByRole("button", { name: "Create Account" }),
);
expect(handleSubmit).not.toHaveBeenCalled();
});

it("should submit the form with correct values", async () => {
expect.hasAssertions();
render(<SignupForm onSubmit={handleSubmit} />);

await userEvent.type(screen.getByLabelText("Full Name"), "John Doe");
await userEvent.type(
screen.getByLabelText("Email Address"),
"[email protected]",
);
await userEvent.type(screen.getByLabelText("Password"), "securepassword");
await userEvent.click(
screen.getByRole("button", { name: "Create Account" }),
);

expect(handleSubmit).toHaveBeenCalledTimes(1);
expect(handleSubmit).toHaveBeenCalledWith({
fullName: "John Doe",
email: "[email protected]",
password: "securepassword",
});
});
});
Loading

0 comments on commit 44ed535

Please sign in to comment.