refactor: replace strong password hook with mild password implementation and update related components
This commit is contained in:
parent
e0353bb6f9
commit
d9fdfabf00
11 changed files with 243 additions and 158 deletions
|
|
@ -12,7 +12,7 @@ import {
|
||||||
} from "~/components/custom_ui/form";
|
} from "~/components/custom_ui/form";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import Input from "~/components/ui/input";
|
import Input from "~/components/ui/input";
|
||||||
import { useStrongPassword } from "~/hooks/useStrongPassword";
|
import { useMildPassword } from "~/hooks/usePassword";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { useZodForm } from "~/lib/zodForm";
|
import { useZodForm } from "~/lib/zodForm";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
|
|
@ -25,8 +25,9 @@ export const ChangePasswordForm = () => {
|
||||||
passwordRefine,
|
passwordRefine,
|
||||||
getStrengthTxt,
|
getStrengthTxt,
|
||||||
strengthScore,
|
strengthScore,
|
||||||
|
maxStrength,
|
||||||
toggleVisibility,
|
toggleVisibility,
|
||||||
} = useStrongPassword();
|
} = useMildPassword();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const ChangePasswordFormSchema = z
|
const ChangePasswordFormSchema = z
|
||||||
|
|
@ -161,7 +162,7 @@ export const ChangePasswordForm = () => {
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
aria-label="Password strength"
|
aria-label="Password strength"
|
||||||
aria-valuemax={4}
|
aria-valuemax={maxStrength}
|
||||||
aria-valuemin={0}
|
aria-valuemin={0}
|
||||||
aria-valuenow={strengthScore}
|
aria-valuenow={strengthScore}
|
||||||
className="mt-3 mb-4 h-1 w-full overflow-hidden rounded-full bg-border"
|
className="mt-3 mb-4 h-1 w-full overflow-hidden rounded-full bg-border"
|
||||||
|
|
@ -172,7 +173,7 @@ export const ChangePasswordForm = () => {
|
||||||
"h-full transition-all duration-500 ease-out",
|
"h-full transition-all duration-500 ease-out",
|
||||||
getStrengthColor(strengthScore),
|
getStrengthColor(strengthScore),
|
||||||
)}
|
)}
|
||||||
style={{ width: `${(strengthScore / 4) * 100}%` }}
|
style={{ width: `${(strengthScore / maxStrength) * 100}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import {
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import Input from "~/components/ui/input";
|
import Input from "~/components/ui/input";
|
||||||
import { PhoneInput } from "~/components/ui/phone-input";
|
import { PhoneInput } from "~/components/ui/phone-input";
|
||||||
import { useStrongPassword } from "~/hooks/useStrongPassword";
|
import { useMildPassword } from "~/hooks/usePassword";
|
||||||
import { generatePassword } from "~/lib/basicPwGenerator";
|
import { generatePassword } from "~/lib/basicPwGenerator";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { useZodForm } from "~/lib/zodForm";
|
import { useZodForm } from "~/lib/zodForm";
|
||||||
|
|
@ -31,7 +31,8 @@ export const FormNewUser = ({
|
||||||
getStrengthTxt,
|
getStrengthTxt,
|
||||||
strengthScore,
|
strengthScore,
|
||||||
toggleVisibility,
|
toggleVisibility,
|
||||||
} = useStrongPassword();
|
maxStrength,
|
||||||
|
} = useMildPassword();
|
||||||
|
|
||||||
const NewUserSchema = z
|
const NewUserSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
|
@ -206,7 +207,7 @@ export const FormNewUser = ({
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
aria-label="Password strength"
|
aria-label="Password strength"
|
||||||
aria-valuemax={4}
|
aria-valuemax={maxStrength}
|
||||||
aria-valuemin={0}
|
aria-valuemin={0}
|
||||||
aria-valuenow={strengthScore}
|
aria-valuenow={strengthScore}
|
||||||
className="mt-3 mb-4 h-1 w-full overflow-hidden rounded-full bg-border"
|
className="mt-3 mb-4 h-1 w-full overflow-hidden rounded-full bg-border"
|
||||||
|
|
@ -217,7 +218,9 @@ export const FormNewUser = ({
|
||||||
"h-full transition-all duration-500 ease-out",
|
"h-full transition-all duration-500 ease-out",
|
||||||
getStrengthColor(strengthScore),
|
getStrengthColor(strengthScore),
|
||||||
)}
|
)}
|
||||||
style={{ width: `${(strengthScore / 4) * 100}%` }}
|
style={{
|
||||||
|
width: `${(strengthScore / maxStrength) * 100}%`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import {
|
||||||
import Input from "~/components/ui/input";
|
import Input from "~/components/ui/input";
|
||||||
import { Label } from "~/components/ui/label";
|
import { Label } from "~/components/ui/label";
|
||||||
import { Separator } from "~/components/ui/separator";
|
import { Separator } from "~/components/ui/separator";
|
||||||
import { useStrongPassword } from "~/hooks/useStrongPassword";
|
import { useMildPassword } from "~/hooks/usePassword";
|
||||||
import { generatePassword } from "~/lib/basicPwGenerator";
|
import { generatePassword } from "~/lib/basicPwGenerator";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { useZodForm } from "~/lib/zodForm";
|
import { useZodForm } from "~/lib/zodForm";
|
||||||
|
|
@ -38,7 +38,8 @@ export const OverridePasswordForm = ({ id }: { id: UsersId }) => {
|
||||||
getStrengthTxt,
|
getStrengthTxt,
|
||||||
strengthScore,
|
strengthScore,
|
||||||
toggleVisibility,
|
toggleVisibility,
|
||||||
} = useStrongPassword();
|
maxStrength,
|
||||||
|
} = useMildPassword();
|
||||||
|
|
||||||
const OverridePasswordFormSchema = z
|
const OverridePasswordFormSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
|
@ -203,7 +204,7 @@ export const OverridePasswordForm = ({ id }: { id: UsersId }) => {
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
aria-label="Password strength"
|
aria-label="Password strength"
|
||||||
aria-valuemax={4}
|
aria-valuemax={maxStrength}
|
||||||
aria-valuemin={0}
|
aria-valuemin={0}
|
||||||
aria-valuenow={strengthScore}
|
aria-valuenow={strengthScore}
|
||||||
className="mt-3 mb-4 h-1 w-full overflow-hidden rounded-full bg-border"
|
className="mt-3 mb-4 h-1 w-full overflow-hidden rounded-full bg-border"
|
||||||
|
|
@ -214,7 +215,9 @@ export const OverridePasswordForm = ({ id }: { id: UsersId }) => {
|
||||||
"h-full transition-all duration-500 ease-out",
|
"h-full transition-all duration-500 ease-out",
|
||||||
getStrengthColor(strengthScore),
|
getStrengthColor(strengthScore),
|
||||||
)}
|
)}
|
||||||
style={{ width: `${(strengthScore / 4) * 100}%` }}
|
style={{
|
||||||
|
width: `${(strengthScore / maxStrength) * 100}%`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useState } from "react";
|
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import {
|
import {
|
||||||
|
|
@ -12,6 +11,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "~/components/custom_ui/form";
|
} from "~/components/custom_ui/form";
|
||||||
import Input from "~/components/ui/input";
|
import Input from "~/components/ui/input";
|
||||||
|
import { useMildPassword } from "~/hooks/usePassword";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { useZodForm } from "~/lib/zodForm";
|
import { useZodForm } from "~/lib/zodForm";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
|
|
@ -20,38 +20,16 @@ import { api } from "~/utils/api";
|
||||||
export const FormRstPwFromToken = (props: { token: string }) => {
|
export const FormRstPwFromToken = (props: { token: string }) => {
|
||||||
const { locale, t } = useTranslation();
|
const { locale, t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [strengthScore, setStrengthScore] = useState(0);
|
|
||||||
const [isVisible, setIsVisible] = useState<boolean>(false);
|
|
||||||
const toggleVisibility = () => setIsVisible((prevState) => !prevState);
|
|
||||||
|
|
||||||
const checkStrength = (pass: string) => {
|
const {
|
||||||
const requirements = [
|
isVisible,
|
||||||
{ regex: /.{8,}/, text: t.auth.signup.pw.lenght },
|
getStrengthColor,
|
||||||
{ regex: /[0-9]/, text: t.auth.signup.pw.number },
|
passwordRefine,
|
||||||
{ regex: /[a-z]/, text: t.auth.signup.pw.lowercase },
|
getStrengthTxt,
|
||||||
{ regex: /[A-Z]/, text: t.auth.signup.pw.uppercase },
|
strengthScore,
|
||||||
];
|
toggleVisibility,
|
||||||
|
maxStrength,
|
||||||
return requirements.map((req) => ({
|
} = useMildPassword();
|
||||||
met: req.regex.test(pass),
|
|
||||||
text: req.text,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStrengthColor = (score: number) => {
|
|
||||||
if (score === 0) return "bg-border";
|
|
||||||
if (score <= 1) return "bg-red-500";
|
|
||||||
if (score <= 2) return "bg-orange-500";
|
|
||||||
if (score === 3) return "bg-amber-500";
|
|
||||||
return "bg-emerald-500";
|
|
||||||
};
|
|
||||||
const getStrengthTxt = (score: number) => {
|
|
||||||
if (score === 0) return "text-border";
|
|
||||||
if (score <= 1) return "text-red-500";
|
|
||||||
if (score <= 2) return "text-orange-500";
|
|
||||||
if (score === 3) return "text-amber-500";
|
|
||||||
return "text-emerald-500";
|
|
||||||
};
|
|
||||||
|
|
||||||
const ResetFormSchema = z
|
const ResetFormSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
|
@ -59,21 +37,11 @@ export const FormRstPwFromToken = (props: { token: string }) => {
|
||||||
repeat_password: z.string(),
|
repeat_password: z.string(),
|
||||||
})
|
})
|
||||||
.superRefine(({ password, repeat_password }, refinementContext) => {
|
.superRefine(({ password, repeat_password }, refinementContext) => {
|
||||||
const complexity = checkStrength(password);
|
passwordRefine({
|
||||||
|
password,
|
||||||
if (complexity.filter((req) => req.met).length !== complexity.length) {
|
path: "password",
|
||||||
refinementContext.issues.push({
|
refinementContext,
|
||||||
code: "custom",
|
|
||||||
input: "",
|
|
||||||
message: complexity
|
|
||||||
.filter((req) => !req.met)
|
|
||||||
.map((req) => req.text)
|
|
||||||
.join("\n"),
|
|
||||||
path: ["password"],
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
setStrengthScore(complexity.filter((req) => req.met).length);
|
|
||||||
|
|
||||||
if (password !== repeat_password) {
|
if (password !== repeat_password) {
|
||||||
refinementContext.issues.push({
|
refinementContext.issues.push({
|
||||||
code: "custom",
|
code: "custom",
|
||||||
|
|
@ -164,7 +132,7 @@ export const FormRstPwFromToken = (props: { token: string }) => {
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
aria-label="Password strength"
|
aria-label="Password strength"
|
||||||
aria-valuemax={4}
|
aria-valuemax={maxStrength}
|
||||||
aria-valuemin={0}
|
aria-valuemin={0}
|
||||||
aria-valuenow={strengthScore}
|
aria-valuenow={strengthScore}
|
||||||
className="mt-3 mb-4 h-1 w-full overflow-hidden rounded-full bg-border"
|
className="mt-3 mb-4 h-1 w-full overflow-hidden rounded-full bg-border"
|
||||||
|
|
@ -175,7 +143,9 @@ export const FormRstPwFromToken = (props: { token: string }) => {
|
||||||
"h-full transition-all duration-500 ease-out",
|
"h-full transition-all duration-500 ease-out",
|
||||||
getStrengthColor(strengthScore),
|
getStrengthColor(strengthScore),
|
||||||
)}
|
)}
|
||||||
style={{ width: `${(strengthScore / 4) * 100}%` }}
|
style={{
|
||||||
|
width: `${(strengthScore / maxStrength) * 100}%`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
190
apps/infoalloggi/src/hooks/usePassword.ts
Normal file
190
apps/infoalloggi/src/hooks/usePassword.ts
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { type RefinementCtx, z } from "zod/v4";
|
||||||
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
|
|
||||||
|
export const zStrongPassword = z.string().superRefine((password, ctx) => {
|
||||||
|
const test = [/.{8,}/, /[0-9]/, /[a-z]/, /[A-Z]/].every((regex) =>
|
||||||
|
regex.test(password),
|
||||||
|
);
|
||||||
|
if (!test) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message:
|
||||||
|
"La password deve contenere almeno 8 caratteri, un numero, una lettera minuscola e una maiuscola.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
type PasswordManager = {
|
||||||
|
getStrengthColor: (score: number) => string;
|
||||||
|
getStrengthTxt: (score: number) => string;
|
||||||
|
isVisible: boolean;
|
||||||
|
passwordRefine: ({
|
||||||
|
password,
|
||||||
|
refinementContext,
|
||||||
|
path,
|
||||||
|
}: {
|
||||||
|
password: string;
|
||||||
|
refinementContext: RefinementCtx;
|
||||||
|
path: string;
|
||||||
|
}) => void;
|
||||||
|
strengthScore: number;
|
||||||
|
toggleVisibility: () => void;
|
||||||
|
maxStrength: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useStrongPassword = (): PasswordManager => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [isVisible, setIsVisible] = useState<boolean>(false);
|
||||||
|
const [strengthScore, setStrengthScore] = useState(0);
|
||||||
|
|
||||||
|
const checkStrength = (pass: string) => {
|
||||||
|
const requirements = [
|
||||||
|
{ regex: /.{8,}/, text: t.auth.signup.pw.lenght8 },
|
||||||
|
{ regex: /[0-9]/, text: t.auth.signup.pw.number },
|
||||||
|
{ regex: /[a-z]/, text: t.auth.signup.pw.lowercase },
|
||||||
|
{ regex: /[A-Z]/, text: t.auth.signup.pw.uppercase },
|
||||||
|
];
|
||||||
|
|
||||||
|
return requirements.map((req) => ({
|
||||||
|
met: req.regex.test(pass),
|
||||||
|
text: req.text,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStrengthColor = (score: number) => {
|
||||||
|
if (score === 0) return "bg-border";
|
||||||
|
if (score <= 1) return "bg-red-500";
|
||||||
|
if (score <= 2) return "bg-orange-500";
|
||||||
|
if (score === 3) return "bg-amber-500";
|
||||||
|
return "bg-emerald-500";
|
||||||
|
};
|
||||||
|
const getStrengthTxt = (score: number) => {
|
||||||
|
if (score === 0) return "text-border";
|
||||||
|
if (score <= 1) return "text-red-500";
|
||||||
|
if (score <= 2) return "text-orange-500";
|
||||||
|
if (score === 3) return "text-amber-500";
|
||||||
|
return "text-emerald-500";
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleVisibility = () => setIsVisible((prevState) => !prevState);
|
||||||
|
|
||||||
|
const passwordRefine = ({
|
||||||
|
password,
|
||||||
|
refinementContext,
|
||||||
|
path,
|
||||||
|
}: {
|
||||||
|
password: string;
|
||||||
|
refinementContext: RefinementCtx;
|
||||||
|
path: string;
|
||||||
|
}) => {
|
||||||
|
const complexity = checkStrength(password);
|
||||||
|
|
||||||
|
if (complexity.filter((req) => req.met).length !== complexity.length) {
|
||||||
|
refinementContext.issues.push({
|
||||||
|
code: "custom",
|
||||||
|
input: "",
|
||||||
|
message: complexity
|
||||||
|
.filter((req) => !req.met)
|
||||||
|
.map((req) => req.text)
|
||||||
|
.join("\n"),
|
||||||
|
path: [path],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setStrengthScore(complexity.filter((req) => req.met).length);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getStrengthColor,
|
||||||
|
getStrengthTxt,
|
||||||
|
isVisible,
|
||||||
|
passwordRefine,
|
||||||
|
strengthScore,
|
||||||
|
toggleVisibility,
|
||||||
|
maxStrength: 4,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const zMildPassword = z.string().superRefine((password, ctx) => {
|
||||||
|
const test = [/.{6,}/, /[a-z]/, /[A-Z]/].every((regex) =>
|
||||||
|
regex.test(password),
|
||||||
|
);
|
||||||
|
if (!test) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message:
|
||||||
|
"La password deve contenere almeno 6 caratteri, una lettera minuscola e una maiuscola.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useMildPassword = (): PasswordManager => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [isVisible, setIsVisible] = useState<boolean>(false);
|
||||||
|
const [strengthScore, setStrengthScore] = useState(0);
|
||||||
|
|
||||||
|
const checkStrength = (pass: string) => {
|
||||||
|
const requirements = [
|
||||||
|
{ regex: /.{6,}/, text: t.auth.signup.pw.lenght6 },
|
||||||
|
{ regex: /[a-z]/, text: t.auth.signup.pw.lowercase },
|
||||||
|
{ regex: /[A-Z]/, text: t.auth.signup.pw.uppercase },
|
||||||
|
];
|
||||||
|
|
||||||
|
return requirements.map((req) => ({
|
||||||
|
met: req.regex.test(pass),
|
||||||
|
text: req.text,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStrengthColor = (score: number) => {
|
||||||
|
if (score === 0) return "bg-border";
|
||||||
|
if (score <= 1) return "bg-red-500";
|
||||||
|
if (score === 2) return "bg-amber-500";
|
||||||
|
return "bg-emerald-500";
|
||||||
|
};
|
||||||
|
const getStrengthTxt = (score: number) => {
|
||||||
|
if (score === 0) return "text-border";
|
||||||
|
if (score <= 1) return "text-red-500";
|
||||||
|
if (score === 2) return "text-amber-500";
|
||||||
|
return "text-emerald-500";
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleVisibility = () => setIsVisible((prevState) => !prevState);
|
||||||
|
|
||||||
|
const passwordRefine = ({
|
||||||
|
password,
|
||||||
|
refinementContext,
|
||||||
|
path,
|
||||||
|
}: {
|
||||||
|
password: string;
|
||||||
|
refinementContext: RefinementCtx;
|
||||||
|
path: string;
|
||||||
|
}) => {
|
||||||
|
const complexity = checkStrength(password);
|
||||||
|
|
||||||
|
if (complexity.filter((req) => req.met).length !== complexity.length) {
|
||||||
|
refinementContext.issues.push({
|
||||||
|
code: "custom",
|
||||||
|
input: "",
|
||||||
|
message: complexity
|
||||||
|
.filter((req) => !req.met)
|
||||||
|
.map((req) => req.text)
|
||||||
|
.join("\n"),
|
||||||
|
path: [path],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setStrengthScore(complexity.filter((req) => req.met).length);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getStrengthColor,
|
||||||
|
getStrengthTxt,
|
||||||
|
isVisible,
|
||||||
|
passwordRefine,
|
||||||
|
strengthScore,
|
||||||
|
toggleVisibility,
|
||||||
|
maxStrength: 3,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
import { useState } from "react";
|
|
||||||
import { type RefinementCtx, z } from "zod/v4";
|
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
|
||||||
|
|
||||||
export const zStrongPassword = z.string().superRefine((password, ctx) => {
|
|
||||||
const test = [/.{8,}/, /[0-9]/, /[a-z]/, /[A-Z]/].every((regex) =>
|
|
||||||
regex.test(password),
|
|
||||||
);
|
|
||||||
if (!test) {
|
|
||||||
ctx.addIssue({
|
|
||||||
code: "custom",
|
|
||||||
message:
|
|
||||||
"La password deve contenere almeno 8 caratteri, un numero, una lettera minuscola e una maiuscola.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const useStrongPassword = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [isVisible, setIsVisible] = useState<boolean>(false);
|
|
||||||
const [strengthScore, setStrengthScore] = useState(0);
|
|
||||||
|
|
||||||
const checkStrength = (pass: string) => {
|
|
||||||
const requirements = [
|
|
||||||
{ regex: /.{8,}/, text: t.auth.signup.pw.lenght },
|
|
||||||
{ regex: /[0-9]/, text: t.auth.signup.pw.number },
|
|
||||||
{ regex: /[a-z]/, text: t.auth.signup.pw.lowercase },
|
|
||||||
{ regex: /[A-Z]/, text: t.auth.signup.pw.uppercase },
|
|
||||||
];
|
|
||||||
|
|
||||||
return requirements.map((req) => ({
|
|
||||||
met: req.regex.test(pass),
|
|
||||||
text: req.text,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStrengthColor = (score: number) => {
|
|
||||||
if (score === 0) return "bg-border";
|
|
||||||
if (score <= 1) return "bg-red-500";
|
|
||||||
if (score <= 2) return "bg-orange-500";
|
|
||||||
if (score === 3) return "bg-amber-500";
|
|
||||||
return "bg-emerald-500";
|
|
||||||
};
|
|
||||||
const getStrengthTxt = (score: number) => {
|
|
||||||
if (score === 0) return "text-border";
|
|
||||||
if (score <= 1) return "text-red-500";
|
|
||||||
if (score <= 2) return "text-orange-500";
|
|
||||||
if (score === 3) return "text-amber-500";
|
|
||||||
return "text-emerald-500";
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleVisibility = () => setIsVisible((prevState) => !prevState);
|
|
||||||
|
|
||||||
const passwordRefine = ({
|
|
||||||
password,
|
|
||||||
refinementContext,
|
|
||||||
path,
|
|
||||||
}: {
|
|
||||||
password: string;
|
|
||||||
refinementContext: RefinementCtx;
|
|
||||||
path: string;
|
|
||||||
}) => {
|
|
||||||
const complexity = checkStrength(password);
|
|
||||||
|
|
||||||
if (complexity.filter((req) => req.met).length !== complexity.length) {
|
|
||||||
refinementContext.issues.push({
|
|
||||||
code: "custom",
|
|
||||||
input: "",
|
|
||||||
message: complexity
|
|
||||||
.filter((req) => !req.met)
|
|
||||||
.map((req) => req.text)
|
|
||||||
.join("\n"),
|
|
||||||
path: [path],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setStrengthScore(complexity.filter((req) => req.met).length);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
getStrengthColor,
|
|
||||||
getStrengthTxt,
|
|
||||||
isVisible,
|
|
||||||
passwordRefine,
|
|
||||||
strengthScore,
|
|
||||||
toggleVisibility,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -369,7 +369,8 @@ export const en: LangDict = {
|
||||||
nome: "Name",
|
nome: "Name",
|
||||||
password: "Password",
|
password: "Password",
|
||||||
pw: {
|
pw: {
|
||||||
lenght: "At least 8 characters",
|
lenght8: "At least 8 characters",
|
||||||
|
lenght6: "At least 6 characters",
|
||||||
lowercase: "At least 1 lowercase letter",
|
lowercase: "At least 1 lowercase letter",
|
||||||
number: "At least 1 number",
|
number: "At least 1 number",
|
||||||
uppercase: "At least 1 uppercase letter",
|
uppercase: "At least 1 uppercase letter",
|
||||||
|
|
|
||||||
|
|
@ -374,7 +374,8 @@ export const it: LangDict = {
|
||||||
nome: "Nome",
|
nome: "Nome",
|
||||||
password: "Password",
|
password: "Password",
|
||||||
pw: {
|
pw: {
|
||||||
lenght: "Almeno 8 caratteri",
|
lenght8: "Almeno 8 caratteri",
|
||||||
|
lenght6: "Almeno 6 caratteri",
|
||||||
lowercase: "Almeno 1 lettera minuscola",
|
lowercase: "Almeno 1 lettera minuscola",
|
||||||
number: "Almeno 1 numero",
|
number: "Almeno 1 numero",
|
||||||
uppercase: "Almeno 1 lettera maiuscola",
|
uppercase: "Almeno 1 lettera maiuscola",
|
||||||
|
|
|
||||||
|
|
@ -550,7 +550,8 @@ export type LangDict = {
|
||||||
success: string;
|
success: string;
|
||||||
fail: string;
|
fail: string;
|
||||||
pw: {
|
pw: {
|
||||||
lenght: string;
|
lenght6: string;
|
||||||
|
lenght8: string;
|
||||||
number: string;
|
number: string;
|
||||||
lowercase: string;
|
lowercase: string;
|
||||||
uppercase: string;
|
uppercase: string;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import {
|
||||||
} from "~/components/ui/alert-dialog";
|
} from "~/components/ui/alert-dialog";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import Input from "~/components/ui/input";
|
import Input from "~/components/ui/input";
|
||||||
import { useStrongPassword } from "~/hooks/useStrongPassword";
|
import { useMildPassword } from "~/hooks/usePassword";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { useZodForm } from "~/lib/zodForm";
|
import { useZodForm } from "~/lib/zodForm";
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
|
|
@ -64,7 +64,8 @@ const AcceptInvitePage: NextPageWithLayout<InviteTokenProps> = ({
|
||||||
getStrengthTxt,
|
getStrengthTxt,
|
||||||
strengthScore,
|
strengthScore,
|
||||||
toggleVisibility,
|
toggleVisibility,
|
||||||
} = useStrongPassword();
|
maxStrength,
|
||||||
|
} = useMildPassword();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const schema = z
|
const schema = z
|
||||||
|
|
@ -189,7 +190,7 @@ const AcceptInvitePage: NextPageWithLayout<InviteTokenProps> = ({
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
aria-label="Password strength"
|
aria-label="Password strength"
|
||||||
aria-valuemax={4}
|
aria-valuemax={maxStrength}
|
||||||
aria-valuemin={0}
|
aria-valuemin={0}
|
||||||
aria-valuenow={strengthScore}
|
aria-valuenow={strengthScore}
|
||||||
className="mt-3 mb-4 h-1 w-full overflow-hidden rounded-full bg-border"
|
className="mt-3 mb-4 h-1 w-full overflow-hidden rounded-full bg-border"
|
||||||
|
|
@ -200,7 +201,9 @@ const AcceptInvitePage: NextPageWithLayout<InviteTokenProps> = ({
|
||||||
"h-full transition-all duration-500 ease-out",
|
"h-full transition-all duration-500 ease-out",
|
||||||
getStrengthColor(strengthScore),
|
getStrengthColor(strengthScore),
|
||||||
)}
|
)}
|
||||||
style={{ width: `${(strengthScore / 4) * 100}%` }}
|
style={{
|
||||||
|
width: `${(strengthScore / maxStrength) * 100}%`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import { zStrongPassword } from "~/hooks/useStrongPassword";
|
import { zMildPassword } from "~/hooks/usePassword";
|
||||||
import {
|
import {
|
||||||
adminProcedure,
|
adminProcedure,
|
||||||
createTRPCRouter,
|
createTRPCRouter,
|
||||||
|
|
@ -38,7 +38,7 @@ export const authRouter = createTRPCRouter({
|
||||||
cognome: z.string().nonempty("Inserisci un cognome valido"),
|
cognome: z.string().nonempty("Inserisci un cognome valido"),
|
||||||
email: z.email(),
|
email: z.email(),
|
||||||
nome: z.string().nonempty("Inserisci un nome valido"),
|
nome: z.string().nonempty("Inserisci un nome valido"),
|
||||||
password: zStrongPassword,
|
password: zMildPassword,
|
||||||
telefono: z.string().nonempty("Inserisci un numero di telefono"),
|
telefono: z.string().nonempty("Inserisci un numero di telefono"),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue