feat: add updateActiveProject function for in-place project updates
This commit is contained in:
@@ -107,7 +107,8 @@ export function SecuritySettings({metrics, isLoading}: SecuritySettingsProps) {
|
|||||||
<CardTitle>Bounce Rate</CardTitle>
|
<CardTitle>Bounce Rate</CardTitle>
|
||||||
<CardDescription>Hard bounces indicate invalid or non-existent email addresses</CardDescription>
|
<CardDescription>Hard bounces indicate invalid or non-existent email addresses</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="p-0">
|
||||||
|
<div className="divide-y divide-neutral-100">
|
||||||
<HealthMetric
|
<HealthMetric
|
||||||
label="Last 7 Days"
|
label="Last 7 Days"
|
||||||
level={levels.bounce7Day}
|
level={levels.bounce7Day}
|
||||||
@@ -118,6 +119,7 @@ export function SecuritySettings({metrics, isLoading}: SecuritySettingsProps) {
|
|||||||
level={levels.bounceAllTime}
|
level={levels.bounceAllTime}
|
||||||
detail={`${status.allTime.bounceRate.toFixed(2)}% bounce rate (${status.allTime.bounces.toLocaleString()} of ${status.allTime.total.toLocaleString()} emails)`}
|
detail={`${status.allTime.bounceRate.toFixed(2)}% bounce rate (${status.allTime.bounces.toLocaleString()} of ${status.allTime.total.toLocaleString()} emails)`}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -127,7 +129,8 @@ export function SecuritySettings({metrics, isLoading}: SecuritySettingsProps) {
|
|||||||
<CardTitle>Complaint Rate</CardTitle>
|
<CardTitle>Complaint Rate</CardTitle>
|
||||||
<CardDescription>Complaints occur when recipients mark emails as spam</CardDescription>
|
<CardDescription>Complaints occur when recipients mark emails as spam</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="p-0">
|
||||||
|
<div className="divide-y divide-neutral-100">
|
||||||
<HealthMetric
|
<HealthMetric
|
||||||
label="Last 7 Days"
|
label="Last 7 Days"
|
||||||
level={levels.complaint7Day}
|
level={levels.complaint7Day}
|
||||||
@@ -138,6 +141,7 @@ export function SecuritySettings({metrics, isLoading}: SecuritySettingsProps) {
|
|||||||
level={levels.complaintAllTime}
|
level={levels.complaintAllTime}
|
||||||
detail={`${status.allTime.complaintRate.toFixed(3)}% complaint rate (${status.allTime.complaints.toLocaleString()} of ${status.allTime.total.toLocaleString()} emails)`}
|
detail={`${status.allTime.complaintRate.toFixed(3)}% complaint rate (${status.allTime.complaints.toLocaleString()} of ${status.allTime.total.toLocaleString()} emails)`}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -155,10 +159,10 @@ function HealthMetric({label, level, detail}: HealthMetricProps) {
|
|||||||
const Icon = config.icon;
|
const Icon = config.icon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between border border-neutral-200 rounded-lg p-4">
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium text-neutral-900">{label}</h3>
|
<h3 className="font-medium text-neutral-900">{label}</h3>
|
||||||
<p className="text-sm text-neutral-600 mt-1">{detail}</p>
|
<p className="text-sm text-neutral-600 mt-0.5">{detail}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={`flex items-center gap-2 ${config.color}`}>
|
<div className={`flex items-center gap-2 ${config.color}`}>
|
||||||
<Icon className="h-4 w-4" />
|
<Icon className="h-4 w-4" />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {useProjects} from '../hooks/useProject';
|
|||||||
interface ActiveProjectContextValue {
|
interface ActiveProjectContextValue {
|
||||||
activeProject: Project | null;
|
activeProject: Project | null;
|
||||||
setActiveProject: (project: Project) => void;
|
setActiveProject: (project: Project) => void;
|
||||||
|
updateActiveProject: (project: Project) => void;
|
||||||
availableProjects: Project[];
|
availableProjects: Project[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
@@ -55,9 +56,16 @@ export function ActiveProjectProvider({children}: {children: ReactNode}) {
|
|||||||
void mutate(() => true, undefined, {revalidate: true});
|
void mutate(() => true, undefined, {revalidate: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Updates the active project's data in-place without invalidating the SWR cache.
|
||||||
|
// Use this when saving settings for the current project, not when switching projects.
|
||||||
|
const updateActiveProject = (project: Project) => {
|
||||||
|
setActiveProjectState(project);
|
||||||
|
};
|
||||||
|
|
||||||
const value: ActiveProjectContextValue = {
|
const value: ActiveProjectContextValue = {
|
||||||
activeProject,
|
activeProject,
|
||||||
setActiveProject,
|
setActiveProject,
|
||||||
|
updateActiveProject,
|
||||||
availableProjects: projects ?? [],
|
availableProjects: projects ?? [],
|
||||||
isLoading,
|
isLoading,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const buildTabs = (options: {billingEnabled: boolean; smtpEnabled: boolean}): Ta
|
|||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const {activeProject, setActiveProject} = useActiveProject();
|
const {activeProject, setActiveProject, updateActiveProject} = useActiveProject();
|
||||||
const {mutate: projectsMutate} = useProjects();
|
const {mutate: projectsMutate} = useProjects();
|
||||||
const {data: config} = useConfig();
|
const {data: config} = useConfig();
|
||||||
const {data: user} = useUser();
|
const {data: user} = useUser();
|
||||||
@@ -201,8 +201,8 @@ export default function Settings() {
|
|||||||
values,
|
values,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update the active project in context
|
// Update the active project in context without invalidating the full SWR cache
|
||||||
setActiveProject(updatedProject);
|
updateActiveProject(updatedProject);
|
||||||
|
|
||||||
// Refresh projects list
|
// Refresh projects list
|
||||||
await projectsMutate();
|
await projectsMutate();
|
||||||
@@ -395,6 +395,7 @@ export default function Settings() {
|
|||||||
|
|
||||||
{/* General Tab */}
|
{/* General Tab */}
|
||||||
<TabsContent value="general">
|
<TabsContent value="general">
|
||||||
|
<div className="space-y-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Project Settings</CardTitle>
|
<CardTitle>Project Settings</CardTitle>
|
||||||
@@ -491,12 +492,6 @@ export default function Settings() {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button type="submit" disabled={form.formState.isSubmitting}>
|
|
||||||
{form.formState.isSubmitting ? 'Saving...' : 'Save Changes'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Success/Error Messages */}
|
{/* Success/Error Messages */}
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
{successMessage && (
|
{successMessage && (
|
||||||
@@ -520,13 +515,19 @@ export default function Settings() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button type="submit" disabled={form.formState.isSubmitting}>
|
||||||
|
{form.formState.isSubmitting ? 'Saving...' : 'Save Changes'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* API Keys - Separate Card */}
|
{/* API Keys - Separate Card */}
|
||||||
<Card className="mt-6">
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -554,7 +555,7 @@ export default function Settings() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Danger Zone - Separate Card */}
|
{/* Danger Zone - Separate Card */}
|
||||||
<Card className="border-red-200 mt-6">
|
<Card className="border-red-200">
|
||||||
<CardHeader className="border-b border-red-100 bg-red-50">
|
<CardHeader className="border-b border-red-100 bg-red-50">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-2 bg-white rounded-lg shadow-sm border border-red-200">
|
<div className="p-2 bg-white rounded-lg shadow-sm border border-red-200">
|
||||||
@@ -568,12 +569,12 @@ export default function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-6 space-y-6">
|
<CardContent className="p-0">
|
||||||
|
<div className="divide-y divide-neutral-100">
|
||||||
{/* Reset Project */}
|
{/* Reset Project */}
|
||||||
<div className="group">
|
<div className="flex items-start justify-between gap-4 px-6 py-5">
|
||||||
<div className="flex items-start justify-between gap-4 p-5 rounded-lg border border-neutral-200 bg-white transition-all">
|
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-1.5">
|
||||||
<Database className="h-4 w-4 text-amber-700" />
|
<Database className="h-4 w-4 text-amber-700" />
|
||||||
<h4 className="font-semibold text-neutral-900">Reset Project Data</h4>
|
<h4 className="font-semibold text-neutral-900">Reset Project Data</h4>
|
||||||
</div>
|
</div>
|
||||||
@@ -590,13 +591,11 @@ export default function Settings() {
|
|||||||
Reset Data
|
Reset Data
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Delete Project */}
|
{/* Delete Project */}
|
||||||
<div className="group">
|
<div className="flex items-start justify-between gap-4 px-6 py-5 bg-red-50/40">
|
||||||
<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-1">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-1.5">
|
||||||
<AlertTriangle className="h-4 w-4 text-red-600" />
|
<AlertTriangle className="h-4 w-4 text-red-600" />
|
||||||
<h4 className="font-semibold text-red-900">Delete Project Permanently</h4>
|
<h4 className="font-semibold text-red-900">Delete Project Permanently</h4>
|
||||||
</div>
|
</div>
|
||||||
@@ -624,6 +623,7 @@ export default function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Billing Tab */}
|
{/* Billing Tab */}
|
||||||
|
|||||||
Reference in New Issue
Block a user