fix(settings): lock site context to current URL, rename Workspace to Organization
- Site context is locked to the site from the current URL — no dropdown switcher. If not on a site page, defaults to Organization context. - Renamed "Workspace" to "Organization" in all user-facing text. - Removed unused CaretDown import and dropdown state.
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useCallback, useEffect } from 'react'
|
import { useState, useCallback, useEffect } from 'react'
|
||||||
import { AnimatePresence, motion } from 'framer-motion'
|
import { AnimatePresence, motion } from 'framer-motion'
|
||||||
import { X, GearSix, Buildings, User, CaretDown } from '@phosphor-icons/react'
|
import { X, GearSix, Buildings, User } from '@phosphor-icons/react'
|
||||||
import { useUnifiedSettings } from '@/lib/unified-settings-context'
|
import { useUnifiedSettings } from '@/lib/unified-settings-context'
|
||||||
import { useAuth } from '@/lib/auth/context'
|
import { useAuth } from '@/lib/auth/context'
|
||||||
import { useSite } from '@/lib/swr/dashboard'
|
import { useSite } from '@/lib/swr/dashboard'
|
||||||
@@ -65,28 +65,18 @@ const ACCOUNT_TABS: TabDef[] = [
|
|||||||
function ContextSwitcher({
|
function ContextSwitcher({
|
||||||
active,
|
active,
|
||||||
onChange,
|
onChange,
|
||||||
sites,
|
activeSiteDomain,
|
||||||
activeSiteId,
|
|
||||||
onSiteChange,
|
|
||||||
}: {
|
}: {
|
||||||
active: SettingsContext
|
active: SettingsContext
|
||||||
onChange: (ctx: SettingsContext) => void
|
onChange: (ctx: SettingsContext) => void
|
||||||
sites: Site[]
|
activeSiteDomain: string | null
|
||||||
activeSiteId: string | null
|
|
||||||
onSiteChange: (id: string) => void
|
|
||||||
}) {
|
}) {
|
||||||
const [siteDropdownOpen, setSiteDropdownOpen] = useState(false)
|
|
||||||
const activeSite = sites.find(s => s.id === activeSiteId)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1 p-1 bg-neutral-800/50 rounded-xl">
|
<div className="flex items-center gap-1 p-1 bg-neutral-800/50 rounded-xl">
|
||||||
{/* Site button with dropdown */}
|
{/* Site button — locked to current site, no dropdown */}
|
||||||
<div className="relative">
|
{activeSiteDomain && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => onChange('site')}
|
||||||
onChange('site')
|
|
||||||
if (active === 'site') setSiteDropdownOpen(!siteDropdownOpen)
|
|
||||||
}}
|
|
||||||
className={`flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-lg transition-all duration-200 ${
|
className={`flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-lg transition-all duration-200 ${
|
||||||
active === 'site'
|
active === 'site'
|
||||||
? 'bg-neutral-700 text-white shadow-sm'
|
? 'bg-neutral-700 text-white shadow-sm'
|
||||||
@@ -94,45 +84,12 @@ function ContextSwitcher({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<GearSix weight="bold" className="w-4 h-4" />
|
<GearSix weight="bold" className="w-4 h-4" />
|
||||||
<span className="hidden sm:inline">
|
<span className="hidden sm:inline">{activeSiteDomain}</span>
|
||||||
{activeSite ? activeSite.domain : 'Site'}
|
|
||||||
</span>
|
|
||||||
<CaretDown weight="bold" className="w-3 h-3" />
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<AnimatePresence>
|
|
||||||
{siteDropdownOpen && active === 'site' && sites.length > 1 && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: -4 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
exit={{ opacity: 0, y: -4 }}
|
|
||||||
transition={{ duration: 0.15 }}
|
|
||||||
className="absolute top-full left-0 mt-1 w-56 rounded-xl bg-neutral-800 border border-neutral-700 shadow-xl z-50 py-1 overflow-hidden"
|
|
||||||
>
|
|
||||||
{sites.map(site => (
|
|
||||||
<button
|
|
||||||
key={site.id}
|
|
||||||
onClick={() => {
|
|
||||||
onSiteChange(site.id)
|
|
||||||
setSiteDropdownOpen(false)
|
|
||||||
}}
|
|
||||||
className={`w-full text-left px-3 py-2 text-sm transition-colors ${
|
|
||||||
site.id === activeSiteId
|
|
||||||
? 'bg-brand-orange/10 text-brand-orange'
|
|
||||||
: 'text-neutral-300 hover:bg-neutral-700/50'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span className="font-medium">{site.name}</span>
|
|
||||||
<span className="ml-2 text-neutral-500 text-xs">{site.domain}</span>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => { onChange('workspace'); setSiteDropdownOpen(false) }}
|
onClick={() => onChange('workspace')}
|
||||||
className={`flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-lg transition-all duration-200 ${
|
className={`flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-lg transition-all duration-200 ${
|
||||||
active === 'workspace'
|
active === 'workspace'
|
||||||
? 'bg-neutral-700 text-white shadow-sm'
|
? 'bg-neutral-700 text-white shadow-sm'
|
||||||
@@ -140,11 +97,11 @@ function ContextSwitcher({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Buildings weight="bold" className="w-4 h-4" />
|
<Buildings weight="bold" className="w-4 h-4" />
|
||||||
<span className="hidden sm:inline">Workspace</span>
|
<span className="hidden sm:inline">Organization</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => { onChange('account'); setSiteDropdownOpen(false) }}
|
onClick={() => onChange('account')}
|
||||||
className={`flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-lg transition-all duration-200 ${
|
className={`flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-lg transition-all duration-200 ${
|
||||||
active === 'account'
|
active === 'account'
|
||||||
? 'bg-neutral-700 text-white shadow-sm'
|
? 'bg-neutral-700 text-white shadow-sm'
|
||||||
@@ -282,26 +239,28 @@ export default function UnifiedSettingsModal() {
|
|||||||
}
|
}
|
||||||
}, [isOpen, initTab])
|
}, [isOpen, initTab])
|
||||||
|
|
||||||
// Load sites when modal opens
|
// Detect site from URL and load sites list when modal opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen && user?.org_id) {
|
if (!isOpen || !user?.org_id) return
|
||||||
listSites().then(data => {
|
|
||||||
const list = Array.isArray(data) ? data : []
|
|
||||||
setSites(list)
|
|
||||||
if (!activeSiteId && list.length > 0) {
|
|
||||||
setActiveSiteId(list[0].id)
|
|
||||||
}
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
}, [isOpen, user?.org_id])
|
|
||||||
|
|
||||||
// Try to pick up site from URL
|
// Pick up site ID from URL — this is the only site the user can configure
|
||||||
useEffect(() => {
|
if (typeof window !== 'undefined') {
|
||||||
if (isOpen && typeof window !== 'undefined') {
|
|
||||||
const match = window.location.pathname.match(/\/sites\/([a-f0-9-]+)/)
|
const match = window.location.pathname.match(/\/sites\/([a-f0-9-]+)/)
|
||||||
if (match) setActiveSiteId(match[1])
|
if (match) {
|
||||||
|
setActiveSiteId(match[1])
|
||||||
|
setContext('site')
|
||||||
|
} else {
|
||||||
|
// Not on a site page — default to organization context
|
||||||
|
setActiveSiteId(null)
|
||||||
|
if (!initTab?.context) setContext('workspace')
|
||||||
}
|
}
|
||||||
}, [isOpen])
|
}
|
||||||
|
|
||||||
|
// Load sites for domain display
|
||||||
|
listSites().then(data => {
|
||||||
|
setSites(Array.isArray(data) ? data : [])
|
||||||
|
}).catch(() => {})
|
||||||
|
}, [isOpen, user?.org_id])
|
||||||
|
|
||||||
// Escape key closes
|
// Escape key closes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -363,9 +322,7 @@ export default function UnifiedSettingsModal() {
|
|||||||
<ContextSwitcher
|
<ContextSwitcher
|
||||||
active={context}
|
active={context}
|
||||||
onChange={handleContextChange}
|
onChange={handleContextChange}
|
||||||
sites={sites}
|
activeSiteDomain={sites.find(s => s.id === activeSiteId)?.domain ?? null}
|
||||||
activeSiteId={activeSiteId}
|
|
||||||
onSiteChange={setActiveSiteId}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ export default function WorkspaceGeneralTab() {
|
|||||||
setSaving(true)
|
setSaving(true)
|
||||||
try {
|
try {
|
||||||
await updateOrganization(user.org_id, name, slug)
|
await updateOrganization(user.org_id, name, slug)
|
||||||
toast.success('Workspace updated')
|
toast.success('Organization updated')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(getAuthErrorMessage(err as Error) || 'Failed to update workspace')
|
toast.error(getAuthErrorMessage(err as Error) || 'Failed to update organization')
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false)
|
setSaving(false)
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ export default function WorkspaceGeneralTab() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-semibold text-white mb-1">General Information</h3>
|
<h3 className="text-base font-semibold text-white mb-1">General Information</h3>
|
||||||
<p className="text-sm text-neutral-400">Basic details about your workspace.</p>
|
<p className="text-sm text-neutral-400">Basic details about your organization.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export default function WorkspaceMembersTab() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-semibold text-white mb-1">Members</h3>
|
<h3 className="text-base font-semibold text-white mb-1">Members</h3>
|
||||||
<p className="text-sm text-neutral-400">{members.length} member{members.length !== 1 ? 's' : ''} in your workspace.</p>
|
<p className="text-sm text-neutral-400">{members.length} member{members.length !== 1 ? 's' : ''} in your organization.</p>
|
||||||
</div>
|
</div>
|
||||||
{canManage && !showInvite && (
|
{canManage && !showInvite && (
|
||||||
<Button onClick={() => setShowInvite(true)} variant="primary" className="text-sm gap-1.5">
|
<Button onClick={() => setShowInvite(true)} variant="primary" className="text-sm gap-1.5">
|
||||||
|
|||||||
Reference in New Issue
Block a user