Skip to content

Commit

Permalink
Merge pull request #679 from taiwonaf/feat-Hgn-email-template
Browse files Browse the repository at this point in the history
  • Loading branch information
Prudent Bird authored Jul 25, 2024
2 parents 07723a6 + 8517201 commit 471e6bd
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const UserNavbar = () => {
<Menu className="h-[18px] w-[18px] text-neutral-dark-2 transition-all duration-300 hover:text-neutral-dark-2/50" />
<Logo />
</div>
<div className="flex w-full max-w-[220px] items-center justify-between gap-1">
<div className="flex w-full max-w-[290px] items-center justify-between gap-1">
{navlinks.map((item, index) => (
<Link
key={index}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use client";

import { useState } from "react";

import CustomButton from "~/components/common/common-button/common-button";
import CustomInput from "~/components/common/input/input";
import PasswordSuccessfulModal from "~/components/common/modals/password-successful";

const Password = () => {
const [open, setOpen] = useState<boolean>(false);
return (
<div className="w-full max-w-[674px] px-8 pt-[50px]">
<div className="mb-8">
<h2 className="mb-2 text-2xl font-semibold text-neutral-dark-1">
Password Settings
</h2>
<p className="text-base text-neutral-dark-1">
Update password for enhanced account security
</p>
</div>
<div>
<div className="mb-6 grid gap-4">
<CustomInput
placeholder="Enter current password"
label="Current Password"
className="border-border"
type="password"
/>
<CustomInput
placeholder="Enter new password"
label="New Password"
className="border-border"
type="password"
/>
<CustomInput
placeholder="Confirm new password"
label="Confrim new Password"
className="border-border"
type="password"
/>
</div>
<div className="flex items-center justify-start gap-6">
<CustomButton variant="outline" onClick={() => setOpen(false)}>
Cancel
</CustomButton>
<CustomButton className="bg-primary" onClick={() => setOpen(true)}>
Update Password
</CustomButton>
</div>
</div>
<PasswordSuccessfulModal onClose={() => setOpen(!open)} show={open} />
</div>
);
};

export default Password;
8 changes: 7 additions & 1 deletion src/app/dashboard/(user-dashboard)/settings/account/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import PasswordSettings from "./_component.tsx/password";

const page = () => {
return <div>page</div>;
return (
<div>
<PasswordSettings />
</div>
);
};

export default page;
50 changes: 50 additions & 0 deletions src/components/common/modals/notification-settings-saved/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use client";

import React from "react";

import CustomButton from "~/components/common/common-button/common-button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogOverlay,
DialogTitle,
} from "~/components/ui/dialog";

interface ModalProperties {
show: boolean;
onClose: () => void;
}

const NotificationSettingSavedModal: React.FC<ModalProperties> = ({
show,
onClose,
}) => {
return (
<Dialog open={show} onOpenChange={onClose}>
<DialogOverlay
data-testid="overlay"
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
/>
<DialogContent
className="mx-10 rounded-md bg-white p-6 shadow-md"
onClick={(event) => event.stopPropagation()}
>
<DialogTitle className="text-lg font-semibold">
Notification Updated!
</DialogTitle>
<DialogDescription className="text-sm font-normal text-muted-foreground">
Notification preferences updated successfully. Remember, you can
always adjust these settings later.
</DialogDescription>
<div className="flex justify-end">
<div onClick={onClose}>
<CustomButton variant="primary">Done</CustomButton>
</div>
</div>
</DialogContent>
</Dialog>
);
};

export default NotificationSettingSavedModal;
50 changes: 50 additions & 0 deletions src/components/common/modals/password-successful/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use client";

import React from "react";

import CustomButton from "~/components/common/common-button/common-button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogOverlay,
DialogTitle,
} from "~/components/ui/dialog";

interface ModalProperties {
show: boolean;
onClose: () => void;
}

const PasswordSuccessfulModal: React.FC<ModalProperties> = ({
show,
onClose,
}) => {
return (
<Dialog open={show} onOpenChange={onClose}>
<DialogOverlay
data-testid="overlay"
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
/>
<DialogContent
className="mx-10 rounded-md bg-white p-6 shadow-md"
onClick={(event) => event.stopPropagation()}
>
<DialogTitle className="text-lg font-semibold">
Password Successfully Updated!
</DialogTitle>
<DialogDescription className="text-sm font-normal text-muted-foreground">
Your password has been successfully updated! You can now log in with
your new password.
</DialogDescription>
<div className="flex justify-end">
<div onClick={onClose}>
<CustomButton variant="primary">Continue</CustomButton>
</div>
</div>
</DialogContent>
</Dialog>
);
};

export default PasswordSuccessfulModal;
104 changes: 104 additions & 0 deletions src/components/common/password-check/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"use client";

import { CircleCheck } from "lucide-react";
import React, { useEffect, useState } from "react";

interface PasswordCheckProperties {
minLength: number;
password: string;
onStrengthChange: (strength: PasswordCheck) => void;
}

interface PasswordCheck {
minLengthCheck: boolean;
containsUppercase: boolean;
containNumber: boolean;
}

const handlerContainsNumber = (value: string) => {
const regex = /\d/;
return regex.test(value);
};

const handleContainsUpperCase = (value: string) => {
const regex = /[A-Z]/;
return regex.test(value);
};

const PasswordCheck: React.FC<PasswordCheckProperties> = ({
password,
minLength,
onStrengthChange,
}) => {
const [checkPassword, setCheckPassword] = useState<PasswordCheck>({
minLengthCheck: false,
containsUppercase: false,
containNumber: false,
});

useEffect(() => {
const handleCheckPassword = (password: string) => {
const containsUpperCase = handleContainsUpperCase(password);
const containsNumber = handlerContainsNumber(password);
const minLengthCheck = password.length >= minLength;

const newStrength: PasswordCheck = {
minLengthCheck,
containsUppercase: containsUpperCase,
containNumber: containsNumber,
};

setCheckPassword(newStrength);
onStrengthChange(newStrength);
};
handleCheckPassword(password);
}, [password, minLength, onStrengthChange]);

return (
<div className="l-4 mt-[8px]">
<div className="flex items-center gap-[16px]">
<div
className={`w-[100px] border-[3px] border-solid ${checkPassword.minLengthCheck || checkPassword.containNumber || checkPassword.containsUppercase ? "border-[#6DC347]" : "border-[#B6B6B6]"}`}
></div>
<div
className={`w-[100px] border-[3px] border-solid ${(checkPassword.minLengthCheck && checkPassword.containNumber) || (checkPassword.containNumber && checkPassword.containsUppercase) || (checkPassword.minLengthCheck && checkPassword.containsUppercase) ? "border-[#6DC347]" : "border-[#B6B6B6]"}`}
></div>
<div
className={`w-[100px] border-[3px] border-solid ${checkPassword.minLengthCheck && checkPassword.containNumber && checkPassword.containsUppercase ? "border-[#6DC347]" : "border-[#B6B6B6]"}`}
></div>
</div>
<div>
<p className="mt-[24px]">Weak password. Must contain:</p>
<div className="mt-[12px] flex items-center gap-[8px]">
<CircleCheck
data-testid="circle-check-icon"
style={{
color: checkPassword.containsUppercase ? "#6DC347" : "#B6B6B6",
}}
/>
<p className="">At least 1 uppercase</p>
</div>
<div className="mt-[8px] flex items-center gap-[8px]">
<CircleCheck
data-testid="circle-check-icon"
style={{
color: checkPassword.containNumber ? "#6DC347" : "#B6B6B6",
}}
/>
<p>At least 1 number</p>
</div>
<div className="mt-[8px] flex items-center gap-[8px]">
<CircleCheck
data-testid="circle-check-icon"
style={{
color: checkPassword.minLengthCheck ? "#6DC347" : "#B6B6B6",
}}
/>
<p>At least {minLength} characters</p>
</div>
</div>
</div>
);
};

export default PasswordCheck;
57 changes: 57 additions & 0 deletions src/components/common/password-input/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use client";

import { EyeIcon, EyeOff } from "lucide-react";
import { useState } from "react";

import { Input } from "~/components/ui/input";

interface PasswordToggleProperties {
password: string;
onPasswordChange: (password: string) => void;
name: string;
placeholder: string;
id: string;
}

const PasswordInput: React.FC<PasswordToggleProperties> = ({
password,
onPasswordChange,
name,
id,
placeholder,
}) => {
const [isPasswordHidden, setPasswordHidden] = useState(true);

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

return (
<div className="flex flex-col gap-10">
<div className="relative w-full">
<button
type="button"
className="absolute inset-y-0 right-3 my-auto text-[#939393] active:text-[#434343]"
onClick={() => setPasswordHidden(!isPasswordHidden)}
aria-label={isPasswordHidden ? "Show password" : "Hide password"}
title={isPasswordHidden ? "Show password" : "Hide password"}
>
{isPasswordHidden ? <EyeIcon /> : <EyeOff />}
</button>

<Input
aria-label="Password"
type={isPasswordHidden ? "password" : "text"}
id={id}
name={name}
placeholder={placeholder}
value={password}
onChange={handleChange}
className="h-[48px] w-full rounded-[8px] border border-[#B2B0B0] bg-transparent px-[16px] py-[12px] pr-[48px] text-[#939393] shadow-none outline-none placeholder:text-[14px] placeholder:text-[#939393] focus:border-primary focus-visible:ring-0 focus-visible:ring-transparent"
/>
</div>
</div>
);
};

export { PasswordInput };

0 comments on commit 471e6bd

Please sign in to comment.