+
+
\ No newline at end of file
diff --git a/src/lib/components/admin/ProjectBrandingForm.svelte b/src/lib/components/admin/ProjectBrandingForm.svelte
new file mode 100644
index 0000000..db38ebe
--- /dev/null
+++ b/src/lib/components/admin/ProjectBrandingForm.svelte
@@ -0,0 +1,186 @@
+
+
+
+
Branding
+
+
+
+ {#if formData.logoUrl}
+
+
+
+
+ {:else}
+
+ {/if}
+
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/admin/ProjectForm.svelte b/src/lib/components/admin/ProjectForm.svelte
new file mode 100644
index 0000000..619ddf2
--- /dev/null
+++ b/src/lib/components/admin/ProjectForm.svelte
@@ -0,0 +1,601 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/admin/ProjectStylingForm.svelte b/src/lib/components/admin/ProjectStylingForm.svelte
new file mode 100644
index 0000000..a7237a5
--- /dev/null
+++ b/src/lib/components/admin/ProjectStylingForm.svelte
@@ -0,0 +1,125 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/routes/admin/projects/+page.svelte b/src/routes/admin/projects/+page.svelte
index e58264e..e76228e 100644
--- a/src/routes/admin/projects/+page.svelte
+++ b/src/routes/admin/projects/+page.svelte
@@ -1,9 +1,9 @@
-
+
Projects
@@ -100,23 +151,39 @@
{#if error}
{error}
- {:else}
-
-
- {total}
- Total projects
-
+ {:else if isLoading}
+
+
+
Loading projects...
+
+ {:else if projects.length === 0}
+
+
No projects found. Create your first project!
+
+ {:else}
+
+ {#each projects as project}
+
+ {/each}
-
-
{/if}
-
+
+
+{#if showDeleteModal && projectToDelete}
+
+{/if}
diff --git a/src/routes/admin/projects/[id]/edit/+page.svelte b/src/routes/admin/projects/[id]/edit/+page.svelte
index f9f280b..877afad 100644
--- a/src/routes/admin/projects/[id]/edit/+page.svelte
+++ b/src/routes/admin/projects/[id]/edit/+page.svelte
@@ -2,91 +2,15 @@
import { onMount } from 'svelte'
import { goto } from '$app/navigation'
import { page } from '$app/stores'
- import { z } from 'zod'
- import AdminSegmentedControl from '$lib/components/admin/AdminSegmentedControl.svelte'
- import FormFieldWrapper from '$lib/components/admin/FormFieldWrapper.svelte'
- import Editor from '$lib/components/admin/Editor.svelte'
+ import ProjectForm from '$lib/components/admin/ProjectForm.svelte'
+ import type { Project } from '$lib/types/project'
- // Zod schema for project validation
- const projectSchema = z.object({
- title: z.string().min(1, 'Title is required'),
- description: z.string().optional(),
- year: z
- .number()
- .min(1990)
- .max(new Date().getFullYear() + 1),
- client: z.string().optional(),
- externalUrl: z.string().url().optional().or(z.literal('')),
- backgroundColor: z
- .string()
- .regex(/^#[0-9A-Fa-f]{6}$/)
- .optional()
- .or(z.literal('')),
- highlightColor: z
- .string()
- .regex(/^#[0-9A-Fa-f]{6}$/)
- .optional()
- .or(z.literal('')),
- status: z.enum(['draft', 'published'])
- })
-
- interface Project {
- id: number
- slug: string
- title: string
- subtitle: string | null
- description: string | null
- year: number
- client: string | null
- role: string | null
- technologies: string[] | null
- featuredImage: string | null
- gallery: any[] | null
- externalUrl: string | null
- caseStudyContent: any | null
- backgroundColor: string | null
- highlightColor: string | null
- displayOrder: number
- status: string
- }
-
- // State
let project = $state(null)
let isLoading = $state(true)
- let isSaving = $state(false)
let error = $state('')
- let successMessage = $state('')
- let activeTab = $state('metadata')
- let validationErrors = $state>({})
- let showPublishMenu = $state(false)
-
- // Form fields as individual state variables for reactivity
- let title = $state('')
- let subtitle = $state('') // Hidden but kept for backward compatibility
- let description = $state('')
- let year = $state(new Date().getFullYear())
- let client = $state('')
- let role = $state('') // Hidden but kept for backward compatibility
- let technologies = $state('') // Hidden but kept for backward compatibility
- let externalUrl = $state('')
- let backgroundColor = $state('')
- let highlightColor = $state('')
- let status = $state<'draft' | 'published'>('draft')
- let caseStudyContent = $state({
- type: 'doc',
- content: [{ type: 'paragraph' }]
- })
-
- // Ref to the editor component
- let editorRef: any
const projectId = $derived($page.params.id)
- const tabOptions = [
- { value: 'metadata', label: 'Metadata' },
- { value: 'case-study', label: 'Case Study' }
- ]
-
onMount(async () => {
await loadProject()
})
@@ -109,23 +33,6 @@
const data = await response.json()
project = data
-
- // Populate form fields
- title = data.title || ''
- subtitle = data.subtitle || ''
- description = data.description || ''
- year = data.year || new Date().getFullYear()
- client = data.client || ''
- role = data.role || ''
- technologies = Array.isArray(data.technologies) ? data.technologies.join(', ') : ''
- externalUrl = data.externalUrl || ''
- backgroundColor = data.backgroundColor || ''
- highlightColor = data.highlightColor || ''
- status = data.status || 'draft'
-
- if (data.caseStudyContent) {
- caseStudyContent = data.caseStudyContent
- }
} catch (err) {
error = 'Failed to load project'
console.error(err)
@@ -133,521 +40,19 @@
isLoading = false
}
}
-
- function validateForm() {
- try {
- projectSchema.parse({
- title,
- description: description || undefined,
- year,
- client: client || undefined,
- externalUrl: externalUrl || undefined,
- backgroundColor: backgroundColor || undefined,
- highlightColor: highlightColor || undefined,
- status
- })
- validationErrors = {}
- return true
- } catch (err) {
- if (err instanceof z.ZodError) {
- const errors: Record = {}
- err.errors.forEach((e) => {
- if (e.path[0]) {
- errors[e.path[0].toString()] = e.message
- }
- })
- validationErrors = errors
- }
- return false
- }
- }
-
- function handleEditorChange(content: any) {
- caseStudyContent = content
- }
-
- async function handleSave() {
- // Check if we're on the case study tab and should save editor content
- if (activeTab === 'case-study' && editorRef) {
- const editorData = await editorRef.save()
- if (editorData) {
- caseStudyContent = editorData
- }
- }
-
- if (!validateForm()) {
- error = 'Please fix the validation errors'
- return
- }
-
- try {
- isSaving = true
- error = ''
- successMessage = ''
-
- const auth = localStorage.getItem('admin_auth')
- if (!auth) {
- goto('/admin/login')
- return
- }
-
- const response = await fetch(`/api/projects/${projectId}`, {
- method: 'PUT',
- headers: {
- Authorization: `Basic ${auth}`,
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- title,
- subtitle,
- description,
- year,
- client,
- role,
- technologies: technologies
- .split(',')
- .map((t) => t.trim())
- .filter(Boolean),
- externalUrl,
- backgroundColor,
- highlightColor,
- status,
- caseStudyContent:
- caseStudyContent && caseStudyContent.content && caseStudyContent.content.length > 0
- ? caseStudyContent
- : null
- })
- })
-
- if (!response.ok) {
- throw new Error('Failed to save project')
- }
-
- successMessage = 'Project saved successfully!'
-
- setTimeout(() => {
- successMessage = ''
- }, 3000)
- } catch (err) {
- error = 'Failed to save project'
- console.error(err)
- } finally {
- isSaving = false
- }
- }
-
- async function handlePublish() {
- status = 'published'
- await handleSave()
-
- if (!error) {
- await loadProject()
- }
- showPublishMenu = false
- }
-
- async function handleUnpublish() {
- status = 'draft'
- await handleSave()
-
- if (!error) {
- await loadProject()
- }
- showPublishMenu = false
- }
-
- function togglePublishMenu() {
- showPublishMenu = !showPublishMenu
- }
-
- // Close menu when clicking outside
- function handleClickOutside(event: MouseEvent) {
- const target = event.target as HTMLElement
- if (!target.closest('.save-actions')) {
- showPublishMenu = false
- }
- }
-
- // Check for unsaved changes
- async function checkForUnsavedChanges() {
- if (editorRef && typeof editorRef.getIsDirty === 'function') {
- const isDirty = editorRef.getIsDirty()
- if (isDirty) {
- return confirm('You have unsaved changes. Are you sure you want to leave?')
- }
- }
- return true
- }
-
- $effect(() => {
- if (showPublishMenu) {
- document.addEventListener('click', handleClickOutside)
- return () => {
- document.removeEventListener('click', handleClickOutside)
- }
- }
- })
-