Skip to content

Latest commit

Β 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Sheffex Arts β€” Artist Portfolio & CMS

Next.js TypeScript Supabase Tailwind CSS GSAP

A full-stack artist portfolio platform with a custom-built headless CMS. Built so a working artist never has to touch code again.

Live Demo Β· Admin Demo Β· Report Bug Β· Request Feature


What Is This?

Sheffex Arts is a production-ready portfolio and booking platform built for a professional artist specialising in face painting, body art, and special effects makeup. What makes this project stand out is not just the frontend β€” it's the complete content management system built behind it.

The artist can log into a private admin dashboard and update every single word, image, price, and section on their live website. No Wordpress. No plugins. No developer needed after handoff.

This was built as a complete freelance product β€” from database schema design to deployment β€” and represents a real-world example of a modern fullstack web application.


The Problem It Solves

Most artist websites are either:

  • Static β€” beautiful but frozen in time, requiring a developer for every update
  • CMS-built β€” flexible but generic, slow, and hard to customise

This project solves both problems by pairing a handcrafted, high-performance frontend with a purpose-built admin CMS that gives the artist complete control β€” without sacrificing design quality or performance.


Features

Public Portfolio Site

  • 🎬 Cinematic hero section with scrubable video slides and smooth transitions
  • πŸ–ΌοΈ Masonry portfolio gallery with category filtering and fullscreen lightbox
  • 🎨 Work categories grid with 3D scroll-triggered tilt animations
  • ✨ Featured projects section with alternating layout and scroll reveals
  • πŸ’¬ Accordion testimonials with hover-to-expand client reviews
  • πŸ‘€ About the artist section with clip-path image reveal animation
  • πŸ“± Fully responsive across all screen sizes
  • ⚑ Server-side rendering β€” pages load with data, zero layout shift

Admin CMS Dashboard

  • πŸ” Single-admin authentication β€” only one email can ever access the dashboard
  • πŸ–ŠοΈ Hero editor β€” update heading, subtext, and manage video slides
  • πŸ‘€ About editor β€” edit bio paragraphs, heading, eyebrow, and upload artist photo
  • πŸ—‚οΈ Category editor β€” update the 3 homepage category cards independently
  • πŸ“Œ Featured works manager β€” full CRUD with accordion UI and inline image upload
  • ⭐ Testimonials manager β€” add, edit, delete reviews with star rating picker
  • πŸ› οΈ Services editor β€” tab-based editor for each service with dynamic pricing tiers and icon bullets
  • πŸ–ΌοΈ Gallery manager β€” bulk image upload, per-image category tagging, inline title editing
  • βš™οΈ Site settings β€” WhatsApp number, CTA text, studio brand name β€” all editable
  • πŸ” Global search β€” search any admin section with ⌘K keyboard shortcut

Tech Stack

Layer Technology Why
Framework Next.js 15 App Router Server components, file-based routing, ISR
Language TypeScript (strict) End-to-end type safety, zero any
Database Supabase (PostgreSQL) Row Level Security, realtime, storage
Auth Supabase Auth Single-admin session, middleware protection
Storage Supabase Storage Image/video uploads with public CDN URLs
Styling Tailwind CSS Utility-first, no CSS files needed
Animations GSAP + ScrollTrigger Production-grade scroll animations
Images Next.js Image Automatic optimisation, lazy loading

Architecture Decisions

Server Components for Data Fetching

All public pages are async Server Components that fetch data directly from Supabase at request time. This means:

  • No loading spinners on the public site
  • Data is always fresh without client-side fetching
  • The HTML that ships to the browser already contains real content

Service Role Client for Admin Writes

Admin API routes use Supabase's service role key which bypasses Row Level Security. The security boundary is the verifyAdmin() middleware check at the top of every route β€” not RLS. The service role key never touches the browser.

Single Source of Truth for Types

All database row shapes live in lib/types/database.ts. Every API route, fetch function, and component imports from there. This means a schema change requires updating one file, and TypeScript catches every breakage at compile time.

Props-Down Data Pattern

Server pages fetch data and pass it as props to Client Components. Client Components contain zero data fetching logic β€” only UI logic. This keeps animations, interactions, and GSAP code fully in the client while keeping data concerns fully on the server.


Database Schema

The CMS manages 11 tables across two concerns β€” content and configuration:

site_settings      β†’ Key-value store for global config (WhatsApp, CTA, brand name)
hero_slides        β†’ Video slides for the hero section
hero_content       β†’ Hero heading and subtext
about_section      β†’ Artist bio, paragraphs, and photo
categories         β†’ The 3 homepage work category cards
featured_works     β†’ Selected projects on the homepage
testimonials       β†’ Client reviews with star ratings
services           β†’ Service pages (face painting, body art, SFX)
service_details    β†’ Icon + text bullet points per service
service_pricing    β†’ Pricing tiers per service
gallery_items      β†’ Portfolio images with category tags

All tables have Row Level Security enabled with public read and authenticated write policies.


Getting Started

Prerequisites

  • Node.js 18+
  • A Supabase account (free tier works)

1. Clone the repository

git clone https://github.com/yourusername/sheffex-arts.git
cd sheffex-arts

2. Install dependencies

npm install

3. Set up environment variables

Create a .env.local file in the root:

NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
ADMIN_EMAIL=admin@yourdomain.com
NEXT_PUBLIC_WHATSAPP_NUMBER=780097654678

4. Set up the database

Copy the contents of supabase/schema.sql and run it in your Supabase SQL Editor. This creates all 11 tables, enables RLS, and seeds initial data.

5. Create the storage bucket

In your Supabase dashboard go to Storage β†’ New Bucket and create a public bucket named media. Then run the storage policies from the schema file.

6. Create the admin account

In your Supabase dashboard go to Authentication β†’ Users β†’ Invite User and invite the email you set as ADMIN_EMAIL.

7. Run the development server

npm run dev

Open http://localhost:3000 for the portfolio site and http://localhost:3000/admin for the CMS.


Environment Variables

Variable Description Required
NEXT_PUBLIC_SUPABASE_URL Your Supabase project URL βœ…
NEXT_PUBLIC_SUPABASE_ANON_KEY Supabase anonymous/public key βœ…
SUPABASE_SERVICE_ROLE_KEY Supabase service role key (server only) βœ…
ADMIN_EMAIL The only email allowed to access /admin βœ…
NEXT_PUBLIC_WHATSAPP_NUMBER WhatsApp number with country code, no + βœ…

Key Implementation Highlights

Scroll Animations

Every section uses GSAP's ScrollTrigger for entrance animations. The pattern used throughout β€” gsap.context() with cleanup via ctx.revert() β€” ensures animations don't leak between navigations in the App Router:

useEffect(() => {
  const ctx = gsap.context(() => {
    gsap.fromTo(".element", { y: 40, opacity: 0 }, {
      y: 0, opacity: 1,
      scrollTrigger: { trigger: section, start: "top 80%" }
    });
  }, sectionRef);

  return () => ctx.revert(); // cleanup on unmount
}, []);

Video Scrubbing

The hero section supports drag-to-scrub video playback using requestAnimationFrame and pointer events β€” giving the feel of an interactive film reel.

Type-Safe Database Layer

The fetch layer maps raw database column names (snake_case) to component-friendly prop names (camelCase) in one place:

// lib/data/fetch.ts β€” mapping happens once here
return services.map((s: ServiceRow) => ({
  slug: s.slug,
  heroImage: s.hero_image_url,       // snake_case β†’ camelCase
  longDescription: s.long_description,
  ...
}));

Components always receive clean camelCase props. If the database schema changes, only fetch.ts needs updating.


What I Learned Building This

  • Designing a CMS from scratch forces you to think about data relationships before writing a single line of frontend code
  • The boundary between Server and Client Components in Next.js App Router is a genuine architectural decision, not just a performance hint
  • Supabase's Row Level Security is powerful but you need to understand when to bypass it (admin writes via service role) versus when to respect it (public reads)
  • TypeScript's null vs undefined distinction matters enormously when your data comes from a PostgreSQL database via a JavaScript ORM
  • GSAP's context() API is essential in React β€” without it, ScrollTrigger instances accumulate across renders and cause subtle animation bugs

Roadmap

  • Email notifications when a booking inquiry comes in via WhatsApp link
  • Image optimisation pipeline β€” auto-compress uploads before sending to Supabase Storage
  • Drag-and-drop reordering for gallery items and featured works
  • Preview mode β€” see CMS changes before publishing
  • Analytics dashboard showing most-visited portfolio pieces

License

MIT β€” feel free to use this as a reference for your own projects. If you build something with it, I'd love to see it.


Author

Built by Ovuoba Emmanuel β€” a fullstack developer focused on building polished, production-ready web applications.

Portfolio LinkedIn GitHub


If this project helped you, consider giving it a ⭐ β€” it helps others find it.

About

A web designed website to showcase an artists works and also to make contact with him.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages