dorkhub

Portofolio_V5

Personal portfolio built with React + Supabase, featuring an admin dashboard for managing projects, certificates, and c…

EkiZR
JavaScript719292 forksupdated 1 month ago
visit the demogit clone https://github.com/EkiZR/Portofolio_V5.gitEkiZR/Portofolio_V5

Portfolio V5

Hello everyone! 👋

Let me introduce myself, I'm Eki Zulfar Rachman. On this occasion, I'd like to share the portfolio website project that I've developed. built with React and Supabase, featuring a public-facing site and an admin dashboard.

Live Demo: https://ekizr.com


🛠️ Tech Stack

This project is built using modern web technologies:

  • ReactJS - Frontend framework
  • Tailwind CSS - Utility-first CSS framework
  • Supabase - Backend for portfolio data, certificates, and comment system
  • AOS - Animate On Scroll library
  • Framer Motion - Animation library
  • Lucide - Icon library
  • Material UI - React component library
  • SweetAlert2 - Beautiful alert dialogs

User Roles

Role Access
Visitor (Public) View projects, certificates, and comments — leave a comment
Admin Login to dashboard — full CRUD on projects & certificates — delete & pin/unpin comments

Getting Started

Prerequisites

  • Node.js >= 14.x
  • npm or yarn

1. Clone & Install

git clone https://github.com/EkiZR/Portofolio_V5.git
cd Portofolio_V5
npm install

If you encounter peer dependency issues: npm install --legacy-peer-deps

2. Environment Variables

Create a .env file in the root directory:

VITE_SUPABASE_URL=your-supabase-project-url
VITE_SUPABASE_ANON_KEY=your-supabase-anon-key

Find these in your Supabase project under Settings → API.
⚠️ Never commit .env to version control — make sure it's in .gitignore.

3. Supabase Client (src/supabase.js)

import { createClient } from '@supabase/supabase-js'

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY

if (!supabaseUrl || !supabaseKey) {
  throw new Error('Supabase credentials missing. Check your .env file.')
}

export const supabase = createClient(supabaseUrl, supabaseKey)

4. Database Setup

Go to your Supabase project → SQL Editor → run the script below (run once):

-- ============================
-- TABLES
-- ============================
CREATE TABLE public.projects (
  id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  "Title" text,
  "Description" text,
  "Img" text,
  "Link" text,
  "Github" text,
  "Features" jsonb,
  "TechStack" jsonb,
  is_published boolean DEFAULT true,
  order_index int DEFAULT 0,
  created_at timestamptz DEFAULT now()
);

CREATE TABLE public.certificates (
  id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  "Img" text,
  created_at timestamptz DEFAULT now()
);

CREATE TABLE public.portfolio_comments (
  id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
  content text NOT NULL,
  user_name text NOT NULL,
  profile_image text,
  is_pinned boolean DEFAULT false,
  created_at timestamptz DEFAULT now()
);

CREATE TABLE public.profiles (
  id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
  username text UNIQUE NOT NULL,
  role text NOT NULL CHECK (role IN ('admin', 'user')),
  created_at timestamptz DEFAULT now()
);

-- ============================
-- RLS
-- ============================
ALTER TABLE public.projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.certificates ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.portfolio_comments ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;

-- Authenticated users may only read their own profile.
-- This is required by the login flow to check the user's role.
CREATE POLICY "User can read own profile"
ON public.profiles
FOR SELECT
TO authenticated
USING ((SELECT auth.uid()) = id);

CREATE POLICY "public read projects"
ON public.projects FOR SELECT USING (true);

CREATE POLICY "public read certificates"
ON public.certificates FOR SELECT USING (true);

CREATE POLICY "public read comments"
ON public.portfolio_comments FOR SELECT USING (true);

CREATE POLICY "public insert comment"
ON public.portfolio_comments FOR INSERT
WITH CHECK (is_pinned = false);

CREATE POLICY "admin manage projects"
ON public.projects FOR ALL
USING (
  EXISTS (
    SELECT 1 FROM public.profiles
    WHERE id = auth.uid() AND role = 'admin'
  )
);

CREATE POLICY "admin manage certificates"
ON public.certificates FOR ALL
USING (
  EXISTS (
    SELECT 1 FROM public.profiles
    WHERE id = auth.uid() AND role = 'admin'
  )
);

CREATE POLICY "admin manage comments"
ON public.portfolio_comments FOR ALL
TO authenticated
USING (
  EXISTS (
    SELECT 1 FROM public.profiles
    WHERE id = auth.uid() AND role = 'admin'
  )
)
WITH CHECK (
  EXISTS (
    SELECT 1 FROM public.profiles
    WHERE id = auth.uid() AND role = 'admin'
  )
);

-- ============================
-- STORAGE
-- ============================
INSERT INTO storage.buckets (id, name, public)
VALUES ('project-images', 'project-images', true)
ON CONFLICT DO NOTHING;

CREATE POLICY "admin upload project images"
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'project-images'
  AND EXISTS (
    SELECT 1 FROM public.profiles
    WHERE id = auth.uid() AND role = 'admin'
  )
);

CREATE POLICY "public read project images"
ON storage.objects FOR SELECT
USING (bucket_id = 'project-images');

INSERT INTO storage.buckets (id, name, public)
VALUES ('certificate-images', 'certificate-images', true)
ON CONFLICT DO NOTHING;

CREATE POLICY "admin upload certificate images"
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'certificate-images'
  AND EXISTS (
    SELECT 1 FROM public.profiles
    WHERE id = auth.uid() AND role = 'admin'
  )
);

CREATE POLICY "public read certificate images"
ON storage.objects FOR SELECT
USING (bucket_id = 'certificate-images');

INSERT INTO storage.buckets (id, name, public)
VALUES ('profile-images', 'profile-images', true)
ON CONFLICT DO NOTHING;

CREATE POLICY "public upload profile images"
ON storage.objects FOR INSERT
TO public
WITH CHECK (bucket_id = 'profile-images');

CREATE POLICY "public read profile images"
ON storage.objects FOR SELECT
TO public
USING (bucket_id = 'profile-images');

-- Optional: default pinned comment
INSERT INTO public.portfolio_comments (
  content,
  user_name,
  profile_image,
  is_pinned,
  created_at
)
SELECT
  'developed by ekizr. This project is open-source and free to use.',
  'ekizr',
  'https://egwzigagwyrmwjsrebzx.supabase.co/storage/v1/object/public/profile-images/profile-images/1771939421615_xx2q8hgya6e.jpeg',
  true,
  now()
WHERE NOT EXISTS (
  SELECT 1
  FROM public.portfolio_comments
  WHERE is_pinned = true
);

5. Enable Realtime (Comments)

Go to Table Editor → portfolio_comments → Enable Realtime.

6. Create Admin Account

Step 1 — Go to Authentication → Users → Add User in Supabase Dashboard, then copy the generated User ID.

Step 2 — Run this in the SQL Editor (replace USER_UUID with the copied ID):

INSERT INTO public.profiles (id, username, role)
VALUES ('USER_UUID', 'eki', 'admin');

7. Run Locally

npm run dev

Open http://localhost:5173 in your browser.


Pages & Features

Public (Visitor)

  • Home — Hero section, about, skills
  • Projects — List of published projects with detail modal
  • Certificates — Certificate gallery
  • Comments — View all comments, submit a new comment with name and optional profile photo

Admin (Dashboard)

  • Login Page — Email & password authentication via Supabase Auth
  • Dashboard — Overview panel after login
  • Projects — Create, edit, delete projects; manage image, links, features, tech stack, publish status, and order
  • Certificates — Upload and delete certificate images
  • Comments — View all comments; pin/unpin for highlighting; delete inappropriate comments

Build for Production

npm run build

Upload the contents of the dist/ folder to your hosting provider.


Credits & Contact

Eki Zulfar Rachman
Website: eki.my.id · GitHub: EkiZR

Thanks to LottieFiles and Claude.

⭐ If this project helped you, consider giving it a star on GitHub!

more like this

agentlytics

Comprehensive analytics dashboard for AI coding agents — Cursor, Windsurf, Claude Code, VS Code Copilot, Zed, Antigravi…

JavaScript555
@f

ESP_OTA_Dashboard

an efficient and user friendly OTA server equipped with a powerful WEB UI, designed to effortlessly manage both your ES…

HTML54

React-Media-Library

React Media Library is a UI package similar to the WordPress' media library, but for React. It features file drag-and-d…

TypeScript56

search

search projects, people, and tags