+
+
+
+
+
+
+
+
+ ✨ Premium Event Ticketing Platform
+
-
-
-
-
-
-
- Black Canyon
-
- Tickets
-
-
-
- Elegant ticketing platform for Colorado's most prestigious venues
-
-
-
-
- Self-serve event setup
-
-
-
- Automated Stripe payouts
-
-
-
- Mobile QR scanning — no apps required
-
-
-
-
-
-
-
-
- 💡
-
-
Quick Setup
-
Create events in minutes
-
-
-
-
- 💸
-
-
Fast Payments
-
Automated Stripe payouts
-
-
-
-
- 📊
-
-
Live Analytics
-
Dashboard + exports
-
-
-
-
-
-
-
-
-
-
-
-
-
Organizer Login
-
Manage your events and track ticket sales
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- © 2024 Black Canyon Tickets • Montrose, CO
+
+
+ Premium Ticketing for
+
+ Colorado's Elite
+
+
+
+
+
+ Elegant self-service platform designed for upscale venues, prestigious events, and discerning organizers
+
+
+
+
+
+
+
+
+ No setup fees
+
+
+
+ Instant payouts
+
+
+
+ Mobile-first design
+
+
-
-
-
\ No newline at end of file
+ }
+
+ .animate-pulse {
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+ }
+
+ .delay-1000 {
+ animation-delay: 1s;
+ }
+
+ .delay-500 {
+ animation-delay: 0.5s;
+ }
+
\ No newline at end of file
diff --git a/src/pages/login.astro b/src/pages/login.astro
new file mode 100644
index 0000000..dc3ac7d
--- /dev/null
+++ b/src/pages/login.astro
@@ -0,0 +1,294 @@
+---
+import LoginLayout from '../layouts/LoginLayout.astro';
+import { generateCSRFToken } from '../lib/auth';
+
+// Generate CSRF token for the form
+const csrfToken = generateCSRFToken();
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Black Canyon
+
+ Tickets
+
+
+
+ Elegant ticketing platform for Colorado's most prestigious venues
+
+
+
+
+ Self-serve event setup
+
+
+
+ Automated Stripe payouts
+
+
+
+ Mobile QR scanning — no apps required
+
+
+
+
+
+
+
+
+ 💡
+
+
Quick Setup
+
Create events in minutes
+
+
+
+
+ 💸
+
+
Fast Payments
+
Automated Stripe payouts
+
+
+
+
+ 📊
+
+
Live Analytics
+
Dashboard + exports
+
+
+
+
+
+
+
+
+
+
+
+
+
Organizer Login
+
Manage your events and track ticket sales
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ © 2024 Black Canyon Tickets • Montrose, CO
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/supabase/migrations/20250708_add_event_image_support.sql b/supabase/migrations/20250708_add_event_image_support.sql
new file mode 100644
index 0000000..a27dec7
--- /dev/null
+++ b/supabase/migrations/20250708_add_event_image_support.sql
@@ -0,0 +1,54 @@
+-- Add image_url column to events table
+ALTER TABLE events ADD COLUMN image_url TEXT;
+
+-- Create storage bucket for event images
+INSERT INTO storage.buckets (id, name, public)
+VALUES ('event-images', 'event-images', true);
+
+-- Create storage policy for authenticated users to upload event images
+CREATE POLICY "Users can upload event images" ON storage.objects
+FOR INSERT WITH CHECK (
+ bucket_id = 'event-images' AND
+ auth.uid() IS NOT NULL
+);
+
+-- Create storage policy for public read access to event images
+CREATE POLICY "Public read access to event images" ON storage.objects
+FOR SELECT USING (bucket_id = 'event-images');
+
+-- Create storage policy for users to delete their own event images
+CREATE POLICY "Users can delete their own event images" ON storage.objects
+FOR DELETE USING (
+ bucket_id = 'event-images' AND
+ auth.uid() IS NOT NULL
+);
+
+-- Update RLS policy for events to include image_url in SELECT
+DROP POLICY IF EXISTS "Users can view events in their organization" ON events;
+CREATE POLICY "Users can view events in their organization" ON events
+FOR SELECT USING (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE user_id = auth.uid()
+ )
+);
+
+-- Update RLS policy for events to include image_url in INSERT
+DROP POLICY IF EXISTS "Users can create events in their organization" ON events;
+CREATE POLICY "Users can create events in their organization" ON events
+FOR INSERT WITH CHECK (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE user_id = auth.uid()
+ )
+);
+
+-- Update RLS policy for events to include image_url in UPDATE
+DROP POLICY IF EXISTS "Users can update events in their organization" ON events;
+CREATE POLICY "Users can update events in their organization" ON events
+FOR UPDATE USING (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE user_id = auth.uid()
+ )
+);
+
+-- Add index on image_url for faster queries
+CREATE INDEX IF NOT EXISTS idx_events_image_url ON events(image_url) WHERE image_url IS NOT NULL;
\ No newline at end of file
diff --git a/supabase/migrations/20250708_add_marketing_kit_support.sql b/supabase/migrations/20250708_add_marketing_kit_support.sql
new file mode 100644
index 0000000..9e28ac5
--- /dev/null
+++ b/supabase/migrations/20250708_add_marketing_kit_support.sql
@@ -0,0 +1,203 @@
+-- Marketing Kit support for Event Marketing Toolkit
+-- This migration adds tables to store marketing kit assets and templates
+
+-- Table to store marketing kit assets for each event
+CREATE TABLE IF NOT EXISTS marketing_kit_assets (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ event_id UUID NOT NULL REFERENCES events(id) ON DELETE CASCADE,
+ organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
+ asset_type TEXT NOT NULL CHECK (asset_type IN ('social_post', 'flyer', 'email_template', 'qr_code')),
+ platform TEXT, -- For social posts: 'facebook', 'instagram', 'twitter', 'linkedin'
+ title TEXT NOT NULL,
+ content TEXT, -- JSON content for templates/configs
+ image_url TEXT, -- Generated image URL
+ download_url TEXT, -- Direct download URL for assets
+ file_format TEXT, -- 'png', 'jpg', 'html', 'txt', etc.
+ dimensions JSON, -- {"width": 1080, "height": 1080} for images
+ generated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+ expires_at TIMESTAMP WITH TIME ZONE, -- For temporary download links
+ metadata JSON, -- Additional configuration/settings
+ is_active BOOLEAN DEFAULT true,
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
+);
+
+-- Table to store reusable marketing templates
+CREATE TABLE IF NOT EXISTS marketing_templates (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
+ template_type TEXT NOT NULL CHECK (template_type IN ('social_post', 'flyer', 'email_template')),
+ platform TEXT, -- For social posts: 'facebook', 'instagram', 'twitter', 'linkedin'
+ name TEXT NOT NULL,
+ description TEXT,
+ template_data JSON NOT NULL, -- Template configuration and layout
+ preview_image_url TEXT,
+ is_default BOOLEAN DEFAULT false,
+ is_active BOOLEAN DEFAULT true,
+ created_by UUID REFERENCES users(id),
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
+);
+
+-- Table to track marketing kit generations and downloads
+CREATE TABLE IF NOT EXISTS marketing_kit_generations (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ event_id UUID NOT NULL REFERENCES events(id) ON DELETE CASCADE,
+ organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
+ generated_by UUID REFERENCES users(id),
+ generation_type TEXT NOT NULL CHECK (generation_type IN ('full_kit', 'individual_asset')),
+ assets_included TEXT[], -- Array of asset types included
+ zip_file_url TEXT, -- URL to download complete kit
+ zip_expires_at TIMESTAMP WITH TIME ZONE,
+ generation_status TEXT DEFAULT 'pending' CHECK (generation_status IN ('pending', 'processing', 'completed', 'failed')),
+ error_message TEXT,
+ download_count INTEGER DEFAULT 0,
+ last_downloaded_at TIMESTAMP WITH TIME ZONE,
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
+);
+
+-- Indexes for performance
+CREATE INDEX IF NOT EXISTS idx_marketing_kit_assets_event_id ON marketing_kit_assets(event_id);
+CREATE INDEX IF NOT EXISTS idx_marketing_kit_assets_org_id ON marketing_kit_assets(organization_id);
+CREATE INDEX IF NOT EXISTS idx_marketing_kit_assets_type ON marketing_kit_assets(asset_type);
+CREATE INDEX IF NOT EXISTS idx_marketing_templates_org_id ON marketing_templates(organization_id);
+CREATE INDEX IF NOT EXISTS idx_marketing_templates_type ON marketing_templates(template_type);
+CREATE INDEX IF NOT EXISTS idx_marketing_kit_generations_event_id ON marketing_kit_generations(event_id);
+CREATE INDEX IF NOT EXISTS idx_marketing_kit_generations_org_id ON marketing_kit_generations(organization_id);
+
+-- RLS Policies for multi-tenant security
+ALTER TABLE marketing_kit_assets ENABLE ROW LEVEL SECURITY;
+ALTER TABLE marketing_templates ENABLE ROW LEVEL SECURITY;
+ALTER TABLE marketing_kit_generations ENABLE ROW LEVEL SECURITY;
+
+-- Policies for marketing_kit_assets
+CREATE POLICY "Users can view marketing kit assets from their organization" ON marketing_kit_assets
+ FOR SELECT USING (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE id = auth.uid()
+ )
+ );
+
+CREATE POLICY "Users can create marketing kit assets for their organization" ON marketing_kit_assets
+ FOR INSERT WITH CHECK (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE id = auth.uid()
+ )
+ );
+
+CREATE POLICY "Users can update marketing kit assets from their organization" ON marketing_kit_assets
+ FOR UPDATE USING (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE id = auth.uid()
+ )
+ );
+
+CREATE POLICY "Users can delete marketing kit assets from their organization" ON marketing_kit_assets
+ FOR DELETE USING (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE id = auth.uid()
+ )
+ );
+
+-- Policies for marketing_templates
+CREATE POLICY "Users can view marketing templates from their organization or default templates" ON marketing_templates
+ FOR SELECT USING (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE id = auth.uid()
+ )
+ OR organization_id IS NULL -- Global default templates
+ );
+
+CREATE POLICY "Users can create marketing templates for their organization" ON marketing_templates
+ FOR INSERT WITH CHECK (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE id = auth.uid()
+ )
+ );
+
+CREATE POLICY "Users can update marketing templates from their organization" ON marketing_templates
+ FOR UPDATE USING (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE id = auth.uid()
+ )
+ );
+
+CREATE POLICY "Users can delete marketing templates from their organization" ON marketing_templates
+ FOR DELETE USING (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE id = auth.uid()
+ )
+ );
+
+-- Policies for marketing_kit_generations
+CREATE POLICY "Users can view marketing kit generations from their organization" ON marketing_kit_generations
+ FOR SELECT USING (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE id = auth.uid()
+ )
+ );
+
+CREATE POLICY "Users can create marketing kit generations for their organization" ON marketing_kit_generations
+ FOR INSERT WITH CHECK (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE id = auth.uid()
+ )
+ );
+
+CREATE POLICY "Users can update marketing kit generations from their organization" ON marketing_kit_generations
+ FOR UPDATE USING (
+ organization_id IN (
+ SELECT organization_id FROM users WHERE id = auth.uid()
+ )
+ );
+
+-- Admin bypass policies
+CREATE POLICY "Admins can manage all marketing kit assets" ON marketing_kit_assets
+ FOR ALL USING (is_admin(auth.uid()));
+
+CREATE POLICY "Admins can manage all marketing templates" ON marketing_templates
+ FOR ALL USING (is_admin(auth.uid()));
+
+CREATE POLICY "Admins can manage all marketing kit generations" ON marketing_kit_generations
+ FOR ALL USING (is_admin(auth.uid()));
+
+-- Insert some default templates
+INSERT INTO marketing_templates (name, description, template_type, platform, template_data, is_default, organization_id) VALUES
+-- Facebook Post Template
+('Default Facebook Post', 'Standard Facebook event promotion post', 'social_post', 'facebook',
+ '{"background": "gradient-blue", "textColor": "#FFFFFF", "layout": "center", "includeQR": true, "dimensions": {"width": 1200, "height": 630}}',
+ true, NULL),
+
+-- Instagram Post Template
+('Default Instagram Post', 'Square Instagram event promotion post', 'social_post', 'instagram',
+ '{"background": "gradient-purple", "textColor": "#FFFFFF", "layout": "center", "includeQR": true, "dimensions": {"width": 1080, "height": 1080}}',
+ true, NULL),
+
+-- Twitter Post Template
+('Default Twitter Post', 'Twitter event promotion post', 'social_post', 'twitter',
+ '{"background": "gradient-blue", "textColor": "#FFFFFF", "layout": "left", "includeQR": true, "dimensions": {"width": 1200, "height": 675}}',
+ true, NULL),
+
+-- Email Template
+('Default Email Template', 'Standard event promotion email template', 'email_template', NULL,
+ '{"subject": "You''re Invited: {EVENT_TITLE}", "headerImage": true, "includeQR": true, "ctaText": "Get Your Tickets", "layout": "centered"}',
+ true, NULL),
+
+-- Flyer Template
+('Default Event Flyer', 'Standard event flyer design', 'flyer', NULL,
+ '{"background": "gradient-blue", "textColor": "#FFFFFF", "layout": "poster", "includeQR": true, "dimensions": {"width": 1080, "height": 1350}}',
+ true, NULL);
+
+-- Trigger to update updated_at timestamp
+CREATE OR REPLACE FUNCTION update_updated_at_column()
+RETURNS TRIGGER AS $$
+BEGIN
+ NEW.updated_at = NOW();
+ RETURN NEW;
+END;
+$$ language 'plpgsql';
+
+CREATE TRIGGER update_marketing_kit_assets_updated_at BEFORE UPDATE ON marketing_kit_assets FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+CREATE TRIGGER update_marketing_templates_updated_at BEFORE UPDATE ON marketing_templates FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+CREATE TRIGGER update_marketing_kit_generations_updated_at BEFORE UPDATE ON marketing_kit_generations FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
\ No newline at end of file
diff --git a/supabase/migrations/20250708_add_referral_tracking.sql b/supabase/migrations/20250708_add_referral_tracking.sql
new file mode 100644
index 0000000..83460e8
--- /dev/null
+++ b/supabase/migrations/20250708_add_referral_tracking.sql
@@ -0,0 +1,25 @@
+-- Add referral tracking columns to purchase_attempts table
+ALTER TABLE purchase_attempts
+ADD COLUMN IF NOT EXISTS referral_source TEXT,
+ADD COLUMN IF NOT EXISTS utm_campaign TEXT,
+ADD COLUMN IF NOT EXISTS utm_medium TEXT,
+ADD COLUMN IF NOT EXISTS utm_source TEXT,
+ADD COLUMN IF NOT EXISTS utm_term TEXT,
+ADD COLUMN IF NOT EXISTS utm_content TEXT,
+ADD COLUMN IF NOT EXISTS referrer_url TEXT,
+ADD COLUMN IF NOT EXISTS landing_page TEXT;
+
+-- Add indexes for better query performance
+CREATE INDEX IF NOT EXISTS idx_purchase_attempts_referral_source ON purchase_attempts(referral_source);
+CREATE INDEX IF NOT EXISTS idx_purchase_attempts_utm_source ON purchase_attempts(utm_source);
+CREATE INDEX IF NOT EXISTS idx_purchase_attempts_utm_campaign ON purchase_attempts(utm_campaign);
+
+-- Add comments to explain the columns
+COMMENT ON COLUMN purchase_attempts.referral_source IS 'High-level referral source (e.g., google, facebook, direct, email)';
+COMMENT ON COLUMN purchase_attempts.utm_campaign IS 'Campaign name from UTM parameters';
+COMMENT ON COLUMN purchase_attempts.utm_medium IS 'Medium from UTM parameters (e.g., email, social, paid)';
+COMMENT ON COLUMN purchase_attempts.utm_source IS 'Source from UTM parameters (e.g., google, facebook, newsletter)';
+COMMENT ON COLUMN purchase_attempts.utm_term IS 'Term from UTM parameters (paid search keywords)';
+COMMENT ON COLUMN purchase_attempts.utm_content IS 'Content from UTM parameters (ad variant)';
+COMMENT ON COLUMN purchase_attempts.referrer_url IS 'Full HTTP referrer URL';
+COMMENT ON COLUMN purchase_attempts.landing_page IS 'Page where user first landed on the site';
\ No newline at end of file
diff --git a/supabase/migrations/20250708_add_social_media_links.sql b/supabase/migrations/20250708_add_social_media_links.sql
new file mode 100644
index 0000000..04ce33e
--- /dev/null
+++ b/supabase/migrations/20250708_add_social_media_links.sql
@@ -0,0 +1,30 @@
+-- Add social media links and website to events table for marketing kit
+ALTER TABLE events ADD COLUMN IF NOT EXISTS social_links JSON DEFAULT '{}';
+ALTER TABLE events ADD COLUMN IF NOT EXISTS website_url TEXT;
+ALTER TABLE events ADD COLUMN IF NOT EXISTS contact_email TEXT;
+
+-- Add social media links to organizations table as well for branding
+ALTER TABLE organizations ADD COLUMN IF NOT EXISTS social_links JSON DEFAULT '{}';
+ALTER TABLE organizations ADD COLUMN IF NOT EXISTS website_url TEXT;
+ALTER TABLE organizations ADD COLUMN IF NOT EXISTS contact_email TEXT;
+
+-- Update the marketing templates to include social handles
+UPDATE marketing_templates
+SET template_data = jsonb_set(
+ template_data::jsonb,
+ '{includeSocialHandles}',
+ 'true'::jsonb
+)
+WHERE template_type = 'social_post';
+
+-- Add some example social links structure as comments
+-- Social links JSON structure:
+-- {
+-- "facebook": "https://facebook.com/yourpage",
+-- "instagram": "https://instagram.com/yourhandle",
+-- "twitter": "https://twitter.com/yourhandle",
+-- "linkedin": "https://linkedin.com/company/yourcompany",
+-- "youtube": "https://youtube.com/channel/yourchannel",
+-- "tiktok": "https://tiktok.com/@yourhandle",
+-- "website": "https://yourwebsite.com"
+-- }
\ No newline at end of file