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
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
| 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 |
- Node.js
>= 14.x - npm or yarn
git clone https://github.com/EkiZR/Portofolio_V5.git
cd Portofolio_V5
npm installIf you encounter peer dependency issues:
npm install --legacy-peer-deps
Create a .env file in the root directory:
VITE_SUPABASE_URL=your-supabase-project-url
VITE_SUPABASE_ANON_KEY=your-supabase-anon-keyFind these in your Supabase project under Settings β API.
β οΈ Never commit.envto version control β make sure it's in.gitignore.
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)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
);Go to Table Editor β portfolio_comments β Enable Realtime.
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');npm run devOpen http://localhost:5173 in your browser.
- 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
- 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
npm run buildUpload the contents of the dist/ folder to your hosting provider.
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!