for dev branch

This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-03-30 15:12:12 +05:00
parent bd227796e1
commit 8906cf6634
9 changed files with 144 additions and 103 deletions

View File

@@ -12,6 +12,7 @@ import { ReactNode } from 'react';
import { setRequestLocale } from 'next-intl/server'; import { setRequestLocale } from 'next-intl/server';
import QueryProvider from '@/shared/config/react-query/QueryProvider'; import QueryProvider from '@/shared/config/react-query/QueryProvider';
import Script from 'next/script'; import Script from 'next/script';
import Provider from '@/features/providers/provider';
export const metadata: Metadata = { export const metadata: Metadata = {
title: PRODUCT_INFO.name, title: PRODUCT_INFO.name,
@@ -48,9 +49,11 @@ export default async function RootLayout({ children, params }: Props) {
disableTransitionOnChange disableTransitionOnChange
> >
<QueryProvider> <QueryProvider>
<Navbar /> <Provider>
{children} <Navbar />
<Footer /> {children}
<Footer />
</Provider>
</QueryProvider> </QueryProvider>
</ThemeProvider> </ThemeProvider>
</NextIntlClientProvider> </NextIntlClientProvider>

View File

@@ -1,4 +1,4 @@
export const formatPhone = (value: string) => { const formatPhone = (value: string) => {
if (value.length <= 2) return value; if (value.length <= 2) return value;
if (value.length <= 5) return `${value.slice(0, 2)} ${value.slice(2)}`; if (value.length <= 5) return `${value.slice(0, 2)} ${value.slice(2)}`;
if (value.length <= 7) if (value.length <= 7)
@@ -9,5 +9,6 @@ export const formatPhone = (value: string) => {
)} ${value.slice(7)}`; )} ${value.slice(7)}`;
}; };
export const normalizeDigits = (value: string) => const normalizeDigits = (value: string) => value.replace(/\D/g, '').slice(0, 9);
value.replace(/\D/g, '').slice(0, 9);
export { formatPhone, normalizeDigits };

View File

@@ -1 +1,4 @@
// Ushbu fayl loyiha structurasi uchun qo'shilgan. O'chirib tashlasangiz bo'ladi // Ushbu fayl loyiha structurasi uchun qo'shilgan. O'chirib tashlasangiz bo'ladi
export * from './togle';
export * from './useLoginForm';
export * from './formatPhone';

View File

@@ -1 +1 @@
export { RegisterModal } from './model'; export { AuthModals } from './model';

View File

@@ -1,5 +1,14 @@
function RegisterModal() { 'use client';
return <div></div>; import { AnimatePresence } from 'framer-motion';
import LoginForm from '../ui/form';
import { useLoginModal } from '../lib/togle';
function AuthModals() {
const openLoginModal = useLoginModal((state) => state.openLoginModal);
return (
<div>
<AnimatePresence>{openLoginModal && <LoginForm />}</AnimatePresence>
</div>
);
} }
export { RegisterModal }; export { AuthModals };

View File

@@ -10,110 +10,123 @@ import { useLoginForm } from '../lib/useLoginForm';
import { Label } from '@/shared/ui/label'; import { Label } from '@/shared/ui/label';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { useLoginModal } from '../lib/togle'; import { useLoginModal } from '../lib/togle';
import { MotionWrapper } from './motion';
// ============================= //
export default function LoginForm() { export default function LoginForm() {
const t = useTranslations(); const t = useTranslations();
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
// ========== Handlers ========== // const { phone, setPhone, submit, error, loading } = useLoginForm();
const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal);
const handlePhoneChange = useCallback( const handlePhoneChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => {
setPhone(normalizeDigits(e.target.value)); setPhone(normalizeDigits(e.target.value));
}, },
[], [setPhone],
); );
// ============================== //
const { phone, setPhone, submit, error, loading } = useLoginForm();
const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal);
return ( return (
<div> <>
<header className="w-full " onClick={toggleLoginModal}> {/* Backdrop */}
<X /> <div
</header> className="fixed inset-0 z-10 bg-black/40 backdrop-blur-sm"
<form onSubmit={submit} className="space-y-6 text-center"> onClick={toggleLoginModal}
{/* PHONE FIELD */} />
<div className="space-y-2">
<Label htmlFor="phone" className="text-sm font-medium">
{t('auth.login_phone')}
</Label>
<div {/* Modal */}
className={`relative transition-all duration-300 ${ <div className="fixed inset-0 z-20 flex items-center justify-center pointer-events-none">
isFocused ? 'scale-[1.02]' : '' <MotionWrapper>
}`} <div className="pointer-events-auto bg-background rounded-2xl shadow-2xl p-8 w-full max-w-sm mx-4">
> {/* Close button */}
<PhonePrefix isFocused={isFocused} /> <div className="flex justify-end mb-4">
<button
onClick={toggleLoginModal}
className="p-1 rounded-full hover:bg-muted transition-colors"
>
<X className="h-5 w-5 text-muted-foreground" />
</button>
</div>
<Input <form onSubmit={submit} className="space-y-6 text-center">
id="phone" {/* PHONE FIELD */}
type="tel" <div className="space-y-2">
placeholder="90 123 45 67" <Label htmlFor="phone" className="text-sm font-medium">
value={formatPhone(phone)} Telefon raqam
onChange={handlePhoneChange} </Label>
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)} <div
className={`pl-30 h-12 text-base font-medium border-2 transition-all ${ className={`relative transition-all duration-300 ${
isFocused isFocused ? 'scale-[1.02]' : ''
? 'border-kok shadow-md shadow-kok/20 bg-kok/5' }`}
: 'border-border hover:border-kok/50' >
} ${error && 'border-destructive'}`} <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={`pl-30 h-12 text-base font-medium border-2 transition-all ${
isFocused
? 'border-kok shadow-md shadow-kok/20 bg-kok/5'
: 'border-border hover:border-kok/50'
} ${error && 'border-destructive'}`}
/>
</div>
<div className="flex justify-between items-center px-1">
<p className="text-xs text-muted-foreground">
{phone.length > 0 &&
t('auth.entered_digits', { count: phone.length })}
</p>
{phone.length === 9 && (
<span className="text-xs text-green-600 font-medium">
{t('auth.full')}
</span>
)}
</div>
</div>
{error && (
<div className="text-sm text-destructive bg-destructive/10 p-3 rounded-md border border-destructive/20">
{error}
</div>
)}
<Button
type="submit"
disabled={loading || phone.length !== 9}
className="w-full h-12 mt-5 bg-linear-to-r from-blue-600 to-indigo-600 hover:scale-[1.02] text-base font-semibold transition-all shadow-lg hover:shadow-xl active:scale-95"
>
{loading ? (
<span className="flex items-center gap-2">
<span className="h-4 w-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
{t('auth.otp_sending')}
</span>
) : (
<p>Kirish</p>
)}
</Button>
{/* DIVIDER */}
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-border" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Ro&lsquo;yhatdan o&lsquo;tish
</span>
</div>
</div>
</form>
</div> </div>
</MotionWrapper>
{/* Helper text */} </div>
<div className="flex justify-between items-center px-1"> </>
<p className="text-xs text-muted-foreground">
{phone.length > 0 &&
t('auth.entered_digits', { count: phone.length })}
</p>
{phone.length === 9 && (
<span className="text-xs text-green-600 font-medium slide-in-from-right-2">
{t('auth.full')}
</span>
)}
</div>
</div>
{error && (
<div className="text-sm text-destructive bg-destructive/10 p-3 rounded-md border border-destructive/20 animate-in">
{error}
</div>
)}
<Button
type="submit"
disabled={loading || phone.length !== 9}
className="w-full h-12 mt-5 bg-linear-to-r from-blue-600 to-indigo-600 hover:scale-[1.02] text-base font-semibold transition-all shadow-lg hover:shadow-xl active:scale-95"
>
{loading ? (
<span className="flex items-center gap-2">
<span className="h-4 w-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
{t('auth.otp_sending')}
</span>
) : (
t('auth.otp')
)}
</Button>
{/* DIVIDER */}
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-border" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
{t('auth.or_continue')}
</span>
</div>
</div>
</form>
</div>
); );
} }

View File

@@ -6,9 +6,10 @@ import { ReactNode } from 'react';
function MotionWrapper({ children }: { children: ReactNode }) { function MotionWrapper({ children }: { children: ReactNode }) {
return ( return (
<motion.div <motion.div
initial={{ opacity: 0, x: -50 }} initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, scale: 1, y: 0 }}
transition={{ duration: 0.5 }} exit={{ opacity: 0, scale: 0.95, y: 10 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
> >
{children} {children}
</motion.div> </motion.div>

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { AuthModals } from '../auth/login/model';
export default function Provider({ children }: { children: React.ReactNode }) {
return (
<div>
<AuthModals />
{children}
</div>
);
}

View File

@@ -46,7 +46,7 @@ function AuthButtons() {
<div className="lg:flex hidden"> <div className="lg:flex hidden">
<ChangeLang /> <ChangeLang />
</div> </div>
<Button asChild variant="outline" onClick={toggleLoginModal}> <Button variant="outline" onClick={() => toggleLoginModal()}>
<Link href={auth.login.url}>{auth.login.title}</Link> <Link href={auth.login.url}>{auth.login.title}</Link>
</Button> </Button>
<Button asChild> <Button asChild>