buy dkala

This commit is contained in:
hamid zarghami
2026-02-15 14:04:13 +03:30
parent aae0706e03
commit 0569e5cac1
11 changed files with 238 additions and 13 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 606 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 KiB

+18 -11
View File
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'
import { ItemServiceType } from '../pages/service/types/ServiecTypes' import { ItemServiceType } from '../pages/service/types/ServiecTypes'
import moment from 'moment-jalaali' import moment from 'moment-jalaali'
import { NumberFormat } from '../config/func' import { NumberFormat } from '../config/func'
import { useDirectLogin, useGetDmenuSubscription } from '../pages/service/hooks/useServiceData' import { useDirectLogin, useDirectLoginDkala, useGetDmenuSubscription, useGetDkalaSubscription } from '../pages/service/hooks/useServiceData'
import { toast } from './Toast' import { toast } from './Toast'
import { ErrorType } from '../helpers/types' import { ErrorType } from '../helpers/types'
@@ -31,8 +31,15 @@ const ServiceItem: FC<Props> = (props: Props) => {
const { item } = props const { item } = props
const isDmenu = item?.slug === 'Dmenu' const isDmenu = item?.slug === 'Dmenu'
const { data: dmenuSubscription, isPending, isError } = useGetDmenuSubscription(isChecked && isDmenu && props.isLinkPanel ? props.subscriptionId! : '',) const isDkala = item?.slug === 'Dkala'
const isDmenuOrDkala = isDmenu || isDkala
const { data: dmenuSubscription, isPending: isDmenuPending, isError: isDmenuError } = useGetDmenuSubscription(isChecked && isDmenu && props.isLinkPanel ? props.subscriptionId! : '')
const { data: dkalaSubscription, isPending: isDkalaPending, isError: isDkalaError } = useGetDkalaSubscription(isChecked && isDkala && props.isLinkPanel ? props.subscriptionId! : '')
const subscription = isDmenu ? dmenuSubscription : dkalaSubscription
const isPending = isDmenu ? isDmenuPending : isDkalaPending
const isError = isDmenu ? isDmenuError : isDkalaError
const { mutate: directLogin } = useDirectLogin() const { mutate: directLogin } = useDirectLogin()
const { mutate: directLoginDkala } = useDirectLoginDkala()
const getRemainingDays = (endDate: string): number => { const getRemainingDays = (endDate: string): number => {
const now = moment() const now = moment()
@@ -63,14 +70,15 @@ const ServiceItem: FC<Props> = (props: Props) => {
} }
const handleDirectLogin = () => { const handleDirectLogin = () => {
if (!isDmenu) { if (!isDmenuOrDkala) {
window.open(item.link, '_blank') window.open(item.link, '_blank')
return return
} }
if (!props.subscriptionId) { if (!props.subscriptionId) {
return return
} }
directLogin(props.subscriptionId, { const doLogin = isDkala ? directLoginDkala : directLogin
doLogin(props.subscriptionId, {
onSuccess: (data) => { onSuccess: (data) => {
window.open(item.link + `?token=${data?.data?.tokens?.accessToken?.token}&refreshToken=${data?.data?.tokens?.refreshToken?.token}`, '_blank') window.open(item.link + `?token=${data?.data?.tokens?.accessToken?.token}&refreshToken=${data?.data?.tokens?.refreshToken?.token}`, '_blank')
}, },
@@ -78,12 +86,11 @@ const ServiceItem: FC<Props> = (props: Props) => {
toast(error.response?.data?.error?.message[0], 'error') toast(error.response?.data?.error?.message[0], 'error')
} }
}) })
} }
useEffect(() => { useEffect(() => {
if (isChecked && isDmenu && props.isLinkPanel) { if (isChecked && isDmenuOrDkala && props.isLinkPanel) {
if (dmenuSubscription && !isPending) { if (subscription && !isPending) {
if (item.link) { if (item.link) {
// window.open(item.link, '_blank') // window.open(item.link, '_blank')
handleDirectLogin() handleDirectLogin()
@@ -91,12 +98,12 @@ const ServiceItem: FC<Props> = (props: Props) => {
setIsChecked(false) setIsChecked(false)
} else if (isError && !isPending) { } else if (isError && !isPending) {
if (props.subscriptionId) { if (props.subscriptionId) {
navigate(Pages.services.manageRestaurant + props.subscriptionId) navigate(isDmenu ? Pages.services.manageRestaurant + props.subscriptionId : Pages.services.manageDkala + props.subscriptionId)
} }
setIsChecked(false) setIsChecked(false)
} }
} }
}, [dmenuSubscription, isError, isPending, isChecked, isDmenu, props.isLinkPanel, props.subscriptionId, item.link, navigate]) }, [subscription, isError, isPending, isChecked, isDmenuOrDkala, props.isLinkPanel, props.subscriptionId, item.link, navigate])
const handleCheckSubscription = () => { const handleCheckSubscription = () => {
if (props.isDisabled) { if (props.isDisabled) {
@@ -104,7 +111,7 @@ const ServiceItem: FC<Props> = (props: Props) => {
} }
if (props.isLinkPanel && item.link) { if (props.isLinkPanel && item.link) {
if (isDmenu && props.subscriptionId) { if (isDmenuOrDkala && props.subscriptionId) {
setIsChecked(true) setIsChecked(true)
} else { } else {
// window.open(item.link, '_blank') // window.open(item.link, '_blank')
@@ -159,7 +166,7 @@ const ServiceItem: FC<Props> = (props: Props) => {
<Button <Button
className='h-8 sm:w-fit w-full px-2 text-xs text-black bg-description bg-opacity-20 rounded-xl' className='h-8 sm:w-fit w-full px-2 text-xs text-black bg-description bg-opacity-20 rounded-xl'
onClick={handleCheckSubscription} onClick={handleCheckSubscription}
isLoading={isChecked && isDmenu && props.isLinkPanel ? isPending : false} isLoading={isChecked && isDmenuOrDkala && props.isLinkPanel ? isPending : false}
> >
<div className='flex gap-2 whitespace-nowrap'> <div className='flex gap-2 whitespace-nowrap'>
+1
View File
@@ -7,6 +7,7 @@ export const Pages = {
dashboard: "/dashboard", dashboard: "/dashboard",
services: { services: {
manageRestaurant: "/services/manage-restaurant/", manageRestaurant: "/services/manage-restaurant/",
manageDkala: "/services/manage-dkala/",
mine: "/services", mine: "/services",
other: "/other-service", other: "/other-service",
detail: "/services/detail/", detail: "/services/detail/",
+12
View File
@@ -50,6 +50,7 @@
}, },
"errors": { "errors": {
"required": "این فیلد اجباری می باشد", "required": "این فیلد اجباری می باشد",
"subscription_not_found": "شناسه اشتراک یافت نشد",
"password_not_match": "رمز عبور با تکرار آن مطابقت ندارد", "password_not_match": "رمز عبور با تکرار آن مطابقت ندارد",
"is_not_image": "فرمت فایل انتخاب شده اشتباه می باشد", "is_not_image": "فرمت فایل انتخاب شده اشتباه می باشد",
"format_email_error": "فرمت ایمیل اشتباه است" "format_email_error": "فرمت ایمیل اشتباه است"
@@ -540,5 +541,16 @@
"enter_slug": "اسلاگ را وارد کنید", "enter_slug": "اسلاگ را وارد کنید",
"established_year": "سال تأسیس", "established_year": "سال تأسیس",
"enter_year": "سال تأسیس را وارد کنید" "enter_year": "سال تأسیس را وارد کنید"
},
"dkala": {
"manage": "مدیریت دی‌کالا",
"shop_info": "اطلاعات فروشگاه",
"enter_shop_info": "لطفا اطلاعات فروشگاه خود را وارد کنید",
"slug": "اسلاگ",
"enter_slug": "اسلاگ را وارد کنید",
"slug_validation": "اسلاگ باید فقط شامل حروف انگلیسی، اعداد و خط تیره باشد",
"service_info_error": "خطا در دریافت اطلاعات سرویس",
"direct_login_error": "خطا در ورود مستقیم",
"create_shop_error": "خطا در ایجاد فروشگاه"
} }
} }
+144
View File
@@ -0,0 +1,144 @@
import { type FC } from 'react'
import { useParams } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { useCreateDkalaShop, useDirectLoginDkala, useGetSubscription } from './hooks/useServiceData'
import { useFormik } from 'formik'
import { DkalaCreateShopType } from './types/ServiecTypes'
import * as Yup from 'yup'
import { toast } from '../../components/Toast'
import { ErrorType } from '../../helpers/types'
import Button from '../../components/Button'
import Error from '../../components/Error'
import { TickCircle } from 'iconsax-react'
import { Helmet } from 'react-helmet-async'
import GuideSlugImage from '../../assets/images/dkala.png'
export const ManageDkala: FC = () => {
const { t } = useTranslation('global')
const { subscriptionId } = useParams()
const createDkalaShop = useCreateDkalaShop()
const getSubscription = useGetSubscription(subscriptionId || '')
const { mutate: directLoginDkala } = useDirectLoginDkala()
const handleDirectLogin = () => {
if (!subscriptionId) {
toast(t('errors.subscription_not_found'), 'error')
return
}
directLoginDkala(subscriptionId, {
onSuccess: (data) => {
const serviceLink = getSubscription?.data?.data?.userSubscription?.plan?.service?.link
if (serviceLink && data?.data?.tokens?.accessToken?.token && data?.data?.tokens?.refreshToken?.token) {
window.location.href = `${serviceLink}?token=${data.data.tokens.accessToken.token}&refreshToken=${data.data.tokens.refreshToken.token}`
} else {
toast(t('dkala.service_info_error'), 'error')
}
},
onError: (error: ErrorType) => {
toast(error.response?.data?.error?.message?.[0] || t('dkala.direct_login_error'), 'error')
}
})
}
const formik = useFormik<DkalaCreateShopType>({
initialValues: {
slug: '',
userSubscriptionId: subscriptionId || ''
},
enableReinitialize: true,
validationSchema: Yup.object({
slug: Yup.string()
.required(t('errors.required'))
.matches(/^[a-zA-Z0-9-]+$/, t('dkala.slug_validation')),
userSubscriptionId: Yup.string().required(t('errors.required'))
}),
onSubmit: (values) => {
createDkalaShop.mutate(values, {
onSuccess: () => {
toast(t('success'), 'success')
formik.resetForm()
handleDirectLogin()
},
onError: (error: ErrorType) => {
toast(error.response?.data?.error?.message?.[0] || t('dkala.create_shop_error'), 'error')
}
})
}
})
return (
<>
<Helmet>
<title>{t('dkala.manage')}</title>
</Helmet>
<div className='mt-4'>
<div className='text-lg font-semibold mb-2'>
{t('dkala.manage')}
</div>
<div className='bg-white rounded-3xl xl:p-6 p-4 xl:pb-0 pb-0 mt-8 text-sm'>
<div className='mt-4'>
<div className='mb-6'>
<div className='text-base font-medium mb-2'>
{t('dkala.shop_info')}
</div>
<div className='text-description text-xs'>
{t('dkala.enter_shop_info')}
</div>
</div>
<div className='flex lg:max-w-[500px] mx-auto flex-col gap-6'>
<div className='w-full'>
<label className='text-sm font-medium mb-2 block'>
{t('dkala.slug')}
</label>
<div className='w-full relative mt-1'>
<div className='flex items-center border border-border rounded-xl overflow-hidden bg-white hover:border-gray-400 transition-colors focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/20' dir='ltr'>
<div className='px-4 py-2.5 bg-gray-50 text-xs text-gray-600 border-r border-border whitespace-nowrap font-medium'>
dkala.danakcorp.com/
</div>
<input
type='text'
value={formik.values.slug}
onChange={(e) => {
formik.setFieldValue('slug', e.target.value)
}}
onBlur={formik.handleBlur}
placeholder={t('dkala.enter_slug')}
className='flex-1 h-10 text-black block px-4 text-xs border-0 focus:outline-none focus:ring-0 bg-transparent'
dir='ltr'
/>
</div>
{formik.touched.slug && formik.errors.slug && (
<Error errorText={formik.errors.slug} />
)}
</div>
</div>
<div className='mt-4 flex justify-center'>
<Button
className='xl:w-fit px-16'
onClick={() => formik.handleSubmit()}
isLoading={createDkalaShop.isPending}
>
<div className='flex gap-2 items-center'>
<TickCircle
size={24}
color='white'
/>
<div>{t('save')}</div>
</div>
</Button>
</div>
<div className='mt-10'>
<img src={GuideSlugImage} className='w-[300px] mx-auto' />
</div>
</div>
</div>
</div>
</div>
</>
)
}
+1 -1
View File
@@ -11,7 +11,7 @@ import Button from '../../components/Button'
import Error from '../../components/Error' import Error from '../../components/Error'
import { TickCircle } from 'iconsax-react' import { TickCircle } from 'iconsax-react'
import { Helmet } from 'react-helmet-async' import { Helmet } from 'react-helmet-async'
import GuideSlugImage from '../../assets/images/guide-slug.png' import GuideSlugImage from '../../assets/images/dmenu.png'
export const ManageRestaurant: FC = () => { export const ManageRestaurant: FC = () => {
const { t } = useTranslation('global') const { t } = useTranslation('global')
+33 -1
View File
@@ -1,6 +1,10 @@
import { useMutation, useQuery } from "@tanstack/react-query"; import { useMutation, useQuery } from "@tanstack/react-query";
import * as api from "../service/ServiceService"; import * as api from "../service/ServiceService";
import { BuyServiceType, CreateReviewType } from "../types/ServiecTypes"; import {
BuyServiceType,
CreateReviewType,
DkalaCreateShopType,
} from "../types/ServiecTypes";
export const useGetSuggestedServices = () => { export const useGetSuggestedServices = () => {
return useQuery({ return useQuery({
@@ -79,12 +83,40 @@ export const useGetDmenuSubscription = (subscriptionId: string) => {
}); });
}; };
export const useGetDkalaSubscription = (subscriptionId: string) => {
return useQuery({
queryKey: ["dkala-subscription", subscriptionId],
queryFn: () => api.getDkalaSubscription(subscriptionId),
enabled: !!subscriptionId,
});
};
export const useCreateDkalaShop = () => {
return useMutation({
mutationFn: (variables: DkalaCreateShopType) =>
api.createDkalaShop(variables),
});
};
export const useDirectLoginDkala = () => {
return useMutation({
mutationFn: api.directLoginDkala,
mutationKey: ["direct-login-dkala"],
});
};
export const useCreateDmenuRestaurant = () => { export const useCreateDmenuRestaurant = () => {
return useMutation({ return useMutation({
mutationFn: api.createDmenuRestaurant, mutationFn: api.createDmenuRestaurant,
}); });
}; };
export const useCreateDkalaRestaurant = () => {
return useMutation({
mutationFn: api.createDkalaShop,
});
};
export const useGetSubscription = (subscriptionId: string) => { export const useGetSubscription = (subscriptionId: string) => {
return useQuery({ return useQuery({
queryKey: ["subscription", subscriptionId], queryKey: ["subscription", subscriptionId],
@@ -5,6 +5,7 @@ import {
ExtentSubscriptionType, ExtentSubscriptionType,
ExtentSubscriptionResponseType, ExtentSubscriptionResponseType,
DmenuCreateRestaurantType, DmenuCreateRestaurantType,
DkalaCreateShopType,
GetSubscriptionResponseType, GetSubscriptionResponseType,
DirectLoginResponseType, DirectLoginResponseType,
} from "../types/ServiecTypes"; } from "../types/ServiecTypes";
@@ -82,6 +83,13 @@ export const getDmenuSubscription = async (subscriptionId: string) => {
return data; return data;
}; };
export const getDkalaSubscription = async (subscriptionId: string) => {
const { data } = await axios.get(
`/dkala/shops/subscription/${subscriptionId}`,
);
return data;
};
export const createDmenuRestaurant = async ( export const createDmenuRestaurant = async (
params: DmenuCreateRestaurantType, params: DmenuCreateRestaurantType,
) => { ) => {
@@ -89,6 +97,20 @@ export const createDmenuRestaurant = async (
return data; return data;
}; };
export const createDkalaShop = async (params: DkalaCreateShopType) => {
const { data } = await axios.post(`/dkala/shops`, params);
return data;
};
export const directLoginDkala = async (
userSubscriptionId: string,
): Promise<DirectLoginResponseType> => {
const { data } = await axios.post(
`/dkala/auth/direct-login/${userSubscriptionId}`,
);
return data;
};
export const getSubscription = async ( export const getSubscription = async (
subscriptionId: string, subscriptionId: string,
): Promise<GetSubscriptionResponseType> => { ): Promise<GetSubscriptionResponseType> => {
+5
View File
@@ -283,6 +283,11 @@ export type DmenuCreateRestaurantType = {
userSubscriptionId: string; userSubscriptionId: string;
}; };
export type DkalaCreateShopType = {
slug: string;
userSubscriptionId: string;
};
export type ServiceImageType = { export type ServiceImageType = {
id: string; id: string;
createdAt: string; createdAt: string;
+2
View File
@@ -33,6 +33,7 @@ import Support from '../pages/support/Support'
import SupportInfo from '../pages/support/SupportInfo' import SupportInfo from '../pages/support/SupportInfo'
import Invoice from '../pages/receipts/Invoice' import Invoice from '../pages/receipts/Invoice'
import { ManageRestaurant } from '../pages/service/ManageRestaurant' import { ManageRestaurant } from '../pages/service/ManageRestaurant'
import { ManageDkala } from '../pages/service/ManageDkala'
const MainRouter: FC = () => { const MainRouter: FC = () => {
return ( return (
@@ -47,6 +48,7 @@ const MainRouter: FC = () => {
<Route path={Pages.support.index} element={<Support />} /> <Route path={Pages.support.index} element={<Support />} />
<Route path={Pages.support.info} element={<SupportInfo />} /> <Route path={Pages.support.info} element={<SupportInfo />} />
<Route path={Pages.services.manageRestaurant + ':subscriptionId'} element={<ManageRestaurant />} /> <Route path={Pages.services.manageRestaurant + ':subscriptionId'} element={<ManageRestaurant />} />
<Route path={Pages.services.manageDkala + ':subscriptionId'} element={<ManageDkala />} />
<Route path={Pages.services.mine} element={<MyServices />} /> <Route path={Pages.services.mine} element={<MyServices />} />
<Route path={Pages.services.other} element={<OtherServices />} /> <Route path={Pages.services.other} element={<OtherServices />} />
<Route path={Pages.services.detail + ':id'} element={<DetailService />} /> <Route path={Pages.services.detail + ':id'} element={<DetailService />} />