diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d9a739b..d4190549 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,14 @@ git clone https://github.com/stackblitz/bolt.new.git pnpm install ``` -3. Create a `.env.local` file in the root directory and add your Anthropic API key: +3. Set Up Supabase Auth & Providers: + +- Create a new project on Supabase and generate a new anon key. +- Add the anon key to the `.env.local` file. +- Add the supabase url to the `.env.local` file. +- Configure supabase providers (Google, GitHub, etc). + +4. Create a `.env.local` file in the root directory and add your Anthropic API key: ``` ANTHROPIC_API_KEY=XXX @@ -66,6 +73,13 @@ ANTHROPIC_API_KEY=XXX TOGETHER_API_KEY=XXX ``` +``` +SUPABASE_URL=XXX +``` +``` +SUPABASE_ANON_KEY=XXX +``` + Optionally, you can set the debug level: ``` diff --git a/app/components/auth/Auth.tsx b/app/components/auth/Auth.tsx new file mode 100644 index 00000000..dfdec059 --- /dev/null +++ b/app/components/auth/Auth.tsx @@ -0,0 +1,208 @@ +'use client' + +import { useState, useEffect } from 'react' +import { useNavigate } from '@remix-run/react' +import { createClient } from '~/utils/supabase.client' +import type { SupabaseClient } from '@supabase/supabase-js' +import { motion, AnimatePresence } from 'framer-motion' +import { Github, Mail, Apple } from 'lucide-react' + +interface AuthComponentProps { + onClose: () => void +} + +export default function AuthComponent({ onClose }: AuthComponentProps = { onClose: () => {} }) { + const [supabase, setSupabase] = useState(null) + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [activeTab, setActiveTab] = useState<'signin' | 'signup'>('signin') + const navigate = useNavigate() + + useEffect(() => { + const client = createClient() + if (client) { + setSupabase(client) + } + }, []) + + useEffect(() => { + if (!supabase) return + + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange((event, session) => { + if (event === 'SIGNED_IN' && session) { + onClose() + navigate('/') + } + }) + + return () => subscription.unsubscribe() + }, [supabase, navigate, onClose]) + + const handleSignIn = async (e: React.FormEvent) => { + e.preventDefault() + if (!supabase) return + + setLoading(true) + setError(null) + + const { error } = await supabase.auth.signInWithPassword({ email, password }) + + if (error) { + setError(error.message) + } + + setLoading(false) + } + + const handleSignUp = async (e: React.FormEvent) => { + e.preventDefault() + if (!supabase) return + + setLoading(true) + setError(null) + + const { error } = await supabase.auth.signUp({ email, password }) + + if (error) { + setError(error.message) + } else { + setError('Please check your email for the confirmation link.') + } + + setLoading(false) + } + + const handleOAuthSignIn = async (provider: 'github' | 'google' | 'apple') => { + if (!supabase) return + + const { error } = await supabase.auth.signInWithOAuth({ + provider, + options: { + redirectTo: `${window.location.origin}/auth/callback`, + }, + }) + + if (error) { + setError(error.message) + } + } + + if (!supabase) { + return
Loading...
+ } + + return ( +
+
+ + +
+ + + +
+ + setEmail(e.target.value)} + className="w-full px-3 py-2 bg-[#1C2128] text-white border border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400" + required + /> +
+
+ + setPassword(e.target.value)} + className="w-full px-3 py-2 bg-[#1C2128] text-white border border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400" + required + /> +
+ + {loading ? 'Processing...' : activeTab === 'signin' ? 'Sign In' : 'Sign Up'} + +
+
+ + {error && ( + + {error} + + )} + +
+

Or continue with

+
+ handleOAuthSignIn('github')} + className="p-2 bg-[#1C2128] rounded-full hover:bg-[#2D3748] transition-colors duration-300" + whileHover={{ scale: 1.1 }} + whileTap={{ scale: 0.9 }} + > + + + handleOAuthSignIn('google')} + className="p-2 bg-[#1C2128] rounded-full hover:bg-[#2D3748] transition-colors duration-300" + whileHover={{ scale: 1.1 }} + whileTap={{ scale: 0.9 }} + > + + + handleOAuthSignIn('apple')} + className="p-2 bg-[#1C2128] rounded-full hover:bg-[#2D3748] transition-colors duration-300" + whileHover={{ scale: 1.1 }} + whileTap={{ scale: 0.9 }} + > + + +
+
+
+ ) +} \ No newline at end of file diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index c4f90f43..171ce46d 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -9,6 +9,8 @@ import { Messages } from './Messages.client'; import { SendButton } from './SendButton.client'; import styles from './BaseChat.module.scss'; +import { ArrowBigLeftIcon } from 'lucide-react'; +import { ProviderSelector } from './ProviderSelector'; interface BaseChatProps { textareaRef?: React.RefObject | undefined; @@ -25,14 +27,17 @@ interface BaseChatProps { sendMessage?: (event: React.UIEvent, messageInput?: string) => void; handleInputChange?: (event: React.ChangeEvent) => void; enhancePrompt?: () => void; + append: (message: { role: 'user', content: string }) => void; + isLoading: boolean; } const EXAMPLE_PROMPTS = [ - { text: 'Build a todo app in React using Tailwind' }, - { text: 'Build a simple blog using Astro' }, - { text: 'Create a cookie consent form using Material UI' }, - { text: 'Make a space invaders game' }, - { text: 'How do I center a div?' }, + { text: 'Start a blog with Astro' }, + { text: 'Build a mobile app with NativeScript' }, + { text: 'Create a docs site with Vitepress' }, + { text: 'Scaffold UI with shadcn' }, + { text: 'Draft a presentation with Slidev' }, + { text: 'Code a video with Remotion' }, ]; const TEXTAREA_MIN_HEIGHT = 76; @@ -54,6 +59,8 @@ export const BaseChat = React.forwardRef( handleInputChange, enhancePrompt, handleStop, + append, + isLoading, }, ref, ) => { @@ -64,17 +71,20 @@ export const BaseChat = React.forwardRef( ref={ref} className={classNames( styles.BaseChat, - 'relative flex h-full w-full overflow-hidden bg-bolt-elements-background-depth-1', + 'relative flex h-full w-full overflow-hidden', )} data-chat-visible={showChat} > +
+
+
{() => } -
+
{!chatStarted && ( -
+

- Where ideas begin + What do you want to build?

Bring ideas to life in seconds or get help on existing projects. @@ -82,7 +92,7 @@ export const BaseChat = React.forwardRef(

)}
@@ -103,109 +113,121 @@ export const BaseChat = React.forwardRef( 'sticky bottom-0': chatStarted, })} > -
-