A production-ready, SEO-optimized events calendar application designed for managing and promoting community events. The platform features a responsive weekly calendar view, role-based authentication, and a robust admin dashboard for approving, editing, and organizing events. Built with React 18, TypeScript, Tailwind CSS, and shadcn/ui on the frontend, and powered by Supabase for authentication, PostgreSQL database, file storage, and RLS-based security. The site supports image uploads, multi-language functionality (English/Russian), and is fully deployed on Vercel for fast, scalable hosting. This project demonstrates my ability to deliver a secure, scalable, and user-friendly web application from design to deployment, complete with modern UI/UX and backend integration.
Tags:
#Web #Development React TypeScript Supabase Vercel TailwindCSS shadcn/ui Full-Stack Development SEO Multilingual Authentication
SQL Here's the complete SQL to recreate the database structure:
-- 1. Create enum for roles
CREATE TYPE public.app_role AS ENUM ('admin', 'user');
-- 2. Create events table
CREATE TABLE public.events (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
title TEXT NOT NULL,
description TEXT NOT NULL,
event_date DATE NOT NULL,
event_time TIME WITHOUT TIME ZONE,
event_url TEXT,
image_url TEXT,
approved BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- 3. Create user_roles table
CREATE TABLE public.user_roles (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL,
role app_role NOT NULL DEFAULT 'user'::app_role,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- 4. Create banner_config table
CREATE TABLE public.banner_config (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
image_url TEXT NOT NULL DEFAULT ''::text,
link_url TEXT NOT NULL DEFAULT ''::text,
is_active BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- 5. Create helper functions
CREATE OR REPLACE FUNCTION public.is_admin(_user_id uuid)
RETURNS boolean
LANGUAGE sql
STABLE SECURITY DEFINER
SET search_path TO 'public'
AS $$
SELECT CASE
WHEN _user_id IS NULL THEN false
ELSE EXISTS (
SELECT 1 FROM public.user_roles
WHERE user_id = _user_id AND role = 'admin'::app_role
)
END;
$$;
CREATE OR REPLACE FUNCTION public.get_user_role(_user_id uuid)
RETURNS app_role
LANGUAGE sql
STABLE SECURITY DEFINER
SET search_path TO 'public'
AS $$
SELECT role FROM public.user_roles
WHERE user_id = _user_id LIMIT 1;
$$;
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public'
AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$;
CREATE OR REPLACE FUNCTION public.handle_new_user_role()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public'
AS $$
BEGIN
IF (SELECT COUNT(*) FROM auth.users) = 1 THEN
INSERT INTO public.user_roles (user_id, role) VALUES (NEW.id, 'admin'::app_role);
ELSE
INSERT INTO public.user_roles (user_id, role) VALUES (NEW.id, 'user'::app_role);
END IF;
RETURN NEW;
END;
$$;
-- 6. Enable RLS on all tables
ALTER TABLE public.events ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.user_roles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.banner_config ENABLE ROW LEVEL SECURITY;
-- 7. Events RLS policies
CREATE POLICY "view_events_policy" ON public.events FOR SELECT
USING (CASE WHEN auth.uid() IS NULL THEN approved = true
WHEN is_admin(auth.uid()) THEN true
ELSE approved = true END);
CREATE POLICY "insert_events_policy" ON public.events FOR INSERT WITH CHECK (true);
CREATE POLICY "update_events_policy" ON public.events FOR UPDATE USING (is_admin(auth.uid()));
CREATE POLICY "delete_events_policy" ON public.events FOR DELETE USING (is_admin(auth.uid()));
-- 8. User roles RLS policies
CREATE POLICY "Users can view their own role" ON public.user_roles FOR SELECT USING (user_id = auth.uid());
CREATE POLICY "Admins can view all roles" ON public.user_roles FOR SELECT USING (is_admin(auth.uid()));
CREATE POLICY "Admins can manage roles" ON public.user_roles FOR ALL USING (is_admin(auth.uid()));
-- 9. Banner config RLS policies
CREATE POLICY "Anyone can view banner config" ON public.banner_config FOR SELECT USING (true);
CREATE POLICY "Admins can insert banner config" ON public.banner_config FOR INSERT WITH CHECK (is_admin(auth.uid()));
CREATE POLICY "Admins can update banner config" ON public.banner_config FOR UPDATE USING (is_admin(auth.uid()));
CREATE POLICY "Admins can delete banner config" ON public.banner_config FOR DELETE USING (is_admin(auth.uid()));
-- 10. Create trigger for auto-assigning roles on user signup
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user_role();