Skip to content

Commit ef0df0e

Browse files
committed
feat: Refactor to Firebase backend (Track B)
1 parent fe7eaf3 commit ef0df0e

30 files changed

Lines changed: 1001 additions & 604 deletions

File tree

.firebase/hosting.YXBwcy93ZWIvY2xpZW50Ly5uZXh0.cache

Lines changed: 214 additions & 0 deletions
Large diffs are not rendered by default.

.firebase/hosting.cHVibGlj.cache

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
index.html,1749144121075,201a610e70f7a503846c577256326fcb4df903b2daf15217e395f87b0b95fa13

.firebaserc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"projects": {
3+
"default": "botcodes-1cdc0"
4+
}
5+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# This file was auto-generated by the Firebase CLI
2+
# https://github.com/firebase/firebase-tools
3+
4+
name: Deploy to Firebase Hosting on merge
5+
on:
6+
push:
7+
branches:
8+
- main
9+
jobs:
10+
build_and_deploy:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- run: npm ci && npm run build
15+
- uses: FirebaseExtended/action-hosting-deploy@v0
16+
with:
17+
repoToken: ${{ secrets.GITHUB_TOKEN }}
18+
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_BOTCODES_1CDC0 }}
19+
channelId: live
20+
projectId: botcodes-1cdc0
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# This file was auto-generated by the Firebase CLI
2+
# https://github.com/firebase/firebase-tools
3+
4+
name: Deploy to Firebase Hosting on PR
5+
on: pull_request
6+
permissions:
7+
checks: write
8+
contents: read
9+
pull-requests: write
10+
jobs:
11+
build_and_preview:
12+
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- run: npm ci && npm run build
17+
- uses: FirebaseExtended/action-hosting-deploy@v0
18+
with:
19+
repoToken: ${{ secrets.GITHUB_TOKEN }}
20+
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_BOTCODES_1CDC0 }}
21+
projectId: botcodes-1cdc0

apps/web/client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
"@types/react-dom": "^19.0.0",
107107
"eslint": "^9.23.0",
108108
"eslint-config-next": "^15.2.3",
109+
"firebase-admin": "^12.2.0",
109110
"postcss": "^8.5.3",
110111
"prettier": "^3.5.3",
111112
"prettier-plugin-tailwindcss": "^0.6.11",
Lines changed: 4 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,6 @@
1-
import { client } from '@/utils/analytics/server';
2-
import { createClient } from '@/utils/supabase/server';
3-
import type { User } from '@onlook/db';
4-
import { NextResponse } from 'next/server';
5-
import { api } from '~/trpc/server';
1+
import { handleRedirect } from '@/app/login/actions';
2+
import { type NextRequest } from 'next/server';
63

7-
export async function GET(request: Request) {
8-
const { searchParams, origin } = new URL(request.url);
9-
const code = searchParams.get('code');
10-
11-
if (code) {
12-
const supabase = await createClient();
13-
const { error, data } = await supabase.auth.exchangeCodeForSession(code);
14-
if (!error) {
15-
const forwardedHost = request.headers.get('x-forwarded-host'); // original origin before load balancer
16-
const isLocalEnv = process.env.NODE_ENV === 'development';
17-
const user = await getOrCreateUser(data.user.id);
18-
19-
trackUserSignedIn(user.id, {
20-
name: data.user.user_metadata.name,
21-
email: data.user.email,
22-
avatar_url: data.user.user_metadata.avatar_url,
23-
});
24-
25-
// Redirect to the redirect page which will handle the return URL
26-
if (isLocalEnv) {
27-
return NextResponse.redirect(`${origin}/auth/redirect`);
28-
} else if (forwardedHost) {
29-
return NextResponse.redirect(`https://${forwardedHost}/auth/redirect`);
30-
} else {
31-
return NextResponse.redirect(`${origin}/auth/redirect`);
32-
}
33-
}
34-
console.error(`Error exchanging code for session: ${error}`);
35-
}
36-
37-
// return the user to an error page with instructions
38-
return NextResponse.redirect(`${origin}/auth/auth-code-error`);
39-
}
40-
41-
async function getOrCreateUser(userId: string): Promise<User> {
42-
const user = await api.user.getById(userId);
43-
if (!user) {
44-
console.log(`User ${userId} not found, creating...`);
45-
const newUser = await api.user.create({ id: userId });
46-
return newUser;
47-
}
48-
console.log(`User ${userId} found, returning...`);
49-
return user;
50-
}
51-
52-
function trackUserSignedIn(userId: string, properties: Record<string, any>) {
53-
try {
54-
if (!client) {
55-
console.warn('PostHog client not found, skipping user signed in tracking');
56-
return;
57-
}
58-
client.identify({
59-
distinctId: userId,
60-
properties: {
61-
...properties,
62-
$set_once: {
63-
signup_date: new Date().toISOString(),
64-
}
65-
}
66-
});
67-
client.capture({ event: 'user_signed_in', distinctId: userId });
68-
} catch (error) {
69-
console.error('Error tracking user signed in:', error);
70-
}
4+
export async function GET(request: NextRequest) {
5+
return await handleRedirect();
716
}
Lines changed: 42 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,55 @@
11
'use server';
22

3-
import { Routes } from '@/utils/constants';
4-
import { createClient } from '@/utils/supabase/server';
5-
import { SEED_USER } from '@onlook/db';
6-
import { SignInMethod } from '@onlook/models';
3+
import {
4+
getRedirectResult,
5+
GoogleAuthProvider,
6+
signInWithEmailAndPassword,
7+
signInWithRedirect,
8+
} from 'firebase/auth';
79
import { headers } from 'next/headers';
810
import { redirect } from 'next/navigation';
911

10-
export async function login(provider: SignInMethod) {
11-
const supabase = await createClient();
12-
const origin = (await headers()).get('origin');
13-
14-
// If already session, redirect
15-
const {
16-
data: { session },
17-
} = await supabase.auth.getSession();
18-
if (session) {
19-
redirect('/');
20-
}
21-
22-
// Start OAuth flow
23-
// Note: User object will be created in the auth callback route if it doesn't exist
24-
const { data, error } = await supabase.auth.signInWithOAuth({
25-
provider,
26-
options: {
27-
redirectTo: `${origin}/auth/callback`,
28-
},
29-
});
12+
import { auth } from '@onlook/db/firebase';
13+
import { SEED_USER } from '@onlook/db';
14+
import { SignInMethod } from '@onlook/models';
15+
import { Routes } from '@/utils/constants';
3016

31-
if (error) {
32-
redirect('/error');
33-
}
17+
export async function login(provider: SignInMethod) {
18+
const origin = (await headers()).get('origin');
19+
const googleProvider = new GoogleAuthProvider();
20+
21+
// We can add more providers here based on the `provider` argument
22+
switch (provider) {
23+
case 'google':
24+
await signInWithRedirect(auth, googleProvider);
25+
break;
26+
default:
27+
throw new Error(`Provider ${provider} not supported`);
28+
}
29+
}
3430

35-
redirect(data.url);
31+
export async function handleRedirect() {
32+
const result = await getRedirectResult(auth);
33+
if (result) {
34+
// User is signed in.
35+
// You can get the user info from result.user
36+
redirect(Routes.HOME);
37+
} else {
38+
// Handle the case where there is no redirect result
39+
redirect('/login');
40+
}
3641
}
3742

3843
export async function devLogin() {
39-
if (process.env.NODE_ENV !== 'development') {
40-
throw new Error('Dev login is only available in development mode');
41-
}
42-
43-
const supabase = await createClient();
44-
45-
const {
46-
data: { session },
47-
} = await supabase.auth.getSession();
48-
if (session) {
49-
redirect('/');
50-
}
51-
52-
const { data, error } = await supabase.auth.signInWithPassword({
53-
email: SEED_USER.EMAIL,
54-
password: SEED_USER.PASSWORD,
55-
});
56-
57-
if (error) {
58-
console.error('Error signing in with password:', error);
59-
throw new Error('Error signing in with password');
60-
}
44+
if (process.env.NODE_ENV !== 'development') {
45+
throw new Error('Dev login is only available in development mode');
46+
}
6147

48+
try {
49+
await signInWithEmailAndPassword(auth, SEED_USER.EMAIL, SEED_USER.PASSWORD);
6250
redirect(Routes.HOME);
51+
} catch (error) {
52+
console.error('Error signing in with password:', error);
53+
throw new Error('Error signing in with password');
54+
}
6355
}

apps/web/client/src/app/project/[id]/_components/top-bar/project-breadcrumb.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEditorEngine } from '@/components/store/editor';
22
import { useProjectManager } from '@/components/store/project';
33
import { Routes } from '@/utils/constants';
4-
import { uploadBlobToStorage } from '@/utils/supabase/client';
4+
import { uploadBlobToStorage } from '@/utils/firebase/storage';
55
import { STORAGE_BUCKETS } from '@onlook/constants';
66
import { Button } from '@onlook/ui/button';
77
import {
@@ -71,7 +71,7 @@ export const ProjectBreadcrumb = observer(() => {
7171
type: 'storage',
7272
storagePath: {
7373
bucket: STORAGE_BUCKETS.PREVIEW_IMAGES,
74-
path: data?.path,
74+
path: data?.ref.fullPath,
7575
},
7676
}
7777
}
@@ -85,7 +85,8 @@ export const ProjectBreadcrumb = observer(() => {
8585
return;
8686
}
8787
const file = base64ToBlob(screenshotData, mimeType);
88-
const data = await uploadBlobToStorage(STORAGE_BUCKETS.PREVIEW_IMAGES, getScreenshotPath(project.id, mimeType), file, {
88+
const path = `${STORAGE_BUCKETS.PREVIEW_IMAGES}/${getScreenshotPath(project.id, mimeType)}`;
89+
const data = await uploadBlobToStorage(path, file, {
8990
contentType: mimeType,
9091
});
9192
if (!data) {

apps/web/client/src/app/projects/_components/carousel.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getFileUrlFromStorage } from '@/utils/supabase/client';
1+
import { getFileUrlFromStorage } from '@/utils/firebase/storage';
22
import { STORAGE_BUCKETS } from '@onlook/constants';
33
import type { Project } from '@onlook/models';
44
import { Icons } from '@onlook/ui/icons';
@@ -108,7 +108,8 @@ export const Carousel: React.FC<CarouselProps> = ({ slides, onSlideChange }) =>
108108
const images: { [key: string]: string } = {};
109109
for (const slide of slides) {
110110
if (slide.metadata.previewImg) {
111-
const img = await getFileUrlFromStorage(STORAGE_BUCKETS.PREVIEW_IMAGES, slide.metadata.previewImg.storagePath?.path ?? '');
111+
const path = `${STORAGE_BUCKETS.PREVIEW_IMAGES}/${slide.metadata.previewImg.storagePath?.path ?? ''}`;
112+
const img = await getFileUrlFromStorage(path);
112113
if (img) {
113114
images[slide.id] = img;
114115
} else {

0 commit comments

Comments
 (0)