Add prepaid resource credits with prorated deploy billing.

When users delete an app before plan expiry, remaining resources become credits for a new deploy. The deploy calculator shows covered vs additional charges, prices optional services correctly, and prorates extras to days left on the credit.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
keyhan
2026-05-15 17:37:44 +03:30
parent 35dd771f63
commit 5239e8aa94
17 changed files with 1021 additions and 96 deletions
+14 -4
View File
@@ -566,9 +566,14 @@ export default function AppDetailPage() {
const deleteMutation = useMutation({
mutationFn: () => api.delete(`/applications/${appId}`),
onSuccess: () => {
onSuccess: (res) => {
queryClient.invalidateQueries({ queryKey: ['applications'] });
toast.success('Application deleted');
queryClient.invalidateQueries({ queryKey: ['resource-credits'] });
if (res.data?.resourceCredit) {
toast.success('Application deleted. Prepaid resources are on your dashboard.');
} else {
toast.success('Application deleted');
}
router.push('/dashboard/apps');
},
onError: () => toast.error('Failed to delete application'),
@@ -862,7 +867,11 @@ export default function AppDetailPage() {
const handleDelete = async () => {
const ok = await confirm({
title: `Delete "${app.name}"?`,
message: 'This will permanently remove:\n• All Kubernetes resources (pods, services, ingress)\n• Database and volumes\n• All deployment records\n• Uploaded source code',
message:
'This will permanently remove all Kubernetes resources, data, and deployment records.\n\n' +
(app.planExpiresAt && new Date(app.planExpiresAt) > new Date()
? 'Your remaining paid resources will appear on the dashboard for use on a new app at no extra charge.'
: ''),
confirmText: 'Delete',
variant: 'danger',
});
@@ -937,11 +946,12 @@ export default function AppDetailPage() {
</>
)}
<button onClick={handleDelete} disabled={deleteMutation.isPending} className="btn-danger text-sm disabled:opacity-50">
{deleteMutation.isPending ? <><Clock className="w-3 h-3 inline animate-spin" /></> : 'Delete'}
{deleteMutation.isPending ? <><Clock className="w-3 h-3 inline animate-spin" /></> : 'Delete'}
</button>
</div>
</div>
{/* Renewal Banner for Expired/Suspended Apps */}
{needsRenewal && (
<div className={`rounded-xl p-4 border-2 ${