Skip to content

EkiZR/Portofolio_V5

Repository files navigation

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!

About

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

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages