refactor: more use cases with useToggle

This commit is contained in:
Mahyar Khanbolooki
2025-08-10 18:29:48 +03:30
parent 389ac492c2
commit 7fca4375fc
45 changed files with 155 additions and 133 deletions
@@ -4,6 +4,7 @@ import AnimatedBottomSheet from '@/components/bottomsheet/AnimatedBottomSheet';
import Button from '@/components/button/PrimaryButton';
import Combobox, { ComboboxOption } from '@/components/combobox/Combobox';
import InputField from '@/components/input/InputField';
import useToggle from '@/hooks/helpers/useToggle';
import clsx from 'clsx';
import { motion } from 'framer-motion';
import { ArrowLeft, Box, Card, Cup, Icon, Shop, Ticket, TicketDiscount, TruckTick, Wallet2 } from 'iconsax-react';
@@ -26,12 +27,12 @@ function OrderDetailInex() {
const [paymentType, setPaymentType] = React.useState<string>('0');
const [couponType, setCouponType] = React.useState<string>('0');
const [couponCode, setCouponCode] = React.useState<string>('');
const [cartModal, setCartModal] = React.useState<boolean>(false);
const [couponCodeError, setCouponCodeError] = React.useState<string>(''); // TODO: handle coupon code error
const [tableNumber, setTableNumber] = useState(0) // TODO: must be set to table number
const incrementTableNumber = () => setTableNumber((prev) => prev + 1); // TODO: must be set to table number
const decrementTableNumber = () => setTableNumber((prev) => prev - 1); // TODO: must be set to table number
const address = "اراک، مصطفی خمینی، خیابان سینا کوچه چمران ساختمان شهرزاد"
const { state: cartModal, toggle: toggleCartModal } = useToggle();
const deliveryOptions: Array<ComboboxOption> = [
{ id: '0', title: t("SectionShipping.InputDeliveryType.Options.Delivery"), icon: TruckTick },
@@ -76,10 +77,6 @@ function OrderDetailInex() {
setCouponCode(id);
}, []);
const toggleCartModal = useCallback(() => {
setCartModal((prev) => !prev);
}, []);
const addressSection = () => {
if (deliveryType !== '0') {
return null;
@@ -313,4 +310,4 @@ function OrderDetailInex() {
)
}
export default OrderDetailInex
export default OrderDetailInex
@@ -8,11 +8,11 @@ import Button from '@/components/button/PrimaryButton';
import { Skeleton } from '@/components/ui/skeleton';
import { MarkerData } from '@/app/[name]/(Profile)/profile/address/new/page';
import { HandPlatter, Package } from 'lucide-react';
import { motion } from 'framer-motion';
import AnimatedBar from '@/components/utils/AnimatedBar';
import { Clock } from 'iconsax-react';
import BlurredOverlayContainer from '@/components/overlays/BlurredOverlayContainer';
import clsx from 'clsx';
import useToggle from '@/hooks/helpers/useToggle';
import Prompt from '@/components/utils/Prompt';
const CustomMap = dynamic(
() => import('@/components/map/CustomMap'),
@@ -29,9 +29,8 @@ function OrderTrackingPage() {
const [markers, setMarkers] = useState<MarkerData[]>([]);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [mapCenter, setMapCenter] = useState<[number, number]>([Number(params?.get('lon')) || 35.6892, Number(params?.get('lon')) || 51.3890]);
const [bottomSheet1State, setBottomSheet1State] = useState<boolean>(false);
const [cancelModalVisibility, setCancelModalVisibility] = useState<boolean>(false);
const router = useRouter();
const { state: cancelModal, toggle: toggleCancelModal } = useToggle();
// Example initial position (Tehran)
const initialPosition = React.useMemo<[number, number]>(() => [35.6892, 51.3890], []);
@@ -68,17 +67,7 @@ function OrderTrackingPage() {
console.log('Map clicked at:', e.latlng);
};
const toggleBottomSheet1 = () => {
setBottomSheet1State((prev) => !prev);
}
const toggleCancelModal = async (e?: React.MouseEvent) => {
e?.preventDefault();
e?.stopPropagation();
setCancelModalVisibility((state) => !state);
}
const onCancelOrder = (e: React.MouseEvent<HTMLButtonElement> | null) => {
const onCancelOrder = (e: React.MouseEvent | null) => {
if (!e) return;
};
@@ -99,30 +88,6 @@ function OrderTrackingPage() {
<div className="absolute bottom-0 left-0 right-0 z-[1000]">
<div className='relative w-full h-full' >
<AnimatedBottomSheet
bgOpacity={0}
inDuration={0}
blurOpacity={100}
noBlur
visible={bottomSheet1State}
showCloseButton={false}
showTitle={false}
overrideClassName='!pt-6 bg-container!'
>
<div className='grid grid-cols-2 gap-4 px-4'>
<Button
onClick={toggleCancelModal}
>
انتخاب
</Button>
<Button
onClick={toggleBottomSheet1}
className='bg-disabled! text-primary!'
>
انصراف
</Button>
</div>
</AnimatedBottomSheet>
<AnimatedBottomSheet
bgOpacity={0}
@@ -193,32 +158,18 @@ function OrderTrackingPage() {
<div className={clsx(
'fixed inset-0 z-1001',
!cancelModalVisibility && 'pointer-events-none'
!cancelModal && 'pointer-events-none'
)}>
<BlurredOverlayContainer visible={cancelModalVisibility} onClick={toggleCancelModal} inDuration={100} outDuration={100} outDelay={75}>
<div className="absolute top-1/2 left-6 right-6 px-6">
<motion.div
animate={{ y: cancelModalVisibility ? [10, 0] : [0, 10] }}
transition={{ duration: 0.1, ease: 'easeInOut' }}
className="top-1/2 absolute left-1/2 min-w-xs w-full max-w-sm text-center -translate-1/2 px-6 pt-8 pb-9 drop-shadow-container bg-white/63 rounded-4xl"
>
<h2 className="text-base font-medium">
لفو سفارش
</h2>
<p className="text-xs font-medium mt-6">
آیا از درخواست خود اطمینان دارید؟
</p>
<div className="grid grid-cols-2 gap-5.5 mt-8">
<Button onClick={onCancelOrder} className="text-sm font-normal">
بله
</Button>
<Button onClick={toggleCancelModal} className="bg-disabled! text-disabled2! text-sm font-normal">
منصرف شدم
</Button>
</div>
</motion.div>
</div>
</BlurredOverlayContainer>
<Prompt
title={'لفو سفارش'}
description={'آیا از درخواست خود اطمینان دارید؟'}
textConfirm={'بله'}
textCancel={'منصرف شدم'}
onConfirm={onCancelOrder}
visible={cancelModal}
onClick={toggleCancelModal}
onCancel={toggleCancelModal}
/>
</div>
</div>
);
+4 -6
View File
@@ -6,11 +6,12 @@ import TabContainer from '@/components/tab/TabContainer';
import { TabHeader } from '@/components/tab/TabHeader';
import Comment from '@/components/utils/Comment';
import RateBar from '@/components/utils/RateBar';
import useToggle from '@/hooks/helpers/useToggle';
import { AboutDataModel } from '@/lib/api/info/getAboutData';
import { ArrowDown2, CallCalling, Clock, Gallery, InfoCircle, Instagram, Location, Star1, Whatsapp } from 'iconsax-react';
import { useQueryState } from 'next-usequerystate';
import Image from 'next/image';
import React, { useCallback, useState } from 'react'
import React from 'react'
const sortings = [
'جدیدترین',
@@ -18,12 +19,8 @@ const sortings = [
]
function AboutPage({ data }: Readonly<{ data: AboutDataModel; }>) {
const [sortingModal, setSortingModal] = useState(false);
const [sorting, setSorting] = useQueryState('sortBy', { defaultValue: '0' });
const toggleSortingModal = useCallback(() => {
setSortingModal((state) => !state);
}, []);
const { state: sortingModal, toggle: toggleSortingModal, set: setSortingModal } = useToggle();
const changeSorting = (index: number) => {
setSorting(() => String(index));
@@ -31,6 +28,7 @@ function AboutPage({ data }: Readonly<{ data: AboutDataModel; }>) {
}
const firstTab = () => {
return (
<section aria-labelledby="about-title" className='py-4'>
<section
+3 -3
View File
@@ -35,7 +35,7 @@ function ChatIndex({ }: Props) {
const [search, setSearch] = useState('');
const [selectedChats, setSelectedChats] = useState<Array<string>>([]);
const router = useRouter();
const { active: deleteModal, onToggle: onToggleDeleteModal } = useToggle();
const { state: deleteModal, toggle: toggleDeleteModal } = useToggle();
const [chatsList, setChatList] = useState<Array<ChatEntryModel>>([
{
@@ -106,7 +106,7 @@ function ChatIndex({ }: Props) {
if (deleteModal) {
setSelectedChats([]);
}
onToggleDeleteModal()
toggleDeleteModal()
};
const deleteSelectedChats = () => {
@@ -153,7 +153,7 @@ function ChatIndex({ }: Props) {
<SearchBox placeholder={t('InputSearch.Placeholder')} value={search} onChange={(e) => setSearch(e.target.value)} />
{selectedChats.length > 0 ?
<span onClick={onToggleDeleteModal} className='bg-container p-2 rounded-xl active:bg-gray-50'>
<span onClick={toggleDeleteModal} className='bg-container p-2 rounded-xl active:bg-gray-50'>
<Trash size={24} className='stroke-primary' />
</span>
:
+4 -10
View File
@@ -22,6 +22,7 @@ import CategorySmallItemRenderer from "@/components/listview/CategorySmallItemRe
import { motion } from "framer-motion";
import { useTranslation } from "react-i18next";
import ComboBox, { ComboboxOption } from "@/components/combobox/Combobox";
import useToggle from "@/hooks/helpers/useToggle";
const categories = new Array(13).fill({ title: "خوراک", icon: "" });
@@ -148,8 +149,9 @@ const MenuIndex = () => {
{ id: '0', title: tMenu('MenuFilterDrawer.SelectDelivery.Options.Courier'), label: '' },
]
const [filterModal, setFilterModal] = useState(false);
const [sortingModal, setSortingModal] = useState(false);
const { state: filterModal, toggle: toggleFilterModal } = useToggle();
const { state: sortingModal, toggle: toggleSortingModal } = useToggle();
const [, setSorting] = useQueryState('sortBy', { defaultValue: '0' });
const [search, setSearch] = useQueryState("q", { defaultValue: '' });
const [selectedCategory, setSelectedCategory] = useQueryState('category', { defaultValue: '0' });
@@ -191,14 +193,6 @@ const MenuIndex = () => {
setSelectedCategory(String(id));
}, [setSelectedCategory]);
const toggleFilterModal = useCallback(() => {
setFilterModal((state) => !state);
}, []);
const toggleSortingModal = useCallback(() => {
setSortingModal((state) => !state);
}, []);
const changeSorting = (index: number) => {
setSorting(() => String(index));
}
@@ -8,6 +8,7 @@ import AnimatedBottomSheet from '@/components/bottomsheet/AnimatedBottomSheet';
import Button from '@/components/button/PrimaryButton';
import InputField from '@/components/input/InputField';
import { Skeleton } from '@/components/ui/skeleton';
import useToggle from '@/hooks/helpers/useToggle';
const CustomMap = dynamic(
() => import('@/components/map/CustomMap'),
@@ -60,8 +61,8 @@ function OrderTrackingPage() {
const [markers, setMarkers] = useState<MarkerData[]>([]);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [mapCenter, setMapCenter] = useState<[number, number]>([Number(params?.get('lon')) || 35.6892, Number(params?.get('lon')) || 51.3890]);
const [bottomSheet1State, setBottomSheet1State] = useState<boolean>(false);
const [bottomSheet2State, setBottomSheet2State] = useState<boolean>(false);
const { state: confirmModal, toggle: toggleConfirmModal, set: setConfirmModal } = useToggle();
const { state: detailsModal, toggle: _toggleDetailsModal, set: setDetailsModal } = useToggle();
// Example initial position (Tehran)
const initialPosition = React.useMemo<[number, number]>(() => [35.6892, 51.3890], []);
@@ -97,8 +98,8 @@ function OrderTrackingPage() {
setSelectedAddress(null);
return position;
});
setBottomSheet1State(position && true);
setBottomSheet2State(false);
setConfirmModal(position && true);
setDetailsModal(false);
// You can handle the selected position here
console.log('Selected position:', position);
};
@@ -111,19 +112,16 @@ function OrderTrackingPage() {
console.log('Map clicked at:', e.latlng);
};
const toggleBottomSheet1 = () => {
setBottomSheet1State((prev) => !prev);
}
const toggleBottomSheet2 = async () => {
const toggleDetailsModal = async () => {
if (!selectedPosition) {
setBottomSheet1State(false);
setBottomSheet2State(false);
setConfirmModal(false);
setDetailsModal(false);
return;
}
setBottomSheet1State(false);
setBottomSheet2State((prev) => !prev);
if (!bottomSheet2State) {
setConfirmModal(false);
_toggleDetailsModal();
if (!detailsModal) {
const address = await fetchAddressFromCoordinates(selectedPosition[0], selectedPosition[1]);
setSelectedAddress(address);
}
@@ -148,6 +146,32 @@ function OrderTrackingPage() {
console.log(formData);
}
const getSelectedAddress = () => {
if (!selectedAddress) {
return "آدرس یافت نشد"
}
try {
return [
selectedAddress.address.state ?? selectedAddress.address.province,
selectedAddress.address.city,
selectedAddress.address.neighbourhood,
!selectedAddress.address.road.startsWith('میدان') &&
!selectedAddress.address.road.startsWith('بلوار') &&
!selectedAddress.address.road.startsWith('خیابان') &&
!selectedAddress.address.road.startsWith('کوچه') ?
selectedAddress.type === 'secondary' ? `بلوار ${selectedAddress.address.road}` :
selectedAddress.type === 'tertiary' ? `خیابان ${selectedAddress.address.road}` :
selectedAddress.type === 'residential' ? `کوچه ${selectedAddress.address.road}` :
selectedAddress.address.road : selectedAddress.address.road
].filter(x => x).join('، ')
} catch {
if (selectedAddress.display_name)
return selectedAddress.display_name;
else
return "آدرس یافت نشد"
}
}
return (
<div className="fixed inset-0 bg-gray-50">
@@ -175,19 +199,19 @@ function OrderTrackingPage() {
inDuration={0}
blurOpacity={null}
noBlur
visible={bottomSheet1State}
visible={confirmModal}
showCloseButton={false}
showTitle={false}
overrideClassName='!pt-6 bg-container!'
>
<div className='grid grid-cols-2 gap-4 px-4'>
<Button
onClick={toggleBottomSheet2}
onClick={toggleDetailsModal}
>
انتخاب
</Button>
<Button
onClick={toggleBottomSheet1}
onClick={toggleConfirmModal}
className='bg-disabled! text-foreground!'
>
انصراف
@@ -200,9 +224,9 @@ function OrderTrackingPage() {
inDuration={0}
blurOpacity={null}
noBlur
visible={bottomSheet2State}
visible={detailsModal}
title='آدرس'
onClick={toggleBottomSheet2}
onClick={toggleDetailsModal}
overrideClassName='bg-container!'
>
<form onSubmit={submitAddressAction}>
@@ -214,19 +238,7 @@ function OrderTrackingPage() {
className='h-6 w-full'
/>
:
[
selectedAddress.address.state ?? selectedAddress.address.province,
selectedAddress.address.city,
selectedAddress.address.neighbourhood,
!selectedAddress.address.road.startsWith('میدان') &&
!selectedAddress.address.road.startsWith('بلوار') &&
!selectedAddress.address.road.startsWith('خیابان') &&
!selectedAddress.address.road.startsWith('کوچه') ?
selectedAddress.type === 'secondary' ? `بلوار ${selectedAddress.address.road}` :
selectedAddress.type === 'tertiary' ? `خیابان ${selectedAddress.address.road}` :
selectedAddress.type === 'residential' ? `کوچه ${selectedAddress.address.road}` :
selectedAddress.address.road : selectedAddress.address.road
].filter(x => x).join('، ')
getSelectedAddress()
}
</span>
<hr className='border border-border mt-3 mb-10 ' />
@@ -252,7 +264,7 @@ function OrderTrackingPage() {
</Button>
<Button
type='button'
onClick={toggleBottomSheet2}
onClick={toggleDetailsModal}
className='bg-disabled! text-foreground!'
>
انصراف
+1 -1
View File
@@ -1,6 +1,6 @@
import { Metadata, Viewport } from "next";
import "./globals.css";
import { ReactQueryProvider } from "@/components/providers/ReactQueryProvider";
import { ReactQueryProvider } from "@/features/providers/ReactQueryProvider";
import { notFound } from "next/navigation";
import i18nConfig from "../../i18nConfig";
import initTranslations from '@/lib/i18n';
@@ -1,3 +1,5 @@
'use client';
import React, { useRef } from 'react'
import BlurredOverlayContainer, { BlurredOverlayContainerProps } from '../overlays/BlurredOverlayContainer'
import clsx from 'clsx'
@@ -1,3 +1,5 @@
'use client';
import React from 'react'
type Props = {
+2
View File
@@ -1,3 +1,5 @@
'use client';
import clsx from 'clsx';
import React from 'react'
import { motion } from 'framer-motion';
+2
View File
@@ -1,3 +1,5 @@
'use client';
import clsx from 'clsx';
import { motion } from 'framer-motion';
import { Refresh } from 'iconsax-react';
+2
View File
@@ -1,3 +1,5 @@
'use client';
import React, { useState, useEffect, useRef } from 'react';
import { motion, Variants } from 'framer-motion';
import { Icon, SearchNormal } from 'iconsax-react';
+2
View File
@@ -1,3 +1,5 @@
'use client';
import React from 'react'
import Tooltip from '../utils/Tooltip';
import clsx from 'clsx';
+2
View File
@@ -1,3 +1,5 @@
'use client';
import React from 'react';
import SearchIcon from '../icons/SearchIcon';
import clsx from 'clsx';
@@ -1,3 +1,5 @@
'use client';
import React from 'react';
type Props = {
@@ -1,3 +1,5 @@
'use client';
import React from 'react';
type Props = {
@@ -1,3 +1,5 @@
'use client';
import React, { ReactElement, ReactNode } from 'react';
type Props = {
-1
View File
@@ -1,4 +1,3 @@
// components/menu/MenuItem.tsx
'use client'
import { memo } from "react";
@@ -1,3 +1,5 @@
'use client';
import React from 'react';
type Props = {
@@ -1,3 +1,5 @@
'use client';
import React, { ReactElement, ReactNode } from 'react';
type Props = {
+6 -6
View File
@@ -53,8 +53,8 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
const menuStateMemo = useMemo(() => menuState, [menuState]);
const closeRef = useRef<HTMLDivElement>(null);
const userIsAuthenticated = true; // useAuthStore((state) => state.isAuthenticated);
const { active: logoutModal, onToggle: onToggleLogoutModal } = useToggle();
const { active: shareModal, onToggle: onToggleShareModal } = useToggle();
const { state: logoutModal, toggle: toggleLogoutModal } = useToggle();
const { state: shareModal, toggle: toggleShareModal } = useToggle();
const params = useParams();
const { name } = params;
@@ -78,8 +78,8 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
};
const hrefOnClicks = [
{ href: '?logout', handler: onToggleLogoutModal },
{ href: '?share', handler: onToggleShareModal }
{ href: '?logout', handler: toggleLogoutModal },
{ href: '?share', handler: toggleShareModal }
];
const renderMenu = () => {
@@ -202,13 +202,13 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
{/* Logout Modal */}
<LogoutPrompt
visible={logoutModal}
onClick={onToggleLogoutModal}
onClick={toggleLogoutModal}
/>
{/* Share modal */}
<Modal
visible={shareModal}
onClick={onToggleShareModal}
onClick={toggleShareModal}
>
<h2 className="text-base font-medium">
<Share className="inline-block ml-2 mb-1.5 stroke-primary" size={24} />
+2
View File
@@ -1,3 +1,5 @@
'use client';
import Link, { LinkProps } from 'next/link'
import React from 'react'
@@ -1,3 +1,5 @@
'use client';
import Link from 'next/link'
import React from 'react'
import type { LinkProps } from 'next/link'
@@ -1,3 +1,5 @@
'use client';
import Link from 'next/link'
import React from 'react'
import type { LinkProps } from 'next/link'
@@ -1,3 +1,5 @@
'use client';
import React from 'react'
type Props = object
+2
View File
@@ -1,3 +1,5 @@
'use client';
import React, { ReactNode } from 'react';
export interface TabItemProps {
+2
View File
@@ -1,3 +1,5 @@
'use client';
import React from 'react';
type Props = {
+2
View File
@@ -1,3 +1,5 @@
'use client';
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
+2
View File
@@ -1,3 +1,5 @@
'use client';
import * as React from "react"
import { cn } from "@/lib/utils"
+2
View File
@@ -1,3 +1,5 @@
'use client';
import { cn } from "@/lib/utils"
function Skeleton({
+2
View File
@@ -1,3 +1,5 @@
'use client';
import * as React from "react"
import { cn } from "@/lib/utils"
+2
View File
@@ -1,3 +1,5 @@
'use client';
import { motion } from 'framer-motion'
import React from 'react'
+2
View File
@@ -1,3 +1,5 @@
'use client';
import { Star1 } from 'iconsax-react'
import React from 'react'
+2
View File
@@ -1,3 +1,5 @@
'use client';
import { motion } from 'framer-motion'
import React from 'react'
import BlurredOverlayContainer, { BlurredOverlayContainerProps } from '../overlays/BlurredOverlayContainer'
+2
View File
@@ -1,3 +1,5 @@
'use client';
import { BlurredOverlayContainerProps } from '@/components/overlays/BlurredOverlayContainer'
import Modal from '@/components/utils/Modal'
import React from 'react'
+2
View File
@@ -1,3 +1,5 @@
'use client';
import { Star1 } from 'iconsax-react'
import React from 'react'
import { motion } from 'framer-motion'
@@ -1,3 +1,5 @@
'use client';
import { Star, Star1 } from 'iconsax-react'
import React from 'react'
import { Slider } from '../ui/slider';
+1
View File
@@ -1,4 +1,5 @@
'use client';
import React, { ReactNode } from 'react';
type Props = {
@@ -1,3 +1,5 @@
'use client';
// import LoadingOverlay from '@/components/overlays/LoadingOverlay';
import React from 'react'
@@ -1,3 +1,5 @@
'use client';
import Button from '@/components/button/PrimaryButton';
import OTPInputField from '@/components/input/MultiInputField';
import { AUTH_PAGE_ELEMENT, AUTH_PAGE_ELEMENT as AUTH_PAGE_ELEMENTS } from '@/enums';
@@ -1,3 +1,5 @@
'use client';
import Button from '@/components/button/PrimaryButton';
import { EyeToggleIcon } from '@/components/icons/EyeToggleIcon';
import InputField from '@/components/input/InputField';
@@ -1,3 +1,5 @@
'use client';
import Button from '@/components/button/PrimaryButton';
import { EyeToggleIcon } from '@/components/icons/EyeToggleIcon';
import InputField from '@/components/input/InputField';
+2
View File
@@ -1,3 +1,5 @@
'use client';
import Prompt, { PromptProps } from '@/components/utils/Prompt'
import { useAuthStore } from '@/zustand/authStore';
import { useRouter } from 'next/navigation';
+8 -4
View File
@@ -1,17 +1,21 @@
import { useState } from 'react'
const useToggle = () => {
const [active, setActive] = useState<boolean>(false);
const [state, setState] = useState<boolean>(false);
const onToggle = (e?: React.MouseEvent | null) => {
const toggle = (e?: React.MouseEvent | null) => {
if (e) {
e.preventDefault();
e.stopPropagation();
}
setActive((prev) => !prev);
setState((prev) => !prev);
}
return { active, onToggle }
const set = (value: boolean) => {
setState(value);
}
return { state, toggle, set }
}
export default useToggle