register , login model complated. plagiraism component complated(essential part of main page is complated )
This commit is contained in:
249
src/features/auth/register/ui/form.tsx
Normal file
249
src/features/auth/register/ui/form.tsx
Normal file
@@ -0,0 +1,249 @@
|
||||
'use client';
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useRegisterForm } from '../lib/useRegisterForm';
|
||||
import { formatPhone, normalizeDigits } from '@/shared/lib/formatPhone';
|
||||
import PhonePrefix from '@/shared/ui/phonePrefix';
|
||||
|
||||
function Field({
|
||||
id,
|
||||
label,
|
||||
type = 'text',
|
||||
placeholder,
|
||||
value,
|
||||
name,
|
||||
onChange,
|
||||
error,
|
||||
}: {
|
||||
id: string;
|
||||
label: string;
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
value: string;
|
||||
name: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
error?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label
|
||||
htmlFor={id}
|
||||
className="text-[0.7rem] font-medium tracking-widest uppercase text-stone-500"
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
<input
|
||||
id={id}
|
||||
name={name}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
autoComplete="off"
|
||||
className={`
|
||||
w-full rounded-sm border bg-stone-50 px-3.5 py-2.5
|
||||
font-sans text-[0.95rem] text-stone-900 outline-none
|
||||
placeholder:text-stone-300
|
||||
transition-all duration-150
|
||||
focus:border-stone-400 focus:ring-2 focus:ring-stone-300/30
|
||||
${
|
||||
error
|
||||
? 'border-red-400 ring-2 ring-red-200/40'
|
||||
: 'border-stone-200 hover:border-stone-300'
|
||||
}
|
||||
`}
|
||||
/>
|
||||
{error && (
|
||||
<span className="text-[0.7rem] text-red-500 tracking-tight">
|
||||
{error}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RegisterFormUI() {
|
||||
const {
|
||||
registerData,
|
||||
errors,
|
||||
loading,
|
||||
success,
|
||||
handleChange,
|
||||
handleOferta,
|
||||
handleSubmit,
|
||||
} = useRegisterForm();
|
||||
const [phone, setPhone] = React.useState(registerData.phone || '');
|
||||
const [isFocused, setIsFocused] = React.useState(false);
|
||||
const handlePhoneChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPhone(normalizeDigits(e.target.value));
|
||||
},
|
||||
[setPhone],
|
||||
);
|
||||
|
||||
// ── Success state ──────────────────────────────────────
|
||||
if (success) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-stone-100 p-8">
|
||||
<div className="w-full max-w-sm rounded border border-stone-200 bg-white px-10 py-16 text-center shadow-sm">
|
||||
<div className="mx-auto mb-5 flex h-14 w-14 items-center justify-center rounded-full bg-emerald-50 text-2xl text-emerald-600">
|
||||
✓
|
||||
</div>
|
||||
<h2 className="mb-1 font-serif text-2xl text-stone-900">
|
||||
You‘re registered
|
||||
</h2>
|
||||
<p className="text-sm text-stone-400">
|
||||
We‘ll be in touch shortly.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Form ───────────────────────────────────────────────
|
||||
return (
|
||||
<div className="w-full">
|
||||
{/* Header */}
|
||||
<p className="mb-1 text-[0.65rem] font-semibold uppercase tracking-[0.16em] text-stone-400">
|
||||
Create account
|
||||
</p>
|
||||
<h1 className="mb-1 font-serif text-3xl leading-tight text-stone-900">
|
||||
Register
|
||||
</h1>
|
||||
<p className="mb-8 text-sm text-stone-400">
|
||||
Fill in your details to get started.
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit} noValidate className="flex flex-col gap-5">
|
||||
{/* Name + Surname row */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Field
|
||||
id="name"
|
||||
name="name"
|
||||
label="Name"
|
||||
placeholder="Ali"
|
||||
value={registerData.name}
|
||||
onChange={handleChange}
|
||||
error={errors.name}
|
||||
/>
|
||||
<Field
|
||||
id="surname"
|
||||
name="surname"
|
||||
label="Surname"
|
||||
placeholder="Karimov"
|
||||
value={registerData.surname}
|
||||
onChange={handleChange}
|
||||
error={errors.surname}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Phone */}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="phone"
|
||||
className="text-[0.7rem] font-medium tracking-widest uppercase text-stone-500"
|
||||
>
|
||||
Telefon raqam
|
||||
</label>
|
||||
<div
|
||||
className={`relative transition-transform duration-200 ${isFocused ? 'scale-[1.01]' : ''}`}
|
||||
>
|
||||
<PhonePrefix isFocused={isFocused} />
|
||||
<input
|
||||
id="phone"
|
||||
type="tel"
|
||||
placeholder="90 123 45 67"
|
||||
value={formatPhone(phone)}
|
||||
onChange={handlePhoneChange}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
className={`
|
||||
w-full rounded-sm border bg-stone-50 py-2.5 pl-30 pr-3.5
|
||||
text-[0.95rem] font-medium text-stone-900 outline-none
|
||||
placeholder:text-stone-300 transition-all duration-150
|
||||
${
|
||||
isFocused
|
||||
? 'border-stone-400 ring-2 ring-stone-300/30'
|
||||
: 'border-stone-200 hover:border-stone-300'
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Digit counter / complete hint */}
|
||||
<div className="flex items-center justify-between px-0.5">
|
||||
<span className="text-[0.7rem] text-stone-400">
|
||||
{phone.length > 0 && `${phone.length} ta raqam kiritildi`}
|
||||
</span>
|
||||
{phone.length === 9 && (
|
||||
<span className="text-[0.7rem] font-medium text-emerald-600">
|
||||
✓ To‘liq
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Terms checkbox */}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="flex items-start gap-2.5">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="oferta"
|
||||
checked={!!registerData.oferta}
|
||||
onChange={handleOferta}
|
||||
className="mt-0.5 h-4 w-4 shrink-0 cursor-pointer accent-stone-700"
|
||||
/>
|
||||
<label
|
||||
htmlFor="oferta"
|
||||
className="cursor-pointer text-[0.78rem] leading-relaxed text-stone-500"
|
||||
>
|
||||
I agree to the
|
||||
<a
|
||||
href="/terms"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-stone-800 underline underline-offset-2 hover:text-stone-600"
|
||||
>
|
||||
Terms of Service
|
||||
</a>
|
||||
and
|
||||
<a
|
||||
href="/privacy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-stone-800 underline underline-offset-2 hover:text-stone-600"
|
||||
>
|
||||
Privacy Policy
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
{errors.oferta && (
|
||||
<p className="text-[0.7rem] text-red-500">{errors.oferta}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Submit */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="
|
||||
mt-1 w-full rounded-sm bg-stone-900 py-3
|
||||
text-[0.82rem] font-semibold uppercase tracking-widest text-stone-100
|
||||
transition-all duration-150
|
||||
hover:bg-stone-800 active:scale-[0.99]
|
||||
disabled:cursor-not-allowed disabled:opacity-40
|
||||
"
|
||||
>
|
||||
{loading ? (
|
||||
<span className="flex items-center justify-center gap-2">
|
||||
<span className="h-3.5 w-3.5 rounded-full border-2 border-stone-500 border-t-stone-100 animate-spin" />
|
||||
Submitting…
|
||||
</span>
|
||||
) : (
|
||||
'Create account'
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user