From 8d9b1fcfddd2ead100fb3d27d7e78657f3c00910 Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Mon, 30 Mar 2026 14:35:20 +0500 Subject: [PATCH] navbar changed --- package-lock.json | 75 ++++++++++- package.json | 6 +- src/features/auth/login/lib/formatPhone.ts | 13 ++ src/features/auth/{ => login}/lib/index.ts | 0 src/features/auth/login/lib/togle.ts | 14 +++ src/features/auth/login/lib/useLoginForm.ts | 31 +++++ src/features/auth/login/model/index.ts | 1 + src/features/auth/login/model/model.tsx | 5 + src/features/auth/login/ui/form.tsx | 119 ++++++++++++++++++ .../auth/{model => login/ui}/index.ts | 1 + src/features/auth/login/ui/motion.tsx | 17 +++ src/features/auth/login/ui/phonePrefix.tsx | 19 +++ src/features/auth/ui/index.ts | 1 - src/shared/config/i18n/routing.ts | 2 +- src/shared/config/i18n/types.ts | 2 +- src/shared/ui/label.tsx | 23 ++++ src/shared/zustand/userLogin.ts | 21 ++++ src/widgets/navbar/lib/data.ts | 88 +++---------- src/widgets/navbar/ui/authButtons.tsx | 59 +++++++++ src/widgets/navbar/ui/index.tsx | 26 +--- 20 files changed, 423 insertions(+), 100 deletions(-) create mode 100644 src/features/auth/login/lib/formatPhone.ts rename src/features/auth/{ => login}/lib/index.ts (100%) create mode 100644 src/features/auth/login/lib/togle.ts create mode 100644 src/features/auth/login/lib/useLoginForm.ts create mode 100644 src/features/auth/login/model/index.ts create mode 100644 src/features/auth/login/model/model.tsx create mode 100644 src/features/auth/login/ui/form.tsx rename src/features/auth/{model => login/ui}/index.ts (65%) create mode 100644 src/features/auth/login/ui/motion.tsx create mode 100644 src/features/auth/login/ui/phonePrefix.tsx delete mode 100644 src/features/auth/ui/index.ts create mode 100644 src/shared/ui/label.tsx create mode 100644 src/shared/zustand/userLogin.ts create mode 100644 src/widgets/navbar/ui/authButtons.tsx diff --git a/package-lock.json b/package-lock.json index 174dfc2..1ff4093 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dayjs": "^1.11.13", + "framer-motion": "^12.38.0", "lucide-react": "^0.503.0", "next": "15.5.9", "next-intl": "^4.3.9", @@ -43,7 +44,8 @@ "sonner": "^2.0.3", "tailwind-merge": "^3.2.0", "vaul": "^1.1.2", - "zod": "^4.1.11" + "zod": "^4.1.11", + "zustand": "^5.0.12" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -5735,6 +5737,33 @@ "node": ">= 6" } }, + "node_modules/framer-motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", + "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.38.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -7160,6 +7189,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/motion-dom": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", + "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" + } + }, + "node_modules/motion-utils": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -9354,6 +9398,35 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zustand": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", + "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 984a934..ea0c581 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dayjs": "^1.11.13", + "framer-motion": "^12.38.0", "lucide-react": "^0.503.0", "next": "15.5.9", "next-intl": "^4.3.9", @@ -46,7 +47,8 @@ "sonner": "^2.0.3", "tailwind-merge": "^3.2.0", "vaul": "^1.1.2", - "zod": "^4.1.11" + "zod": "^4.1.11", + "zustand": "^5.0.12" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -71,4 +73,4 @@ "eslint src --fix" ] } -} \ No newline at end of file +} diff --git a/src/features/auth/login/lib/formatPhone.ts b/src/features/auth/login/lib/formatPhone.ts new file mode 100644 index 0000000..860154f --- /dev/null +++ b/src/features/auth/login/lib/formatPhone.ts @@ -0,0 +1,13 @@ +export const formatPhone = (value: string) => { + if (value.length <= 2) return value; + if (value.length <= 5) return `${value.slice(0, 2)} ${value.slice(2)}`; + if (value.length <= 7) + return `${value.slice(0, 2)} ${value.slice(2, 5)} ${value.slice(5)}`; + return `${value.slice(0, 2)} ${value.slice(2, 5)} ${value.slice( + 5, + 7, + )} ${value.slice(7)}`; +}; + +export const normalizeDigits = (value: string) => + value.replace(/\D/g, '').slice(0, 9); diff --git a/src/features/auth/lib/index.ts b/src/features/auth/login/lib/index.ts similarity index 100% rename from src/features/auth/lib/index.ts rename to src/features/auth/login/lib/index.ts diff --git a/src/features/auth/login/lib/togle.ts b/src/features/auth/login/lib/togle.ts new file mode 100644 index 0000000..adf849b --- /dev/null +++ b/src/features/auth/login/lib/togle.ts @@ -0,0 +1,14 @@ +import { create } from 'zustand'; + +interface LoginModalStore { + openLoginModal: boolean; + toggleLoginModal: () => void; +} + +const useLoginModal = create((set) => ({ + openLoginModal: false, + toggleLoginModal: () => + set((state) => ({ openLoginModal: !state.openLoginModal })), +})); + +export { useLoginModal }; diff --git a/src/features/auth/login/lib/useLoginForm.ts b/src/features/auth/login/lib/useLoginForm.ts new file mode 100644 index 0000000..f7f26b1 --- /dev/null +++ b/src/features/auth/login/lib/useLoginForm.ts @@ -0,0 +1,31 @@ +'use client'; + +import React, { useState } from 'react'; +import { useTranslations } from 'next-intl'; + +export function useLoginForm() { + const [phone, setPhone] = useState(''); + const [error, setError] = useState(''); + + const t = useTranslations(); + + const submit = (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + + if (phone.length !== 9) { + setError(t('auth.phone_invalid')); + return; + } + + sessionStorage.setItem('prev_page', 'login'); + }; + + return { + phone, + setPhone, + submit, + error, + loading: false, + }; +} diff --git a/src/features/auth/login/model/index.ts b/src/features/auth/login/model/index.ts new file mode 100644 index 0000000..a8e428b --- /dev/null +++ b/src/features/auth/login/model/index.ts @@ -0,0 +1 @@ +export { RegisterModal } from './model'; diff --git a/src/features/auth/login/model/model.tsx b/src/features/auth/login/model/model.tsx new file mode 100644 index 0000000..b91568c --- /dev/null +++ b/src/features/auth/login/model/model.tsx @@ -0,0 +1,5 @@ +function RegisterModal() { + return
; +} + +export { RegisterModal }; diff --git a/src/features/auth/login/ui/form.tsx b/src/features/auth/login/ui/form.tsx new file mode 100644 index 0000000..98de8d8 --- /dev/null +++ b/src/features/auth/login/ui/form.tsx @@ -0,0 +1,119 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { useCallback, useState } from 'react'; +import { formatPhone, normalizeDigits } from '../lib/formatPhone'; +import { Input } from '@/shared/ui/input'; +import { Button } from '@/shared/ui/button'; +import PhonePrefix from './phonePrefix'; +import { useLoginForm } from '../lib/useLoginForm'; +import { Label } from '@/shared/ui/label'; +import { X } from 'lucide-react'; +import { useLoginModal } from '../lib/togle'; + +// ============================= // + +export default function LoginForm() { + const t = useTranslations(); + const [isFocused, setIsFocused] = useState(false); + + // ========== Handlers ========== // + + const handlePhoneChange = useCallback( + (e: React.ChangeEvent) => { + setPhone(normalizeDigits(e.target.value)); + }, + [], + ); + + // ============================== // + + const { phone, setPhone, submit, error, loading } = useLoginForm(); + const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal); + + return ( +
+
+ +
+
+ {/* PHONE FIELD */} +
+ + +
+ + + 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'}`} + /> +
+ + {/* Helper text */} +
+

+ {phone.length > 0 && + t('auth.entered_digits', { count: phone.length })} +

+ + {phone.length === 9 && ( + + ✓ {t('auth.full')} + + )} +
+
+ + {error && ( +
+ {error} +
+ )} + + + + {/* DIVIDER */} +
+
+ +
+
+ + {t('auth.or_continue')} + +
+
+
+
+ ); +} diff --git a/src/features/auth/model/index.ts b/src/features/auth/login/ui/index.ts similarity index 65% rename from src/features/auth/model/index.ts rename to src/features/auth/login/ui/index.ts index 2b42e9e..f905d91 100644 --- a/src/features/auth/model/index.ts +++ b/src/features/auth/login/ui/index.ts @@ -1 +1,2 @@ // Ushbu fayl loyiha structurasi uchun qo'shilgan. O'chirib tashlasangiz bo'ladi +export { MotionWrapper } from './motion'; diff --git a/src/features/auth/login/ui/motion.tsx b/src/features/auth/login/ui/motion.tsx new file mode 100644 index 0000000..4a1c342 --- /dev/null +++ b/src/features/auth/login/ui/motion.tsx @@ -0,0 +1,17 @@ +'use client'; + +import { motion } from 'framer-motion'; +import { ReactNode } from 'react'; + +function MotionWrapper({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} +export { MotionWrapper }; diff --git a/src/features/auth/login/ui/phonePrefix.tsx b/src/features/auth/login/ui/phonePrefix.tsx new file mode 100644 index 0000000..cc9e610 --- /dev/null +++ b/src/features/auth/login/ui/phonePrefix.tsx @@ -0,0 +1,19 @@ +import { Phone } from 'lucide-react'; + +function PhonePrefix({ isFocused }: { isFocused: boolean }) { + return ( +
+ + + +998 + + | +
+ ); +} + +export default PhonePrefix; diff --git a/src/features/auth/ui/index.ts b/src/features/auth/ui/index.ts deleted file mode 100644 index 2b42e9e..0000000 --- a/src/features/auth/ui/index.ts +++ /dev/null @@ -1 +0,0 @@ -// Ushbu fayl loyiha structurasi uchun qo'shilgan. O'chirib tashlasangiz bo'ladi diff --git a/src/shared/config/i18n/routing.ts b/src/shared/config/i18n/routing.ts index fd73da4..d93fe6b 100644 --- a/src/shared/config/i18n/routing.ts +++ b/src/shared/config/i18n/routing.ts @@ -3,7 +3,7 @@ import { LanguageRoutes } from './types'; export const routing = defineRouting({ // A list of all locales that are supported - locales: [LanguageRoutes.UZ, LanguageRoutes.RU, LanguageRoutes.KI], + locales: [LanguageRoutes.UZ, LanguageRoutes.RU, LanguageRoutes.EN], // Used when no locale matches defaultLocale: LanguageRoutes.UZ, diff --git a/src/shared/config/i18n/types.ts b/src/shared/config/i18n/types.ts index e64b8bc..a2b5052 100644 --- a/src/shared/config/i18n/types.ts +++ b/src/shared/config/i18n/types.ts @@ -1,5 +1,5 @@ export enum LanguageRoutes { UZ = 'uz', // o'zbekcha RU = 'ru', // ruscha - KI = 'ki', // kirilcha + EN = 'en', // english } diff --git a/src/shared/ui/label.tsx b/src/shared/ui/label.tsx new file mode 100644 index 0000000..64fef36 --- /dev/null +++ b/src/shared/ui/label.tsx @@ -0,0 +1,23 @@ +'use client'; + +import * as React from 'react'; +import * as LabelPrimitive from '@radix-ui/react-label'; +import { cn } from '../lib/utils'; + +function Label({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Label }; diff --git a/src/shared/zustand/userLogin.ts b/src/shared/zustand/userLogin.ts new file mode 100644 index 0000000..65b1899 --- /dev/null +++ b/src/shared/zustand/userLogin.ts @@ -0,0 +1,21 @@ +import { create } from 'zustand'; + +interface User { + id: string; + name: string; + surname: string; +} + +interface UserLoginStore { + user: User | null; + setUser: (user: User) => void; + clearUser: () => void; + getUser: () => User | null; +} + +export const useUserLogin = create((set, get) => ({ + user: null, + setUser: (user: User) => set({ user }), + clearUser: () => set({ user: null }), + getUser: () => get().user, +})); diff --git a/src/widgets/navbar/lib/data.ts b/src/widgets/navbar/lib/data.ts index 866e27e..4d5e68f 100644 --- a/src/widgets/navbar/lib/data.ts +++ b/src/widgets/navbar/lib/data.ts @@ -1,77 +1,23 @@ -import { Book, Sunset, Trees, Zap } from 'lucide-react'; import { MenuItem } from './model'; import { LanguageRoutes } from '@/shared/config/i18n/types'; const menu: MenuItem[] = [ - { title: 'Home', url: '#' }, + { title: 'About Site', url: '/about' }, + // { + // title: 'Products', + // url: '#', + // items: [ + // { + // title: 'Blog', + // description: 'The latest industry news, updates, and info', + // icon: Book, + // url: '#', + // }, + // ], + // }, { - title: 'Products', - url: '#', - items: [ - { - title: 'Blog', - description: 'The latest industry news, updates, and info', - icon: Book, - url: '#', - }, - { - title: 'Company', - description: 'Our mission is to innovate and empower the world', - icon: Trees, - url: '#', - }, - { - title: 'Careers', - description: 'Browse job listing and discover our workspace', - icon: Sunset, - url: '#', - }, - { - title: 'Support', - description: - 'Get in touch with our support team or visit our community forums', - icon: Zap, - url: '#', - }, - ], - }, - { - title: 'Resources', - url: '#', - items: [ - { - title: 'Help Center', - description: 'Get all the answers you need right here', - icon: Zap, - url: '#', - }, - { - title: 'Contact Us', - description: 'We are here to help you with any questions you have', - icon: Sunset, - url: '#', - }, - { - title: 'Status', - description: 'Check the current status of our services and APIs', - icon: Trees, - url: '#', - }, - { - title: 'Terms of Service', - description: 'Our terms and conditions for using our services', - icon: Book, - url: '#', - }, - ], - }, - { - title: 'Pricing', - url: '#', - }, - { - title: 'Blog', - url: '#', + title: 'Contact', + url: '/contact', }, ]; @@ -81,8 +27,8 @@ const languages: { name: string; key: LanguageRoutes }[] = [ key: LanguageRoutes.UZ, }, { - name: 'Ўзбекча', - key: LanguageRoutes.KI, + name: 'English', + key: LanguageRoutes.EN, }, { name: 'Русский', diff --git a/src/widgets/navbar/ui/authButtons.tsx b/src/widgets/navbar/ui/authButtons.tsx new file mode 100644 index 0000000..a20ad8a --- /dev/null +++ b/src/widgets/navbar/ui/authButtons.tsx @@ -0,0 +1,59 @@ +'use client'; +import { useLoginModal } from '@/features/auth/login/lib/togle'; +import { Link } from '@/shared/config/i18n/navigation'; +import { Button } from '@/shared/ui/button'; +import { useUserLogin } from '@/shared/zustand/userLogin'; +import { + NavigationMenuContent, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuTrigger, +} from '@/shared/ui/navigation-menu'; +import SubMenuLink from './SubMenuLink'; +import { ChangeLang } from './ChangeLang'; + +function AuthButtons() { + const auth = { + login: { title: 'Login', url: '#' }, + signup: { title: 'Sign up', url: '#' }, + }; + + const userItem = [ + { title: 'Profile', url: '/profile' }, + { title: 'Logout', url: '#' }, + ]; + + const toggleLoginModal = useLoginModal((state) => state.toggleLoginModal); + const user = useUserLogin((state) => state.user); + + if (user) { + return ( + + {user.name} + + {userItem.map((subItem) => ( + + + + ))} + + + ); + } + + return ( +
+
+ +
+ + +
+ ); +} + +export { AuthButtons }; diff --git a/src/widgets/navbar/ui/index.tsx b/src/widgets/navbar/ui/index.tsx index 5189141..65d499d 100644 --- a/src/widgets/navbar/ui/index.tsx +++ b/src/widgets/navbar/ui/index.tsx @@ -18,13 +18,9 @@ import RenderMenuItem from './RenderItem'; import RenderMobileMenuItem from './RenderMobileMenuItem'; import { ChangeLang } from './ChangeLang'; import Link from 'next/link'; +import { AuthButtons } from './authButtons'; const Navbar = () => { - const auth = { - login: { title: 'Login', url: '#' }, - signup: { title: 'Sign up', url: '#' }, - }; - return (
@@ -50,15 +46,7 @@ const Navbar = () => {
-
- - - -
+ {/* Mobile Menu */} @@ -101,15 +89,7 @@ const Navbar = () => { > {menu.map((item) => RenderMobileMenuItem(item))} - -
- - -
+