bulk cart

This commit is contained in:
hamid zarghami
2025-12-01 10:33:22 +03:30
parent 788d23b185
commit b4b900cfc1
8 changed files with 83 additions and 11 deletions
@@ -0,0 +1,8 @@
import { useMutation } from "@tanstack/react-query";
import * as api from "../service/CartService";
export const useBulkCart = () => {
return useMutation({
mutationFn: api.bulkCart,
});
};
@@ -0,0 +1,7 @@
import { api } from "@/config/axios";
import { BulkCartItem } from "../types/Types";
export const bulkCart = async (params: BulkCartItem) => {
const { data } = await api.post("/public/cart/items/bulk", params);
return data;
};
@@ -0,0 +1,6 @@
export type BulkCartItem = {
items: {
foodId: string;
quantity: number;
}[];
};
+17 -5
View File
@@ -10,17 +10,19 @@ import Button from '../button/PrimaryButton';
import { useParams, usePathname } from 'next/navigation'; import { useParams, usePathname } from 'next/navigation';
import clsx from 'clsx'; import clsx from 'clsx';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { CalendarSearch, Cup, DirectboxReceive, DirectInbox, DocumentCopy, Game, Icon, Instagram, Like1, LogoutCurve, Notification, Receipt1, Setting2, Share, TicketDiscount, Whatsapp } from 'iconsax-react'; import { CalendarSearch, Cup, DirectboxReceive, DirectInbox, DocumentCopy, Game, Icon, Instagram, Like1, Login, LogoutCurve, Notification, Receipt1, Setting2, Share, TicketDiscount, Whatsapp } from 'iconsax-react';
import TelegramIcon from '../icons/TelegramIcon'; import TelegramIcon from '../icons/TelegramIcon';
import Modal from '../utils/Modal'; import Modal from '../utils/Modal';
import LogoutPrompt from '@/features/general/LogoutPrompt'; import LogoutPrompt from '@/features/general/LogoutPrompt';
import useToggle from '@/hooks/helpers/useToggle'; import useToggle from '@/hooks/helpers/useToggle';
import { useAuthStore } from '@/zustand/authStore';
type MenuItemType = { type MenuItemType = {
href: string | undefined; href: string | undefined;
title: string; title: string;
icon: Icon; icon: Icon;
auth?: boolean; auth?: boolean;
guestOnly?: boolean;
}; };
const menuItems: Array<Array<MenuItemType>> = [ const menuItems: Array<Array<MenuItemType>> = [
@@ -38,7 +40,8 @@ const menuItems: Array<Array<MenuItemType>> = [
[], [],
[ [
{ auth: true, href: 'profile/settings', title: 'Preferences', icon: Setting2 }, { auth: true, href: 'profile/settings', title: 'Preferences', icon: Setting2 },
{ auth: true, href: '?logout', title: 'Logout', icon: LogoutCurve } { auth: true, href: '?logout', title: 'Logout', icon: LogoutCurve },
{ guestOnly: true, href: 'auth', title: 'LoginSignup', icon: Login }
] ]
]; ];
@@ -52,7 +55,7 @@ type Props = {
function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeState }: Props) { function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeState }: Props) {
const menuStateMemo = useMemo(() => menuState, [menuState]); const menuStateMemo = useMemo(() => menuState, [menuState]);
const closeRef = useRef<HTMLDivElement>(null); const closeRef = useRef<HTMLDivElement>(null);
const userIsAuthenticated = true; // useAuthStore((state) => state.isAuthenticated); const userIsAuthenticated = useAuthStore((state) => state.isAuthenticated);
const { state: logoutModal, toggle: toggleLogoutModal } = useToggle(); const { state: logoutModal, toggle: toggleLogoutModal } = useToggle();
const { state: shareModal, toggle: toggleShareModal } = useToggle(); const { state: shareModal, toggle: toggleShareModal } = useToggle();
const { state: installPwaModal, toggle: toggleInstallPwaModal } = useToggle(); const { state: installPwaModal, toggle: toggleInstallPwaModal } = useToggle();
@@ -92,7 +95,11 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
<nav aria-label={tMenu('NavAriaLabel')}> <nav aria-label={tMenu('NavAriaLabel')}>
<ul> <ul>
{menuItems[0] {menuItems[0]
.filter(item => !item.auth || (item.auth && userIsAuthenticated)) .filter(item => {
if (item.auth && !userIsAuthenticated) return false;
if (item.guestOnly && userIsAuthenticated) return false;
return true;
})
.map(({ icon: Icon, ...item }, index) => { .map(({ icon: Icon, ...item }, index) => {
const href = `/${name}/${item.href}`; const href = `/${name}/${item.href}`;
const isActive = pathname === href; const isActive = pathname === href;
@@ -126,7 +133,11 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
<section aria-label={tMenu('ControlsAriaLabel')}> <section aria-label={tMenu('ControlsAriaLabel')}>
<ul className="flex flex-col pt-16"> <ul className="flex flex-col pt-16">
{menuItems[2] {menuItems[2]
.filter(item => !item.auth || (item.auth && userIsAuthenticated)) .filter(item => {
if (item.auth && !userIsAuthenticated) return false;
if (item.guestOnly && userIsAuthenticated) return false;
return true;
})
.map(({ icon: Icon, ...item }, index) => { .map(({ icon: Icon, ...item }, index) => {
const href = `/${name}/${item.href}`; const href = `/${name}/${item.href}`;
const isActive = pathname === href; const isActive = pathname === href;
@@ -203,6 +214,7 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
<LogoutPrompt <LogoutPrompt
visible={logoutModal} visible={logoutModal}
onClick={toggleLogoutModal} onClick={toggleLogoutModal}
slug={name as string}
/> />
{/* Share modal */} {/* Share modal */}
@@ -22,7 +22,7 @@ type StepEnterNumberProps = {
value?: string value?: string
} }
function StepEnterNumber (_props: StepEnterNumberProps = {}) { function StepEnterNumber(_props: StepEnterNumberProps = {}) {
void _props void _props
const { t } = useTranslation('auth') const { t } = useTranslation('auth')
@@ -31,6 +31,7 @@ function StepEnterNumber (_props: StepEnterNumberProps = {}) {
const { mutate: mutateOtpRequest, isPending } = useOtpRequest() const { mutate: mutateOtpRequest, isPending } = useOtpRequest()
const formik = useFormik<LoginOTPRequestType>({ const formik = useFormik<LoginOTPRequestType>({
initialValues: { initialValues: {
phone: '', phone: '',
+32 -2
View File
@@ -8,6 +8,8 @@ import { useOtpVerify } from '@/app/auth/login/hooks/useAuthData'
import { toast } from '@/components/Toast' import { toast } from '@/components/Toast'
import Button from '@/components/button/PrimaryButton' import Button from '@/components/button/PrimaryButton'
import { extractErrorMessage } from '@/lib/func' import { extractErrorMessage } from '@/lib/func'
import { useReceiptStore } from '@/zustand/receiptStore'
import { useBulkCart } from '@/app/[name]/(Dialogs)/cart/hooks/useCartData'
type Props = { type Props = {
@@ -19,6 +21,10 @@ const StepOtp = ({ phone, slug }: Props) => {
const { t } = useTranslation('auth') const { t } = useTranslation('auth')
const { mutate: mutateOtpVerify, isPending } = useOtpVerify() const { mutate: mutateOtpVerify, isPending } = useOtpVerify()
const { items, clear } = useReceiptStore()
const { mutate: mutateBulkCart, isPending: isBulkCartPending } = useBulkCart()
const formik = useFormik<LoginVerifyOTPType>({ const formik = useFormik<LoginVerifyOTPType>({
initialValues: { initialValues: {
phone: phone, phone: phone,
@@ -34,7 +40,31 @@ const StepOtp = ({ phone, slug }: Props) => {
toast(t('otp_verified'), 'success') toast(t('otp_verified'), 'success')
localStorage.setItem(process.env.NEXT_PUBLIC_TOKEN_NAME as string, data?.data?.tokens?.accessToken?.token as string) localStorage.setItem(process.env.NEXT_PUBLIC_TOKEN_NAME as string, data?.data?.tokens?.accessToken?.token as string)
localStorage.setItem(process.env.NEXT_PUBLIC_REFRESH_TOKEN_NAME as string, data?.data?.tokens?.refreshToken?.token as string) localStorage.setItem(process.env.NEXT_PUBLIC_REFRESH_TOKEN_NAME as string, data?.data?.tokens?.refreshToken?.token as string)
window.location.href = `/${slug}`
if (Object.keys(items).length > 0) {
const bulkCartItems = Object.entries(items).map(([key, value]) => {
if (value.quantity > 0) {
return {
foodId: key,
quantity: value.quantity
}
}
return undefined
}).filter((item): item is { foodId: string; quantity: number } => item !== undefined)
mutateBulkCart({ items: bulkCartItems }, {
onSuccess: () => {
clear()
window.location.href = `/${slug}`
},
onError: (error) => {
toast(extractErrorMessage(error), 'error')
}
})
} else {
window.location.href = `/${slug}`
}
}, },
onError: (error) => { onError: (error) => {
toast(extractErrorMessage(error), 'error') toast(extractErrorMessage(error), 'error')
@@ -64,7 +94,7 @@ const StepOtp = ({ phone, slug }: Props) => {
<Button <Button
className='mt-10' className='mt-10'
disabled={!formik.isValid} disabled={!formik.isValid}
pending={isPending} pending={isPending || isBulkCartPending}
type='button' type='button'
onClick={() => formik.handleSubmit()} onClick={() => formik.handleSubmit()}
> >
+10 -3
View File
@@ -5,9 +5,13 @@ import { useAuthStore } from '@/zustand/authStore';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { removeToken, removeRefreshToken } from '@/lib/api/func';
export type LogoutPromptProps = Omit<PromptProps, 'onConfirm' | 'onCancel' | 'children'> & {
slug?: string;
};
function LogoutPrompt({ ...rest }: Omit<PromptProps, 'onConfirm' | 'onCancel' | 'children'>) { function LogoutPrompt({ slug, ...rest }: LogoutPromptProps) {
const { t } = useTranslation('common', { const { t } = useTranslation('common', {
keyPrefix: 'LogoutModal' keyPrefix: 'LogoutModal'
}); });
@@ -15,12 +19,15 @@ function LogoutPrompt({ ...rest }: Omit<PromptProps, 'onConfirm' | 'onCancel' |
const router = useRouter(); const router = useRouter();
const userIsAuthenticated = useAuthStore((state) => state.isAuthenticated); const userIsAuthenticated = useAuthStore((state) => state.isAuthenticated);
const onConfirmLogout = (e: React.MouseEvent) => { const onConfirmLogout = async (e: React.MouseEvent) => {
if (!e) return; if (!e) return;
if (userIsAuthenticated) { if (userIsAuthenticated) {
authLogout(); authLogout();
await removeToken();
await removeRefreshToken();
} }
router.replace('/'); const redirectPath = slug ? `/${slug}` : '/';
router.replace(redirectPath);
}; };
const toggleShareModalVisiblity = (e: React.MouseEvent) => { const toggleShareModalVisiblity = (e: React.MouseEvent) => {
+1
View File
@@ -19,6 +19,7 @@
"InstallApp": "نصب اپلیکیشن", "InstallApp": "نصب اپلیکیشن",
"Preferences": "تنظیمات", "Preferences": "تنظیمات",
"Logout": "خروج", "Logout": "خروج",
"LoginSignup": "ورود / عضویت",
"AriaLabel": "منوی کناری", "AriaLabel": "منوی کناری",
"NavAriaLabel": "لینک‌های اصلی", "NavAriaLabel": "لینک‌های اصلی",
"ControlsAriaLabel": "پایین منو" "ControlsAriaLabel": "پایین منو"