mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
214 lines
8.6 KiB
TypeScript
214 lines
8.6 KiB
TypeScript
import {
|
|
json,
|
|
redirect,
|
|
type ActionFunctionArgs,
|
|
type LoaderFunctionArgs,
|
|
type MetaFunction,
|
|
} from '@remix-run/cloudflare';
|
|
import { Form, useActionData, useLoaderData, useNavigation } from '@remix-run/react';
|
|
import { useEffect, useState } from 'react';
|
|
import { generateState, getAuthorizationUrl, isAuthenticated, storeState } from '~/lib/auth/github-oauth.server';
|
|
import BackgroundRays from '~/components/ui/BackgroundRays';
|
|
|
|
export const meta: MetaFunction = () => {
|
|
return [
|
|
{ title: 'Welcome to Buildify' },
|
|
{ name: 'description', content: 'AI-assisted development environment powered by Buildify' },
|
|
];
|
|
};
|
|
|
|
export async function loader({ request, context }: LoaderFunctionArgs) {
|
|
// Check if user is already authenticated
|
|
const authenticated = await isAuthenticated(request, context);
|
|
if (authenticated) {
|
|
// Redirect to home page if already logged in
|
|
return redirect('/');
|
|
}
|
|
|
|
// Get redirect URL from query params (if any)
|
|
const url = new URL(request.url);
|
|
const redirectTo = url.searchParams.get('redirectTo') || '/';
|
|
|
|
// Check for authentication error parameters
|
|
const authError = url.searchParams.get('error');
|
|
const authErrorDescription = url.searchParams.get('error_description');
|
|
|
|
// Check if GitHub OAuth is configured using context
|
|
const clientId = (context?.cloudflare?.env?.GITHUB_CLIENT_ID) || (context?.env?.GITHUB_CLIENT_ID) || process.env.GITHUB_CLIENT_ID;
|
|
const clientSecret = (context?.cloudflare?.env?.GITHUB_CLIENT_SECRET) || (context?.env?.GITHUB_CLIENT_SECRET) || process.env.GITHUB_CLIENT_SECRET;
|
|
const isConfigured = Boolean(clientId && clientSecret);
|
|
|
|
// Determine the error message to display
|
|
let errorMessage = null;
|
|
if (authError) {
|
|
errorMessage = authErrorDescription || 'Authentication failed. Please try again.';
|
|
} else if (!isConfigured) {
|
|
errorMessage = 'GitHub OAuth is not configured. Please set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables.';
|
|
}
|
|
|
|
return json({
|
|
redirectTo,
|
|
isConfigured,
|
|
error: errorMessage,
|
|
});
|
|
}
|
|
|
|
export async function action({ request, context }: ActionFunctionArgs) {
|
|
// Check if GitHub OAuth is configured using context
|
|
const clientId = (context?.cloudflare?.env?.GITHUB_CLIENT_ID) || (context?.env?.GITHUB_CLIENT_ID) || process.env.GITHUB_CLIENT_ID;
|
|
const clientSecret = (context?.cloudflare?.env?.GITHUB_CLIENT_SECRET) || (context?.env?.GITHUB_CLIENT_SECRET) || process.env.GITHUB_CLIENT_SECRET;
|
|
|
|
if (!clientId || !clientSecret) {
|
|
return json(
|
|
{
|
|
error:
|
|
'GitHub OAuth is not configured. Please set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables.',
|
|
},
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
// Get form data
|
|
const formData = await request.formData();
|
|
const redirectTo = formData.get('redirectTo')?.toString() || '/';
|
|
|
|
try {
|
|
// Generate state for CSRF protection
|
|
const state = generateState();
|
|
|
|
// Get the callback URL
|
|
const url = new URL(request.url);
|
|
const callbackUrl = `${url.origin}/auth/callback`;
|
|
|
|
// Generate authorization URL
|
|
const { url: authorizationUrl } = getAuthorizationUrl(state, callbackUrl, context);
|
|
|
|
// Store state in session
|
|
const cookie = await storeState(request, state, context);
|
|
|
|
// Redirect to GitHub authorization URL
|
|
return redirect(authorizationUrl, {
|
|
headers: {
|
|
'Set-Cookie': cookie,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error('Error initiating GitHub OAuth flow:', error);
|
|
return json({ error: 'Failed to initiate login. Please try again later.' }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
export default function Login() {
|
|
const { redirectTo, isConfigured, error } = useLoaderData<typeof loader>();
|
|
const actionData = useActionData<typeof action>();
|
|
const navigation = useNavigation();
|
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
|
|
// Set error message from loader or action data
|
|
useEffect(() => {
|
|
setErrorMessage(error || actionData?.error || null);
|
|
}, [error, actionData]);
|
|
|
|
const isSubmitting = navigation.state === 'submitting';
|
|
|
|
return (
|
|
<div className="flex flex-col items-center justify-center min-h-screen bg-bolt-elements-background-depth-1">
|
|
<BackgroundRays />
|
|
|
|
<div className="w-full max-w-4xl p-8 space-y-12">
|
|
<div className="flex flex-col items-center justify-center text-center">
|
|
{/* Buildify Logo */}
|
|
<div className="w-64 h-auto mb-8">
|
|
<img src="/logo-light-styled.png" alt="Buildify Logo" className="w-full h-auto dark:hidden" />
|
|
<img src="/logo-dark-styled.png" alt="Buildify Logo" className="w-full h-auto hidden dark:block" />
|
|
</div>
|
|
|
|
<h1 className="text-4xl font-bold text-bolt-content-primary mb-4">Welcome to Buildify</h1>
|
|
|
|
<p className="text-xl text-bolt-content-secondary max-w-2xl">
|
|
Your AI-assisted development environment for building, modifying, and deploying web applications
|
|
</p>
|
|
</div>
|
|
|
|
<div className="w-full max-w-md mx-auto p-8 space-y-8 bg-bolt-elements-background-depth-2 rounded-lg shadow-lg">
|
|
<div className="text-center">
|
|
<h2 className="text-2xl font-semibold text-bolt-content-primary">Sign In to Get Started</h2>
|
|
<p className="mt-2 text-bolt-content-secondary">Connect with your GitHub account to access Buildify</p>
|
|
</div>
|
|
|
|
{errorMessage && <div className="p-4 text-sm text-red-500 bg-red-100 rounded-md">{errorMessage}</div>}
|
|
|
|
<Form method="post" className="space-y-6">
|
|
<input type="hidden" name="redirectTo" value={redirectTo} />
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={!isConfigured || isSubmitting}
|
|
aria-label="Sign in with GitHub"
|
|
className={`
|
|
w-full flex items-center justify-center py-3 px-4 rounded-md
|
|
${
|
|
isConfigured
|
|
? 'bg-[#2da44e] hover:bg-[#2c974b] text-white'
|
|
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
|
|
}
|
|
transition-colors duration-200 font-medium shadow-md
|
|
`}
|
|
>
|
|
{isSubmitting ? (
|
|
<span className="flex items-center">
|
|
<svg
|
|
className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
></circle>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
></path>
|
|
</svg>
|
|
Connecting...
|
|
</span>
|
|
) : (
|
|
<span className="flex items-center">
|
|
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24" fill="currentColor">
|
|
<path
|
|
fillRule="evenodd"
|
|
clipRule="evenodd"
|
|
d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385c.6.105.825-.255.825-.57c0-.285-.015-1.23-.015-2.235c-3.015.555-3.795-.735-4.035-1.41c-.135-.345-.72-1.41-1.23-1.695c-.42-.225-1.02-.78-.015-.795c.945-.015 1.62.87 1.845 1.23c1.08 1.815 2.805 1.305 3.495.99c.105-.78.42-1.305.765-1.605c-2.67-.3-5.46-1.335-5.46-5.925c0-1.305.465-2.385 1.23-3.225c-.12-.3-.54-1.53.12-3.18c0 0 1.005-.315 3.3 1.23c.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23c.66 1.65.24 2.88.12 3.18c.765.84 1.23 1.905 1.23 3.225c0 4.605-2.805 5.625-5.475 5.925c.435.375.81 1.095.81 2.22c0 1.605-.015 2.895-.015 3.3c0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"
|
|
/>
|
|
</svg>
|
|
Continue with GitHub
|
|
</span>
|
|
)}
|
|
</button>
|
|
</Form>
|
|
|
|
<div className="text-center text-sm text-bolt-content-tertiary">
|
|
<p>
|
|
By logging in, you agree to the{' '}
|
|
<a href="#" className="text-bolt-content-accent hover:underline">
|
|
Terms of Service
|
|
</a>{' '}
|
|
and{' '}
|
|
<a href="#" className="text-bolt-content-accent hover:underline">
|
|
Privacy Policy
|
|
</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|