https://afishadfw.vercel.app/

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();