ignitionstack.pro v1.0 is out! Read the announcement →
Skip to Content

Projects Admin Guide

Complete guide to the projects (portfolio) admin panel, mirroring the product admin structure.

Projects rely on next-intl for translations. The title, description, and badge fields are translation keys.

Architecture

src/app/ ├── types/ │ └── project.ts # TypeScript types ├── server/projects/ │ ├── get-all-projects.ts # Active projects (public) │ ├── get-all-projects-admin.ts # All projects (admin) │ ├── get-project-by-id.ts # Fetch by Supabase ID │ └── get-project-by-slug.ts # Fetch by slug ├── actions/projects/ │ ├── create-project.ts # Create project │ ├── update-project.ts # Update project │ ├── delete-project.ts # Soft/Hard delete │ └── toggle-project-active.ts # Toggle active/inactive ├── [locale]/admin/projects/ │ ├── page.tsx # Project list │ ├── new/page.tsx # Create new │ └── [id]/page.tsx # Edit existing └── components/admin/ ├── project-form.tsx # Form └── project-actions.tsx # Action buttons

Data Structure

Project Interface

interface Project { slug: string // Unique, URL-friendly ID title: string // Translation key description: string // Translation key badge?: string // Translation key (opcional) category: 'mobile' | 'front-end' | 'back-end' | 'full-stack' | 'ai' tech: string[] // Technologies shown on the card stack: string[] // Used for filters link: string // External URL (GitHub, demo, etc.) image?: string // Image URL imagePath?: string // Path inside Supabase Storage isActive: boolean // Visible on the public page isFeatured: boolean // Featured project order: number // Display order createdAt: string updatedAt: string _supabaseId?: string // Internal ID (admin only) }

Translation System

Projects use translation keys. For every project add translations in:

src/i18n/messages/ ├── en.json ├── pt.json └── es.json

Example Structure

{ "Projects": { "items": { "meu-projeto": { "title": "My Amazing Project", "description": "A revolutionary web application...", "badge": "Featured" } } } }

title, description, and badge must match the project slug so the system can resolve the translations.

Accessing the Admin

http://localhost:3000/en/admin/projects

CRUD Operations

List Projects

import { getAllProjectsAdmin } from '@/app/server/projects/get-all-projects-admin' const projects = await getAllProjectsAdmin()

Create Project

1. Fill out the form

import { createProject } from '@/app/actions/projects/create-project' const result = await createProject({ slug: 'my-awesome-project', title: 'my-awesome-project', // Translation key description: 'my-awesome-project', // Translation key badge: 'my-awesome-project', // Translation key (opcional) category: 'front-end', tech: ['React', 'TypeScript', 'Next.js'], stack: ['React', 'TypeScript'], link: 'https://github.com/user/my-awesome-project', isActive: true, isFeatured: false, order: 0, })

2. Add translations

en.json:

{ "Projects": { "items": { "my-awesome-project": { "title": "My Awesome Project", "description": "A revolutionary web application built with modern tech stack...", "badge": "Featured" } } } }

pt.json:

{ "Projects": { "items": { "my-awesome-project": { "title": "My Amazing Project", "description": "A revolutionary web app built with a modern stack...", "badge": "Featured" } } } }

es.json:

{ "Projects": { "items": { "my-awesome-project": { "title": "My Amazing Project (ES)", "description": "A revolutionary web app built with a modern stack (ES)...", "badge": "Featured" } } } }

3. Verify on the public page

Visit /projetos to see the project listed.

Update Project

import { updateProject } from '@/app/actions/projects/update-project' const result = await updateProject(supabaseId, { order: 5, isFeatured: true, })

Delete

// Soft delete (isActive = false) import { deleteProject } from '@/app/actions/projects/delete-project' const result = await deleteProject(supabaseId) // Hard delete (remove from DB) import { hardDeleteProject } from '@/app/actions/projects/delete-project' const result = await hardDeleteProject(supabaseId)

Toggle Active

import { toggleProjectActive } from '@/app/actions/projects/toggle-project-active' const result = await toggleProjectActive(supabaseId)

Project Form

Basic information

Image

Technologies

Classification

Status

Components

ProjectForm

import ProjectForm from '@/app/components/admin/project-form' // Create <ProjectForm mode="create" /> // Edit <ProjectForm mode="edit" project={project} />

ProjectActions

import ProjectActions from '@/app/components/admin/project-actions' <ProjectActions projectId={project._supabaseId} projectTitle={project.title} isActive={project.isActive} />

Public Page Filters

The /projetos page exposes two filter types:

By Category (tabs)

By Stack (buttons)

Differences: Projects vs Products

AspectoProjectsProducts
FocusPortfolio/showcaseE-commerce/sales
PricingNoYes
AuthorNoYes
FeaturesTech stackFeature list
DownloadExternal linkDownload URL
TranslationsYes (title, description, badge)No
Categoriesmobile/front-end/etclanding-page/dashboard/etc

Project Reordering

To reorder projects in the list:

const projects = [ { id: 'proj1', order: 0 }, { id: 'proj2', order: 1 }, { id: 'proj3', order: 2 }, ] for (const proj of projects) { await updateProject(proj.id, { order: proj.order }) }

Feature a Project

await updateProject(projectId, { isFeatured: true })

Troubleshooting

Project does not appear in the list

Translations do not show up

Image does not load

Filters do not work

Implementation Checklist

Resources