57 | {/* Mobile sidebar overlay */}
58 | {sidebarOpen && (
59 |
setSidebarOpen(false)}
62 | />
63 | )}
64 |
65 | {/* Sidebar */}
66 |
70 |
71 |
72 |
73 |
74 |
75 |
Best SAAS Kit
76 |
77 |
setSidebarOpen(false)}
82 | >
83 |
84 |
85 |
86 |
87 |
88 |
89 | {sidebarItems.map((item) => (
90 | setSidebarOpen(false)}
95 | >
96 |
97 | {item.name}
98 |
99 | ))}
100 |
101 |
102 |
103 |
104 | {/* Main content */}
105 |
106 | {/* Top navigation */}
107 |
129 |
130 | {/* Page content */}
131 |
132 | {children}
133 |
134 |
135 |
136 | )
137 | }
138 |
--------------------------------------------------------------------------------
/src/components/landing/navigation-client.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import Link from "next/link"
5 | import { Button } from "@/components/ui/button"
6 | import { ThemeToggle } from "@/components/ui/theme-toggle"
7 | import { cn } from "@/lib/utils"
8 | import { Menu, X, Zap } from "lucide-react"
9 | import { SignInButton } from "@/components/auth/signin-button"
10 | import { UserButtonClient } from "@/components/auth/user-button-client"
11 | import { CreditsDisplay } from "@/components/credits/credits-display"
12 |
13 | interface NavigationClientProps {
14 | session: any
15 | }
16 |
17 | export function NavigationClient({ session }: NavigationClientProps) {
18 | const [isOpen, setIsOpen] = React.useState(false)
19 |
20 | const navItems = [
21 | { name: "Features", href: "#features" },
22 | { name: "Pricing", href: "#pricing" },
23 | { name: "Testimonials", href: "#testimonials" },
24 | { name: "Documentation", href: "/docs" },
25 | { name: "Contact", href: "/contact" },
26 | ]
27 |
28 | return (
29 |
30 |
31 |
32 | {/* Logo */}
33 |
34 |
35 |
36 |
37 |
Best SAAS Kit
38 |
39 |
40 | {/* Desktop Navigation */}
41 |
42 | {navItems.map((item) => (
43 |
48 | {item.name}
49 |
50 | ))}
51 |
52 |
53 | {/* Desktop CTA Buttons */}
54 |
55 |
56 | {!session ? (
57 | <>
58 |
59 | Sign In
60 |
61 |
62 | Get Started
63 |
64 | >
65 | ) : (
66 | <>
67 |
68 |
69 | Dashboard
70 |
71 |
72 | >
73 | )}
74 |
75 |
76 | {/* Mobile menu button */}
77 |
setIsOpen(!isOpen)}
82 | >
83 | {isOpen ? : }
84 |
85 |
86 |
87 | {/* Mobile Navigation */}
88 |
92 |
93 | {navItems.map((item) => (
94 |
setIsOpen(false)}
99 | >
100 | {item.name}
101 |
102 | ))}
103 |
104 |
105 | Theme
106 |
107 |
108 | {!session ? (
109 | <>
110 |
111 | Sign In
112 |
113 |
114 | Get Started
115 |
116 | >
117 | ) : (
118 | <>
119 |
120 | Dashboard
121 |
122 |
123 |
124 | Account
125 |
126 | >
127 | )}
128 |
129 |
130 |
131 |
132 |
133 | )
134 | }
135 |
--------------------------------------------------------------------------------
/src/lib/openrouter.ts:
--------------------------------------------------------------------------------
1 | // OpenRouter API client for AI chat completions
2 |
3 | export interface ChatMessage {
4 | role: 'system' | 'user' | 'assistant';
5 | content: string;
6 | }
7 |
8 | export interface ChatCompletionRequest {
9 | model: string;
10 | messages: ChatMessage[];
11 | temperature?: number;
12 | max_tokens?: number;
13 | top_p?: number;
14 | frequency_penalty?: number;
15 | presence_penalty?: number;
16 | stream?: boolean;
17 | }
18 |
19 | export interface ChatCompletionResponse {
20 | id: string;
21 | object: string;
22 | created: number;
23 | model: string;
24 | choices: {
25 | index: number;
26 | message: ChatMessage;
27 | finish_reason: string;
28 | }[];
29 | usage: {
30 | prompt_tokens: number;
31 | completion_tokens: number;
32 | total_tokens: number;
33 | };
34 | }
35 |
36 | export interface OpenRouterError {
37 | error: {
38 | message: string;
39 | type: string;
40 | code?: string;
41 | };
42 | }
43 |
44 | class OpenRouterClient {
45 | private apiKey: string;
46 | private baseURL: string;
47 | private siteUrl: string;
48 | private siteName: string;
49 |
50 | constructor() {
51 | this.apiKey = process.env.OPENROUTER_API_KEY || '';
52 | this.baseURL = 'https://openrouter.ai/api/v1';
53 | this.siteUrl = process.env.NEXT_PUBLIC_SITE_URL || '';
54 | this.siteName = process.env.NEXT_PUBLIC_SITE_NAME || '';
55 |
56 | if (!this.apiKey) {
57 | throw new Error('OpenRouter API key is required');
58 | }
59 | }
60 |
61 | async createChatCompletion(request: ChatCompletionRequest): Promise
{
62 | try {
63 | const response = await fetch(`${this.baseURL}/chat/completions`, {
64 | method: 'POST',
65 | headers: {
66 | 'Authorization': `Bearer ${this.apiKey}`,
67 | 'HTTP-Referer': this.siteUrl,
68 | 'X-Title': this.siteName,
69 | 'Content-Type': 'application/json',
70 | },
71 | body: JSON.stringify(request),
72 | });
73 |
74 | if (!response.ok) {
75 | const errorData: OpenRouterError = await response.json();
76 | throw new Error(
77 | `OpenRouter API error: ${errorData.error.message} (${response.status})`
78 | );
79 | }
80 |
81 | const data: ChatCompletionResponse = await response.json();
82 | return data;
83 | } catch (error) {
84 | if (error instanceof Error) {
85 | throw error;
86 | }
87 | throw new Error('Unknown error occurred while calling OpenRouter API');
88 | }
89 | }
90 |
91 | async createStreamingChatCompletion(
92 | request: ChatCompletionRequest
93 | ): Promise> {
94 | try {
95 | const response = await fetch(`${this.baseURL}/chat/completions`, {
96 | method: 'POST',
97 | headers: {
98 | 'Authorization': `Bearer ${this.apiKey}`,
99 | 'HTTP-Referer': this.siteUrl,
100 | 'X-Title': this.siteName,
101 | 'Content-Type': 'application/json',
102 | },
103 | body: JSON.stringify({
104 | ...request,
105 | stream: true,
106 | }),
107 | });
108 |
109 | if (!response.ok) {
110 | const errorData: OpenRouterError = await response.json();
111 | throw new Error(
112 | `OpenRouter API error: ${errorData.error.message} (${response.status})`
113 | );
114 | }
115 |
116 | if (!response.body) {
117 | throw new Error('No response body received from OpenRouter API');
118 | }
119 |
120 | return response.body;
121 | } catch (error) {
122 | if (error instanceof Error) {
123 | throw error;
124 | }
125 | throw new Error('Unknown error occurred while calling OpenRouter API');
126 | }
127 | }
128 |
129 | // Helper method to get available models
130 | async getModels(): Promise {
131 | try {
132 | const response = await fetch(`${this.baseURL}/models`, {
133 | headers: {
134 | 'Authorization': `Bearer ${this.apiKey}`,
135 | },
136 | });
137 |
138 | if (!response.ok) {
139 | throw new Error(`Failed to fetch models: ${response.status}`);
140 | }
141 |
142 | return await response.json();
143 | } catch (error) {
144 | if (error instanceof Error) {
145 | throw error;
146 | }
147 | throw new Error('Unknown error occurred while fetching models');
148 | }
149 | }
150 | }
151 |
152 | // Export a singleton instance
153 | export const openRouterClient = new OpenRouterClient();
154 |
155 | // Helper function to create a simple chat completion
156 | export async function createChatCompletion(
157 | messages: ChatMessage[],
158 | options: Partial = {}
159 | ): Promise {
160 | const defaultModel = process.env.OPENROUTER_MODEL || 'qwen/qwen3-235b-a22b-2507';
161 |
162 | return openRouterClient.createChatCompletion({
163 | model: defaultModel,
164 | messages,
165 | temperature: 0.7,
166 | max_tokens: 1000,
167 | ...options,
168 | });
169 | }
170 |
171 | // Helper function for streaming chat completion
172 | export async function createStreamingChatCompletion(
173 | messages: ChatMessage[],
174 | options: Partial = {}
175 | ): Promise> {
176 | const defaultModel = process.env.OPENROUTER_MODEL || 'qwen/qwen3-235b-a22b-2507';
177 |
178 | return openRouterClient.createStreamingChatCompletion({
179 | model: defaultModel,
180 | messages,
181 | temperature: 0.7,
182 | max_tokens: 1000,
183 | ...options,
184 | });
185 | }
186 |
--------------------------------------------------------------------------------
/sql-queries/04-insert-sample-data.sql:
--------------------------------------------------------------------------------
1 | -- ============================================================================
2 | -- BEST SAAS KIT V2 - SAMPLE DATA
3 | -- ============================================================================
4 | -- This file inserts sample data for testing and development
5 | -- ⚠️ WARNING: Only run this in development environments!
6 | -- ⚠️ Do NOT run this in production!
7 |
8 | -- Check if this is a development environment
9 | DO $$
10 | BEGIN
11 | -- Only proceed if there are no existing users (fresh database)
12 | IF (SELECT COUNT(*) FROM users) > 0 THEN
13 | RAISE EXCEPTION 'Database already contains users. Sample data insertion cancelled for safety.';
14 | END IF;
15 |
16 | RAISE NOTICE '🧪 Inserting sample data for development and testing...';
17 | RAISE NOTICE '⚠️ This should only be run in development environments!';
18 | END $$;
19 |
20 | -- Insert sample users for testing
21 | INSERT INTO users (
22 | google_id,
23 | email,
24 | name,
25 | image_url,
26 | credits,
27 | subscription_status,
28 | stripe_customer_id,
29 | created_at,
30 | last_login
31 | ) VALUES
32 | -- Free user samples
33 | (
34 | 'google_123456789',
35 | 'john.doe@example.com',
36 | 'John Doe',
37 | 'https://lh3.googleusercontent.com/a/default-user',
38 | 15,
39 | 'free',
40 | NULL,
41 | CURRENT_TIMESTAMP - INTERVAL '30 days',
42 | CURRENT_TIMESTAMP - INTERVAL '2 days'
43 | ),
44 | (
45 | 'google_987654321',
46 | 'jane.smith@example.com',
47 | 'Jane Smith',
48 | 'https://lh3.googleusercontent.com/a/default-user-2',
49 | 8,
50 | 'free',
51 | NULL,
52 | CURRENT_TIMESTAMP - INTERVAL '15 days',
53 | CURRENT_TIMESTAMP - INTERVAL '1 day'
54 | ),
55 | (
56 | 'google_456789123',
57 | 'mike.johnson@example.com',
58 | 'Mike Johnson',
59 | 'https://lh3.googleusercontent.com/a/default-user-3',
60 | 25,
61 | 'free',
62 | NULL,
63 | CURRENT_TIMESTAMP - INTERVAL '7 days',
64 | CURRENT_TIMESTAMP - INTERVAL '3 hours'
65 | ),
66 |
67 | -- Pro user samples
68 | (
69 | 'google_789123456',
70 | 'sarah.wilson@example.com',
71 | 'Sarah Wilson',
72 | 'https://lh3.googleusercontent.com/a/default-user-4',
73 | 1250,
74 | 'pro',
75 | 'cus_sample_stripe_customer_1',
76 | CURRENT_TIMESTAMP - INTERVAL '45 days',
77 | CURRENT_TIMESTAMP - INTERVAL '1 hour'
78 | ),
79 | (
80 | 'google_321654987',
81 | 'alex.brown@example.com',
82 | 'Alex Brown',
83 | 'https://lh3.googleusercontent.com/a/default-user-5',
84 | 890,
85 | 'pro',
86 | 'cus_sample_stripe_customer_2',
87 | CURRENT_TIMESTAMP - INTERVAL '20 days',
88 | CURRENT_TIMESTAMP - INTERVAL '30 minutes'
89 | ),
90 |
91 | -- Recently registered users
92 | (
93 | 'google_147258369',
94 | 'emma.davis@example.com',
95 | 'Emma Davis',
96 | 'https://lh3.googleusercontent.com/a/default-user-6',
97 | 10,
98 | 'free',
99 | NULL,
100 | CURRENT_TIMESTAMP - INTERVAL '2 days',
101 | CURRENT_TIMESTAMP - INTERVAL '1 day'
102 | ),
103 | (
104 | 'google_963852741',
105 | 'david.miller@example.com',
106 | 'David Miller',
107 | 'https://lh3.googleusercontent.com/a/default-user-7',
108 | 10,
109 | 'free',
110 | NULL,
111 | CURRENT_TIMESTAMP - INTERVAL '1 day',
112 | CURRENT_TIMESTAMP - INTERVAL '6 hours'
113 | ),
114 |
115 | -- Active pro user
116 | (
117 | 'google_852741963',
118 | 'lisa.garcia@example.com',
119 | 'Lisa Garcia',
120 | 'https://lh3.googleusercontent.com/a/default-user-8',
121 | 1500,
122 | 'pro',
123 | 'cus_sample_stripe_customer_3',
124 | CURRENT_TIMESTAMP - INTERVAL '60 days',
125 | CURRENT_TIMESTAMP - INTERVAL '15 minutes'
126 | );
127 |
128 | -- Display sample data statistics
129 | DO $$
130 | DECLARE
131 | total_users INTEGER;
132 | free_users INTEGER;
133 | pro_users INTEGER;
134 | total_credits INTEGER;
135 | BEGIN
136 | SELECT COUNT(*) INTO total_users FROM users;
137 | SELECT COUNT(*) INTO free_users FROM users WHERE subscription_status = 'free';
138 | SELECT COUNT(*) INTO pro_users FROM users WHERE subscription_status = 'pro';
139 | SELECT SUM(credits) INTO total_credits FROM users;
140 |
141 | RAISE NOTICE '';
142 | RAISE NOTICE '✅ Sample data inserted successfully!';
143 | RAISE NOTICE '';
144 | RAISE NOTICE '📊 Sample Data Summary:';
145 | RAISE NOTICE ' 👥 Total Users: %', total_users;
146 | RAISE NOTICE ' 🆓 Free Users: %', free_users;
147 | RAISE NOTICE ' 💎 Pro Users: %', pro_users;
148 | RAISE NOTICE ' 🪙 Total Credits: %', total_credits;
149 | RAISE NOTICE '';
150 | RAISE NOTICE '🧪 Sample Users Created:';
151 | RAISE NOTICE ' • john.doe@example.com (Free, 15 credits)';
152 | RAISE NOTICE ' • jane.smith@example.com (Free, 8 credits)';
153 | RAISE NOTICE ' • mike.johnson@example.com (Free, 25 credits)';
154 | RAISE NOTICE ' • sarah.wilson@example.com (Pro, 1250 credits)';
155 | RAISE NOTICE ' • alex.brown@example.com (Pro, 890 credits)';
156 | RAISE NOTICE ' • emma.davis@example.com (Free, 10 credits)';
157 | RAISE NOTICE ' • david.miller@example.com (Free, 10 credits)';
158 | RAISE NOTICE ' • lisa.garcia@example.com (Pro, 1500 credits)';
159 | RAISE NOTICE '';
160 | RAISE NOTICE '🎯 Use these for testing:';
161 | RAISE NOTICE ' • Authentication flows';
162 | RAISE NOTICE ' • Subscription management';
163 | RAISE NOTICE ' • Credit system';
164 | RAISE NOTICE ' • Analytics dashboard';
165 | RAISE NOTICE ' • User management features';
166 | RAISE NOTICE '';
167 | RAISE NOTICE '⚠️ Remember: This is sample data for development only!';
168 | RAISE NOTICE '🗑️ Delete this data before going to production!';
169 | END $$;
170 |
--------------------------------------------------------------------------------
/sql-queries/05-verify-setup.sql:
--------------------------------------------------------------------------------
1 | -- ============================================================================
2 | -- BEST SAAS KIT V2 - DATABASE SETUP VERIFICATION
3 | -- ============================================================================
4 | -- This file verifies that your database setup is complete and working correctly
5 | -- Run this file after executing all previous SQL files
6 |
7 | -- Check if users table exists and has correct structure
8 | DO $$
9 | DECLARE
10 | table_exists BOOLEAN;
11 | column_count INTEGER;
12 | BEGIN
13 | -- Check if users table exists
14 | SELECT EXISTS (
15 | SELECT FROM information_schema.tables
16 | WHERE table_schema = 'public'
17 | AND table_name = 'users'
18 | ) INTO table_exists;
19 |
20 | IF table_exists THEN
21 | RAISE NOTICE '✅ Users table exists';
22 |
23 | -- Count columns
24 | SELECT COUNT(*) INTO column_count
25 | FROM information_schema.columns
26 | WHERE table_name = 'users' AND table_schema = 'public';
27 |
28 | RAISE NOTICE '📊 Users table has % columns', column_count;
29 | ELSE
30 | RAISE NOTICE '❌ Users table does not exist!';
31 | RAISE NOTICE '➡️ Please run 01-create-users-table.sql first';
32 | END IF;
33 | END $$;
34 |
35 | -- Verify indexes exist
36 | DO $$
37 | DECLARE
38 | index_count INTEGER;
39 | BEGIN
40 | SELECT COUNT(*) INTO index_count
41 | FROM pg_indexes
42 | WHERE tablename = 'users' AND schemaname = 'public';
43 |
44 | RAISE NOTICE '🔍 Found % indexes on users table', index_count;
45 |
46 | IF index_count >= 8 THEN
47 | RAISE NOTICE '✅ Indexes are properly created';
48 | ELSE
49 | RAISE NOTICE '⚠️ Some indexes may be missing';
50 | RAISE NOTICE '➡️ Please run 02-create-indexes.sql';
51 | END IF;
52 | END $$;
53 |
54 | -- Verify functions exist
55 | DO $$
56 | DECLARE
57 | function_count INTEGER;
58 | BEGIN
59 | SELECT COUNT(*) INTO function_count
60 | FROM pg_proc p
61 | JOIN pg_namespace n ON p.pronamespace = n.oid
62 | WHERE n.nspname = 'public'
63 | AND p.proname IN (
64 | 'update_updated_at_column',
65 | 'get_user_stats',
66 | 'get_revenue_stats',
67 | 'add_user_credits',
68 | 'deduct_user_credits',
69 | 'upgrade_user_to_pro'
70 | );
71 |
72 | RAISE NOTICE '⚙️ Found % custom functions', function_count;
73 |
74 | IF function_count >= 6 THEN
75 | RAISE NOTICE '✅ All functions are created';
76 | ELSE
77 | RAISE NOTICE '⚠️ Some functions may be missing';
78 | RAISE NOTICE '➡️ Please run 03-create-functions.sql';
79 | END IF;
80 | END $$;
81 |
82 | -- Verify triggers exist
83 | DO $$
84 | DECLARE
85 | trigger_count INTEGER;
86 | BEGIN
87 | SELECT COUNT(*) INTO trigger_count
88 | FROM information_schema.triggers
89 | WHERE event_object_table = 'users'
90 | AND trigger_schema = 'public';
91 |
92 | RAISE NOTICE '⚡ Found % triggers on users table', trigger_count;
93 |
94 | IF trigger_count >= 1 THEN
95 | RAISE NOTICE '✅ Triggers are properly created';
96 | ELSE
97 | RAISE NOTICE '⚠️ Triggers may be missing';
98 | RAISE NOTICE '➡️ Please run 03-create-functions.sql';
99 | END IF;
100 | END $$;
101 |
102 | -- Test database functions
103 | DO $$
104 | DECLARE
105 | stats_result RECORD;
106 | revenue_result RECORD;
107 | BEGIN
108 | RAISE NOTICE '';
109 | RAISE NOTICE '🧪 Testing database functions...';
110 |
111 | -- Test get_user_stats function
112 | BEGIN
113 | SELECT * INTO stats_result FROM get_user_stats() LIMIT 1;
114 | RAISE NOTICE '✅ get_user_stats() function works correctly';
115 | EXCEPTION WHEN OTHERS THEN
116 | RAISE NOTICE '❌ get_user_stats() function failed: %', SQLERRM;
117 | END;
118 |
119 | -- Test get_revenue_stats function
120 | BEGIN
121 | SELECT * INTO revenue_result FROM get_revenue_stats() LIMIT 1;
122 | RAISE NOTICE '✅ get_revenue_stats() function works correctly';
123 | EXCEPTION WHEN OTHERS THEN
124 | RAISE NOTICE '❌ get_revenue_stats() function failed: %', SQLERRM;
125 | END;
126 | END $$;
127 |
128 | -- Display current database statistics
129 | DO $$
130 | DECLARE
131 | user_count INTEGER;
132 | free_count INTEGER;
133 | pro_count INTEGER;
134 | total_credits INTEGER;
135 | BEGIN
136 | SELECT COUNT(*) INTO user_count FROM users;
137 | SELECT COUNT(*) INTO free_count FROM users WHERE subscription_status = 'free';
138 | SELECT COUNT(*) INTO pro_count FROM users WHERE subscription_status = 'pro';
139 | SELECT COALESCE(SUM(credits), 0) INTO total_credits FROM users;
140 |
141 | RAISE NOTICE '';
142 | RAISE NOTICE '📊 Current Database Statistics:';
143 | RAISE NOTICE ' 👥 Total Users: %', user_count;
144 | RAISE NOTICE ' 🆓 Free Users: %', free_count;
145 | RAISE NOTICE ' 💎 Pro Users: %', pro_count;
146 | RAISE NOTICE ' 🪙 Total Credits: %', total_credits;
147 | END $$;
148 |
149 | -- Final verification summary
150 | DO $$
151 | BEGIN
152 | RAISE NOTICE '';
153 | RAISE NOTICE '🎉 Database Setup Verification Complete!';
154 | RAISE NOTICE '';
155 | RAISE NOTICE '✅ What to do next:';
156 | RAISE NOTICE ' 1. Update your .env.local with the DATABASE_URL';
157 | RAISE NOTICE ' 2. Test your Next.js application connection';
158 | RAISE NOTICE ' 3. Try user authentication and registration';
159 | RAISE NOTICE ' 4. Test the credit system and subscriptions';
160 | RAISE NOTICE '';
161 | RAISE NOTICE '🔗 Connection String Format:';
162 | RAISE NOTICE ' DATABASE_URL=postgresql://username:password@host/database?sslmode=require';
163 | RAISE NOTICE '';
164 | RAISE NOTICE '📚 Need help? Check the main README.md file';
165 | RAISE NOTICE '🐛 Issues? Open a GitHub issue';
166 | RAISE NOTICE '';
167 | RAISE NOTICE '🚀 Your Best SAAS Kit V2 database is ready!';
168 | END $$;
169 |
--------------------------------------------------------------------------------
/src/components/contact/contact-form.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useState } from "react"
4 | import { Button } from "@/components/ui/button"
5 | import { Input } from "@/components/ui/input"
6 | import { Textarea } from "@/components/ui/textarea"
7 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
8 | import { Label } from "@/components/ui/label"
9 | import { Mail, Send, CheckCircle, AlertCircle } from "lucide-react"
10 |
11 | interface ContactFormData {
12 | name: string
13 | email: string
14 | message: string
15 | }
16 |
17 | export function ContactForm() {
18 | const [formData, setFormData] = useState({
19 | name: '',
20 | email: '',
21 | message: ''
22 | })
23 | const [isSubmitting, setIsSubmitting] = useState(false)
24 | const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle')
25 | const [errorMessage, setErrorMessage] = useState('')
26 |
27 | const handleInputChange = (e: React.ChangeEvent) => {
28 | const { name, value } = e.target
29 | setFormData(prev => ({
30 | ...prev,
31 | [name]: value
32 | }))
33 | }
34 |
35 | const handleSubmit = async (e: React.FormEvent) => {
36 | e.preventDefault()
37 | setIsSubmitting(true)
38 | setSubmitStatus('idle')
39 | setErrorMessage('')
40 |
41 | try {
42 | const response = await fetch('/api/emails/contact', {
43 | method: 'POST',
44 | headers: {
45 | 'Content-Type': 'application/json',
46 | },
47 | body: JSON.stringify(formData),
48 | })
49 |
50 | const result = await response.json()
51 |
52 | if (response.ok && result.success) {
53 | setSubmitStatus('success')
54 | setFormData({ name: '', email: '', message: '' })
55 | } else {
56 | setSubmitStatus('error')
57 | setErrorMessage(result.error || 'Failed to send message')
58 | }
59 | } catch (error) {
60 | setSubmitStatus('error')
61 | setErrorMessage('Network error. Please try again.')
62 | } finally {
63 | setIsSubmitting(false)
64 | }
65 | }
66 |
67 | return (
68 |
69 |
70 |
71 |
72 | Contact Us
73 |
74 |
75 | Send us a message and we'll get back to you as soon as possible.
76 |
77 |
78 |
79 | {submitStatus === 'success' ? (
80 |
81 |
82 |
Message Sent Successfully!
83 |
84 | Thank you for contacting us. We'll get back to you within 24 hours.
85 |
86 |
setSubmitStatus('idle')}
88 | variant="outline"
89 | >
90 | Send Another Message
91 |
92 |
93 | ) : (
94 |
163 | )}
164 |
165 |
166 | )
167 | }
168 |
--------------------------------------------------------------------------------
/src/app/demo/page.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/button"
2 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
3 | import Link from "next/link"
4 | import { ArrowLeft, Play, Code, Zap } from "lucide-react"
5 |
6 | export default function DemoPage() {
7 | return (
8 |
9 | {/* Header */}
10 |
11 |
12 |
13 |
14 |
15 |
16 | Back to Home
17 |
18 |
19 |
Live Demo
20 |
21 |
22 |
23 |
24 |
25 | {/* Demo Content */}
26 |
27 |
28 |
29 | Experience the
30 |
31 | {" "}AI SAAS Kit
32 |
33 |
34 |
35 | Explore the features and capabilities of our comprehensive toolkit for building AI-powered applications.
36 |
37 |
38 |
39 |
40 | {/* Authentication Demo */}
41 |
42 |
43 |
44 |
45 | Authentication
46 |
47 |
48 | Complete auth system with Clerk.com integration
49 |
50 |
51 |
52 |
53 |
54 |
55 | Try Sign In
56 |
57 |
58 |
59 | Try Sign Up
60 |
61 |
62 |
63 |
64 |
65 | {/* AI Features Demo */}
66 |
67 |
68 |
69 |
70 | AI Integration
71 |
72 |
73 | OpenRouter integration with multiple AI models
74 |
75 |
76 |
77 |
78 |
79 |
80 | Chat Interface
81 |
82 |
83 |
84 | Code Generation
85 |
86 |
87 |
88 |
89 |
90 | {/* Dashboard Demo */}
91 |
92 |
93 |
94 |
95 | Dashboard
96 |
97 |
98 | Modern dashboard with analytics and management
99 |
100 |
101 |
102 |
103 |
104 |
105 | View Dashboard
106 |
107 |
108 |
109 | Analytics
110 |
111 |
112 |
113 |
114 |
115 |
116 | {/* Coming Soon */}
117 |
118 |
119 |
Interactive Demo Coming Soon
120 |
121 | We're building a fully interactive demo where you can test all features.
122 | For now, you can explore the source code and documentation.
123 |
124 |
125 |
126 |
127 |
128 | View Source Code
129 |
130 |
131 |
132 |
133 | View Documentation
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 | )
142 | }
143 |
--------------------------------------------------------------------------------