feat: add project name editing, remove address fields from settings

- Add updateProjectName server action
- Project settings dialog now shows editable name field
- Removed address editing section from settings (per Wassim's request)
- Privacy toggle and delete project remain
This commit is contained in:
Anton Pascal
2026-02-17 23:07:38 +00:00
parent ee30f17129
commit 84e933f705
2 changed files with 95 additions and 107 deletions
@@ -10,7 +10,7 @@ import {
DialogTitle, DialogTitle,
} from '@/components/ui/primitives/dialog' } from '@/components/ui/primitives/dialog'
import { Switch } from '@/components/ui/primitives/switch' import { Switch } from '@/components/ui/primitives/switch'
import { updateProjectAddress, updateProjectPrivacy, deleteProject } from '../lib/projects/actions' import { updateProjectName, updateProjectPrivacy, deleteProject } from '../lib/projects/actions'
import type { Project } from '../lib/projects/types' import type { Project } from '../lib/projects/types'
interface ProjectSettingsDialogProps { interface ProjectSettingsDialogProps {
@@ -30,19 +30,23 @@ export function ProjectSettingsDialog({
}: ProjectSettingsDialogProps) { }: ProjectSettingsDialogProps) {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [isDeleting, setIsDeleting] = useState(false) const [isDeleting, setIsDeleting] = useState(false)
const [name, setName] = useState(project.name || '')
const [isPrivate, setIsPrivate] = useState(project.is_private) const [isPrivate, setIsPrivate] = useState(project.is_private)
const [address, setAddress] = useState({
street_number: project.address?.street_number || '',
route: project.address?.route || '',
city: project.address?.city || '',
state: project.address?.state || '',
postal_code: project.address?.postal_code || '',
country: project.address?.country || 'US',
})
const handleSave = async () => { const handleSave = async () => {
setLoading(true) setLoading(true)
try { try {
// Update name if changed
const trimmedName = name.trim()
if (trimmedName && trimmedName !== (project.name || '')) {
const nameResult = await updateProjectName(project.id, trimmedName)
if (!nameResult.success) {
alert(`Failed to update name: ${nameResult.error}`)
setLoading(false)
return
}
}
// Update privacy if changed // Update privacy if changed
if (isPrivate !== project.is_private) { if (isPrivate !== project.is_private) {
const privacyResult = await updateProjectPrivacy(project.id, isPrivate) const privacyResult = await updateProjectPrivacy(project.id, isPrivate)
@@ -53,26 +57,6 @@ export function ProjectSettingsDialog({
} }
} }
// Update address if changed and project has an address
if (project.address) {
const addressChanged =
address.street_number !== (project.address.street_number || '') ||
address.route !== (project.address.route || '') ||
address.city !== (project.address.city || '') ||
address.state !== (project.address.state || '') ||
address.postal_code !== (project.address.postal_code || '') ||
address.country !== (project.address.country || 'US')
if (addressChanged) {
const addressResult = await updateProjectAddress(project.id, address)
if (!addressResult.success) {
alert(`Failed to update address: ${addressResult.error}`)
setLoading(false)
return
}
}
}
onUpdate?.() onUpdate?.()
onOpenChange(false) onOpenChange(false)
} catch (error) { } catch (error) {
@@ -108,10 +92,26 @@ export function ProjectSettingsDialog({
<DialogContent className="sm:max-w-[500px]"> <DialogContent className="sm:max-w-[500px]">
<DialogHeader> <DialogHeader>
<DialogTitle>Project Settings</DialogTitle> <DialogTitle>Project Settings</DialogTitle>
<DialogDescription>Update project address and privacy settings</DialogDescription> <DialogDescription>Update project name and privacy settings</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-6 py-4"> <div className="space-y-6 py-4">
{/* Project Name */}
<div>
<label htmlFor="project-name" className="font-medium text-sm">
Project Name
</label>
<input
id="project-name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="My Project"
className="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
disabled={loading}
/>
</div>
{/* Privacy Toggle */} {/* Privacy Toggle */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
@@ -126,83 +126,6 @@ export function ProjectSettingsDialog({
</div> </div>
</div> </div>
{/* Address Fields */}
<div className="space-y-4">
<h3 className="font-medium">Address</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium">Street Number</label>
<input
type="text"
value={address.street_number}
onChange={(e) => setAddress({ ...address, street_number: e.target.value })}
className="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
placeholder="123"
/>
</div>
<div>
<label className="text-sm font-medium">Street</label>
<input
type="text"
value={address.route}
onChange={(e) => setAddress({ ...address, route: e.target.value })}
className="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
placeholder="Main St"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium">City</label>
<input
type="text"
value={address.city}
onChange={(e) => setAddress({ ...address, city: e.target.value })}
className="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
placeholder="San Francisco"
/>
</div>
<div>
<label className="text-sm font-medium">State</label>
<input
type="text"
value={address.state}
onChange={(e) => setAddress({ ...address, state: e.target.value })}
className="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
placeholder="CA"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium">Postal Code</label>
<input
type="text"
value={address.postal_code}
onChange={(e) => setAddress({ ...address, postal_code: e.target.value })}
className="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
placeholder="94102"
/>
</div>
<div>
<label className="text-sm font-medium">Country</label>
<input
type="text"
value={address.country}
onChange={(e) => setAddress({ ...address, country: e.target.value })}
className="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
placeholder="US"
/>
</div>
</div>
</div>
{/* Danger Zone */} {/* Danger Zone */}
<div className="border-t border-border pt-6"> <div className="border-t border-border pt-6">
<h3 className="font-medium text-destructive mb-2">Danger Zone</h3> <h3 className="font-medium text-destructive mb-2">Danger Zone</h3>
@@ -574,6 +574,71 @@ export async function updateProjectPrivacy(
} }
} }
/**
* Update project name
*/
export async function updateProjectName(
projectId: string,
name: string,
): Promise<ActionResult> {
try {
const session = await getSession()
if (!session?.user) {
return {
success: false,
error: 'Not authenticated',
}
}
if (!name.trim()) {
return {
success: false,
error: 'Project name cannot be empty',
}
}
const supabase = await createServerSupabaseClient()
// Verify ownership
const { data: project } = await supabase
.from('projects')
.select('owner_id')
.eq('id', projectId)
.single()
if ((project as any)?.owner_id !== session.user.id) {
return {
success: false,
error: 'Unauthorized',
}
}
// Update name
const { error } = await (supabase
.from('projects') as any)
.update({ name: name.trim() })
.eq('id', projectId)
if (error) {
return {
success: false,
error: error.message,
}
}
return {
success: true,
message: 'Project name updated',
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to update project name',
}
}
}
/** /**
* Update project address * Update project address
*/ */