Skip to content

Commit cdd5dc2

Browse files
committed
chore: working state commit
1 parent 70d1b40 commit cdd5dc2

5 files changed

Lines changed: 421 additions & 58 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Button, type ButtonProps, Icon, IconButton } from "@chakra-ui/react"
2+
import type React from "react"
3+
4+
interface NavbarItemProps extends Omit<ButtonProps, "variant" | "size"> {
5+
icon?: React.ReactElement
6+
label: string
7+
variant?: "button" | "icon"
8+
}
9+
10+
export const NavbarItem = ({
11+
icon,
12+
label,
13+
variant = "button",
14+
children,
15+
...props
16+
}: NavbarItemProps) => {
17+
const commonStyles = {
18+
variant: "ghost" as const,
19+
borderRadius: "md" as const,
20+
px: "4" as const,
21+
py: "2" as const,
22+
mx: "2" as const,
23+
fontSize: "sm" as const,
24+
fontWeight: "medium" as const,
25+
_hover: {
26+
bg: "editorBattleshipGrey.50",
27+
},
28+
}
29+
30+
// If only icon is provided (no children or label to display), use icon variant
31+
if (variant === "icon" || (!children && icon && !label)) {
32+
return (
33+
<IconButton
34+
aria-label={label}
35+
icon={icon ? <Icon as={icon.type} boxSize={5} /> : undefined}
36+
color="editorBattleshipGrey.500"
37+
{...commonStyles}
38+
{...props}
39+
/>
40+
)
41+
}
42+
43+
return (
44+
<Button leftIcon={icon} aria-label={label} {...commonStyles} {...props}>
45+
{children || label}
46+
</Button>
47+
)
48+
}
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import {
2+
Box,
3+
Button,
4+
Flex,
5+
FormControl,
6+
FormLabel,
7+
Icon,
8+
IconButton,
9+
Input,
10+
Text,
11+
} from "@chakra-ui/react"
12+
import { PiGlobe } from "@react-icons/all-files/pi/PiGlobe"
13+
import { PiLock } from "@react-icons/all-files/pi/PiLock"
14+
import { PiTrash } from "@react-icons/all-files/pi/PiTrash"
15+
import { PiX } from "@react-icons/all-files/pi/PiX"
16+
import { useEffect, useState } from "react"
17+
import Select from "react-select"
18+
import { useTemplateStorage } from "../../context/TemplateStorageContext"
19+
import { useEditorStore } from "../../store"
20+
21+
interface SettingsModalProps {
22+
onClose: () => void
23+
}
24+
25+
export function SettingsModal({ onClose }: SettingsModalProps) {
26+
const provider = useTemplateStorage()
27+
const templateId = useEditorStore((s) => s.templateId)
28+
const templateName = useEditorStore((s) => s.templateName)
29+
const setTemplateName = useEditorStore((s) => s.setTemplateName)
30+
const transformations = useEditorStore((s) => s.transformations)
31+
const setSyncStatus = useEditorStore((s) => s.setSyncStatus)
32+
const resetToNewTemplate = useEditorStore((s) => s.resetToNewTemplate)
33+
34+
const [localName, setLocalName] = useState(templateName)
35+
const [localVisibility, setLocalVisibility] = useState<"everyone" | "onlyMe">(
36+
"onlyMe",
37+
)
38+
const [isDeleting, setIsDeleting] = useState(false)
39+
const [isSaving, setIsSaving] = useState(false)
40+
41+
// Fetch current template visibility
42+
useEffect(() => {
43+
let cancelled = false
44+
45+
if (!provider || !templateId) {
46+
return
47+
}
48+
49+
provider
50+
.getTemplate(templateId)
51+
.then((record) => {
52+
if (cancelled || !record) return
53+
setLocalVisibility(record.isPrivate ? "onlyMe" : "everyone")
54+
})
55+
.catch(() => {
56+
// Ignore errors
57+
})
58+
59+
return () => {
60+
cancelled = true
61+
}
62+
}, [provider, templateId])
63+
64+
const handleSave = async () => {
65+
if (!provider || !localName.trim()) return
66+
67+
setIsSaving(true)
68+
setSyncStatus("saving")
69+
70+
try {
71+
await provider.saveTemplate({
72+
id: templateId ?? undefined,
73+
name: localName.trim(),
74+
transformations: transformations.map(({ id: _id, ...rest }) => rest),
75+
isPrivate: localVisibility === "onlyMe",
76+
})
77+
78+
setTemplateName(localName.trim())
79+
setSyncStatus("saved")
80+
onClose()
81+
} catch (err) {
82+
setSyncStatus(
83+
"error",
84+
err instanceof Error ? err.message : "Failed to save",
85+
)
86+
} finally {
87+
setIsSaving(false)
88+
}
89+
}
90+
91+
const handleDelete = async () => {
92+
if (!provider || !templateId) return
93+
94+
setIsDeleting(true)
95+
96+
try {
97+
await provider.deleteTemplate(templateId)
98+
resetToNewTemplate()
99+
onClose()
100+
} catch (err) {
101+
console.error("Failed to delete template:", err)
102+
} finally {
103+
setIsDeleting(false)
104+
}
105+
}
106+
107+
// Close on Escape key
108+
useEffect(() => {
109+
const handleKeyDown = (event: KeyboardEvent) => {
110+
if (event.key === "Escape") {
111+
event.stopPropagation()
112+
onClose()
113+
}
114+
}
115+
window.addEventListener("keydown", handleKeyDown)
116+
return () => {
117+
window.removeEventListener("keydown", handleKeyDown)
118+
}
119+
}, [onClose])
120+
121+
return (
122+
<Box
123+
position="fixed"
124+
inset={0}
125+
bg="blackAlpha.400"
126+
display="flex"
127+
alignItems="center"
128+
justifyContent="center"
129+
zIndex={1400}
130+
onClick={onClose}
131+
>
132+
<Box
133+
w="40vw"
134+
maxW="40vw"
135+
h="50vh"
136+
maxH="50vh"
137+
bg="white"
138+
borderRadius="xl"
139+
overflow="hidden"
140+
boxShadow="xl"
141+
display="flex"
142+
flexDirection="column"
143+
onClick={(e) => e.stopPropagation()}
144+
>
145+
{/* Header */}
146+
<Flex
147+
px="6"
148+
py="4"
149+
alignItems="center"
150+
justifyContent="space-between"
151+
borderBottomWidth="1px"
152+
borderColor="editorGray.300"
153+
>
154+
<Text fontSize="lg" fontWeight="semibold" color="editorGray.900">
155+
Template Settings
156+
</Text>
157+
<IconButton
158+
variant="ghost"
159+
size="sm"
160+
onClick={onClose}
161+
icon={<Icon as={PiX} boxSize={5} />}
162+
aria-label="Close settings"
163+
/>
164+
</Flex>
165+
166+
{/* Content */}
167+
<Box px="6" py="6" flex="1" overflowY="auto">
168+
<Flex direction="column" gap="6">
169+
{/* Template Name */}
170+
<FormControl>
171+
<FormLabel
172+
fontSize="sm"
173+
fontWeight="medium"
174+
color="editorGray.700"
175+
mb="2"
176+
>
177+
Template Name
178+
</FormLabel>
179+
<Input
180+
value={localName}
181+
onChange={(e) => setLocalName(e.target.value)}
182+
placeholder="Enter template name"
183+
fontSize="sm"
184+
/>
185+
</FormControl>
186+
187+
{/* Visibility */}
188+
<FormControl>
189+
<FormLabel
190+
fontSize="sm"
191+
fontWeight="medium"
192+
color="editorGray.700"
193+
mb="2"
194+
>
195+
Visibility
196+
</FormLabel>
197+
<Select
198+
value={{
199+
value: localVisibility,
200+
label:
201+
localVisibility === "everyone"
202+
? "Shared with everyone"
203+
: "Only to me",
204+
}}
205+
onChange={(option) => {
206+
if (option) {
207+
setLocalVisibility(option.value as "everyone" | "onlyMe")
208+
}
209+
}}
210+
options={[
211+
{ value: "onlyMe", label: "Only to me" },
212+
{ value: "everyone", label: "Shared with everyone" },
213+
]}
214+
formatOptionLabel={(data) => (
215+
<Box display="flex" alignItems="center" gap="2">
216+
<Icon
217+
as={data.value === "everyone" ? PiGlobe : PiLock}
218+
boxSize={5}
219+
color="editorBattleshipGrey.500"
220+
/>
221+
<span>{data.label}</span>
222+
</Box>
223+
)}
224+
styles={{
225+
control: (base) => ({
226+
...base,
227+
fontSize: "12px",
228+
minHeight: "32px",
229+
borderColor: "#E2E8F0",
230+
}),
231+
menu: (base) => ({
232+
...base,
233+
zIndex: 10,
234+
}),
235+
option: (base) => ({
236+
...base,
237+
fontSize: "12px",
238+
}),
239+
}}
240+
isSearchable={false}
241+
/>
242+
</FormControl>
243+
</Flex>
244+
</Box>
245+
246+
{/* Footer */}
247+
<Flex
248+
px="6"
249+
py="4"
250+
alignItems="center"
251+
justifyContent="space-between"
252+
borderTopWidth="1px"
253+
borderColor="editorGray.300"
254+
>
255+
<Button
256+
variant="ghost"
257+
colorScheme="red"
258+
size="md"
259+
leftIcon={<Icon as={PiTrash} boxSize={5} />}
260+
onClick={handleDelete}
261+
isLoading={isDeleting}
262+
isDisabled={!templateId || isSaving}
263+
>
264+
Delete
265+
</Button>
266+
267+
<Flex gap="3">
268+
<Button
269+
variant="outline"
270+
size="md"
271+
onClick={onClose}
272+
isDisabled={isDeleting || isSaving}
273+
>
274+
Cancel
275+
</Button>
276+
<Button
277+
colorScheme="blue"
278+
size="md"
279+
onClick={handleSave}
280+
isLoading={isSaving}
281+
isDisabled={!localName.trim() || isDeleting}
282+
>
283+
Save
284+
</Button>
285+
</Flex>
286+
</Flex>
287+
</Box>
288+
</Box>
289+
)
290+
}

packages/imagekit-editor-dev/src/components/header/TemplateStatus.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,12 @@ export function TemplateStatus() {
7070
// "Saving…" is a transient text-only state — no icon yet
7171
if (notificationVisible && syncStatus === "saving") {
7272
return (
73-
<Text fontSize="sm" color="editorBattleshipGrey.600" userSelect="none">
73+
<Text
74+
fontSize="sm"
75+
fontWeight="medium"
76+
color="editorBattleshipGrey.600"
77+
userSelect="none"
78+
>
7479
Saving…
7580
</Text>
7681
)
@@ -165,7 +170,12 @@ export function TemplateStatus() {
165170
</Popover>
166171

167172
{notifText && (
168-
<Text fontSize="sm" color="editorBattleshipGrey.700" userSelect="none">
173+
<Text
174+
fontSize="sm"
175+
fontWeight="medium"
176+
color="editorBattleshipGrey.700"
177+
userSelect="none"
178+
>
169179
{notifText}
170180
</Text>
171181
)}

packages/imagekit-editor-dev/src/components/header/TemplatesDropdown.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ export function TemplatesDropdown({
8080
}
8181
}, [isOpen, fetchTemplates])
8282

83+
useEffect(() => {
84+
// Refetch templates when sync status changes to "saved" to reflect updates
85+
if (syncStatus === "saved") {
86+
fetchTemplates()
87+
}
88+
}, [syncStatus, fetchTemplates])
89+
8390
if (!provider) return null
8491

8592
const activeTemplate = templateId
@@ -213,9 +220,11 @@ export function TemplatesDropdown({
213220
alignItems="center"
214221
gap="2"
215222
cursor="pointer"
216-
height="full"
217-
px="4"
218-
_hover={{ bg: "editorGray.100" }}
223+
borderRadius="md"
224+
paddingX="4"
225+
paddingY="2"
226+
marginX="2"
227+
_hover={{ bg: "editorGray.200" }}
219228
transition="background-color 0.15s"
220229
aria-label="Open templates dropdown"
221230
>

0 commit comments

Comments
 (0)