Update
This commit is contained in:
@@ -16,12 +16,10 @@ import { useEffect, useState } from 'react';
|
||||
import UpgradePlanCarousel from './partials/upgrade-plan-carousel.tsx';
|
||||
|
||||
const UpgradeSheet = () => {
|
||||
const { subscription, one_times, isFetchingPricing, fetchPricing, isCheckingOut, checkoutSubscribe } = usePricingStore();
|
||||
const { plan, billing, user_usage } = useUserStore();
|
||||
const { subscription, one_times, isFetchingPricing, fetchPricing, isCheckingOut, checkoutSubscribe, checkoutPurchase } = usePricingStore();
|
||||
const { plan, billing, user_usage, credits, redirectBillingPortal, isRedirectingToBilling } = useUserStore();
|
||||
const { auth } = usePage().props;
|
||||
|
||||
const [isRedirectingToBilling, setIsRedirectingToBilling] = useState(false);
|
||||
|
||||
// State to control sheet visibility
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
@@ -101,6 +99,14 @@ const UpgradeSheet = () => {
|
||||
checkoutSubscribe(subscription.stripe_monthly_price_id);
|
||||
};
|
||||
|
||||
const handlePurchaseOneTime = (one_time) => {
|
||||
checkoutPurchase(one_time.stripe_price_id);
|
||||
};
|
||||
|
||||
const handleRedirectBillingPortal = () => {
|
||||
redirectBillingPortal();
|
||||
};
|
||||
|
||||
return (
|
||||
<Sheet open={isOpen} onOpenChange={handleOpenChange}>
|
||||
<SheetContent side="bottom" className="max-h-screen overflow-y-scroll pb-1">
|
||||
@@ -131,17 +137,19 @@ const UpgradeSheet = () => {
|
||||
)}
|
||||
|
||||
{/* Credits */}
|
||||
<div className="bg-card relative overflow-hidden rounded-xl border p-6 shadow-sm">
|
||||
<div className="relative z-10">
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<CoinIcon className="text-primary h-5 w-5" />
|
||||
<span className="text-muted-foreground text-xs font-medium">Credits</span>
|
||||
{credits != null && (
|
||||
<div className="bg-card relative overflow-hidden rounded-xl border p-6 shadow-sm">
|
||||
<div className="relative z-10">
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<CoinIcon className="text-primary h-5 w-5" />
|
||||
<span className="text-muted-foreground text-xs font-medium">Credits</span>
|
||||
</div>
|
||||
<div className="text-foreground text-xl font-bold">{credits}</div>
|
||||
<div className="text-muted-foreground text-sm">available</div>
|
||||
</div>
|
||||
<div className="text-foreground text-xl font-bold">999</div>
|
||||
<div className="text-muted-foreground text-sm">available</div>
|
||||
<CoinIcon className="bg-muted absolute -top-2 -right-2 h-20 w-20 opacity-15" />
|
||||
</div>
|
||||
<CoinIcon className="bg-muted absolute -top-2 -right-2 h-20 w-20 opacity-15" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -159,18 +167,15 @@ const UpgradeSheet = () => {
|
||||
<div className="mx-auto space-y-6 rounded-lg border p-4 text-center sm:p-7">
|
||||
<SparklesText className="text-xl font-bold sm:text-2xl">You're now in the {plan?.name} plan!</SparklesText>
|
||||
|
||||
{billing?.portal && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setIsRedirectingToBilling(true);
|
||||
window.location.replace(billing?.portal);
|
||||
}}
|
||||
disabled={isRedirectingToBilling}
|
||||
>
|
||||
{isRedirectingToBilling ? <Spinner className="text-muted-foreground h-4 w-4" /> : 'Manage Subscription'}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
handleRedirectBillingPortal();
|
||||
}}
|
||||
disabled={isRedirectingToBilling}
|
||||
>
|
||||
{isRedirectingToBilling ? <Spinner className="text-muted-foreground h-4 w-4" /> : 'Manage Subscription'}
|
||||
</Button>
|
||||
|
||||
<div className="text-muted-foreground block rounded border p-3 text-center text-xs">
|
||||
<ShieldIcon className="mr-1 mb-1 inline h-4 w-4" />
|
||||
@@ -229,24 +234,38 @@ const UpgradeSheet = () => {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex flex-col gap-3 rounded-lg border p-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="min-w-0 flex-1 space-y-1">
|
||||
<div className="grid">
|
||||
<div className="text inline-flex items-center font-semibold">
|
||||
<CoinIcon className="inline h-4 w-4 flex-shrink-0" />
|
||||
<span className="ml-1">500 Credit Pack</span>
|
||||
{one_times.map((one_time, index) => (
|
||||
<div
|
||||
key={one_time.id}
|
||||
className="flex flex-col gap-3 rounded-lg border p-3 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<div className="min-w-0 flex-1 space-y-1">
|
||||
<div className="grid text-start">
|
||||
<div className="text inline-flex items-center font-semibold">
|
||||
<CoinIcon className="inline h-4 w-4 flex-shrink-0" />
|
||||
<span className="ml-1">{one_time.name}</span>
|
||||
</div>
|
||||
<div className="text-muted-foreground text-xs font-semibold break-words">{one_time.description}</div>
|
||||
</div>
|
||||
<div className="text-muted-foreground text-xs font-semibold break-words">
|
||||
Approx. 250 AI captions & 250 AI backgrounds
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="text-muted-foreground text-sm">$4.00</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="text-muted-foreground text-sm">
|
||||
{one_time.symbol}
|
||||
{one_time.amount} per pack
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
disabled={isCheckingOut}
|
||||
onClick={() => {
|
||||
handlePurchaseOneTime(one_time);
|
||||
}}
|
||||
className="w-full flex-shrink-0 sm:w-auto"
|
||||
>
|
||||
{isCheckingOut ? <Spinner className="text-muted h-4 w-4" /> : <span>Buy</span>}
|
||||
</Button>
|
||||
</div>
|
||||
<Button className="w-full flex-shrink-0 sm:w-auto">Buy</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,6 +14,33 @@ const usePricingStore = create(
|
||||
|
||||
isCheckingOut: false,
|
||||
|
||||
checkoutPurchase: async (price_id) => {
|
||||
console.log('checkoutPurchase', price_id);
|
||||
|
||||
set({ isCheckingOut: true });
|
||||
try {
|
||||
const response = await axiosInstance.post(route('api.user.purchase'), { price_id: price_id });
|
||||
console.log(response);
|
||||
if (response?.data?.success?.data) {
|
||||
if (response.data.success.data.redirect) {
|
||||
window.location.href = response.data.success.data.redirect;
|
||||
}
|
||||
} else {
|
||||
throw 'Invalid API response';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(route('api.user.purchase'));
|
||||
console.error('Error fetching:', error);
|
||||
set({ isCheckingOut: false });
|
||||
if (error?.response?.data?.error?.message?.length > 0) {
|
||||
toast.error(error.response.data.error.message);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
set({ isCheckingOut: false });
|
||||
}
|
||||
},
|
||||
|
||||
checkoutSubscribe: async (price_id) => {
|
||||
console.log('checkoutSubscribe', price_id);
|
||||
|
||||
|
||||
@@ -14,9 +14,28 @@ const useUserStore = create(
|
||||
tier: 'free',
|
||||
},
|
||||
billing: null,
|
||||
credits: null,
|
||||
|
||||
isLoadingUser: false,
|
||||
|
||||
isRedirectingToBilling: false,
|
||||
|
||||
redirectBillingPortal: async () => {
|
||||
set({ isRedirectingToBilling: true });
|
||||
console.log('redirectBillingPortal');
|
||||
try {
|
||||
const response = await axiosInstance.post(route('api.user.billing_portal'));
|
||||
if (response?.data?.success?.data) {
|
||||
window.location.href = response.data.success.data.redirect;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(route('api.user.billing_portal'));
|
||||
console.error('Error fetching:', error);
|
||||
} finally {
|
||||
set({ isRedirectingToBilling: false });
|
||||
}
|
||||
},
|
||||
|
||||
// Fetch backgrounds
|
||||
fetchUser: async () => {
|
||||
set({ isLoadingUser: true });
|
||||
@@ -27,6 +46,7 @@ const useUserStore = create(
|
||||
user_usage: response.data.success.data.user.user_usage,
|
||||
plan: response.data.success.data.user.plan,
|
||||
billing: response.data.success.data.billing,
|
||||
credits: response.data.success.data.credits,
|
||||
});
|
||||
//return response.data.success.data;
|
||||
} catch (error) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user