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:
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user