Merge pull request 'Cleanup and add validation to signup form and related components' (#10) from registration-cleanup into main
Reviewed-on: #10
This commit is contained in:
commit
3a605860e5
@ -10,12 +10,13 @@ import { Button } from "@/components/ui/button"
|
|||||||
import { Switch } from "@/components/ui/switch"
|
import { Switch } from "@/components/ui/switch"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { motion, AnimatePresence } from "motion/react"
|
import { motion, AnimatePresence } from "motion/react"
|
||||||
import { UserPlus, UserCog, Heart, AlertCircle, CheckCircle2, Mail, Lock, User, Bot, Loader, ArrowLeft } from "lucide-react"
|
import { UserPlus, UserCog, Heart, AlertCircle, CheckCircle2, Mail, Lock, User, Bot, Loader2, ArrowLeft } from "lucide-react"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { validateEmail, validatePassword } from "@/lib/utils"
|
import { validateEmail, validatePassword, validateName } from "@/lib/utils"
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||||||
import EmailField from "@/components/custom/signup/EmailField"
|
import EmailField from "@/components/custom/signup/EmailField"
|
||||||
import Altcha from "@/components/custom/Altcha"
|
import Altcha from "@/components/custom/Altcha"
|
||||||
|
import { useSession } from "next-auth/react"
|
||||||
|
|
||||||
export default function Signup() {
|
export default function Signup() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -76,9 +77,11 @@ export default function Signup() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formType === "create") {
|
if (formType === "create") {
|
||||||
const { name, emailUsername, emailDomain, password, terms } = formData
|
const { name, emailUsername, emailDomain, password, terms } = formData
|
||||||
if (name.length < 2) {
|
|
||||||
|
const nameValidation = validateName(name)
|
||||||
|
if (!nameValidation.valid) {
|
||||||
setIsValid(false)
|
setIsValid(false)
|
||||||
setValidationMessage("Enter your name")
|
setValidationMessage(nameValidation.message)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,9 +115,11 @@ export default function Signup() {
|
|||||||
setValidationMessage("Create Account")
|
setValidationMessage("Create Account")
|
||||||
} else if (formType === "migrate") {
|
} else if (formType === "migrate") {
|
||||||
const { emailUsername, emailDomain, migratePassword, migrateTerms, migrateName } = formData
|
const { emailUsername, emailDomain, migratePassword, migrateTerms, migrateName } = formData
|
||||||
if (migrateName.length < 2) {
|
|
||||||
|
const nameValidation = validateName(migrateName)
|
||||||
|
if (!nameValidation.valid) {
|
||||||
setIsValid(false)
|
setIsValid(false)
|
||||||
setValidationMessage("Enter your name")
|
setValidationMessage(nameValidation.message)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,11 +156,12 @@ export default function Signup() {
|
|||||||
|
|
||||||
const getButtonIcon = () => {
|
const getButtonIcon = () => {
|
||||||
if (isValid) return <CheckCircle2 size={30} />
|
if (isValid) return <CheckCircle2 size={30} />
|
||||||
if (validationMessage.includes("name")) return <User size={30} />
|
if (validationMessage.includes("name") || validationMessage.includes("Name")) return <User size={30} />
|
||||||
if (validationMessage.includes("Email") || validationMessage.includes("email")) return <Mail size={30} />
|
if (validationMessage.includes("Email") || validationMessage.includes("email")) return <Mail size={30} />
|
||||||
if (validationMessage.includes("Password") || validationMessage.includes("password")) return <Lock size={30} />
|
if (validationMessage.includes("Password") || validationMessage.includes("password")) return <Lock size={30} />
|
||||||
if (validationMessage.includes("terms")) return <AlertCircle size={30} />
|
if (validationMessage.includes("terms")) return <AlertCircle size={30} />
|
||||||
if (validationMessage.includes("robot") || validationMessage.includes("Security")) return <Bot size={30} />
|
if (validationMessage.includes("robot") || validationMessage.includes("Security")) return <Bot size={30} />
|
||||||
|
if (validationMessage.includes("special characters")) return <AlertCircle size={30} />
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,12 +225,54 @@ export default function Signup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { data: session } = useSession()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session) {
|
||||||
|
router.push("/account/dashboard")
|
||||||
|
}
|
||||||
|
}, [session, router])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen items-center justify-center p-4">
|
<div className="flex h-screen items-center justify-center p-4">
|
||||||
<Card className="w-full max-w-md overflow-hidden">
|
<Card className="w-full max-w-md overflow-hidden">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-2xl">Account Setup</CardTitle>
|
<AnimatePresence mode="wait">
|
||||||
<CardDescription>Create a new account or migrate an existing one.</CardDescription>
|
{formType === "initial" ? (
|
||||||
|
<motion.div
|
||||||
|
key="initial-header"
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -20 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
>
|
||||||
|
<CardTitle className="text-2xl">Account Setup</CardTitle>
|
||||||
|
<CardDescription>Create a new account or migrate an existing one.</CardDescription>
|
||||||
|
</motion.div>
|
||||||
|
) : formType === "create" ? (
|
||||||
|
<motion.div
|
||||||
|
key="create-header"
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -20 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
>
|
||||||
|
<CardTitle className="text-2xl">Create New Account</CardTitle>
|
||||||
|
<CardDescription>Set up your new LibreCloud account.</CardDescription>
|
||||||
|
</motion.div>
|
||||||
|
) : (
|
||||||
|
<motion.div
|
||||||
|
key="migrate-header"
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -20 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
>
|
||||||
|
<CardTitle className="text-2xl">Migrate Account</CardTitle>
|
||||||
|
<CardDescription>Transfer your p0ntus mail account to LibreCloud.</CardDescription>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{errorAlert && (
|
{errorAlert && (
|
||||||
@ -301,11 +349,11 @@ export default function Signup() {
|
|||||||
/>
|
/>
|
||||||
<Label htmlFor="terms" className="text-sm">
|
<Label htmlFor="terms" className="text-sm">
|
||||||
I agree to the{" "}
|
I agree to the{" "}
|
||||||
<Link href="/terms" className="underline">
|
<Link href="/legal/terms" className="underline">
|
||||||
Terms of Service
|
Terms of Service
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
and{" "}
|
and{" "}
|
||||||
<Link href="/privacy" className="underline">
|
<Link href="/legal/privacy" className="underline">
|
||||||
Privacy Policy
|
Privacy Policy
|
||||||
</Link>
|
</Link>
|
||||||
</Label>
|
</Label>
|
||||||
@ -378,11 +426,11 @@ export default function Signup() {
|
|||||||
/>
|
/>
|
||||||
<Label htmlFor="migrateTerms" className="text-sm">
|
<Label htmlFor="migrateTerms" className="text-sm">
|
||||||
I agree to the{" "}
|
I agree to the{" "}
|
||||||
<Link href="/terms" className="underline">
|
<Link href="/legal/terms" className="underline">
|
||||||
Terms of Service
|
Terms of Service
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
and{" "}
|
and{" "}
|
||||||
<Link href="/privacy" className="underline">
|
<Link href="/legal/privacy" className="underline">
|
||||||
Privacy Policy
|
Privacy Policy
|
||||||
</Link>
|
</Link>
|
||||||
</Label>
|
</Label>
|
||||||
@ -416,7 +464,7 @@ export default function Signup() {
|
|||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
>
|
>
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<Loader size={30} className="animate-spin" />
|
<Loader2 size={30} className="animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
getButtonIcon()
|
getButtonIcon()
|
||||||
)}
|
)}
|
||||||
@ -445,5 +493,4 @@ export default function Signup() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -140,7 +140,7 @@ const WelcomePage = () => {
|
|||||||
From here, you can proceed to sign in to your newly created account with Authentik. It will handle all the sign-ins for your account except for Pass (Vaultwarden).
|
From here, you can proceed to sign in to your newly created account with Authentik. It will handle all the sign-ins for your account except for Pass (Vaultwarden).
|
||||||
</p>
|
</p>
|
||||||
<Link href="/account/login">
|
<Link href="/account/login">
|
||||||
<Button className="mt-8"><CircleArrowRight /> Login</Button>
|
<Button className="mt-8 cursor-pointer"><CircleArrowRight /> Login</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { auth } from "@/auth"
|
import { auth } from "@/auth"
|
||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
import { NextResponse } from "next/server"
|
import { NextResponse } from "next/server"
|
||||||
|
import { validatePassword } from "@/lib/utils"
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
@ -14,6 +15,11 @@ export async function POST(request: Request) {
|
|||||||
return NextResponse.json({ error: "Invalid password" }, { status: 400 })
|
return NextResponse.json({ error: "Invalid password" }, { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const passwordValidation = validatePassword(password)
|
||||||
|
if (!passwordValidation.valid) {
|
||||||
|
return NextResponse.json({ error: passwordValidation.message }, { status: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
// Get user ID from email
|
// Get user ID from email
|
||||||
const user = await axios.request({
|
const user = await axios.request({
|
||||||
method: "get",
|
method: "get",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { auth } from "@/auth"
|
import { auth } from "@/auth"
|
||||||
import { NextResponse } from "next/server"
|
import { NextResponse } from "next/server"
|
||||||
|
import { validatePassword } from "@/lib/utils"
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
@ -13,6 +14,11 @@ export async function POST(request: Request) {
|
|||||||
return NextResponse.json({ error: "Invalid password" }, { status: 400 })
|
return NextResponse.json({ error: "Invalid password" }, { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const passwordValidation = validatePassword(password)
|
||||||
|
if (!passwordValidation.valid) {
|
||||||
|
return NextResponse.json({ error: passwordValidation.message }, { status: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
const { email } = session.user
|
const { email } = session.user
|
||||||
|
|
||||||
const response = await fetch(`${process.env.MAIL_CONNECT_API_URL}/accounts/update/password`, {
|
const response = await fetch(`${process.env.MAIL_CONNECT_API_URL}/accounts/update/password`, {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
import { NextResponse } from "next/server"
|
import { NextResponse } from "next/server"
|
||||||
import { validateToken } from "@/lib/utils"
|
import { validateToken, validateName, validateEmail } from "@/lib/utils"
|
||||||
|
import { auth } from "@/auth"
|
||||||
|
|
||||||
// This endpoint has two functions:
|
// This endpoint has two functions:
|
||||||
// (1) Create a new LibreCloud user (Authentik, Email)
|
// (1) Create a new LibreCloud user (Authentik, Email)
|
||||||
@ -68,15 +69,33 @@ export async function POST(request: Request) {
|
|||||||
let atkCreated = false
|
let atkCreated = false
|
||||||
let userID = ""
|
let userID = ""
|
||||||
|
|
||||||
|
const session = await auth()
|
||||||
|
if (session) {
|
||||||
|
return NextResponse.json({ success: false, message: "You are already logged in" }, { status: 403 })
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
const { name, email, password, migrate, token } = body
|
const { name, email, password, migrate, token } = body
|
||||||
|
|
||||||
// Validate fields
|
// Make sure all fields are present
|
||||||
if (!name || !email || !password) {
|
if (!name || !email || !password) {
|
||||||
return NextResponse.json({ success: false, message: "The form you submitted is incomplete" }, { status: 400 })
|
return NextResponse.json({ success: false, message: "The form you submitted is incomplete" }, { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate name again
|
||||||
|
const nameValidation = validateName(name)
|
||||||
|
if (!nameValidation.valid) {
|
||||||
|
return NextResponse.json({ success: false, message: nameValidation.message }, { status: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// and email
|
||||||
|
const [emailUsername, emailDomain] = email.split('@')
|
||||||
|
const emailValidation = validateEmail(emailUsername, emailDomain)
|
||||||
|
if (!emailValidation.valid) {
|
||||||
|
return NextResponse.json({ success: false, message: emailValidation.message }, { status: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
const tokenValidation = await validateToken(token)
|
const tokenValidation = await validateToken(token)
|
||||||
if (!tokenValidation.success) {
|
if (!tokenValidation.success) {
|
||||||
console.error("Altcha validation failed:", tokenValidation.error)
|
console.error("Altcha validation failed:", tokenValidation.error)
|
||||||
|
@ -4,7 +4,7 @@ import React, { useState, useRef, useEffect, useCallback } from "react"
|
|||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { CheckCircleIcon, Key, Loader2, XCircleIcon } from "lucide-react"
|
import { CheckCircleIcon, Key, Loader2, XCircleIcon, AlertCircle } from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -17,6 +17,7 @@ import {
|
|||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { motion, useAnimationControls } from "framer-motion"
|
import { motion, useAnimationControls } from "framer-motion"
|
||||||
|
import { validatePassword } from "@/lib/utils"
|
||||||
|
|
||||||
export function ChangeEmailPassword() {
|
export function ChangeEmailPassword() {
|
||||||
const [newPassword, setNewPassword] = useState("")
|
const [newPassword, setNewPassword] = useState("")
|
||||||
@ -28,6 +29,7 @@ export function ChangeEmailPassword() {
|
|||||||
const [isHolding, setIsHolding] = useState(false)
|
const [isHolding, setIsHolding] = useState(false)
|
||||||
const holdDuration = 10
|
const holdDuration = 10
|
||||||
const [remainingTime, setRemainingTime] = useState(holdDuration)
|
const [remainingTime, setRemainingTime] = useState(holdDuration)
|
||||||
|
const [passwordError, setPasswordError] = useState<string | null>(null)
|
||||||
|
|
||||||
const submitPasswordChange = async () => {
|
const submitPasswordChange = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@ -86,17 +88,16 @@ export function ChangeEmailPassword() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
controls.set({ "--progress": "0%" })
|
controls.set({ "--progress": "0%" })
|
||||||
} finally {
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
setIsHolding(false)
|
setIsHolding(false)
|
||||||
if (holdTimeoutRef.current) {
|
if (holdTimeoutRef.current) {
|
||||||
clearTimeout(holdTimeoutRef.current)
|
clearTimeout(holdTimeoutRef.current)
|
||||||
holdTimeoutRef.current = null
|
holdTimeoutRef.current = null
|
||||||
}
|
}
|
||||||
if (intervalRef.current) {
|
if (intervalRef.current) {
|
||||||
clearInterval(intervalRef.current)
|
clearInterval(intervalRef.current)
|
||||||
intervalRef.current = null
|
intervalRef.current = null
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,10 +105,23 @@ export function ChangeEmailPassword() {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value
|
||||||
|
setNewPassword(value)
|
||||||
|
|
||||||
|
// Validate password
|
||||||
|
const validation = validatePassword(value)
|
||||||
|
if (!validation.valid) {
|
||||||
|
setPasswordError(validation.message)
|
||||||
|
} else {
|
||||||
|
setPasswordError(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const holdDurationMs = holdDuration * 1000
|
const holdDurationMs = holdDuration * 1000
|
||||||
|
|
||||||
const handleHoldStart = () => {
|
const handleHoldStart = () => {
|
||||||
if (loading || newPassword.length < 8) return
|
if (loading || newPassword.length < 8 || passwordError) return
|
||||||
|
|
||||||
setIsHolding(true)
|
setIsHolding(true)
|
||||||
controls.set({ "--progress": "0%" })
|
controls.set({ "--progress": "0%" })
|
||||||
@ -205,12 +219,19 @@ export function ChangeEmailPassword() {
|
|||||||
id="new-password"
|
id="new-password"
|
||||||
type="password"
|
type="password"
|
||||||
value={newPassword}
|
value={newPassword}
|
||||||
onChange={(e) => setNewPassword(e.target.value)}
|
onChange={handlePasswordChange}
|
||||||
className="mt-1.5"
|
className="mt-1.5"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
{passwordError ? (
|
||||||
Password must be at least 8 characters long.
|
<p className="text-xs text-red-500 flex items-center gap-1">
|
||||||
</p>
|
<AlertCircle size={14} />
|
||||||
|
{passwordError}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Password must be 8-128 characters long, include letters and digits, and not contain spaces.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -218,7 +239,7 @@ export function ChangeEmailPassword() {
|
|||||||
style={{ "--progress": "0%", "--progress-color": "hsl(var(--primary) / 0.5)" } as React.CSSProperties}
|
style={{ "--progress": "0%", "--progress-color": "hsl(var(--primary) / 0.5)" } as React.CSSProperties}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
disabled={loading || newPassword.length < 8}
|
disabled={loading || newPassword.length < 8 || !!passwordError}
|
||||||
onMouseDown={handleHoldStart}
|
onMouseDown={handleHoldStart}
|
||||||
onMouseUp={handleHoldEnd}
|
onMouseUp={handleHoldEnd}
|
||||||
onMouseLeave={handleHoldEnd}
|
onMouseLeave={handleHoldEnd}
|
||||||
|
@ -5,6 +5,8 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { validateEmail } from "@/lib/utils"
|
||||||
|
import { AlertCircle } from "lucide-react"
|
||||||
|
|
||||||
interface ChangePasswordProps {
|
interface ChangePasswordProps {
|
||||||
gitEmail: string;
|
gitEmail: string;
|
||||||
@ -14,9 +16,35 @@ export function ChangeEmail({ gitEmail }: ChangePasswordProps) {
|
|||||||
const [newEmail, setNewEmail] = useState("")
|
const [newEmail, setNewEmail] = useState("")
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [message, setMessage] = useState<string | null>(null);
|
const [message, setMessage] = useState<string | null>(null);
|
||||||
|
const [emailError, setEmailError] = useState<string | null>(null);
|
||||||
|
|
||||||
const handleEmailChange = async (e: React.FormEvent<HTMLFormElement>) => {
|
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setNewEmail(value);
|
||||||
|
|
||||||
|
// Validate email
|
||||||
|
const [username, domain] = value.split('@');
|
||||||
|
if (username && domain) {
|
||||||
|
const validation = validateEmail(username, domain);
|
||||||
|
if (!validation.valid) {
|
||||||
|
setEmailError(validation.message);
|
||||||
|
} else {
|
||||||
|
setEmailError(null);
|
||||||
|
}
|
||||||
|
} else if (value) {
|
||||||
|
setEmailError("Invalid email format");
|
||||||
|
} else {
|
||||||
|
setEmailError(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (emailError) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setMessage(null);
|
setMessage(null);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||||
@ -30,7 +58,7 @@ export function ChangeEmail({ gitEmail }: ChangePasswordProps) {
|
|||||||
<CardTitle>Change Email</CardTitle>
|
<CardTitle>Change Email</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form onSubmit={handleEmailChange} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="current-email">Current Email</Label>
|
<Label htmlFor="current-email">Current Email</Label>
|
||||||
<Input
|
<Input
|
||||||
@ -41,9 +69,23 @@ export function ChangeEmail({ gitEmail }: ChangePasswordProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="new-email">New Email</Label>
|
<Label htmlFor="new-email">New Email</Label>
|
||||||
<Input id="new-email" type="email" value={newEmail} onChange={(e) => setNewEmail(e.target.value)} />
|
<Input
|
||||||
|
id="new-email"
|
||||||
|
type="email"
|
||||||
|
value={newEmail}
|
||||||
|
onChange={handleEmailChange}
|
||||||
|
/>
|
||||||
|
{emailError && (
|
||||||
|
<p className="text-xs text-red-500 flex items-center gap-1">
|
||||||
|
<AlertCircle size={14} />
|
||||||
|
{emailError}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button type="submit">
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={!!emailError || !newEmail}
|
||||||
|
>
|
||||||
{loading ? "Changing..." : "Change Email"}
|
{loading ? "Changing..." : "Change Email"}
|
||||||
</Button>
|
</Button>
|
||||||
{message && <p className="text-sm text-center">{message}</p>}
|
{message && <p className="text-sm text-center">{message}</p>}
|
||||||
|
27
lib/utils.ts
27
lib/utils.ts
@ -37,6 +37,11 @@ export function validateEmail(username: string, domain: string) {
|
|||||||
return { valid: false, message: "Email is required" }
|
return { valid: false, message: "Email is required" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const specialCharsRegex = /[<>()[\]\\,;:{}"']/
|
||||||
|
if (specialCharsRegex.test(username)) {
|
||||||
|
return { valid: false, message: "Email contains special characters" }
|
||||||
|
}
|
||||||
|
|
||||||
const email = `${username}@${domain}`
|
const email = `${username}@${domain}`
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||||
|
|
||||||
@ -47,6 +52,28 @@ export function validateEmail(username: string, domain: string) {
|
|||||||
return { valid: true, message: "" }
|
return { valid: true, message: "" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// name validation
|
||||||
|
export function validateName(name: string) {
|
||||||
|
if (!name) {
|
||||||
|
return { valid: false, message: "Name is required" }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.length < 2) {
|
||||||
|
return { valid: false, message: "Name must be at least 2 characters" }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.length > 64) {
|
||||||
|
return { valid: false, message: "Name must be less than 64 characters" }
|
||||||
|
}
|
||||||
|
|
||||||
|
const specialCharsRegex = /[<>()[\]\\,;:{}"']/
|
||||||
|
if (specialCharsRegex.test(name)) {
|
||||||
|
return { valid: false, message: "Name contains special characters" }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { valid: true, message: "" }
|
||||||
|
}
|
||||||
|
|
||||||
// Password validation
|
// Password validation
|
||||||
export function validatePassword(password: string) {
|
export function validatePassword(password: string) {
|
||||||
if (!password) {
|
if (!password) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user