Extend plan

This commit is contained in:
hamid zarghami
2025-11-13 12:26:35 +03:30
parent e31e1b8728
commit 0e63aaaab8
9 changed files with 264 additions and 19 deletions
+17 -2
View File
@@ -14,6 +14,7 @@ type Props = {
isDisabled?: boolean,
isQuikAccess?: boolean,
onMoreClick?: () => void,
subscriptionId?: string,
}
const ServiceItem: FC<Props> = (props: Props) => {
@@ -53,10 +54,10 @@ const ServiceItem: FC<Props> = (props: Props) => {
}
<div className='mt-4 flex-1 flex items-end'>
<div className='mt-4 flex-1 flex gap-2 items-end'>
<Link target={props.isLinkPanel && !props.isDisabled ? '_blank' : ''} to={props.isDisabled ? '#' : props.isLinkPanel ? item.link : Pages.services.detail + item.slug}>
<Button
className='h-8 w-fit px-5 text-xs text-black bg-description bg-opacity-20 rounded-xl'
className='h-8 w-fit px-2 text-xs text-black bg-description bg-opacity-20 rounded-xl'
>
<div className='flex gap-2 whitespace-nowrap'>
{
@@ -69,6 +70,20 @@ const ServiceItem: FC<Props> = (props: Props) => {
</div>
</Button>
</Link>
{
props.subscriptionId &&
<Link to={Pages.services.detail + item.slug + '?id=' + props.subscriptionId}>
<Button
className='h-8 w-fit px-2 text-xs text-black bg-description bg-opacity-20 rounded-xl'
>
<div className='flex gap-2 whitespace-nowrap'>
{t('service.extend')}
<ArrowLeft color='black' size={16} />
</div>
</Button>
</Link>
}
</div>
</div>
)
+3 -1
View File
@@ -134,7 +134,9 @@
"business_name": "نام کسب و کار",
"phone_business": "شماره تماس کسب و کار",
"short_description": "توضیح کوتاه",
"confrim_and_invoice": "تایید و صدور صورت حساب"
"confrim_and_invoice": "تایید و صدور صورت حساب",
"extent": "تمدید پلن",
"extend": "تمدید"
},
"loading": "در حال بارگذاری",
"footer": {
+1
View File
@@ -17,6 +17,7 @@ import { ReviewItemType } from './types/ServiecTypes'
const DetailService: FC = () => {
const { id } = useParams()
const { t } = useTranslation('global')
const getAdsLeft = useGetAds(AdsDisplayLocation.SINGLE_SERVICE_PAGE)
+1
View File
@@ -65,6 +65,7 @@ const MyServices: FC = () => {
isLinkPanel
businessName={item.businessName}
isDisabled={item.status === 'INACTIVE'}
subscriptionId={item.id}
/>
)
})
@@ -0,0 +1,57 @@
import { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '../../../components/Button'
import { ArrowLeft } from 'iconsax-react'
import { useExtentSubscription } from '../hooks/useServiceData'
import { toast } from '../../../components/Toast'
import { ErrorType } from '../../../helpers/types'
import { Pages } from '../../../config/Pages'
import { useNavigate } from 'react-router-dom'
type Props = {
subscriptionId: string
planId: string,
serviceId: string
}
const ExtendPlan: FC<Props> = (props) => {
const { t } = useTranslation('global')
const { subscriptionId, planId, serviceId } = props
const { mutate: extentSubscription, isPending } = useExtentSubscription()
const navigate = useNavigate()
const handleExtentSubscription = () => {
extentSubscription({
subscriptionId: subscriptionId,
planId: planId,
serviceId: serviceId,
}, {
onSuccess: (data) => {
toast(data?.data?.message, 'success')
navigate(Pages.receipts.detail + data?.data?.invoice?.id)
},
onError: (error: ErrorType) => {
toast(error?.response?.data?.error.message[0], 'error')
}
})
}
return (
<div>
<Button
onClick={handleExtentSubscription}
isLoading={isPending}
className='bg-description xl:max-w-full max-w-[100px] text-xs h-8 bg-opacity-20 text-black px-4'
>
<div className='flex gap-1 items-center'>
<div>
{t('service.extent')}
</div>
<ArrowLeft className='size-4' color='black' />
</div>
</Button>
</div>
)
}
export default ExtendPlan
+34 -15
View File
@@ -8,6 +8,7 @@ import { NumberFormat } from '../../../config/func'
import { useBuyService } from '../hooks/useServiceData'
import { Link } from 'react-router-dom'
import { Pages } from '../../../config/Pages'
import ExtendPlan from './ExtendPlan'
type Props = {
data: ServiceDetailDataType,
@@ -17,6 +18,10 @@ const ServiceHeader: FC<Props> = (props: Props) => {
const { data } = props
const { t } = useTranslation('global')
// Get query params from URL
const urlParams = new URLSearchParams(window.location.search)
const [subscriptionId, setSubscriptionId] = useState<string>('')
const [planSelected, setPlanSelected] = useState<string>(data?.subscriptionPlans[0]?.id)
const [price, setPrice] = useState<number>(data?.subscriptionPlans[0]?.price)
const [finalPrice, setFinalPrice] = useState<number>(0)
@@ -30,6 +35,12 @@ const ServiceHeader: FC<Props> = (props: Props) => {
}, [planSelected])
useEffect(() => {
setSubscriptionId(urlParams.get('id') || '')
}, [window.location.search])
return (
<div className='w-full p-6 bg-white rounded-3xl flex xl:flex-row flex-col items-end xl:justify-between'>
@@ -89,21 +100,29 @@ const ServiceHeader: FC<Props> = (props: Props) => {
}
</div>
{
planSelected &&
<Link to={Pages.services.buy + `${data.id}/${planSelected}`}>
<Button
// onClick={handleBuy}
isLoading={buyService.isPending}
className='bg-description xl:max-w-full max-w-[100px] text-xs h-8 bg-opacity-20 text-black px-4'
>
<div className='flex gap-1 items-center'>
<div>
{t('service.buy')}
</div>
<ArrowLeft className='size-4' color='black' />
</div>
</Button>
</Link>
subscriptionId ? (
<ExtendPlan
subscriptionId={subscriptionId}
planId={planSelected}
serviceId={data.id}
/>
) :
planSelected ?
<Link to={Pages.services.buy + `${data.id}/${planSelected}`}>
<Button
// onClick={handleBuy}
isLoading={buyService.isPending}
className='bg-description xl:max-w-full max-w-[100px] text-xs h-8 bg-opacity-20 text-black px-4'
>
<div className='flex gap-1 items-center'>
<div>
{t('service.buy')}
</div>
<ArrowLeft className='size-4' color='black' />
</div>
</Button>
</Link>
: null
}
</div>
@@ -56,3 +56,9 @@ export const useGetServiceBySlug = (slug: string) => {
queryFn: () => api.getServiceBySlug(slug),
});
};
export const useExtentSubscription = () => {
return useMutation({
mutationFn: api.extentSubscription,
});
};
+16 -1
View File
@@ -1,5 +1,10 @@
import axios from "../../../config/axios";
import { BuyServiceType, CreateReviewType } from "../types/ServiecTypes";
import {
BuyServiceType,
CreateReviewType,
ExtentSubscriptionType,
ExtentSubscriptionResponseType,
} from "../types/ServiecTypes";
export const getServiceSuggestedDanak = async () => {
const { data } = await axios.get(`/danak-services/suggested`);
@@ -47,3 +52,13 @@ export const getServiceBySlug = async (slug: string) => {
const { data } = await axios.get(`/danak-services/slug/${slug}`);
return data;
};
export const extentSubscription = async (
params: ExtentSubscriptionType
): Promise<ExtentSubscriptionResponseType> => {
const { data } = await axios.put(
`/subscriptions/${params.subscriptionId}/subscribe`,
params
);
return data;
};
+129
View File
@@ -148,3 +148,132 @@ export type ReviewItemType = {
profilePic: string | null;
};
};
export type ExtentSubscriptionType = {
planId: string;
serviceId: string;
subscriptionId?: string;
};
export type UserType = {
id: string;
createdAt: string;
updatedAt: string;
email: string;
phone: string;
userName: string;
firstName: string;
lastName: string;
birthDate: string;
nationalCode: string;
profilePic: string | null;
emailVerified: boolean;
financialType: string;
deletedAt: string | null;
};
export type PlanType = {
id: string;
createdAt: string;
updatedAt: string;
name: string;
duration: number;
isFree: boolean;
price: number;
originalPrice: number;
isActive: boolean;
service: ServiceType;
deletedAt: string | null;
};
export type ServiceType = {
id: string;
createdAt: string;
updatedAt: string;
name: string;
title: string;
pageTitle: string;
pageDescription: string;
slug: string;
isDanakSuggest: boolean;
description: string;
author: string;
createDate: string;
userCount: number;
serviceLanguage: string;
softwareLanguage: string;
metaDescription: string;
isActive: boolean;
showInSlider: boolean;
link: string;
icon: string;
coverUrl: string;
deletedAt: string | null;
};
export type UserSubscriptionType = {
id: string;
createdAt: string;
updatedAt: string;
user: UserType;
plan: PlanType;
startDate: string;
endDate: string;
businessName: string;
businessPhone: string;
description: string;
slug: string;
status: "ACTIVE" | "INACTIVE" | "CANCELED";
};
export type InvoiceItemType = {
id: string;
createdAt: string;
updatedAt: string;
name: string;
count: number;
discount: number;
unitPrice: number;
totalPrice: number;
subscriptionPlan: UserSubscriptionType;
};
export type InvoiceType = {
id: string;
createdAt: string;
updatedAt: string;
numericId: number;
user: UserType;
totalPrice: {
s: number;
e: number;
d: number[];
};
originalPrice: {
s: number;
e: number;
d: number[];
};
status: "WAIT_PAYMENT" | "PAID" | "CANCELED";
dueDate: string;
paidAt: string | null;
tax: number;
lateFee: number | null;
isRecurring: boolean;
recurringPeriod: string | null;
maxRecurringCycles: number;
currentRecurringCycle: number;
isExternal: boolean;
externalBusinessId: string | null;
items: InvoiceItemType[];
};
export type ExtentSubscriptionResponseType = {
statusCode: number;
success: boolean;
data: {
message: string;
userSubscription: UserSubscriptionType;
invoice: InvoiceType;
};
};