Reset and deletion of project
This commit is contained in:
@@ -78,6 +78,10 @@ export default function Settings() {
|
||||
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const [showRegenerateDialog, setShowRegenerateDialog] = useState(false);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [showResetDialog, setShowResetDialog] = useState(false);
|
||||
const [deleteConfirmText, setDeleteConfirmText] = useState('');
|
||||
const [resetConfirmText, setResetConfirmText] = useState('');
|
||||
const [isLoadingBilling, setIsLoadingBilling] = useState(false);
|
||||
|
||||
const billingEnabled = config?.features.billing.enabled ?? false;
|
||||
@@ -264,6 +268,57 @@ export default function Settings() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetProject = async () => {
|
||||
if (!activeProject || resetConfirmText !== 'RESET') return;
|
||||
|
||||
try {
|
||||
setErrorMessage(null);
|
||||
setSuccessMessage(null);
|
||||
|
||||
await network.fetch('POST', `/users/@me/projects/${activeProject.id}/reset`);
|
||||
|
||||
setSuccessMessage('Project reset successfully. All data has been cleared.');
|
||||
setShowResetDialog(false);
|
||||
setResetConfirmText('');
|
||||
|
||||
// Refresh the page to reload data
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Failed to reset project');
|
||||
setShowResetDialog(false);
|
||||
setResetConfirmText('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteProject = async () => {
|
||||
if (!activeProject || deleteConfirmText !== 'DELETE') return;
|
||||
|
||||
try {
|
||||
setErrorMessage(null);
|
||||
setSuccessMessage(null);
|
||||
|
||||
await network.fetch('DELETE', `/users/@me/projects/${activeProject.id}`);
|
||||
|
||||
setSuccessMessage('Project deleted successfully. Redirecting...');
|
||||
setShowDeleteDialog(false);
|
||||
setDeleteConfirmText('');
|
||||
|
||||
// Refresh projects list and redirect to dashboard
|
||||
await projectsMutate();
|
||||
|
||||
// Redirect to dashboard after a short delay
|
||||
setTimeout(() => {
|
||||
window.location.href = '/';
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Failed to delete project');
|
||||
setShowDeleteDialog(false);
|
||||
setDeleteConfirmText('');
|
||||
}
|
||||
};
|
||||
|
||||
if (!activeProject) {
|
||||
return (
|
||||
<>
|
||||
@@ -282,277 +337,463 @@ export default function Settings() {
|
||||
<NextSeo title="Settings" />
|
||||
<DashboardLayout>
|
||||
<div className="space-y-8">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-neutral-900">Settings</h1>
|
||||
<p className="text-neutral-500 mt-2">Manage your project settings and preferences</p>
|
||||
</div>
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-neutral-900">Settings</h1>
|
||||
<p className="text-neutral-500 mt-2">Manage your project settings and preferences</p>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<Tabs value={currentTab} onValueChange={handleTabChange} className="max-w-4xl">
|
||||
<TabsList>
|
||||
{buildTabs({billingEnabled, smtpEnabled}).map(tab => {
|
||||
const Icon = tab.icon;
|
||||
return (
|
||||
<TabsTrigger key={tab.id} value={tab.id} className="flex items-center gap-2">
|
||||
<Icon className="h-4 w-4" />
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
);
|
||||
})}
|
||||
</TabsList>
|
||||
{/* Tabs */}
|
||||
<Tabs value={currentTab} onValueChange={handleTabChange} className="max-w-4xl">
|
||||
<TabsList>
|
||||
{buildTabs({billingEnabled, smtpEnabled}).map(tab => {
|
||||
const Icon = tab.icon;
|
||||
return (
|
||||
<TabsTrigger key={tab.id} value={tab.id} className="flex items-center gap-2">
|
||||
<Icon className="h-4 w-4" />
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
);
|
||||
})}
|
||||
</TabsList>
|
||||
|
||||
{/* General Tab */}
|
||||
<TabsContent value="general">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Project Settings</CardTitle>
|
||||
<CardDescription>Update your project name and basic information</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Project Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="My Awesome Project" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Email Tracking Toggle - only show if feature is available */}
|
||||
{trackingToggleEnabled && (
|
||||
{/* General Tab */}
|
||||
<TabsContent value="general">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Project Settings</CardTitle>
|
||||
<CardDescription>Update your project name and basic information</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="trackingEnabled"
|
||||
name="name"
|
||||
render={({field}) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border border-neutral-200 p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">Email Tracking</FormLabel>
|
||||
<FormDescription>
|
||||
Enable open and click tracking for emails sent from this project. When disabled, emails
|
||||
will be sent without tracking pixels.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormItem>
|
||||
<FormLabel>Project Name</FormLabel>
|
||||
<FormControl>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||
<Input placeholder="My Awesome Project" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" disabled={form.formState.isSubmitting}>
|
||||
{form.formState.isSubmitting ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
</div>
|
||||
{/* Email Tracking Toggle - only show if feature is available */}
|
||||
{trackingToggleEnabled && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="trackingEnabled"
|
||||
render={({field}) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border border-neutral-200 p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">Email Tracking</FormLabel>
|
||||
<FormDescription>
|
||||
Enable open and click tracking for emails sent from this project. When disabled,
|
||||
emails will be sent without tracking pixels.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* API Keys */}
|
||||
<div className="space-y-4 pt-4 border-t border-neutral-200">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-sm font-medium text-neutral-900">API Keys</h3>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={promptRegenerateKeys}
|
||||
className="text-xs"
|
||||
>
|
||||
Regenerate Keys
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit" disabled={form.formState.isSubmitting}>
|
||||
{form.formState.isSubmitting ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
</div>
|
||||
<ApiKeyDisplay
|
||||
label="Public API Key"
|
||||
value={activeProject.public}
|
||||
description="Use this key for client-side integrations"
|
||||
/>
|
||||
<ApiKeyDisplay
|
||||
label="Secret API Key"
|
||||
value={activeProject.secret}
|
||||
description="Keep this key secure and never expose it publicly"
|
||||
isSecret
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Success/Error Messages */}
|
||||
<AnimatePresence mode="wait">
|
||||
{successMessage && (
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: -10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
exit={{opacity: 0}}
|
||||
className="p-3 bg-green-50 border border-green-200 rounded-lg text-sm text-green-800"
|
||||
>
|
||||
{successMessage}
|
||||
</motion.div>
|
||||
)}
|
||||
{errorMessage && (
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: -10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
exit={{opacity: 0}}
|
||||
className="p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-800"
|
||||
>
|
||||
{errorMessage}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
{/* Billing Tab */}
|
||||
<TabsContent value="billing">
|
||||
<div className="space-y-6">
|
||||
{/* Unpaid Invoice Banner */}
|
||||
<UnpaidInvoiceBanner projectId={activeProject.id} hasSubscription={!!activeProject.subscription} />
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Billing & Subscription</CardTitle>
|
||||
<CardDescription>Manage your subscription and billing information</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-6">
|
||||
{/* Success/Error Messages */}
|
||||
<AnimatePresence mode="wait">
|
||||
{successMessage && (
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: -10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
exit={{opacity: 0}}
|
||||
className="p-3 bg-green-50 border border-green-200 rounded-lg text-sm text-green-800"
|
||||
>
|
||||
{successMessage}
|
||||
</motion.div>
|
||||
)}
|
||||
{errorMessage && (
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: -10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
exit={{opacity: 0}}
|
||||
className="p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-800"
|
||||
>
|
||||
{errorMessage}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{activeProject.subscription ? (
|
||||
// Has subscription - show billing portal button
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-green-50 border border-green-200 rounded-lg">
|
||||
<div className="flex items-center gap-2 text-green-800 mb-2">
|
||||
<CreditCard className="h-5 w-5" />
|
||||
<span className="font-medium">Active Subscription</span>
|
||||
</div>
|
||||
<p className="text-sm text-green-700">
|
||||
Your subscription is active. Manage your billing details, update payment methods, or cancel
|
||||
your subscription through the billing portal.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-start">
|
||||
<Button onClick={handleManageBilling} disabled={isLoadingBilling}>
|
||||
{isLoadingBilling ? 'Loading...' : 'Manage Billing'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// No subscription - show start subscription button
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-neutral-50 border border-neutral-200 rounded-lg">
|
||||
<div className="flex items-center gap-2 text-neutral-800 mb-2">
|
||||
<CreditCard className="h-5 w-5" />
|
||||
<span className="font-medium">No Active Subscription</span>
|
||||
</div>
|
||||
<p className="text-sm text-neutral-600">
|
||||
Start a subscription to unlock premium features and support the development of Plunk.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-start">
|
||||
<Button onClick={handleStartSubscription} disabled={isLoadingBilling}>
|
||||
{isLoadingBilling ? 'Loading...' : 'Start Subscription'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Success/Error Messages */}
|
||||
<AnimatePresence mode="wait">
|
||||
{successMessage && (
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: -10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
exit={{opacity: 0}}
|
||||
className="p-3 bg-green-50 border border-green-200 rounded-lg text-sm text-green-800"
|
||||
>
|
||||
{successMessage}
|
||||
</motion.div>
|
||||
)}
|
||||
{errorMessage && (
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: -10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
exit={{opacity: 0}}
|
||||
className="p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-800"
|
||||
>
|
||||
{errorMessage}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Billing Limits */}
|
||||
<BillingLimits projectId={activeProject.id} hasSubscription={!!activeProject.subscription} />
|
||||
{/* API Keys - Separate Card */}
|
||||
<Card className="mt-6">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>API Credentials</CardTitle>
|
||||
<CardDescription>Use these keys to integrate with the Plunk API</CardDescription>
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="sm" onClick={promptRegenerateKeys}>
|
||||
Regenerate Keys
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<ApiKeyDisplay
|
||||
label="Public API Key"
|
||||
value={activeProject.public}
|
||||
description="Use this key for client-side integrations"
|
||||
/>
|
||||
<ApiKeyDisplay
|
||||
label="Secret API Key"
|
||||
value={activeProject.secret}
|
||||
description="Keep this key secure and never expose it publicly"
|
||||
isSecret
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Current Month Consumption */}
|
||||
<BillingConsumption projectId={activeProject.id} hasSubscription={!!activeProject.subscription} />
|
||||
{/* Danger Zone - Separate Card */}
|
||||
<Card className="border-red-200 mt-6">
|
||||
<CardHeader className="border-b border-red-100 bg-gradient-to-r from-red-50 to-orange-50">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-white rounded-lg shadow-sm border border-red-200">
|
||||
<AlertTriangle className="h-5 w-5 text-red-600" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-lg text-red-900">Danger Zone</CardTitle>
|
||||
<CardDescription className="text-red-700">
|
||||
Irreversible actions that affect your project data
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-6 space-y-6">
|
||||
{/* Reset Project */}
|
||||
<div className="group">
|
||||
<div className="flex items-start justify-between gap-4 p-5 rounded-lg border border-gray-200 bg-white transition-all">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Database className="h-4 w-4 text-orange-600" />
|
||||
<h4 className="font-semibold text-gray-900">Reset Project Data</h4>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mb-3">
|
||||
Clear all campaigns, contacts, workflows, templates, and events. This gives you a blank
|
||||
project to start fresh.
|
||||
</p>
|
||||
<div className="flex items-start gap-2 text-xs text-gray-500">
|
||||
<span className="font-medium">Preserved:</span>
|
||||
<span>API keys, domains, billing information</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setShowResetDialog(true)}
|
||||
className="shrink-0 border-orange-300 text-orange-700 hover:bg-orange-50 hover:border-orange-400"
|
||||
>
|
||||
Reset Data
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Past Invoices */}
|
||||
<BillingInvoices
|
||||
projectId={activeProject.id}
|
||||
hasSubscription={!!activeProject.subscription}
|
||||
onManageBilling={handleManageBilling}
|
||||
{/* Delete Project */}
|
||||
<div className="group">
|
||||
<div className="flex items-start justify-between gap-4 p-5 rounded-lg border-2 border-red-200 bg-red-50/50 transition-all">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<AlertTriangle className="h-4 w-4 text-red-600" />
|
||||
<h4 className="font-semibold text-red-900">Delete Project Permanently</h4>
|
||||
</div>
|
||||
<p className="text-sm text-red-800 mb-3">
|
||||
Permanently delete this project and all associated data. This action{' '}
|
||||
<strong>cannot be undone</strong>.
|
||||
{activeProject?.subscription && ' Your subscription will be canceled.'}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="px-2 py-1 bg-red-100 text-red-700 rounded font-medium">
|
||||
Permanent Deletion
|
||||
</span>
|
||||
<span className="text-red-600">All data will be lost</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
onClick={() => setShowDeleteDialog(true)}
|
||||
className="shrink-0 bg-red-600 hover:bg-red-700 text-white shadow-sm hover:shadow-md transition-all"
|
||||
>
|
||||
Delete Project
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
{/* Billing Tab */}
|
||||
<TabsContent value="billing">
|
||||
<div className="space-y-6">
|
||||
{/* Unpaid Invoice Banner */}
|
||||
<UnpaidInvoiceBanner projectId={activeProject.id} hasSubscription={!!activeProject.subscription} />
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Billing & Subscription</CardTitle>
|
||||
<CardDescription>Manage your subscription and billing information</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-6">
|
||||
{/* Success/Error Messages */}
|
||||
<AnimatePresence mode="wait">
|
||||
{successMessage && (
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: -10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
exit={{opacity: 0}}
|
||||
className="p-3 bg-green-50 border border-green-200 rounded-lg text-sm text-green-800"
|
||||
>
|
||||
{successMessage}
|
||||
</motion.div>
|
||||
)}
|
||||
{errorMessage && (
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: -10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
exit={{opacity: 0}}
|
||||
className="p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-800"
|
||||
>
|
||||
{errorMessage}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{activeProject.subscription ? (
|
||||
// Has subscription - show billing portal button
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-green-50 border border-green-200 rounded-lg">
|
||||
<div className="flex items-center gap-2 text-green-800 mb-2">
|
||||
<CreditCard className="h-5 w-5" />
|
||||
<span className="font-medium">Active Subscription</span>
|
||||
</div>
|
||||
<p className="text-sm text-green-700">
|
||||
Your subscription is active. Manage your billing details, update payment methods, or
|
||||
cancel your subscription through the billing portal.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-start">
|
||||
<Button onClick={handleManageBilling} disabled={isLoadingBilling}>
|
||||
{isLoadingBilling ? 'Loading...' : 'Manage Billing'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// No subscription - show start subscription button
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-neutral-50 border border-neutral-200 rounded-lg">
|
||||
<div className="flex items-center gap-2 text-neutral-800 mb-2">
|
||||
<CreditCard className="h-5 w-5" />
|
||||
<span className="font-medium">No Active Subscription</span>
|
||||
</div>
|
||||
<p className="text-sm text-neutral-600">
|
||||
Start a subscription to unlock premium features and support the development of Plunk.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-start">
|
||||
<Button onClick={handleStartSubscription} disabled={isLoadingBilling}>
|
||||
{isLoadingBilling ? 'Loading...' : 'Start Subscription'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Billing Limits */}
|
||||
<BillingLimits projectId={activeProject.id} hasSubscription={!!activeProject.subscription} />
|
||||
|
||||
{/* Current Month Consumption */}
|
||||
<BillingConsumption projectId={activeProject.id} hasSubscription={!!activeProject.subscription} />
|
||||
|
||||
{/* Past Invoices */}
|
||||
<BillingInvoices
|
||||
projectId={activeProject.id}
|
||||
hasSubscription={!!activeProject.subscription}
|
||||
onManageBilling={handleManageBilling}
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* Domains Tab */}
|
||||
<TabsContent value="domains">
|
||||
<DomainsSettings projectId={activeProject.id} />
|
||||
</TabsContent>
|
||||
|
||||
{/* SMTP Tab */}
|
||||
<TabsContent value="smtp">
|
||||
<SmtpSettings smtpConfig={smtpConfig} />
|
||||
</TabsContent>
|
||||
|
||||
{/* Data Management Tab */}
|
||||
<TabsContent value="data">
|
||||
<DataManagementSettings />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
{/* Regenerate Keys Confirmation Dialog */}
|
||||
<Dialog open={showRegenerateDialog} onOpenChange={setShowRegenerateDialog}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<AlertTriangle className="h-5 w-5 text-orange-500" />
|
||||
Regenerate API Keys
|
||||
</DialogTitle>
|
||||
<DialogDescription className="space-y-2">
|
||||
<p>Are you sure you want to regenerate your API keys?</p>
|
||||
<Alert className="bg-orange-50 border-orange-200 text-orange-900 text-xs">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<div className="ml-2">
|
||||
<strong>Warning:</strong> This action will immediately invalidate your current API keys. Any
|
||||
applications using the old keys will stop working until you update them with the new keys.
|
||||
</div>
|
||||
</Alert>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowRegenerateDialog(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="default" onClick={handleRegenerateKeys} className="bg-orange-600 hover:bg-orange-700">
|
||||
Regenerate Keys
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Reset Project Confirmation Dialog */}
|
||||
<Dialog open={showResetDialog} onOpenChange={setShowResetDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader className="space-y-3">
|
||||
<div className="mx-auto w-12 h-12 bg-orange-100 rounded-full flex items-center justify-center">
|
||||
<Database className="h-6 w-6 text-orange-600" />
|
||||
</div>
|
||||
<DialogTitle className="text-center text-xl">Reset Project Data?</DialogTitle>
|
||||
<DialogDescription className="text-center text-base">
|
||||
All your campaigns, contacts, workflows, and templates will be permanently deleted. Your API keys and
|
||||
settings will remain intact.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-4">
|
||||
<label className="text-sm font-medium text-gray-700 block mb-2 text-center">
|
||||
Type{' '}
|
||||
<span className="font-mono font-semibold text-orange-600 bg-orange-50 px-2 py-0.5 rounded">RESET</span>{' '}
|
||||
to confirm
|
||||
</label>
|
||||
<Input
|
||||
value={resetConfirmText}
|
||||
onChange={e => setResetConfirmText(e.target.value)}
|
||||
placeholder="Type RESET here"
|
||||
className="text-center"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* Domains Tab */}
|
||||
<TabsContent value="domains">
|
||||
<DomainsSettings projectId={activeProject.id} />
|
||||
</TabsContent>
|
||||
<DialogFooter className="flex-col-reverse gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setShowResetDialog(false);
|
||||
setResetConfirmText('');
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleResetProject}
|
||||
disabled={resetConfirmText !== 'RESET'}
|
||||
className="w-full bg-orange-600 hover:bg-orange-700 disabled:opacity-50"
|
||||
>
|
||||
Reset Data
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* SMTP Tab */}
|
||||
<TabsContent value="smtp">
|
||||
<SmtpSettings smtpConfig={smtpConfig} />
|
||||
</TabsContent>
|
||||
{/* Delete Project Confirmation Dialog */}
|
||||
<Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader className="space-y-3">
|
||||
<div className="mx-auto w-12 h-12 bg-red-100 rounded-full flex items-center justify-center">
|
||||
<AlertTriangle className="h-6 w-6 text-red-600" />
|
||||
</div>
|
||||
<DialogTitle className="text-center text-xl">Delete Project Permanently?</DialogTitle>
|
||||
<DialogDescription className="text-center text-base space-y-2">
|
||||
<p>
|
||||
This will <strong className="text-red-600">permanently delete</strong> your entire project and all
|
||||
data. This action cannot be undone.
|
||||
</p>
|
||||
{activeProject?.subscription && (
|
||||
<p className="text-sm text-red-700 bg-red-50 rounded p-2 border border-red-200">
|
||||
Your active subscription will be canceled
|
||||
</p>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{/* Data Management Tab */}
|
||||
<TabsContent value="data">
|
||||
<DataManagementSettings />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<label className="text-sm font-medium text-gray-700 block mb-2 text-center">
|
||||
Type <span className="font-mono font-semibold text-red-600 bg-red-50 px-2 py-0.5 rounded">DELETE</span>{' '}
|
||||
to confirm
|
||||
</label>
|
||||
<Input
|
||||
value={deleteConfirmText}
|
||||
onChange={e => setDeleteConfirmText(e.target.value)}
|
||||
placeholder="Type DELETE here"
|
||||
className="text-center"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Regenerate Keys Confirmation Dialog */}
|
||||
<Dialog open={showRegenerateDialog} onOpenChange={setShowRegenerateDialog}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<AlertTriangle className="h-5 w-5 text-orange-500" />
|
||||
Regenerate API Keys
|
||||
</DialogTitle>
|
||||
<DialogDescription className="space-y-2">
|
||||
<p>Are you sure you want to regenerate your API keys?</p>
|
||||
<Alert className="bg-orange-50 border-orange-200 text-orange-900 text-xs">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<div className="ml-2">
|
||||
<strong>Warning:</strong> This action will immediately invalidate your current API keys. Any
|
||||
applications using the old keys will stop working until you update them with the new keys.
|
||||
</div>
|
||||
</Alert>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowRegenerateDialog(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="default" onClick={handleRegenerateKeys} className="bg-orange-600 hover:bg-orange-700">
|
||||
Regenerate Keys
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DashboardLayout>
|
||||
<DialogFooter className="flex-col-reverse gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setShowDeleteDialog(false);
|
||||
setDeleteConfirmText('');
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleDeleteProject}
|
||||
disabled={deleteConfirmText !== 'DELETE'}
|
||||
className="w-full bg-red-600 hover:bg-red-700 disabled:opacity-50"
|
||||
>
|
||||
Delete Forever
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DashboardLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user