From 087bdcc9d634b1ac155e2413bafd6556703ced06 Mon Sep 17 00:00:00 2001 From: isra el Date: Mon, 24 Feb 2025 00:09:51 +0300 Subject: [PATCH] fix billing issues and improve ui --- api/src/billing/billing.controller.ts | 7 +- api/src/billing/billing.service.ts | 14 +-- api/src/billing/schemas/plan.schema.ts | 12 --- .../billing/schemas/subscription.schema.ts | 23 ++++- api/src/gateway/gateway.service.ts | 10 --- .../(components)/account-settings.tsx | 88 ++++++++++++------- .../dashboard/(components)/bulk-sms-send.tsx | 37 ++++++-- web/config/api.ts | 2 +- 8 files changed, 119 insertions(+), 74 deletions(-) diff --git a/api/src/billing/billing.controller.ts b/api/src/billing/billing.controller.ts index 56a37ca..2d5b368 100644 --- a/api/src/billing/billing.controller.ts +++ b/api/src/billing/billing.controller.ts @@ -19,11 +19,10 @@ export class BillingController { return this.billingService.getPlans() } - - @Get('current-plan') + @Get('current-subscription') @UseGuards(AuthGuard) - async getCurrentPlan(@Request() req: any) { - return this.billingService.getCurrentPlan(req.user) + async getCurrentSubscription(@Request() req: any) { + return this.billingService.getCurrentSubscription(req.user) } @Post('checkout') diff --git a/api/src/billing/billing.service.ts b/api/src/billing/billing.service.ts index 3c4bd1b..68741b5 100644 --- a/api/src/billing/billing.service.ts +++ b/api/src/billing/billing.service.ts @@ -43,11 +43,13 @@ export class BillingService { }) } - async getCurrentPlan(user: any) { - const subscription = await this.subscriptionModel.findOne({ - user: user._id, - isActive: true, - }) + async getCurrentSubscription(user: any) { + const subscription = await this.subscriptionModel + .findOne({ + user: user._id, + isActive: true, + }) + .populate('plan') let plan = null @@ -240,7 +242,7 @@ export class BillingService { // Deactivate current active subscriptions const result = await this.subscriptionModel.updateMany( { user: userObjectId, plan: { $ne: plan._id }, isActive: true }, - { isActive: false, endDate: new Date() }, + { isActive: false, subscriptionEndDate: new Date() }, ) console.log(`Deactivated subscriptions: ${result.modifiedCount}`) diff --git a/api/src/billing/schemas/plan.schema.ts b/api/src/billing/schemas/plan.schema.ts index b9c7dd9..e91cbe9 100644 --- a/api/src/billing/schemas/plan.schema.ts +++ b/api/src/billing/schemas/plan.schema.ts @@ -32,18 +32,6 @@ export class Plan { @Prop({ type: String, unique: true }) polarYearlyProductId?: string - @Prop({ type: Date }) - subscriptionStartDate?: Date - - @Prop({ type: Date }) - subscriptionEndDate?: Date - - @Prop({ type: Date }) - currentPeriodStart?: Date - - @Prop({ type: Date }) - currentPeriodEnd?: Date - @Prop({ type: Boolean, default: true }) isActive: boolean } diff --git a/api/src/billing/schemas/subscription.schema.ts b/api/src/billing/schemas/subscription.schema.ts index 1318e29..b004b06 100644 --- a/api/src/billing/schemas/subscription.schema.ts +++ b/api/src/billing/schemas/subscription.schema.ts @@ -3,6 +3,16 @@ import { Document, Types } from 'mongoose' import { User } from '../../users/schemas/user.schema' import { Plan } from './plan.schema' +export enum SubscriptionStatus { + Incomplete = 'incomplete', + IncompleteExpired = 'incomplete_expired', + Trialing = 'trialing', + Active = 'active', + PastDue = 'past_due', + Canceled = 'canceled', + Unpaid = 'unpaid', +} + export type SubscriptionDocument = Subscription & Document @Schema({ timestamps: true }) @@ -17,10 +27,19 @@ export class Subscription { // polarSubscriptionId?: string @Prop({ type: Date }) - startDate: Date + subscriptionStartDate?: Date + + @Prop({ type: Date }) + subscriptionEndDate?: Date @Prop({ type: Date }) - endDate: Date + currentPeriodStart?: Date + + @Prop({ type: Date }) + currentPeriodEnd?: Date + + @Prop({ type: String }) + status: string @Prop({ type: Boolean, default: true }) isActive: boolean diff --git a/api/src/gateway/gateway.service.ts b/api/src/gateway/gateway.service.ts index ac2e5b9..87c763b 100644 --- a/api/src/gateway/gateway.service.ts +++ b/api/src/gateway/gateway.service.ts @@ -269,16 +269,6 @@ export class GatewayService { ) } - if (body.messages.map((m) => m.recipients).flat().length > 50) { - throw new HttpException( - { - success: false, - error: 'Maximum of 50 recipients per batch is allowed', - }, - HttpStatus.BAD_REQUEST, - ) - } - const { messageTemplate, messages } = body const smsBatch = await this.smsBatchModel.create({ diff --git a/web/app/(app)/dashboard/(components)/account-settings.tsx b/web/app/(app)/dashboard/(components)/account-settings.tsx index ad74e2d..c802557 100644 --- a/web/app/(app)/dashboard/(components)/account-settings.tsx +++ b/web/app/(app)/dashboard/(components)/account-settings.tsx @@ -43,7 +43,12 @@ import { Textarea } from '@/components/ui/textarea' import axios from 'axios' import { useSession } from 'next-auth/react' import { Routes } from '@/config/routes' -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip' const updateProfileSchema = z.object({ name: z.string().min(1, 'Name is required'), @@ -198,26 +203,29 @@ export default function AccountSettings() { }, }) - const CurrentPlan = () => { - + const CurrentSubscription = () => { const { - data: currentPlan, - isLoading: isLoadingPlan, - error: planError, + data: currentSubscription, + isLoading: isLoadingSubscription, + error: subscriptionError, } = useQuery({ - queryKey: ['currentPlan'], + queryKey: ['currentSubscription'], queryFn: () => httpBrowserClient - .get(ApiEndpoints.billing.currentPlan()) + .get(ApiEndpoints.billing.currentSubscription()) .then((res) => res.data), }) - - if (isLoadingPlan) return
- if (planError) + if (isLoadingSubscription) + return ( +
+ +
+ ) + if (subscriptionError) return (

- Failed to load plan information + Failed to load subscription information

) @@ -226,7 +234,7 @@ export default function AccountSettings() {

- {currentPlan?.name} + {currentSubscription?.plan?.name}

Current subscription @@ -234,7 +242,9 @@ export default function AccountSettings() {

- Active + + Active +
@@ -242,9 +252,15 @@ export default function AccountSettings() {
-

Next Payment

+

+ Next Payment +

- {currentPlan?.nextPaymentDate ?? '-:-'} + {currentSubscription?.nextPaymentDate + ? new Date( + currentSubscription?.nextPaymentDate + ).toLocaleDateString() + : '-:-'}

@@ -254,7 +270,7 @@ export default function AccountSettings() {

Quota

- {currentPlan?.quota} + {currentSubscription?.quota}

@@ -262,10 +278,14 @@ export default function AccountSettings() {
-

Daily

+

+ Daily +

- {currentPlan?.dailyLimit === -1 ? 'Unlimited' : currentPlan?.dailyLimit} - {currentPlan?.dailyLimit === -1 && ( + {currentSubscription?.dailyLimit === -1 + ? 'Unlimited' + : currentSubscription?.dailyLimit} + {currentSubscription?.dailyLimit === -1 && ( @@ -282,10 +302,14 @@ export default function AccountSettings() {

-

Monthly

+

+ Monthly +

- {currentPlan?.monthlyLimit === -1 ? 'Unlimited' : currentPlan?.monthlyLimit.toLocaleString()} - {currentPlan?.monthlyLimit === -1 && ( + {currentSubscription?.monthlyLimit === -1 + ? 'Unlimited' + : currentSubscription?.monthlyLimit.toLocaleString()} + {currentSubscription?.monthlyLimit === -1 && ( @@ -304,8 +328,10 @@ export default function AccountSettings() {

Bulk

- {currentPlan?.bulkSendLimit === -1 ? 'Unlimited' : currentPlan?.bulkSendLimit} - {currentPlan?.bulkSendLimit === -1 && ( + {currentSubscription?.bulkSendLimit === -1 + ? 'Unlimited' + : currentSubscription?.bulkSendLimit} + {currentSubscription?.bulkSendLimit === -1 && ( @@ -326,16 +352,16 @@ export default function AccountSettings() {

- {currentPlan?.name?.toLowerCase() === 'free' ? ( - Upgrade to Pro → ) : ( - Manage Subscription → @@ -355,7 +381,7 @@ export default function AccountSettings() { return (
- +
diff --git a/web/app/(app)/dashboard/(components)/bulk-sms-send.tsx b/web/app/(app)/dashboard/(components)/bulk-sms-send.tsx index 374f27b..ab22814 100644 --- a/web/app/(app)/dashboard/(components)/bulk-sms-send.tsx +++ b/web/app/(app)/dashboard/(components)/bulk-sms-send.tsx @@ -28,8 +28,8 @@ import { useMutation, useQuery } from '@tanstack/react-query' import { Spinner } from '@/components/ui/spinner' import httpBrowserClient from '@/lib/httpBrowserClient' -const MAX_FILE_SIZE = 1024 * 1024 // 1 MB -const MAX_ROWS = 50 +const DEFAULT_MAX_FILE_SIZE = 1024 * 1024 // 1 MB +const DEFAULT_MAX_ROWS = 50 export default function BulkSMSSend() { const [csvData, setCsvData] = useState([]) @@ -39,9 +39,29 @@ export default function BulkSMSSend() { const [selectedRecipient, setSelectedRecipient] = useState('') const [error, setError] = useState(null) + const { + data: currentSubscription, + isLoading: isLoadingSubscription, + error: subscriptionError, + } = useQuery({ + queryKey: ['currentSubscription'], + queryFn: () => + httpBrowserClient + .get(ApiEndpoints.billing.currentSubscription()) + .then((res) => res.data), + }) + + const maxRows = useMemo(() => { + if (currentSubscription?.plan?.bulkSendLimit == -1) { + return 9999 + } + + return currentSubscription?.plan?.bulkSendLimit || DEFAULT_MAX_ROWS + }, [currentSubscription]) + const onDrop = useCallback((acceptedFiles: File[]) => { const file = acceptedFiles[0] - if (file.size > MAX_FILE_SIZE) { + if (file.size > DEFAULT_MAX_FILE_SIZE) { setError('File size exceeds 1 MB limit.') return } @@ -49,8 +69,8 @@ export default function BulkSMSSend() { Papa.parse(file, { complete: (results) => { if (results.data && results.data.length > 0) { - if (results.data.length > MAX_ROWS) { - setError(`CSV file exceeds ${MAX_ROWS} rows limit.`) + if (results.data.length > maxRows) { + setError(`CSV file exceeds ${maxRows} rows limit.`) return } setCsvData(results.data as any[]) @@ -136,8 +156,8 @@ export default function BulkSMSSend() {

1. Upload CSV

- Upload a CSV file (max 1MB, {MAX_ROWS} rows) containing recipient - information. + Upload a CSV file (max {DEFAULT_MAX_FILE_SIZE} bytes, {maxRows} + rows) containing recipient information.

- Max file size: 1MB, Max rows: 50 + Max file size: {DEFAULT_MAX_FILE_SIZE} bytes, Max rows:{' '} + {maxRows}

{error && ( diff --git a/web/config/api.ts b/web/config/api.ts index 6952c80..c695271 100644 --- a/web/config/api.ts +++ b/web/config/api.ts @@ -32,7 +32,7 @@ export const ApiEndpoints = { getStats: () => '/gateway/stats', }, billing: { - currentPlan: () => '/billing/current-plan', + currentSubscription: () => '/billing/current-subscription', checkout: () => '/billing/checkout', plans: () => '/billing/plans', },