Compare commits
10 Commits
ecc33cee57
...
6bc8a24683
| Author | SHA1 | Date | |
|---|---|---|---|
| 6bc8a24683 | |||
| 36f3415344 | |||
| 8042bdc481 | |||
| 0569e5cac1 | |||
| aae0706e03 | |||
| 433bd65cb0 | |||
| c9c42cf638 | |||
| 1885bca62a | |||
| 47b9b4b196 | |||
| 80d9c16073 |
+22
-1
@@ -1,5 +1,12 @@
|
|||||||
FROM node:22-alpine AS builder
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
|
# Change Alpine repo
|
||||||
|
RUN sed -i 's|https://dl-cdn.alpinelinux.org/alpine|https://mirror.de.velop.ir/alpine|g' /etc/apk/repositories
|
||||||
|
|
||||||
|
# Configure npm registry mirror (Liara)
|
||||||
|
ENV NPM_CONFIG_REGISTRY=https://package-mirror.liara.ir/repository/npm/
|
||||||
|
RUN npm config set registry https://package-mirror.liara.ir/repository/npm/
|
||||||
|
|
||||||
# Install tzdata to support timezone settings
|
# Install tzdata to support timezone settings
|
||||||
RUN apk add --no-cache tzdata
|
RUN apk add --no-cache tzdata
|
||||||
|
|
||||||
@@ -7,13 +14,27 @@ RUN apk add --no-cache tzdata
|
|||||||
RUN cp /usr/share/zoneinfo/Asia/Tehran /etc/localtime && echo "Asia/Tehran" > /etc/timezone
|
RUN cp /usr/share/zoneinfo/Asia/Tehran /etc/localtime && echo "Asia/Tehran" > /etc/timezone
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Optional build-args (CapRover / docker build --build-arg). When unset, Vite reads .env from COPY.
|
||||||
|
ARG VITE_BASE_URL
|
||||||
|
ARG VITE_TOKEN_NAME
|
||||||
|
ARG VITE_REFRESH_TOKEN_NAME
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci --legacy-peer-deps
|
RUN npm ci --legacy-peer-deps
|
||||||
COPY . ./
|
COPY . ./
|
||||||
RUN npm run build
|
RUN set -e; \
|
||||||
|
env_args=""; \
|
||||||
|
[ -n "$VITE_BASE_URL" ] && env_args="$env_args VITE_BASE_URL=$VITE_BASE_URL"; \
|
||||||
|
[ -n "$VITE_TOKEN_NAME" ] && env_args="$env_args VITE_TOKEN_NAME=$VITE_TOKEN_NAME"; \
|
||||||
|
[ -n "$VITE_REFRESH_TOKEN_NAME" ] && env_args="$env_args VITE_REFRESH_TOKEN_NAME=$VITE_REFRESH_TOKEN_NAME"; \
|
||||||
|
eval "env$env_args npm run build"
|
||||||
|
|
||||||
FROM nginx:stable-alpine AS production-stage
|
FROM nginx:stable-alpine AS production-stage
|
||||||
|
|
||||||
|
# Change Alpine repo
|
||||||
|
RUN sed -i 's|https://dl-cdn.alpinelinux.org/alpine|https://mirror.de.velop.ir/alpine|g' /etc/apk/repositories
|
||||||
|
|
||||||
COPY --from=builder /build/dist /usr/share/nginx/html
|
COPY --from=builder /build/dist /usr/share/nginx/html
|
||||||
|
|
||||||
COPY --from=builder /build/nginx.con[f] /etc/nginx/conf.d/default.conf
|
COPY --from=builder /build/nginx.con[f] /etc/nginx/conf.d/default.conf
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 606 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 727 KiB |
@@ -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'>
|
||||||
@@ -174,7 +181,7 @@ const ServiceItem: FC<Props> = (props: Props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{
|
{
|
||||||
props.subscriptionId &&
|
props.subscriptionId && !props.isQuikAccess &&
|
||||||
<Link className='sm:w-fit w-full' to={Pages.services.detail + item.slug + '?id=' + props.subscriptionId}>
|
<Link className='sm:w-fit w-full' to={Pages.services.detail + item.slug + '?id=' + props.subscriptionId}>
|
||||||
<Button
|
<Button
|
||||||
className='h-8 sm:w-fit px-2 text-xs text-black bg-description bg-opacity-20 rounded-xl'
|
className='h-8 sm:w-fit px-2 text-xs text-black bg-description bg-opacity-20 rounded-xl'
|
||||||
|
|||||||
+2
-1
@@ -7,9 +7,10 @@ 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: "/other-service/detail/",
|
||||||
buy: "/other-service/buy/",
|
buy: "/other-service/buy/",
|
||||||
},
|
},
|
||||||
transactions: "/transactions",
|
transactions: "/transactions",
|
||||||
|
|||||||
+19
-2
@@ -42,12 +42,18 @@
|
|||||||
"enter_mobile_number": "شماره موبایل خود را وارد کنید",
|
"enter_mobile_number": "شماره موبایل خود را وارد کنید",
|
||||||
"try_again": "ارسال مجدد",
|
"try_again": "ارسال مجدد",
|
||||||
"toast_resend_code": "کد جدیدی برای شما ارسال شد",
|
"toast_resend_code": "کد جدیدی برای شما ارسال شد",
|
||||||
"old_version": "نسخه قدیمی داشبورد"
|
"old_version": "نسخه قدیمی داشبورد",
|
||||||
|
"enter_info_forgot": "ایمیل خود را وارد کنید",
|
||||||
|
"welcome_forgot": "فراموشی رمز عبور",
|
||||||
|
"email_code": "کد تایید",
|
||||||
|
"newPassword": "پسورد جدید"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"required": "این فیلد اجباری می باشد",
|
"required": "این فیلد اجباری می باشد",
|
||||||
|
"subscription_not_found": "شناسه اشتراک یافت نشد",
|
||||||
"password_not_match": "رمز عبور با تکرار آن مطابقت ندارد",
|
"password_not_match": "رمز عبور با تکرار آن مطابقت ندارد",
|
||||||
"is_not_image": "فرمت فایل انتخاب شده اشتباه می باشد"
|
"is_not_image": "فرمت فایل انتخاب شده اشتباه می باشد",
|
||||||
|
"format_email_error": "فرمت ایمیل اشتباه است"
|
||||||
},
|
},
|
||||||
"description": "توضیحات",
|
"description": "توضیحات",
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
@@ -535,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": "خطا در ایجاد فروشگاه"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { useState, type FC } from 'react'
|
||||||
|
import LogoImage from '../../assets/images/logo.svg'
|
||||||
|
import LogoSmallImage from '../../assets/images/logo-small.svg'
|
||||||
|
import ForgotStep1 from './components/ForgotStep1'
|
||||||
|
import ForgotStep2 from './components/ForgotStep2'
|
||||||
|
|
||||||
|
|
||||||
|
const Forgot: FC = () => {
|
||||||
|
|
||||||
|
const [step, setStep] = useState<number>(1)
|
||||||
|
const [email, setEmail] = useState<string>('')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full h-full flex justify-center lg:py-[75px] py-4 lg:items-center lg:px-10 px-4'>
|
||||||
|
<div className='flex w-full max-h-[812px] max-w-[1200px] bg-secondary h-full rounded-3xl overflow-hidden'>
|
||||||
|
<div className='flex-1 min-w-[50%] overflow-y-auto bg-white lg:px-9 px-4 py-7'>
|
||||||
|
<div className='flex-1 h-full flex flex-col'>
|
||||||
|
|
||||||
|
<div className='flex relative flex-1 flex-col h-full justify-center'>
|
||||||
|
<img src={LogoSmallImage} className='w-8 hidden xl:block' />
|
||||||
|
<img src={LogoImage} className='w-44 right-0 left-0 mx-auto absolute top-10 xl:hidden' />
|
||||||
|
{
|
||||||
|
step === 1 ?
|
||||||
|
<ForgotStep1 changeEmail={setEmail} changeStep={setStep} />
|
||||||
|
:
|
||||||
|
<ForgotStep2 email={email} changeStep={setStep} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex-1 min-w-[50%] lg:flex hidden justify-center items-center'>
|
||||||
|
<img src={LogoImage} className='h-20' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Forgot
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Input from '../../../components/Input'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { ForgotPasswordType } from '../types/AuthTypes'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import { useForgotPassword } from '../hooks/useAuthData'
|
||||||
|
import { toast } from '../../../components/Toast'
|
||||||
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
changeStep: (value: number) => void,
|
||||||
|
changeEmail: (value: string) => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ForgotStep1: FC<Props> = ({ changeStep, changeEmail }) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const { mutate, isPending } = useForgotPassword()
|
||||||
|
const formik = useFormik<ForgotPasswordType>({
|
||||||
|
initialValues: {
|
||||||
|
email: ''
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
email: Yup.string().email(t('errors.format_email_error')).required(t('errors.required'))
|
||||||
|
}),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
mutate(values, {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast(data?.data?.message)
|
||||||
|
changeEmail(values.email)
|
||||||
|
changeStep(2)
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast(error.response?.data?.error.message[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mt-5'>
|
||||||
|
<h2 className='text-2xl font-bold'>
|
||||||
|
{t('auth.welcome_forgot')}
|
||||||
|
</h2>
|
||||||
|
<p className='text-description text-sm mt-2'>
|
||||||
|
{t('auth.enter_info_forgot')}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className='mt-16'>
|
||||||
|
<Input
|
||||||
|
label={t('email')}
|
||||||
|
{...formik.getFieldProps('email')}
|
||||||
|
error_text={formik.touched.email && formik.errors.email ? formik.errors.email : ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
label={t('auth.next')}
|
||||||
|
className='mt-8'
|
||||||
|
onClick={() => formik.handleSubmit()}
|
||||||
|
isLoading={isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ForgotStep1
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Input from '../../../components/Input'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { ResetPasswordType } from '../types/AuthTypes'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import Button from '../../../components/Button'
|
||||||
|
import { useResetPassword } from '../hooks/useAuthData'
|
||||||
|
import { toast } from '../../../components/Toast'
|
||||||
|
import { ErrorType } from '../../../helpers/types'
|
||||||
|
import { Pages } from '../../../config/Pages'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
changeStep: (value: number) => void,
|
||||||
|
email: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ForgotStep2: FC<Props> = ({ email }) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const { mutate, isPending } = useResetPassword()
|
||||||
|
const formik = useFormik<ResetPasswordType>({
|
||||||
|
initialValues: {
|
||||||
|
email: email,
|
||||||
|
code: '',
|
||||||
|
newPassword: ''
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object({
|
||||||
|
email: Yup.string().email(t('errors.format_email_error')).required(t('errors.required')),
|
||||||
|
code: Yup.string().required(t('errors.required')),
|
||||||
|
newPassword: Yup.string().required(t('errors.required')),
|
||||||
|
}),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
mutate(values, {
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast(data?.data?.message)
|
||||||
|
window.location.href = Pages.auth.login
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast(error.response?.data?.error.message[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mt-5'>
|
||||||
|
<h2 className='text-2xl font-bold'>
|
||||||
|
{t('auth.welcome_forgot')}
|
||||||
|
</h2>
|
||||||
|
<p className='text-description text-sm mt-2'>
|
||||||
|
{t('auth.enter_info_forgot')}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className='mt-16'>
|
||||||
|
<Input
|
||||||
|
readOnly
|
||||||
|
label={t('email')}
|
||||||
|
{...formik.getFieldProps('email')}
|
||||||
|
value={email}
|
||||||
|
error_text={formik.touched.email && formik.errors.email ? formik.errors.email : ''}
|
||||||
|
/>
|
||||||
|
<div className='mt-5'>
|
||||||
|
<Input
|
||||||
|
label={t('auth.email_code')}
|
||||||
|
{...formik.getFieldProps('code')}
|
||||||
|
error_text={formik.touched.code && formik.errors.code ? formik.errors.code : ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='mt-5'>
|
||||||
|
<Input
|
||||||
|
label={t('auth.newPassword')}
|
||||||
|
{...formik.getFieldProps('newPassword')}
|
||||||
|
error_text={formik.touched.newPassword && formik.errors.newPassword ? formik.errors.newPassword : ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
label={t('auth.next')}
|
||||||
|
className='mt-8'
|
||||||
|
onClick={() => formik.handleSubmit()}
|
||||||
|
isLoading={isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ForgotStep2
|
||||||
@@ -11,6 +11,9 @@ import { isEmail } from '../../../config/func'
|
|||||||
import { useCheckHasAccount, useLoginWithOtp } from '../hooks/useAuthData'
|
import { useCheckHasAccount, useLoginWithOtp } from '../hooks/useAuthData'
|
||||||
import { ErrorType } from '../../../helpers/types'
|
import { ErrorType } from '../../../helpers/types'
|
||||||
import { toast } from '../../../components/Toast'
|
import { toast } from '../../../components/Toast'
|
||||||
|
import { ArrowLeft } from 'iconsax-react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Pages } from '../../../config/Pages'
|
||||||
const LoginStep1: FC = () => {
|
const LoginStep1: FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation('global')
|
const { t } = useTranslation('global')
|
||||||
@@ -100,12 +103,12 @@ const LoginStep1: FC = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div> */}
|
</div> */}
|
||||||
|
|
||||||
{/* <a href='https://accounts.danakcorp.com' target='_blank' className='mt-6 flex gap-1 text-sm justify-center text-description'>
|
<Link to={Pages.auth.forgotPassword} className='mt-6 flex gap-1 text-sm justify-center text-description'>
|
||||||
<div>
|
<div>
|
||||||
{t('auth.old_version')}
|
{t('auth.forgot_password')}
|
||||||
</div>
|
</div>
|
||||||
<ArrowLeft size={20} color='black' />
|
<ArrowLeft size={20} color='black' />
|
||||||
</a> */}
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,3 +43,15 @@ export const useLogout = () => {
|
|||||||
mutationFn: (_) => api.logout(),
|
mutationFn: (_) => api.logout(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useForgotPassword = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: api.forgotPassword,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useResetPassword = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: api.resetPassword,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -2,11 +2,13 @@ import axios from "../../../config/axios";
|
|||||||
import {
|
import {
|
||||||
CheckHasAccountPhoneType,
|
CheckHasAccountPhoneType,
|
||||||
CheckHasAccountType,
|
CheckHasAccountType,
|
||||||
|
ForgotPasswordType,
|
||||||
LoginWithOtpType,
|
LoginWithOtpType,
|
||||||
LoginWithPasswordType,
|
LoginWithPasswordType,
|
||||||
OtpVerifyType,
|
OtpVerifyType,
|
||||||
RefreshTokenType,
|
RefreshTokenType,
|
||||||
RegisterType,
|
RegisterType,
|
||||||
|
ResetPasswordType,
|
||||||
} from "../types/AuthTypes";
|
} from "../types/AuthTypes";
|
||||||
|
|
||||||
export const loginWithPassword = async (params: LoginWithPasswordType) => {
|
export const loginWithPassword = async (params: LoginWithPasswordType) => {
|
||||||
@@ -29,7 +31,7 @@ export const checkHasAccount = async (params: CheckHasAccountType) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const checkHasAccountRegister = async (
|
export const checkHasAccountRegister = async (
|
||||||
params: CheckHasAccountPhoneType
|
params: CheckHasAccountPhoneType,
|
||||||
) => {
|
) => {
|
||||||
const { data } = await axios.post(`/auth/register/initiate`, params);
|
const { data } = await axios.post(`/auth/register/initiate`, params);
|
||||||
return data;
|
return data;
|
||||||
@@ -48,3 +50,13 @@ export const logout = async () => {
|
|||||||
const { data } = await axios.post(`/auth/logout`);
|
const { data } = await axios.post(`/auth/logout`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const forgotPassword = async (params: ForgotPasswordType) => {
|
||||||
|
const { data } = await axios.post(`/auth/forgot-password`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetPassword = async (params: ResetPasswordType) => {
|
||||||
|
const { data } = await axios.post(`/auth/reset-password`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -48,3 +48,13 @@ export type RegisterType = {
|
|||||||
export type RefreshTokenType = {
|
export type RefreshTokenType = {
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ForgotPasswordType = {
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResetPasswordType = {
|
||||||
|
newPassword: string;
|
||||||
|
email: string;
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ const Home: FC = () => {
|
|||||||
isQuikAccess
|
isQuikAccess
|
||||||
businessName={item.userSubscription.businessName}
|
businessName={item.userSubscription.businessName}
|
||||||
isLinkPanel
|
isLinkPanel
|
||||||
|
subscriptionId={item.userSubscription.id}
|
||||||
onMoreClick={() => setShowModal(true)}
|
onMoreClick={() => setShowModal(true)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ const BuyService: FC = () => {
|
|||||||
const formik = useFormik<BuyServiceType>({
|
const formik = useFormik<BuyServiceType>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
businessName: '',
|
businessName: '',
|
||||||
businessPhone: '',
|
|
||||||
description: '',
|
description: '',
|
||||||
planId: planId ? planId : '',
|
planId: planId ? planId : '',
|
||||||
serviceId: serviceId ? serviceId : ''
|
serviceId: serviceId ? serviceId : ''
|
||||||
@@ -82,13 +81,13 @@ const BuyService: FC = () => {
|
|||||||
error_text={formik.touched.businessName && formik.errors.businessName ? formik.errors.businessName : ''}
|
error_text={formik.touched.businessName && formik.errors.businessName ? formik.errors.businessName : ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-8'>
|
{/* <div className='mt-8'>
|
||||||
<Input
|
<Input
|
||||||
label={t('service.phone_business')}
|
label={t('service.phone_business')}
|
||||||
{...formik.getFieldProps('businessPhone')}
|
{...formik.getFieldProps('businessPhone')}
|
||||||
error_text={formik.touched.businessPhone && formik.errors.businessPhone ? formik.errors.businessPhone : ''}
|
error_text={formik.touched.businessPhone && formik.errors.businessPhone ? formik.errors.businessPhone : ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> */}
|
||||||
<div className='mt-8'>
|
<div className='mt-8'>
|
||||||
<Textarea
|
<Textarea
|
||||||
label={t('service.short_description')}
|
label={t('service.short_description')}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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')
|
||||||
@@ -77,7 +77,7 @@ export const ManageRestaurant: FC = () => {
|
|||||||
{t('restaurant.manage')}
|
{t('restaurant.manage')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='bg-white rounded-3xl xl:p-6 p-4 mt-8 text-sm'>
|
<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='mt-4'>
|
||||||
<div className='mb-6'>
|
<div className='mb-6'>
|
||||||
<div className='text-base font-medium mb-2'>
|
<div className='text-base font-medium mb-2'>
|
||||||
@@ -116,9 +116,9 @@ export const ManageRestaurant: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-4 flex justify-end'>
|
<div className='mt-4 flex justify-center'>
|
||||||
<Button
|
<Button
|
||||||
className='xl:w-fit px-7'
|
className='xl:w-fit px-16'
|
||||||
onClick={() => formik.handleSubmit()}
|
onClick={() => formik.handleSubmit()}
|
||||||
isLoading={createDmenuRestaurant.isPending}
|
isLoading={createDmenuRestaurant.isPending}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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({
|
||||||
@@ -23,6 +27,14 @@ export const useGetServicesByCategory = (categoryId: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetServiceSearch = (search: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["services-search", search],
|
||||||
|
queryFn: () => api.getServiceSearch(search),
|
||||||
|
enabled: !!search,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useGetDetailService = (serviceId: string) => {
|
export const useGetDetailService = (serviceId: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["service", serviceId],
|
queryKey: ["service", serviceId],
|
||||||
@@ -71,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";
|
||||||
@@ -26,6 +27,15 @@ export const getServicesByCategory = async (categoryId: string) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getServiceSearch = async (search: string) => {
|
||||||
|
let query = ``;
|
||||||
|
if (search) {
|
||||||
|
query = `?q=${search}`;
|
||||||
|
}
|
||||||
|
const { data } = await axios.get(`/danak-services/search${query}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
export const getDetailService = async (serviceId: string) => {
|
export const getDetailService = async (serviceId: string) => {
|
||||||
const { data } = await axios.get(`/danak-services/${serviceId}`);
|
const { data } = await axios.get(`/danak-services/${serviceId}`);
|
||||||
return data;
|
return data;
|
||||||
@@ -34,7 +44,7 @@ export const getDetailService = async (serviceId: string) => {
|
|||||||
export const buyService = async (params: BuyServiceType) => {
|
export const buyService = async (params: BuyServiceType) => {
|
||||||
const { data } = await axios.post(
|
const { data } = await axios.post(
|
||||||
`/subscriptions/${params.serviceId}/subscribe`,
|
`/subscriptions/${params.serviceId}/subscribe`,
|
||||||
params
|
params,
|
||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
@@ -46,7 +56,7 @@ export const getMyServices = async (search: string) => {
|
|||||||
export const createReview = async (params: CreateReviewType) => {
|
export const createReview = async (params: CreateReviewType) => {
|
||||||
const { data } = await axios.post(
|
const { data } = await axios.post(
|
||||||
`/danak-services/${params.id}/reviews`,
|
`/danak-services/${params.id}/reviews`,
|
||||||
params
|
params,
|
||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
@@ -57,41 +67,62 @@ export const getServiceBySlug = async (slug: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const extentSubscription = async (
|
export const extentSubscription = async (
|
||||||
params: ExtentSubscriptionType
|
params: ExtentSubscriptionType,
|
||||||
): Promise<ExtentSubscriptionResponseType> => {
|
): Promise<ExtentSubscriptionResponseType> => {
|
||||||
const { data } = await axios.put(
|
const { data } = await axios.put(
|
||||||
`/subscriptions/${params.subscriptionId}/subscribe`,
|
`/subscriptions/${params.subscriptionId}/subscribe`,
|
||||||
params
|
params,
|
||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDmenuSubscription = async (subscriptionId: string) => {
|
export const getDmenuSubscription = async (subscriptionId: string) => {
|
||||||
const { data } = await axios.get(
|
const { data } = await axios.get(
|
||||||
`/dmenu/restaurants/subscription/${subscriptionId}`
|
`/dmenu/restaurants/subscription/${subscriptionId}`,
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDkalaSubscription = async (subscriptionId: string) => {
|
||||||
|
const { data } = await axios.get(
|
||||||
|
`/dkala/shops/subscription/${subscriptionId}`,
|
||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createDmenuRestaurant = async (
|
export const createDmenuRestaurant = async (
|
||||||
params: DmenuCreateRestaurantType
|
params: DmenuCreateRestaurantType,
|
||||||
) => {
|
) => {
|
||||||
const { data } = await axios.post(`/dmenu/restaurants`, params);
|
const { data } = await axios.post(`/dmenu/restaurants`, params);
|
||||||
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> => {
|
||||||
const { data } = await axios.get(`/subscriptions/user/${subscriptionId}`);
|
const { data } = await axios.get(`/subscriptions/user/${subscriptionId}`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const directLogin = async (
|
export const directLogin = async (
|
||||||
userSubscriptionId: string
|
userSubscriptionId: string,
|
||||||
): Promise<DirectLoginResponseType> => {
|
): Promise<DirectLoginResponseType> => {
|
||||||
const { data } = await axios.post(
|
const { data } = await axios.post(
|
||||||
`/dmenu/auth/direct-login/${userSubscriptionId}`
|
`/dmenu/auth/direct-login/${userSubscriptionId}`,
|
||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,6 +2,7 @@ import { FC } from 'react'
|
|||||||
import { Route, Routes } from 'react-router-dom'
|
import { Route, Routes } from 'react-router-dom'
|
||||||
import { Pages } from '../config/Pages'
|
import { Pages } from '../config/Pages'
|
||||||
import Login from '../pages/auth/Login'
|
import Login from '../pages/auth/Login'
|
||||||
|
import Forgot from '../pages/auth/Forgot'
|
||||||
// import Register from '../pages/auth/Register'
|
// import Register from '../pages/auth/Register'
|
||||||
|
|
||||||
const AuthRouter: FC = () => {
|
const AuthRouter: FC = () => {
|
||||||
@@ -9,6 +10,7 @@ const AuthRouter: FC = () => {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path={Pages.auth.login} element={<Login />} />
|
<Route path={Pages.auth.login} element={<Login />} />
|
||||||
<Route path={Pages.auth.register} element={<Login />} />
|
<Route path={Pages.auth.register} element={<Login />} />
|
||||||
|
<Route path={Pages.auth.forgotPassword} element={<Forgot />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 />} />
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { FC, useEffect, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import Input from '../components/Input'
|
|
||||||
import { ArrowDown2, Card, CloseCircle, Element3, HambergerMenu, Logout, ProfileCircle, Receipt1, Setting2, TicketDiscount, Wallet } from 'iconsax-react'
|
import { ArrowDown2, Card, CloseCircle, Element3, HambergerMenu, Logout, ProfileCircle, Receipt1, Setting2, TicketDiscount, Wallet } from 'iconsax-react'
|
||||||
import AvatarImage from '../assets/images/avatar_image.png'
|
import AvatarImage from '../assets/images/avatar_image.png'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -12,6 +11,7 @@ import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'
|
|||||||
import SideBarItem from './SideBarItem'
|
import SideBarItem from './SideBarItem'
|
||||||
import { useGetWalletBalance } from '../pages/wallet/hooks/useWalletData'
|
import { useGetWalletBalance } from '../pages/wallet/hooks/useWalletData'
|
||||||
import { NumberFormat } from '../config/func'
|
import { NumberFormat } from '../config/func'
|
||||||
|
import Search from './components/Search'
|
||||||
|
|
||||||
const Header: FC = () => {
|
const Header: FC = () => {
|
||||||
|
|
||||||
@@ -29,12 +29,7 @@ const Header: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div className='fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]'>
|
<div className='fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]'>
|
||||||
|
|
||||||
<div className='min-w-[270px] hidden xl:block'>
|
<Search />
|
||||||
<Input
|
|
||||||
variant='search'
|
|
||||||
placeholder={t('header.search')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div onClick={() => setOpenSidebar(!openSidebar)} className='xl:hidden block'>
|
<div onClick={() => setOpenSidebar(!openSidebar)} className='xl:hidden block'>
|
||||||
<HambergerMenu size={24} color='black' />
|
<HambergerMenu size={24} color='black' />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { useEffect, useState, type FC } from 'react'
|
||||||
|
import Input from '../../components/Input'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useGetServiceSearch } from '../../pages/service/hooks/useServiceData'
|
||||||
|
import { ServiceType } from '../../pages/service/types/ServiecTypes'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Pages } from '../../config/Pages'
|
||||||
|
import { useOutsideClick } from '../../hooks/useOutSideClick'
|
||||||
|
import { clx } from '../../helpers/utils'
|
||||||
|
import MoonLoader from 'react-spinners/MoonLoader'
|
||||||
|
|
||||||
|
const Search: FC = () => {
|
||||||
|
|
||||||
|
const { t } = useTranslation('global')
|
||||||
|
const [search, setSearch] = useState<string>('')
|
||||||
|
const [showSearch, setShowSearch] = useState<boolean>(false)
|
||||||
|
const { data, isPending } = useGetServiceSearch(search)
|
||||||
|
const ref = useOutsideClick(() => {
|
||||||
|
setShowSearch(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (search.length) {
|
||||||
|
setShowSearch(true)
|
||||||
|
} else {
|
||||||
|
setShowSearch(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='min-w-[270px] relative hidden xl:block'>
|
||||||
|
<Input
|
||||||
|
variant='search'
|
||||||
|
placeholder={t('header.search')}
|
||||||
|
onChangeSearchFinal={setSearch}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
showSearch &&
|
||||||
|
<div ref={ref} className='absolute shadow bg-white top-[100%] rounded-xl right-0 w-[270px] p-4 flex flex-col gap-4 overflow-y-auto max-h-screen'>
|
||||||
|
{
|
||||||
|
isPending ?
|
||||||
|
<div className='flex justify-center'>
|
||||||
|
<MoonLoader color='black' size={20} />
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
data?.data?.services?.length === 0 ?
|
||||||
|
<div className='flex justify-center text-sm text-gray-400'>
|
||||||
|
نتیجه ای یافت نشد.
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
data?.data?.services?.map((item: ServiceType, index: number) => {
|
||||||
|
return (
|
||||||
|
<Link onClick={() => setShowSearch(false)} to={Pages.services.detail + item.slug} key={item.id} className={clx(
|
||||||
|
'flex gap-3 items-center border-b pb-4',
|
||||||
|
data?.data?.services?.length === index + 1 && 'border-b-0 pb-0'
|
||||||
|
)}>
|
||||||
|
<img src={item.icon} className='size-8 rounded-lg' />
|
||||||
|
<div className='text-[13px]'>{item.name}</div>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Search
|
||||||
Reference in New Issue
Block a user