Portofolio_V5 and the two-role portfolio stack built on Supabase
Personal portfolio built with React + Supabase, featuring an admin dashboard for managing projects, certificates, and comments.
At a glance
- What is it?
- Portofolio_V5 is a React and Supabase portfolio template with a public site and an admin dashboard, where the interesting part is the database design: role checks live in a profiles table and every write is gated by row level security policies that query it. The trade is that all the SQL for a new deployment arrives as one long script you have to paste into the Supabase SQL Editor.
- Who is it for?
- Portofolio_V5 is a good starting point for a personal portfolio that needs projects, certificates and a public comment box, since the Supabase schema and the row level security policies are written out rather than left to guesswork, and the role check is a single profiles lookup rather than a scattered set of conditions.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 50 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two faces of the same app: public site and admin dashboard
Portofolio_V5 is a personal portfolio project built with React and Supabase, and it ships as two applications in one codebase. The public side shows projects, certificates and comments, and lets a visitor leave a comment. The admin side is a dashboard behind a login where the owner gets full create, read, update and delete over projects and certificates, plus the ability to delete and to pin or unpin comments.
That role split is the whole design, and the documentation states it as a simple table. A visitor can view projects, certificates and comments and leave a comment. An admin can log into the dashboard and manage the content.
The stack is a familiar one for a frontend developer landing on a Supabase project: React and Tailwind CSS for the interface, Supabase for the data, AOS and Framer Motion for animation, Lucide for icons, the MUI component library and SweetAlert2 for alert dialogs. The package manifest is broader than that list, with several other animation and component packages alongside them, which is worth a second look before you adopt the whole dependency set.
The repository is at version 0.0.0 and marked private in package.json, and the project publishes no GitHub releases. The live demo sits at ekizr.com, and the README points there as the reference for what the finished thing looks like.
The four tables and why projects uses jsonb
The schema is small and deliberate. Four tables carry the data, and the projects table is the most interesting because its content columns are jsonb rather than typed columns:
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()
);Features and TechStack are jsonb, so a project can carry a list of feature bullets and a list of technologies without a schema change each time you add a new tag. The cost is that the database cannot validate the shape of that data, and a typo in a feature list is a data problem rather than a constraint problem. order_index gives manual sort control, and is_published gives a draft flag.
The certificates table is almost bare, holding an image path and a timestamp. The portfolio_comments table is the interesting one: it uses a uuid primary key defaulted with gen_random_uuid(), and stores content, user_name, profile_image and is_pinned, with content and user_name marked NOT NULL. The profiles table ties a row to auth.users with ON DELETE CASCADE, and constrains role to a CHECK of either admin or user, with a UNIQUE username.
Row level security as the only real gate on writes
Every table has row level security enabled before any policy is created:
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;That is the security model of the whole project, and it is worth understanding in detail because it determines what your frontend code can and cannot do.
Reads of projects, certificates and comments are public. The profiles table is different: authenticated users may only read their own profile, and the comment on the policy says 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);Writes are all admin-only, and the admin check is a subquery against profiles looking for role = 'admin' matching auth.uid(). The comment insert policy is deliberately open, but it constrains what can be written:
CREATE POLICY "public insert comment"
ON public.portfolio_comments FOR INSERT
WITH CHECK (is_pinned = false);So anyone can post a comment, but nobody can post one that claims to be pinned. The pinning decision stays with the admin, and the client cannot forge it. That is the one place in the schema where the public write surface is narrowed, and it is worth remembering that every other column a visitor can submit is unvalidated.
Storage buckets and a public profile image bucket
Images live in three Supabase storage buckets, and the script creates them with the INSERT INTO storage.buckets form and ON CONFLICT DO NOTHING, so re-running the script does not fail on an existing bucket.
project-images and certificate-images are both created as public buckets, with an admin-only insert policy that checks the bucket id and the admin role, and a public read policy. So project and certificate images can be uploaded only by the admin, and read by anyone.
profile-images is different in a way that matters. It is also public, but its insert policy is granted TO public, so any visitor can upload into it. The read policy is likewise TO public. The bucket exists because the comment table stores a profile_image field, which means commenters can attach a picture to a comment. The README's optional default pinned comment references a file in this bucket, so the bucket is expected to hold user-supplied avatars.
The consequence is that profile-images is a public bucket with anonymous write access. Nothing in the script sets a file size limit, a content type restriction or a retention policy, so treat anything uploaded there as untrusted content served from your own domain.
Getting the site running from a clone
The setup path is short. The prerequisites are Node.js 14.x or newer and npm or yarn, and the clone and install are three commands:
git clone https://github.com/EkiZR/Portofolio_V5.git
cd Portofolio_V5
npm installIf npm complains about peer dependencies, the documented fallback is npm install --legacy-peer-deps, which is a hint that the dependency set contains version ranges that do not agree with each other.
Two environment variables are needed, read by Vite, and the client module refuses to start without them:
VITE_SUPABASE_URL=your-supabase-project-url
VITE_SUPABASE_ANON_KEY=your-supabase-anon-keyThe supabase.js module reads those values and throws a Supabase credentials missing. Check your .env file. error if either is absent, which is a good failure mode because you find out at startup rather than at the first query.
The scripts in package.json are the standard Vite set: dev for the dev server, build for a production bundle, lint for eslint and preview for serving the build locally. The tree also carries a vercel.json, so a Vercel deployment is the assumed target.
Turning a Supabase user into the admin row
The admin account is created in two steps, and the second one is a single insert.
First you add a user in the Supabase dashboard under Authentication and Users, then copy the generated User ID. That user is an ordinary auth.users row with no privileges of its own.
Then you run this in the SQL Editor, replacing USER_UUID with the copied id:
INSERT INTO public.profiles (id, username, role)
VALUES ('USER_UUID', 'eki', 'admin');The role value is what every admin policy checks, so this one row is the entire authorization model. Because the profiles table has a foreign key to auth.users with ON DELETE CASCADE, deleting the auth user also removes the profile row, and with it the admin's write access.
There is one more manual step, and it is easy to miss because it is not SQL. To have new comments appear without a page reload, you enable Realtime on the portfolio_comments table through Table Editor in the Supabase dashboard. The README describes this as a step in getting started, not as an optional extra.
The whole schema, the policies and the storage buckets arrive as one script that you paste into the SQL Editor and run once. That is convenient for a template and a problem for a production deployment, where you would want migrations instead of a paste.
What the project does not give you, and the licence question
The limits of this template are structural, and they show up in the data model. There are four tables and three buckets, and they cover projects, certificates, comments and profiles. There is no table for blog posts, for tags, for work experience, for a contact form, for analytics or for file downloads, and there is no admin UI for any of those because there is no model for them.
The role system has exactly two values, admin and user, enforced by a CHECK constraint. A third role, or per-user permissions, would require changing the constraint and every policy that references it. That is a schema-level constraint, not a configuration one.
The comment surface is open by design. Any visitor can insert a row into portfolio_comments, and the only condition is that is_pinned is false. There is no rate limit in the schema, no moderation queue, and no length limit on content, so the admin dashboard's delete button is the entire moderation story.
The licence is the other thing to settle. The repository listing shows no recognised licence, and the tree has a .gitignore and a README but no LICENSE file. A template with no licence is legally ambiguous: without a grant of rights you do not have permission to reuse the code, and without a warranty you take on the risk yourself. If you plan to publish your own version, ask the author for a licence file or write your own implementation.
The repository is not archived and the last push was on 2026-09-03, with no GitHub releases published.
Editorial conclusion
Portofolio_V5 is a good starting point for a personal portfolio that needs projects, certificates and a public comment box, since the Supabase schema and the row level security policies are written out rather than left to guesswork, and the role check is a single profiles lookup rather than a scattered set of conditions. It is a poor fit for a multi-tenant site, for a portfolio that needs anything beyond projects, certificates and comments, and for anyone unwilling to read SQL before deploying. Before using it, note that the repository publishes no licence file, that the Node.js requirement is stated as 14.x while the dependency tree pins React 18 and Vite-era tooling, and that the comments insert policy is explicitly open to the public with pinning blocked, so treat the comment box as untrusted input.
Frequently asked questions
How do I set up Portofolio_V5 locally?
Clone the repository, run npm install (or npm install --legacy-peer-deps if you hit peer dependency issues), then create a .env file with VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY and run the schema script once in the Supabase SQL Editor. The npm scripts include dev, build, lint and preview.
How do I make a user an admin in Portofolio_V5?
Add the user in the Supabase dashboard under Authentication and Users, copy the generated User ID, then insert a row into public.profiles with that id, a username and role set to admin. Every admin write policy checks that profiles row, so the insert is what grants access.
What are the different user roles in Portofolio_V5?
There are two. A visitor can view projects, certificates and comments and leave a comment, while an admin can log into the dashboard with full CRUD on projects and certificates and can delete and pin or unpin comments. The role column in the profiles table is constrained to admin or user.
Which Supabase storage buckets does Portofolio_V5 use?
Three buckets are created: project-images and certificate-images, which only the admin can upload to, and profile-images, which is public including for inserts, so any visitor can upload a profile picture to attach to a comment.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ekizr-portofolio-v5)Was this useful?