feat(settings): wire WorkspaceGeneralTab into dirty tracking + modal save bar

This commit is contained in:
Usman Baig
2026-03-25 21:26:48 +01:00
parent 5d21a81fad
commit 3f884fca76
2 changed files with 23 additions and 15 deletions

View File

@@ -199,7 +199,7 @@ function TabContent({
// Workspace tabs
if (context === 'workspace') {
switch (activeTab) {
case 'general': return <WorkspaceGeneralTab />
case 'general': return <WorkspaceGeneralTab {...dirtyProps} />
case 'billing': return <WorkspaceBillingTab />
case 'members': return <WorkspaceMembersTab />
case 'notifications': return <WorkspaceNotificationsTab />

View File

@@ -1,6 +1,6 @@
'use client'
import { useState, useEffect } from 'react'
import { useState, useEffect, useRef, useCallback } from 'react'
import { useRouter } from 'next/navigation'
import { Input, Button, toast } from '@ciphera-net/ui'
import { Spinner } from '@ciphera-net/ui'
@@ -9,17 +9,18 @@ import { getOrganization, updateOrganization, deleteOrganization } from '@/lib/a
import { getAuthErrorMessage } from '@ciphera-net/ui'
import { useUnifiedSettings } from '@/lib/unified-settings-context'
export default function WorkspaceGeneralTab() {
export default function WorkspaceGeneralTab({ onDirtyChange, onRegisterSave }: { onDirtyChange?: (dirty: boolean) => void; onRegisterSave?: (fn: () => Promise<void>) => void }) {
const { user } = useAuth()
const router = useRouter()
const { closeUnifiedSettings } = useUnifiedSettings()
const [name, setName] = useState('')
const [slug, setSlug] = useState('')
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
const [deleteText, setDeleteText] = useState('')
const [deleting, setDeleting] = useState(false)
const initialRef = useRef('')
const hasInitialized = useRef(false)
useEffect(() => {
if (!user?.org_id) return
@@ -28,23 +29,36 @@ export default function WorkspaceGeneralTab() {
.then(org => {
setName(org.name || '')
setSlug(org.slug || '')
if (!hasInitialized.current) {
initialRef.current = JSON.stringify({ name: org.name || '', slug: org.slug || '' })
hasInitialized.current = true
}
})
.catch(() => {})
.finally(() => setLoading(false))
}, [user?.org_id])
const handleSave = async () => {
// Track dirty state
useEffect(() => {
if (!initialRef.current) return
onDirtyChange?.(JSON.stringify({ name, slug }) !== initialRef.current)
}, [name, slug, onDirtyChange])
const handleSave = useCallback(async () => {
if (!user?.org_id) return
setSaving(true)
try {
await updateOrganization(user.org_id, name, slug)
initialRef.current = JSON.stringify({ name, slug })
onDirtyChange?.(false)
toast.success('Organization updated')
} catch (err) {
toast.error(getAuthErrorMessage(err as Error) || 'Failed to update organization')
} finally {
setSaving(false)
}
}
}, [user?.org_id, name, slug, onDirtyChange])
useEffect(() => {
onRegisterSave?.(handleSave)
}, [handleSave, onRegisterSave])
const handleDelete = async () => {
if (!user?.org_id || deleteText !== 'DELETE') return
@@ -91,12 +105,6 @@ export default function WorkspaceGeneralTab() {
</div>
</div>
<div className="flex justify-end">
<Button onClick={handleSave} variant="primary" disabled={saving}>
{saving ? 'Saving...' : 'Save Changes'}
</Button>
</div>
{/* Danger Zone */}
<div className="space-y-3 pt-4 border-t border-neutral-800">
<h3 className="text-base font-semibold text-red-500">Danger Zone</h3>