A personal note gallery with file uploads, folders, tags, and password protection. Backend powered by Supabase (free tier). No Firebase needed.
- What You Need (Prerequisites)
- Download & Extract the Project
- Create a Free Supabase Account
- Create a Supabase Project
- Run the Database Migrations (SQL)
- Deploy the Edge Functions
- Get Your Supabase API Keys
- Edit the .env File
- Install & Run the App
- Set Your Admin Password
- Log In & Use the App
- Troubleshooting
- How the App Works (Quick Reference)
Before starting, make sure you have these installed on your computer:
| Tool | Purpose | Download |
|---|---|---|
| Node.js (v18 or newer) | Runs the app | https://nodejs.org → click "LTS" |
| npm | Installs packages (comes with Node.js) | Included with Node.js |
| Supabase CLI | Deploys backend functions | See Step 6 below |
| A web browser | Chrome, Firefox, Edge | Already have it |
| A text editor | Edit config files | VS Code: https://code.visualstudio.com |
Check Node.js is installed — open your Terminal and run:
node --version
You should see something like v20.0.0. If you see an error, install Node.js first.
-
Download the
ratulnotehub-clean.zipfile (the one Claude gave you). -
Open your Downloads folder.
-
Right-click on
ratulnotehub-clean.zip→ Extract Here (or Extract All on Windows). -
You will now have a folder called
notehub-clean-srcinside a folder calledratulnotehub-clean. -
Open your Terminal (or Command Prompt on Windows) and navigate into the project:
cd ~/Downloads/ratulnotehub-clean/notehub-clean-src💡 Tip: On Windows, use
cd %USERPROFILE%\Downloads\ratulnotehub-clean\notehub-clean-src
- Confirm you are in the right place:
lsYou should see files like: index.html, package.json, vite.config.ts, src/, supabase/
Supabase is a free backend service that stores your files, notes, and password.
-
Go to https://supabase.com
-
Click "Start your project" (green button, top right).
-
Sign up with GitHub (easiest) or your email.
-
Verify your email if asked.
-
You are now on the Supabase Dashboard.
-
On the Supabase Dashboard, click "New project".
-
Fill in the form:
- Organization: Your name (auto-created)
- Project name:
notehub(or anything you like) - Database Password: Create a strong password and save it somewhere — you'll need it later
- Region: Pick the one closest to you (e.g.,
Southeast Asiaif you're in Bangladesh)
-
Click "Create new project".
-
Wait about 1-2 minutes while Supabase sets up your database. You'll see a loading spinner.
-
Once done, you'll see your project dashboard. Keep this tab open — you'll need it.
This step creates all the database tables the app needs (notes, folders, tags, password storage, file storage).
-
In your Supabase project dashboard, look at the left sidebar.
-
Click "SQL Editor" (the icon looks like
</>). -
Click "New query" (top left of the SQL editor).
-
You need to run 3 SQL files from your project. Open the folder:
notehub-clean-src/supabase/migrations/You will see 3
.sqlfiles. Run them in order (oldest timestamp first):
-- Create auth_user table for single admin user
CREATE TABLE public.auth_user (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL
);
ALTER TABLE public.auth_user ENABLE ROW LEVEL SECURITY;
-- Create login_attempts table for rate limiting
CREATE TABLE public.login_attempts (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
ip_address TEXT NOT NULL,
attempt_count INTEGER NOT NULL DEFAULT 0,
last_attempt_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
blocked_until TIMESTAMP WITH TIME ZONE
);
CREATE UNIQUE INDEX idx_login_attempts_ip ON public.login_attempts (ip_address);
ALTER TABLE public.login_attempts ENABLE ROW LEVEL SECURITY;
-- Create sessions table
CREATE TABLE public.sessions (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES public.auth_user(id) ON DELETE CASCADE,
token TEXT NOT NULL UNIQUE,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
last_activity TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
ALTER TABLE public.sessions ENABLE ROW LEVEL SECURITY;-
After pasting, click the green "Run" button (or press
Ctrl+Enter). -
You should see "Success. No rows returned" at the bottom. That means it worked.
-- Folders table
CREATE TABLE public.folders (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL,
parent_id UUID REFERENCES public.folders(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
ALTER TABLE public.folders ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow all access to folders" ON public.folders FOR ALL USING (true) WITH CHECK (true);
-- Notes table
CREATE TABLE public.notes (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
title TEXT NOT NULL,
file_url TEXT,
file_type TEXT,
file_size BIGINT DEFAULT 0,
folder_id UUID REFERENCES public.folders(id) ON DELETE SET NULL,
order_index INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
ALTER TABLE public.notes ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow all access to notes" ON public.notes FOR ALL USING (true) WITH CHECK (true);
CREATE INDEX idx_notes_folder ON public.notes(folder_id);
CREATE INDEX idx_notes_order ON public.notes(order_index);
-- Tags table
CREATE TABLE public.tags (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
ALTER TABLE public.tags ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow all access to tags" ON public.tags FOR ALL USING (true) WITH CHECK (true);
-- Note-Tags junction
CREATE TABLE public.note_tags (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
note_id UUID NOT NULL REFERENCES public.notes(id) ON DELETE CASCADE,
tag_id UUID NOT NULL REFERENCES public.tags(id) ON DELETE CASCADE,
UNIQUE(note_id, tag_id)
);
ALTER TABLE public.note_tags ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow all access to note_tags" ON public.note_tags FOR ALL USING (true) WITH CHECK (true);
-- Auto-update updated_at on notes
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql SET search_path = public;
CREATE TRIGGER update_notes_updated_at
BEFORE UPDATE ON public.notes
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
-- Storage bucket for files
INSERT INTO storage.buckets (id, name, public) VALUES ('note-files', 'note-files', true);
CREATE POLICY "Anyone can read note files" ON storage.objects FOR SELECT USING (bucket_id = 'note-files');
CREATE POLICY "Anyone can upload note files" ON storage.objects FOR INSERT WITH CHECK (bucket_id = 'note-files');
CREATE POLICY "Anyone can update note files" ON storage.objects FOR UPDATE USING (bucket_id = 'note-files');
CREATE POLICY "Anyone can delete note files" ON storage.objects FOR DELETE USING (bucket_id = 'note-files');Click Run. You should see "Success".
-- Add favorites column
ALTER TABLE public.notes ADD COLUMN is_favorited boolean NOT NULL DEFAULT false;
-- Version history table
CREATE TABLE public.note_versions (
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
note_id uuid NOT NULL REFERENCES public.notes(id) ON DELETE CASCADE,
content text,
file_url text,
version_number integer NOT NULL DEFAULT 1,
created_at timestamp with time zone NOT NULL DEFAULT now()
);
ALTER TABLE public.note_versions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow all access to note_versions"
ON public.note_versions FOR ALL TO public
USING (true) WITH CHECK (true);
CREATE INDEX idx_note_versions_note_id ON public.note_versions(note_id);Click Run. You should see "Success".
✅ All 3 migrations done! Your database tables are now created.
Edge Functions are the backend logic that handles login, logout, and password management. You need to deploy them from your terminal using the Supabase CLI.
On Linux/macOS:
npm install -g supabaseOn Windows (PowerShell as Administrator):
npm install -g supabaseVerify it installed:
supabase --versionYou should see a version number like 1.x.x.
supabase loginThis will open your browser. Log in with your Supabase account. Then come back to the terminal — it should say "Logged in".
- Go to your Supabase project dashboard in the browser.
- Click "Project Settings" (gear icon, bottom of left sidebar).
- Click "General".
- Find "Reference ID" — it looks like:
srmpdcpvfecpeomsgqlq - Copy it.
In your terminal, make sure you are inside the project folder:
cd ~/Downloads/ratulnotehub-clean/notehub-clean-srcThen run (replace YOUR_PROJECT_REF with your actual Reference ID):
supabase link --project-ref YOUR_PROJECT_REFIt will ask for your database password — enter the password you set in Step 4.
Now deploy all 5 Edge Functions with one command:
supabase functions deploy login
supabase functions deploy logout
supabase functions deploy validate-session
supabase functions deploy change-password
supabase functions deploy setup-adminEach one should say "Done: Deployed Function".
✅ Edge Functions are now live on Supabase!
-
In your Supabase project dashboard, click "Project Settings" (gear icon).
-
Click "API" in the settings menu.
-
You will see two important things — copy both:
-
Project URL — looks like:
https://srmpdcpvfecpeomsgqlq.supabase.co -
anon / public key — a long string starting with
eyJ...eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
-
⚠️ Do NOT share theservice_rolekey with anyone. Only copy theanonkey.
The .env file tells the app where your Supabase database is and how to connect to it.
-
Open the project folder
notehub-clean-srcin your text editor (VS Code). -
Find the file named
.envin the root of the folder.💡 On Linux/macOS, files starting with
.are hidden by default. In your file manager, pressCtrl+Hto show hidden files. In VS Code it shows automatically. -
Open
.env. It looks like this:VITE_SUPABASE_PROJECT_ID="srmpdcpvfecpeomsgqlq" VITE_SUPABASE_PUBLISHABLE_KEY="eyJhbGci..." VITE_SUPABASE_URL="https://srmpdcpvfecpeomsgqlq.supabase.co" -
Replace all three values with YOUR values from Step 7:
VITE_SUPABASE_PROJECT_ID="YOUR_REFERENCE_ID_HERE" VITE_SUPABASE_PUBLISHABLE_KEY="YOUR_ANON_KEY_HERE" VITE_SUPABASE_URL="YOUR_PROJECT_URL_HERE"Example (with real-looking fake values):
VITE_SUPABASE_PROJECT_ID="abcdefghijklmnop" VITE_SUPABASE_PUBLISHABLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.abc123" VITE_SUPABASE_URL="https://abcdefghijklmnop.supabase.co" -
Save the file (
Ctrl+S).
-
Open your terminal and go to the project folder:
cd ~/Downloads/ratulnotehub-clean/notehub-clean-src
-
Install all dependencies (only needed once):
npm install
This will take 1-2 minutes. You will see a progress bar.
-
Start the development server:
npm run dev
-
You will see output like:
VITE v5.x.x ready in 500 ms ➜ Local: http://localhost:8080/ -
Open your browser and go to: http://localhost:8080
You should see the Note Hub login page! 🎉
The app needs a password to protect your notes. You need to set it once using the setup-admin Edge Function.
There are two ways to do this:
- Go to your Supabase project dashboard.
- Click "Edge Functions" in the left sidebar.
- Click on "setup-admin".
- Click the "Test" button (or use the "Invoke" section).
- In the request body, enter:
Replace
{ "password": "your_password_here" }your_password_herewith your actual password (minimum 6 characters). - Click "Send".
- You should see:
{"message": "Admin user created"}
curl -X POST https://YOUR_PROJECT_REF.supabase.co/functions/v1/setup-admin \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ANON_KEY" \
-d '{"password": "your_password_here"}'Replace:
YOUR_PROJECT_REFwith your project Reference IDYOUR_ANON_KEYwith your anon/public keyyour_password_herewith your chosen password
You should see: {"message":"Admin user created"}
✅ Your password is now saved securely in the database (hashed — not stored as plain text).
- Go to http://localhost:8080 in your browser.
- You will be redirected to the login page.
- Enter the password you set in Step 10.
- Click "Continue".
- You are now in your Note Hub dashboard!
| Feature | How to use |
|---|---|
| Upload files | Click "New" → "Upload file" or drag & drop files onto the screen |
| Create text notes | Click "New" → "New text file" |
| Create folders | Click "New folder" in the left sidebar |
| Preview notes | Click on any note card |
| Edit text files | Click ⋮ menu on a note → "Edit" |
| View CSV files | Click a .csv note — opens as a table |
| View Word docs | Click a .docx note — renders the text |
| Search notes | Type in the search bar at the top |
| Add tags | Click ⋮ on a note → "Add tag" |
| Favorite a note | Click ⋮ → "Favorite" (pins it to top) |
| Move to folder | Click ⋮ → "Move to…" |
| Drag & reorder | Click and drag note cards to reorder them |
| Bulk select | Click "Select" button → check multiple notes → Delete or Move |
| Change password | Bottom of left sidebar → "Change password" |
- 📄 Text:
.txt,.md - 📊 Spreadsheet:
.csv - 📑 Word:
.docx - 📕 PDF:
.pdf - 🖼️ Images:
.png,.jpg,.jpeg,.gif,.webp
You are in the wrong folder. Make sure you are inside notehub-clean-src:
cd ~/Downloads/ratulnotehub-clean/notehub-clean-src
npm install- Open browser DevTools (
F12) → Console tab - If you see "Failed to fetch" or "Invalid API key" — your
.envfile has wrong values. Re-check Step 8. - Make sure the
.envfile is saved. - Stop the server (
Ctrl+C) and restart:npm run dev
- Make sure you completed Step 10 (set admin password).
- The password is case-sensitive.
- If you forgot your password, call
setup-adminagain with a new password — it will update it.
Wait 15 minutes. This is a security feature — 5 wrong attempts triggers a lockout.
To reset immediately, go to Supabase → SQL Editor → run:
DELETE FROM public.login_attempts;- Check your Supabase Storage bucket exists: Supabase dashboard → "Storage" → you should see
note-filesbucket. - If the bucket is missing, go to SQL Editor and run Migration 2 again (the storage part).
Make sure you are logged in:
supabase login
supabase link --project-ref YOUR_PROJECT_REF
supabase functions deploy setup-adminSessions last 30 minutes by default. To change this, edit supabase/functions/login/index.ts:
const SESSION_DURATION_MINUTES = 30; // Change this numberThen re-deploy: supabase functions deploy login
Architecture overview:
Your Browser (React app)
│
├── Supabase Database (stores notes metadata, folders, tags, password hash)
├── Supabase Storage (stores actual files: PDFs, images, text files)
└── Supabase Edge Functions (handles login, logout, password change)
Security model:
- Your password is hashed (SHA-256) before saving — Supabase never stores plain text passwords
- Login creates a session token valid for 30 minutes
- After 5 wrong password attempts, login is locked for 15 minutes (brute-force protection)
- All file storage is tied to your Supabase project — only you have access
Free tier limits (Supabase):
- Database: 500 MB
- Storage: 1 GB
- Edge Function calls: 500,000/month
- All plenty for personal use
Your Note Hub is running locally. Every file you upload goes to Supabase Storage, and all your note data is saved in Supabase Database — both are free and persist even after you close your computer.
To stop the app: press Ctrl+C in the terminal.
To start it again later:
cd ~/Downloads/ratulnotehub-clean/notehub-clean-src
npm run devThen open http://localhost:8080.
Built with React, TypeScript, Vite, Tailwind CSS, shadcn/ui, and Supabase.