A Next.js web application for creating and sharing interactive Valentine's Day templates with Supabase backend.
- π Email-only authentication (magic link/OTP) via Supabase Auth
- π Customizable Valentine question editor
- π¨ Beautiful animated UI with floating hearts and sparkles
- π Interactive "Yes/No" buttons with confetti effects
- π Shareable public links
- π± Mobile responsive design
- β¨ Smooth animations and pastel gradients
- Next.js 15 - App Router with TypeScript
- Supabase - Authentication and Database
- Tailwind CSS - Styling
- Framer Motion - Animations
- Canvas Confetti - Celebration effects
Before you begin, ensure you have:
- Node.js 18+ installed
- A Supabase account and project
-
Create a new Supabase project at https://supabase.com
-
Create a
projectstable with the following schema:
CREATE TABLE projects (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
owner_email TEXT NOT NULL,
template_type TEXT NOT NULL,
template_code TEXT NOT NULL,
slug TEXT UNIQUE,
is_published BOOLEAN DEFAULT true,
data JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create index for faster lookups
CREATE INDEX idx_projects_owner_type ON projects(owner_email, template_type);
CREATE INDEX idx_projects_slug ON projects(slug);
CREATE INDEX idx_projects_type_published ON projects(template_type, is_published);
CREATE INDEX idx_projects_owner_code ON projects(owner_email, template_code);- Enable Row Level Security (RLS) (Optional - API routes use service role key to bypass RLS):
-- Enable RLS on projects table
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
-- Policy: Anyone can view published templates (for public sharing)
CREATE POLICY "Public can view published templates"
ON projects
FOR SELECT
USING (is_published = true);Note: The application uses API routes with the service role key to handle authentication and authorization, which bypasses RLS. You can optionally enable these policies for additional security layers.
- Create test user and template record:
-- No Supabase Auth user needed!
-- Just insert a template record with email + template_code:
INSERT INTO projects (owner_email, template_type, template_code, slug, is_published, data)
VALUES
('your-test-email@example.com', 'simp', 'YOUR-SECRET-CODE', 'test-slug-123', true, '{"question": "Will you be my Valentine? π"}');Important: Users access the editor by entering their email + template_code. No Supabase Auth account needed.
- Clone or navigate to this directory:
cd d:/template/simp- Install dependencies:
npm install- Create a
.env.localfile in the root directory:
cp .env.example .env.local- Add your Supabase credentials to
.env.local:
NEXT_PUBLIC_SUPABASE_URL=your-supabase-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-supabase-service-role-key
NEXT_PUBLIC_BASE_URL=http://localhost:3000
Important: The SUPABASE_SERVICE_ROLE_KEY is required for API routes to bypass Row Level Security policies. You can find this in your Supabase project settings under API β Project API keys β service_role key. Keep this secret!
Run the development server:
npm run devOpen http://localhost:3000 in your browser.
- User logs in with email and template_code (no password, no Supabase Auth)
- System verifies template exists with matching email, template_code, and template_type
- If match found, grants access via session storage
- Strict Access Control: Backend checks if a row exists where:
owner_email = user.email(normalized: trimmed and lowercased)template_code = user's entered codetemplate_type = 'simp'
- If NO match:
- β Access denied on login page
- Shows: "Invalid email or template code"
- If match exists:
- β
Load
data.questioninto editor - If
datais NULL, initialize with default:{ "question": "Will you be my Valentine? π" } - User can edit and save
- β
Load
On every save, the system:
- Updates
data.questionwith new text - Sets
is_published = true - Ensures
slugfield is not null (generates one if missing) - Auto-generates and displays shareable link:
https://yourdomain.com/v/{slug}
- Accessible via
/v/[slug] - Fetches template where:
slug = params.slugtemplate_type = 'simp'is_published = true
- Displays interactive Valentine UI (read-only)
- Anyone can view, only owner can edit
- Row Level Security (RLS) enabled on templates table
- Users can SELECT and UPDATE only their own templates (where
owner_email = auth.email()) - Public can SELECT templates where
is_published = true - Email matching is exact (trimmed and lowercased for consistency)
simp/
βββ app/
β βββ auth/
β β βββ login/ # Login page
β β βββ callback/ # Auth callback handler
β β βββ auth-code-error/ # Error page
β βββ editor/ # Template editor (protected)
β βββ v/[slug]/ # Public view page
β βββ layout.tsx # Root layout
β βββ page.tsx # Home (redirects to login)
β βββ globals.css # Global styles
βββ components/
β βββ FloatingHearts.tsx # Animated hearts background
β βββ Sparkles.tsx # Sparkle animations
β βββ ValentineCard.tsx # Main Valentine UI card
βββ lib/
β βββ supabase/
β β βββ client.ts # Client-side Supabase
β β βββ server.ts # Server-side Supabase
β βββ types.ts # TypeScript types
β βββ utils.ts # Utility functions
βββ package.json
- Push your code to GitHub
- Import project to Vercel
- Add environment variables in Vercel dashboard
- Deploy
Make sure to set these in your hosting platform:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEY
Edit app/editor/page.tsx line with the default question value.
Edit tailwind.config.ts to change the color scheme.
The system supports multiple template types. Just change template_type = 'simp' to your desired type in the queries.
This means no template record exists for your email with template_type = 'simp'.
Solution: Create a template record manually:
INSERT INTO templates (owner_email, template_type, slug, is_published, data)
VALUES
('your-email@example.com', 'simp', 'unique-slug-here', true, '{"question": "Will you be my Valentine? π"}');- Emails are normalized (trimmed and lowercased)
- Check browser console for logs:
π Auth check,π§ Normalized email,π Template query - Verify your email in Supabase Auth matches exactly (no trailing spaces)
If you see "permission denied" errors:
- Verify RLS policies are created correctly
- Check that
auth.email()matches your logged-in email - Try disabling RLS temporarily for debugging:
ALTER TABLE templates DISABLE ROW LEVEL SECURITY;
- Open browser DevTools β Console
- Look for debug logs:
π Auth check:- Shows if user is authenticatedπ§ Normalized email:- Shows the email being used in queriesπ Template query:- Shows the database query resultβ Template found:orβ No template found
- Verify in Supabase Dashboard:
- Go to Table Editor β templates
- Check if a row exists with your email and
template_type = 'simp'
-
Check Authentication:
// In browser console on editor page: console.log('Look for: π Auth check')
-
Verify Email Matching:
- Check the console for
π§ Normalized email - Compare with Supabase Auth users table
- Ensure no case mismatch or spaces
- Check the console for
-
Check Template Query:
- Console shows
π Template querywith results - If
fetchError, check RLS policies - If no data, template doesn't exist
- Console shows
-
Test Database Direct Query:
SELECT * FROM templates WHERE owner_email = 'your-email@example.com' AND template_type = 'simp';
- Verify Supabase URL and anon key are correct
- Check email provider is enabled in Supabase
- Ensure callback URL is whitelisted in Supabase
- Verify the
templatestable exists with correct schema - Check Row Level Security (RLS) policies if enabled
MIT
For issues and questions, please check the Supabase and Next.js documentation.