diff --git a/docs/backlog/initial-build-backlog.md b/docs/backlog/initial-build-backlog.md
index c700d7c..02b9b47 100644
--- a/docs/backlog/initial-build-backlog.md
+++ b/docs/backlog/initial-build-backlog.md
@@ -261,6 +261,7 @@ Tasks:
- create `/events`;
- create `/events/{slug}` placeholder;
- create `/projects`;
+- position `/projects` as a showcase for DDS-built tooling, software, plugins, apps, integrations, and selected community builds;
- create `/news`;
- create `/locations`;
- create `/about`;
@@ -273,6 +274,7 @@ Acceptance criteria:
- all public routes render;
- navigation uses `Events`, `Projects`, `News`, `About`, and `Contact`;
- pages can use placeholder content, but no generic theme copy;
+- `/projects` placeholder copy clearly frames projects as a public showcase rather than internal project management;
- each page has a clear purpose, heading hierarchy, and primary next action.
### DDS-008: Baseline SEO And Redirect Shape
@@ -410,6 +412,69 @@ Acceptance criteria:
- imported WordPress media can be deduplicated by legacy ID;
- alt text is supported.
+### DDS-014A: Project Showcase Model
+
+Goal: model public showcase projects for DDS-built tooling, software, plugins, apps, integrations, and selected community builds.
+
+Tasks:
+
+- create `Project` model and migration;
+- include title, slug, excerpt, content, status, project_type, visibility, featured flag, and sort order;
+- support project types such as `plugin`, `app`, `integration`, `event_tooling`, `community_build`, and `other`;
+- add optional links for GitHub, live demo, documentation, download, and contact;
+- add optional ownership/credit fields for maintainers or contributors;
+- add SEO fields and media references;
+- include locale-aware content fields.
+
+Acceptance criteria:
+
+- RotorHazard plugins, TrackDraw-style apps, race tooling, livestream overlays, and community utilities can be represented cleanly;
+- projects can distinguish open source, public platform, internal/private, and archived work;
+- only published and public projects are visible on the public site;
+- the model does not imply internal task or project-management workflows;
+- project records can be created through factories/tests.
+
+### DDS-014B: Public Project Showcase Pages
+
+Goal: show DDS projects publicly as a credible development and tooling showcase.
+
+Tasks:
+
+- build `/projects` project index page;
+- build `/projects/{slug}` project detail page;
+- add filters or grouping by project type when useful;
+- show project links, status, media, and concise value proposition;
+- add useful empty states.
+
+Acceptance criteria:
+
+- `/projects` lists published public projects;
+- `/projects/{slug}` shows a published public project;
+- project cards communicate type, purpose, status, and primary link clearly;
+- archived or internal-only projects are not publicly listed unless explicitly marked visible;
+- pages can support both software/tooling projects and selected community builds without separate content types.
+
+### DDS-014C: Admin Project CRUD
+
+Goal: manage project showcase content from the `/dashboard` management area.
+
+Tasks:
+
+- create `/dashboard/projects` project index;
+- create project create/edit forms;
+- add server-side validation through Form Requests;
+- add project policies;
+- add publish status and featured controls;
+- make link fields easy to add, remove, and validate.
+
+Acceptance criteria:
+
+- admins can create, edit, publish, feature, archive, and reorder projects;
+- editors can create and update projects according to their seeded permissions;
+- validation errors are shown clearly;
+- public visibility follows status and visibility fields;
+- forms are structured for concise project showcase editing, not task tracking.
+
## Epic 6: WordPress Import Spike
### DDS-015: WordPress Export Discovery
@@ -465,7 +530,10 @@ Acceptance criteria:
13. DDS-012
14. DDS-013
15. DDS-014
-16. DDS-015
-17. DDS-016
+16. DDS-014A
+17. DDS-014B
+18. DDS-014C
+19. DDS-015
+20. DDS-016
The public website rebuild can begin once DDS-007 and DDS-010 exist, while admin and import work continue behind it.
diff --git a/resources/js/app.tsx b/resources/js/app.tsx
index 402353f..d192217 100644
--- a/resources/js/app.tsx
+++ b/resources/js/app.tsx
@@ -4,16 +4,17 @@ import { TooltipProvider } from '@/components/ui/tooltip';
import { initializeTheme } from '@/hooks/use-appearance';
import AppLayout from '@/layouts/app-layout';
import AuthLayout from '@/layouts/auth-layout';
+import PublicLayout from '@/layouts/public-layout';
import SettingsLayout from '@/layouts/settings/layout';
-const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
+const appName = import.meta.env.VITE_APP_NAME || 'Dutch Drone Squad';
createInertiaApp({
title: (title) => (title ? `${title} - ${appName}` : appName),
layout: (name) => {
switch (true) {
case name === 'welcome':
- return null;
+ return PublicLayout;
case name.startsWith('auth/'):
return AuthLayout;
case name.startsWith('settings/'):
diff --git a/resources/js/components/app-header.tsx b/resources/js/components/app-header.tsx
index 7be23c1..77191e6 100644
--- a/resources/js/components/app-header.tsx
+++ b/resources/js/components/app-header.tsx
@@ -1,5 +1,16 @@
import { Link, usePage } from '@inertiajs/react';
-import { BookOpen, Folder, LayoutGrid, Menu, Search } from 'lucide-react';
+import {
+ CalendarDays,
+ ClipboardList,
+ Home,
+ LayoutDashboard,
+ LifeBuoy,
+ MapPin,
+ Menu,
+ Newspaper,
+ Search,
+ ShieldCheck,
+} from 'lucide-react';
import AppLogo from '@/components/app-logo';
import AppLogoIcon from '@/components/app-logo-icon';
import { Breadcrumbs } from '@/components/breadcrumbs';
@@ -31,8 +42,8 @@ import {
import { UserMenuContent } from '@/components/user-menu-content';
import { useCurrentUrl } from '@/hooks/use-current-url';
import { useInitials } from '@/hooks/use-initials';
-import { cn, toUrl } from '@/lib/utils';
-import { dashboard } from '@/routes';
+import { cn } from '@/lib/utils';
+import { dashboard, home } from '@/routes';
import type { BreadcrumbItem, NavItem } from '@/types';
type Props = {
@@ -41,22 +52,47 @@ type Props = {
const mainNavItems: NavItem[] = [
{
- title: 'Dashboard',
+ title: 'Overzicht',
href: dashboard(),
- icon: LayoutGrid,
+ icon: LayoutDashboard,
+ },
+ {
+ title: 'Events',
+ href: `${dashboard.url()}#events`,
+ icon: CalendarDays,
+ },
+ {
+ title: 'Projecten',
+ href: `${dashboard.url()}#projects`,
+ icon: ClipboardList,
+ },
+ {
+ title: 'Nieuws',
+ href: `${dashboard.url()}#news`,
+ icon: Newspaper,
+ },
+ {
+ title: 'Locaties',
+ href: `${dashboard.url()}#locations`,
+ icon: MapPin,
},
];
const rightNavItems: NavItem[] = [
{
- title: 'Repository',
- href: 'https://github.com/laravel/react-starter-kit',
- icon: Folder,
+ title: 'Publieke site',
+ href: home(),
+ icon: Home,
+ },
+ {
+ title: 'Huisregels',
+ href: `${dashboard.url()}#house-rules`,
+ icon: ShieldCheck,
},
{
- title: 'Documentation',
- href: 'https://laravel.com/docs/starter-kits#react',
- icon: BookOpen,
+ title: 'Support',
+ href: `${dashboard.url()}#support`,
+ icon: LifeBuoy,
},
];
@@ -81,6 +117,7 @@ export function AppHeader({ breadcrumbs = [] }: Props) {
variant="ghost"
size="icon"
className="mr-2 h-[34px] w-[34px]"
+ aria-label="Open navigatiemenu"
>
@@ -90,10 +127,17 @@ export function AppHeader({ breadcrumbs = [] }: Props) {
className="flex h-full w-64 flex-col items-stretch justify-between bg-sidebar"
>
- Navigation menu
+ Navigatiemenu
-
+
+
+
+
+
+ Dutch Drone Squad
+
+
@@ -114,18 +158,16 @@ export function AppHeader({ breadcrumbs = [] }: Props) {
@@ -182,17 +224,16 @@ export function AppHeader({ breadcrumbs = [] }: Props) {
variant="ghost"
size="icon"
className="group h-9 w-9 cursor-pointer"
+ aria-label="Zoeken"
>
{rightNavItems.map((item) => (
-
-
+
@@ -201,7 +242,7 @@ export function AppHeader({ breadcrumbs = [] }: Props) {
{item.icon && (
)}
-
+
{item.title}
diff --git a/resources/js/components/app-logo-icon.tsx b/resources/js/components/app-logo-icon.tsx
index 0c31215..70538e4 100644
--- a/resources/js/components/app-logo-icon.tsx
+++ b/resources/js/components/app-logo-icon.tsx
@@ -2,11 +2,11 @@ import type { SVGAttributes } from 'react';
export default function AppLogoIcon(props: SVGAttributes) {
return (
-