11import {
22 Box ,
3+ Button ,
34 Flex ,
45 Icon ,
56 Popover ,
@@ -16,6 +17,7 @@ import { MdSyncProblem } from "@react-icons/all-files/md/MdSyncProblem"
1617import type React from "react"
1718import { useEffect , useRef , useState } from "react"
1819import { useTemplateStorage } from "../../context/TemplateStorageContext"
20+ import { useSaveTemplate } from "../../hooks/useSaveTemplate"
1921import { useEditorStore } from "../../store"
2022
2123const NOTIFICATION_DURATION_MS = 3000
@@ -35,12 +37,16 @@ export function TemplateStatus() {
3537 const syncStatus = useEditorStore ( ( s ) => s . syncStatus )
3638 const storageError = useEditorStore ( ( s ) => s . storageError )
3739 const isPristine = useEditorStore ( ( s ) => s . isPristine )
40+ const templateStorageWriteBlocked = useEditorStore (
41+ ( s ) => s . templateStorageWriteBlocked ,
42+ )
3843 const hasPendingLocalWork = useEditorStore (
3944 ( s ) =>
4045 s . localChangeVersion !== s . lastSyncedVersion ||
4146 s . transformationConfigFormDirty ,
4247 )
4348 const provider = useTemplateStorage ( )
49+ const { save } = useSaveTemplate ( )
4450
4551 const [ notificationVisible , setNotificationVisible ] = useState ( false )
4652 const [ lastSyncResult , setLastSyncResult ] = useState <
@@ -115,10 +121,12 @@ export function TemplateStatus() {
115121 | "template-status-saved"
116122 | "template-status-error"
117123
124+ const isUnsavedState = hasPendingLocalWork || syncStatus === "unsaved"
125+
118126 if ( notificationVisible ) {
119127 // Unsynced edits take precedence over the last successful save result.
120128 // This prevents showing the green cloud when newer changes exist locally.
121- if ( hasPendingLocalWork || syncStatus === "unsaved" ) {
129+ if ( isUnsavedState ) {
122130 activeIcon = MdSync
123131 activeColor = "editorBattleshipGrey.500"
124132 notifText = "Unsaved local changes"
@@ -143,7 +151,7 @@ export function TemplateStatus() {
143151 if ( hasPendingLocalWork ) {
144152 activeIcon = MdSync
145153 activeColor = "editorBattleshipGrey.500"
146- isInteractive = false
154+ isInteractive = true
147155 iconAriaLabel = "template-status-unsaved"
148156 } else {
149157 if ( lastSyncResult === null ) return null
@@ -157,10 +165,15 @@ export function TemplateStatus() {
157165 }
158166 }
159167
160- const popupTitle =
161- lastSyncResult === "success" ? "All changes saved" : "Sync failed"
162- const popupBody =
163- lastSyncResult === "success"
168+ const popupTitle = isUnsavedState
169+ ? "Unsaved changes"
170+ : lastSyncResult === "success"
171+ ? "All changes saved"
172+ : "Sync failed"
173+
174+ const popupBody = isUnsavedState
175+ ? "Your current local changes haven't been synced to the library yet."
176+ : lastSyncResult === "success"
164177 ? `Your changes are synced to ${ providerName } successfully.`
165178 : ( storageError ?? "Failed to save changes. Please try again." )
166179
@@ -215,6 +228,19 @@ export function TemplateStatus() {
215228 < TextAny2 fontSize = "sm" color = "editorBattleshipGrey.600" >
216229 { popupBody }
217230 </ TextAny2 >
231+ { isUnsavedState && (
232+ < Box mt = "3" >
233+ < Button
234+ size = "sm"
235+ colorScheme = "blue"
236+ onClick = { ( ) => void save ( ) }
237+ isLoading = { syncStatus === "saving" }
238+ isDisabled = { templateStorageWriteBlocked }
239+ >
240+ Save
241+ </ Button >
242+ </ Box >
243+ ) }
218244 </ PopoverBodyAny >
219245 </ PopoverContentAny >
220246 </ Popover >
0 commit comments