Compare commits

...

2 Commits

8 changed files with 72 additions and 24 deletions

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Qlockify.ir | Time Tracking for Modern Teams</title>
<title>Qlockify</title>
</head>
<body>
<div id="root"></div>

View File

@@ -334,7 +334,7 @@ export const Sidebar = ({ mobileOpen = false, onMobileClose }: SidebarProps) =>
<div className="flex min-h-0 flex-1 flex-col overflow-y-auto">
<div className="flex shrink-0 items-center justify-between border-b border-slate-200 px-5 py-5 dark:border-slate-800">
<h2 className="truncate text-lg font-bold text-slate-800 dark:text-white">
Qlockify.ir
{t.title || "Qlockify"}
</h2>
<button

View File

@@ -56,11 +56,14 @@ export const en = {
continueToResetPassword: "Continue to reset password",
resetPasswordTitle: "Choose a new password",
resetPasswordDescription: "Set a new password for your account and confirm it.",
resetPasswordCta: "Reset password",
newPasswordPlaceholder: "New password",
confirmPasswordPlaceholder: "Confirm password",
passwordMismatch: "The password confirmation does not match.",
enterPassword: "Enter your password",
resetPasswordCta: "Reset password",
newPasswordPlaceholder: "New password",
confirmPasswordPlaceholder: "Confirm password",
passwordMismatch: "The password confirmation does not match.",
passwordRequirements:
"Password must be at least 8 characters and include at least one lowercase letter, one uppercase letter, one digit, and one symbol.",
passwordReuse: "New password must be different from your previous password.",
enterPassword: "Enter your password",
verifyNumber: "Verify your number",
enterMobileDesc: "Enter your mobile number to continue",
signInDesc: "Sign in using your account password",

View File

@@ -55,11 +55,14 @@ export const fa = {
continueToResetPassword: "ادامه برای تعیین رمز جدید",
resetPasswordTitle: "انتخاب رمز عبور جدید",
resetPasswordDescription: "رمز عبور جدید خود را وارد کنید و آن را تایید کنید.",
resetPasswordCta: "تغییر رمز عبور",
newPasswordPlaceholder: "رمز عبور جدید",
confirmPasswordPlaceholder: "تکرار رمز عبور",
passwordMismatch: "رمز عبور و تکرار آن یکسان نیستند.",
welcome: (title: string = "Qlockifiy") => `به ${title} خوش آمدید`,
resetPasswordCta: "تغییر رمز عبور",
newPasswordPlaceholder: "رمز عبور جدید",
confirmPasswordPlaceholder: "تکرار رمز عبور",
passwordMismatch: "رمز عبور و تکرار آن یکسان نیستند.",
passwordRequirements:
"رمز عبور باید حداقل 8 کاراکتر باشد و حداقل یک حرف کوچک، یک حرف بزرگ، یک عدد و یک نماد داشته باشد.",
passwordReuse: "رمز عبور جدید نباید با رمز عبور قبلی یکسان باشد.",
welcome: (title: string = "Qlockifiy") => `به ${title} خوش آمدید`,
enterPassword: "رمز عبور خود را وارد کنید",
verifyNumber: "تایید شماره موبایل",
enterMobileDesc: "برای ادامه، شماره موبایل خود را وارد کنید",

View File

@@ -12,10 +12,11 @@ import { Button } from "../components/ui/button"
import { Camera, Edit2, Trash2, User as UserIcon, UploadCloud, X, Check } from "lucide-react"
import JalaliDatePicker from "../components/ui/JalaliDatePicker"
import { toast } from "sonner"
import { Modal } from "../components/Modal"
import { Input } from "../components/ui/input"
import { TextAreaInput } from "../components/ui/TextAreaInput"
import { AuthPasswordField } from "./auth/AuthPasswordField"
import { Modal } from "../components/Modal"
import { Input } from "../components/ui/input"
import { TextAreaInput } from "../components/ui/TextAreaInput"
import { AuthPasswordField } from "./auth/AuthPasswordField"
import { getPasswordValidationMessage } from "./auth/utils"
export interface UserProfile {
id?: string;
@@ -187,12 +188,23 @@ export default function Profile() {
return
}
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
toast.error(t.login.passwordMismatch)
return
}
setIsSaving(true)
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
toast.error(t.login.passwordMismatch)
return
}
const passwordValidationMessage = getPasswordValidationMessage(passwordForm.newPassword, t.login)
if (passwordValidationMessage) {
toast.error(passwordValidationMessage)
return
}
if (passwordForm.currentPassword === passwordForm.newPassword) {
toast.error(t.login.passwordReuse)
return
}
setIsSaving(true)
try {
await changePassword(
passwordForm.currentPassword,

View File

@@ -9,7 +9,7 @@ import { useAuthFlow } from "../../context/AuthFlowContext"
import { useTranslation } from "../../hooks/useTranslation"
import { AuthPanel } from "./AuthPanel"
import { AuthPasswordField } from "./AuthPasswordField"
import { getApiErrorMessage } from "./utils"
import { getApiErrorMessage, getPasswordValidationMessage } from "./utils"
export function ForgotPasswordPasswordPage() {
const navigate = useNavigate()
@@ -40,6 +40,12 @@ export function ForgotPasswordPasswordPage() {
return
}
const passwordValidationMessage = getPasswordValidationMessage(password, t.login)
if (passwordValidationMessage) {
toast.error(passwordValidationMessage)
return
}
setLoading(true)
try {
await resetPasswordWithOtp(state.forgotPassword.mobile, state.forgotPassword.code, password, confirmation)

View File

@@ -9,7 +9,7 @@ import { useAuthFlow } from "../../context/AuthFlowContext"
import { useTranslation } from "../../hooks/useTranslation"
import { AuthPanel } from "./AuthPanel"
import { AuthPasswordField } from "./AuthPasswordField"
import { completeAuthentication, getApiErrorMessage } from "./utils"
import { completeAuthentication, getApiErrorMessage, getPasswordValidationMessage } from "./utils"
export function SignupPasswordPage() {
const navigate = useNavigate()
@@ -40,6 +40,12 @@ export function SignupPasswordPage() {
return
}
const passwordValidationMessage = getPasswordValidationMessage(password, t.login)
if (passwordValidationMessage) {
toast.error(passwordValidationMessage)
return
}
setLoading(true)
try {
const data = await registerWithOtp(state.signup.mobile, state.signup.code, password, confirmation)

View File

@@ -29,6 +29,24 @@ export const getApiErrorMessage = (error: unknown, fallbackMessage: string) => {
return fallbackMessage
}
export const getPasswordValidationMessage = (
password: string,
copy: {
passwordRequirements: string
},
) => {
const hasLowercase = /[a-z]/.test(password)
const hasUppercase = /[A-Z]/.test(password)
const hasDigit = /\d/.test(password)
const hasSymbol = /[^A-Za-z0-9]/.test(password)
if (password.length < 8 || !hasLowercase || !hasUppercase || !hasDigit || !hasSymbol) {
return copy.passwordRequirements
}
return null
}
export const handleThrottleError = ({
error,
cooldownKey,