Skip to content

Commit 5354eb9

Browse files
committed
Timed auto-save badge
1 parent d8f2e35 commit 5354eb9

1 file changed

Lines changed: 109 additions & 57 deletions

File tree

apps/web/components/admin/mails/editor-layout.tsx

Lines changed: 109 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Button2 } from "@courselit/components-library";
22
import { CheckCircled, Sync } from "@courselit/icons";
33
import { Check, Copy } from "lucide-react";
4-
import { useState } from "react";
4+
import { useState, useEffect, useRef } from "react";
55

66
export const EmailEditorLayout = ({
77
children,
@@ -11,68 +11,120 @@ export const EmailEditorLayout = ({
1111
children: React.ReactNode;
1212
isSaving?: boolean;
1313
type?: "sequence" | "product";
14-
}) => (
15-
<div className="flex flex-col h-screen bg-muted/10">
16-
<div className="flex w-full h-full gap-4 p-4 bg-muted/10">
17-
<div className="max-w-[220px] flex flex-col">
18-
<div className="flex items-center mb-4">
19-
<h2 className="text-base font-semibold text-gray-800">
20-
Variables
21-
</h2>
22-
</div>
23-
<div className="flex flex-col gap-5 flex-1">
24-
<p className="text-xs text-muted-foreground mb-1">
25-
You can use the following variables in your content.
26-
</p>
27-
<p className="text-xs text-muted-foreground mb-2">
28-
These will be replaced with the actual data while
29-
sending emails.
30-
</p>
31-
<div className="flex flex-col gap-3">
32-
{type === "product" && (
33-
<>
34-
<VariableDisplay
35-
variable="{{ product.title }}"
36-
description="The title of the product"
37-
/>
38-
<VariableDisplay
39-
variable="{{ product.url }}"
40-
description="The URL of the product"
41-
/>
42-
</>
14+
}) => {
15+
const [showSavedToast, setShowSavedToast] = useState(false);
16+
const [prevIsSaving, setPrevIsSaving] = useState(isSaving);
17+
const timerRef = useRef<NodeJS.Timeout | null>(null);
18+
19+
useEffect(() => {
20+
// Hide toast when starting a new save operation
21+
if (!prevIsSaving && isSaving) {
22+
setShowSavedToast(false);
23+
// Clear any existing timer
24+
if (timerRef.current) {
25+
clearTimeout(timerRef.current);
26+
timerRef.current = null;
27+
}
28+
}
29+
30+
// Show toast when saving changes from true to false (save completed)
31+
if (prevIsSaving && !isSaving) {
32+
setShowSavedToast(true);
33+
// Clear any existing timer before setting a new one
34+
if (timerRef.current) {
35+
clearTimeout(timerRef.current);
36+
}
37+
timerRef.current = setTimeout(() => {
38+
setShowSavedToast(false);
39+
timerRef.current = null;
40+
}, 5000);
41+
}
42+
setPrevIsSaving(isSaving);
43+
}, [isSaving, prevIsSaving]);
44+
45+
// Cleanup timer on unmount
46+
useEffect(() => {
47+
return () => {
48+
if (timerRef.current) {
49+
clearTimeout(timerRef.current);
50+
}
51+
};
52+
}, []);
53+
54+
return (
55+
<div className="flex flex-col h-screen bg-muted/10">
56+
<div className="flex w-full h-full gap-4 p-4 bg-muted/10">
57+
<div className="max-w-[220px] flex flex-col">
58+
<div className="flex items-center mb-4">
59+
<h2 className="text-base font-semibold text-gray-800">
60+
Variables
61+
</h2>
62+
</div>
63+
<div className="flex flex-col gap-5 flex-1">
64+
<p className="text-xs text-muted-foreground mb-1">
65+
You can use the following variables in your content.
66+
</p>
67+
<p className="text-xs text-muted-foreground mb-2">
68+
These will be replaced with the actual data while
69+
sending emails.
70+
</p>
71+
<div className="flex flex-col gap-3">
72+
{type === "product" && (
73+
<>
74+
<VariableDisplay
75+
variable="{{ product.title }}"
76+
description="The title of the product"
77+
/>
78+
<VariableDisplay
79+
variable="{{ product.url }}"
80+
description="The URL of the product"
81+
/>
82+
</>
83+
)}
84+
<VariableDisplay
85+
variable="{{ subscriber.email }}"
86+
description="The email of the subscriber"
87+
/>
88+
<VariableDisplay
89+
variable="{{ subscriber.name }}"
90+
description="The name of the subscriber"
91+
/>
92+
<VariableDisplay
93+
variable="{{ address }}"
94+
description="Your mailing address"
95+
/>
96+
<VariableDisplay
97+
variable="{{ unsubscribe_link }}"
98+
description="A link to unsubscribe from the marketing emails"
99+
/>
100+
</div>
101+
</div>
102+
<div className="flex items-center mt-4 h-6">
103+
{isSaving && (
104+
<div className="flex items-center gap-2">
105+
<Sync className="h-4 w-4 animate-spin text-muted-foreground" />
106+
<span className="text-sm text-muted-foreground">
107+
Saving...
108+
</span>
109+
</div>
110+
)}
111+
{showSavedToast && (
112+
<div className="flex items-center gap-2 bg-background border px-3 py-1 rounded-md shadow-sm animate-in fade-in-0 duration-300">
113+
<CheckCircled className="h-4 w-4 text-primary" />
114+
<span className="text-sm font-medium text-foreground">
115+
Changes are saved
116+
</span>
117+
</div>
43118
)}
44-
<VariableDisplay
45-
variable="{{ subscriber.email }}"
46-
description="The email of the subscriber"
47-
/>
48-
<VariableDisplay
49-
variable="{{ subscriber.name }}"
50-
description="The name of the subscriber"
51-
/>
52-
<VariableDisplay
53-
variable="{{ address }}"
54-
description="Your mailing address"
55-
/>
56-
<VariableDisplay
57-
variable="{{ unsubscribe_link }}"
58-
description="A link to unsubscribe from the marketing emails"
59-
/>
60119
</div>
61120
</div>
62-
<div className="flex items-center mt-4">
63-
{isSaving ? (
64-
<Sync className="h-4 w-4 animate-spin text-muted-foreground" />
65-
) : (
66-
<CheckCircled className="h-4 w-4 text-green-500" />
67-
)}
121+
<div className="w-full rounded-xl overflow-hidden border bg-background/98 backdrop-blur supports-[backdrop-filter]:bg-background/80 shadow-sm">
122+
{children}
68123
</div>
69124
</div>
70-
<div className="w-full rounded-xl overflow-hidden border bg-background/98 backdrop-blur supports-[backdrop-filter]:bg-background/80 shadow-sm">
71-
{children}
72-
</div>
73125
</div>
74-
</div>
75-
);
126+
);
127+
};
76128

77129
export const VariableDisplay = ({
78130
variable,

0 commit comments

Comments
 (0)