From c8b1a7e6fcf85a37307fc7ebde4a00576691e691 Mon Sep 17 00:00:00 2001 From: estib Date: Mon, 16 Jun 2025 17:29:33 +0200 Subject: [PATCH 1/8] feat(web): add GitHub and Google OAuth button components Add reusable OAuth login button components (GitHub and Google) for use on login/signup pages. Each button includes style and click logic for OAuth redirect. --- .../lib/components/login/GitHubButton.svelte | 51 ++++++++++++++++ .../lib/components/login/GoogleButton.svelte | 61 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 apps/web/src/lib/components/login/GitHubButton.svelte create mode 100644 apps/web/src/lib/components/login/GoogleButton.svelte diff --git a/apps/web/src/lib/components/login/GitHubButton.svelte b/apps/web/src/lib/components/login/GitHubButton.svelte new file mode 100644 index 0000000000..595048ba1a --- /dev/null +++ b/apps/web/src/lib/components/login/GitHubButton.svelte @@ -0,0 +1,51 @@ + + + + + Sign in with GitHub + + + diff --git a/apps/web/src/lib/components/login/GoogleButton.svelte b/apps/web/src/lib/components/login/GoogleButton.svelte new file mode 100644 index 0000000000..4d48d7a282 --- /dev/null +++ b/apps/web/src/lib/components/login/GoogleButton.svelte @@ -0,0 +1,61 @@ + + + + + Sign in with Google + + + From 88a3b32fd61a65aa48d54789f6b8a7d1fd88345d Mon Sep 17 00:00:00 2001 From: estib Date: Mon, 16 Jun 2025 17:29:33 +0200 Subject: [PATCH 2/8] feat(web/auth): add redirect guard components Add Svelte components for handling redirect logic: - RedirectIfLoggedIn redirects authenticated users away from login/signup/reset pages. - RedirectIfNotFinalized redirects logged-in users missing required data to finalize account. --- .../src/lib/auth/RedirectIfLoggedIn.svelte | 18 ++++++++++++ .../lib/auth/RedirectIfNotFinalized.svelte | 28 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 apps/web/src/lib/auth/RedirectIfLoggedIn.svelte create mode 100644 apps/web/src/lib/auth/RedirectIfNotFinalized.svelte diff --git a/apps/web/src/lib/auth/RedirectIfLoggedIn.svelte b/apps/web/src/lib/auth/RedirectIfLoggedIn.svelte new file mode 100644 index 0000000000..ccb886fc88 --- /dev/null +++ b/apps/web/src/lib/auth/RedirectIfLoggedIn.svelte @@ -0,0 +1,18 @@ + diff --git a/apps/web/src/lib/auth/RedirectIfNotFinalized.svelte b/apps/web/src/lib/auth/RedirectIfNotFinalized.svelte new file mode 100644 index 0000000000..ee12c931d7 --- /dev/null +++ b/apps/web/src/lib/auth/RedirectIfNotFinalized.svelte @@ -0,0 +1,28 @@ + From 54c96b80508b162347cc8a402b7fb4ee07300a7d Mon Sep 17 00:00:00 2001 From: estib Date: Mon, 16 Jun 2025 17:29:33 +0200 Subject: [PATCH 3/8] feat(web): update Navigation/Header for new login/signup Refactor navigation components for new login/signup flow: - Navigation now uses webRoutes for login/signup links instead of direct link logic. - Remove old env-based login redirect logic from Navigation. - Home Header shows login/signup via webRoutes. --- apps/web/src/lib/components/Navigation.svelte | 15 +++++++-------- .../src/routes/(home)/components/Header.svelte | 15 ++++----------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/apps/web/src/lib/components/Navigation.svelte b/apps/web/src/lib/components/Navigation.svelte index 4ede786c6c..584216961f 100644 --- a/apps/web/src/lib/components/Navigation.svelte +++ b/apps/web/src/lib/components/Navigation.svelte @@ -7,7 +7,6 @@ import { inject } from '@gitbutler/shared/context'; import { WEB_ROUTES_SERVICE } from '@gitbutler/shared/routing/webRoutes.svelte'; import { Button, ContextMenu, ContextMenuItem, ContextMenuSection, Icon } from '@gitbutler/ui'; - import { env } from '$env/dynamic/public'; const authService = inject(AUTH_SERVICE); @@ -21,13 +20,9 @@ let ctxUserTriggerButton = $state(); let isCtxMenuOpen = $state(false); - function login() { - window.location.href = `${env.PUBLIC_APP_HOST}cloud/login?callback=${window.location.href}`; - } - function logout() { authService.clearToken(); - window.location.href = `${env.PUBLIC_APP_HOST}cloud/logout?returnTo=${window.location.href}`; + window.location.href = `${env.PUBLIC_APP_HOST}cloud/logout`; } @@ -79,8 +74,12 @@ > {:else} {/if} diff --git a/apps/web/src/routes/(home)/components/Header.svelte b/apps/web/src/routes/(home)/components/Header.svelte index eaf8c07efc..5603bfae82 100644 --- a/apps/web/src/routes/(home)/components/Header.svelte +++ b/apps/web/src/routes/(home)/components/Header.svelte @@ -4,8 +4,8 @@ import * as jsonLinks from '$home/data/links.json'; import { AUTH_SERVICE } from '$lib/auth/authService.svelte'; import { inject } from '@gitbutler/shared/context'; + import { WEB_ROUTES_SERVICE } from '@gitbutler/shared/routing/webRoutes.svelte'; import { fly } from 'svelte/transition'; - import { env } from '$env/dynamic/public'; let isMobileMenuOpen = $state(false); @@ -14,6 +14,7 @@ } const authService = inject(AUTH_SERVICE); + const routes = inject(WEB_ROUTES_SERVICE); let token = $derived(authService.tokenReadable); @@ -115,11 +116,7 @@ {#if $token} {:else} - + {/if} @@ -171,11 +168,7 @@ {#if $token} {:else} - + {/if} From 17185ca5bf2c3cba952135768e11a3304873c673 Mon Sep 17 00:00:00 2001 From: estib Date: Mon, 16 Jun 2025 17:29:33 +0200 Subject: [PATCH 4/8] refactor(web): update layout for new authentication state and redirects Update the app layout: - Provide the LoginService for downstream components. - Integrate RedirectIfNotFinalized component at the top-level. - Only show Navigation if not on commit/login/signup page. --- apps/web/src/routes/(app)/+layout.svelte | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/web/src/routes/(app)/+layout.svelte b/apps/web/src/routes/(app)/+layout.svelte index 7564e8f532..a49663c902 100644 --- a/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/web/src/routes/(app)/+layout.svelte @@ -2,6 +2,7 @@ import '$lib/styles/global.css'; import { page } from '$app/state'; import { ButlerAIClient, BUTLER_AI_CLIENT } from '$lib/ai/service'; + import RedirectIfNotFinalized from '$lib/auth/RedirectIfNotFinalized.svelte'; import { AUTH_SERVICE } from '$lib/auth/authService.svelte'; import Footer from '$lib/components/Footer.svelte'; import Navigation from '$lib/components/Navigation.svelte'; @@ -20,6 +21,7 @@ } from '@gitbutler/shared/chat/chatChannelsService'; import { inject, provide } from '@gitbutler/shared/context'; import { FeedService, FEED_SERVICE } from '@gitbutler/shared/feeds/service'; + import LoginService, { LOGIN_SERVICE } from '@gitbutler/shared/login/loginService'; import { HttpClient, HTTP_CLIENT } from '@gitbutler/shared/network/httpClient'; import { OrganizationService, @@ -71,6 +73,9 @@ const httpClient = new HttpClient(window.fetch, env.PUBLIC_APP_HOST, authService.tokenReadable); provide(HTTP_CLIENT, httpClient); + const loginService = new LoginService(httpClient); + provide(LOGIN_SERVICE, loginService); + const aiService = new ButlerAIClient(httpClient); provide(BUTLER_AI_CLIENT, aiService); @@ -142,10 +147,15 @@ provide(RULES_SERVICE, rulesService); const isCommitPage = $derived(page.url.pathname.includes('/commit/')); + const isLoginPage = $derived(page.url.pathname.includes('/login')); + const isSignupPage = $derived(page.url.pathname.includes('/signup')); + const hasNavigation = $derived(!isCommitPage && !isLoginPage && !isSignupPage); + +
- {#if !isCommitPage} + {#if hasNavigation} {/if} From 11b3f664915fb2e73ddb9bb1130ac7195abee699 Mon Sep 17 00:00:00 2001 From: estib Date: Wed, 30 Jul 2025 12:22:26 +0200 Subject: [PATCH 5/8] feat(web): show logged out view in main app page Show a logged out friendly message and prompt if user is not logged in. Previously checked for recent projects regardless of login state. --- apps/web/src/routes/(app)/+page.svelte | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/web/src/routes/(app)/+page.svelte b/apps/web/src/routes/(app)/+page.svelte index 0e3cff6880..2100deab63 100644 --- a/apps/web/src/routes/(app)/+page.svelte +++ b/apps/web/src/routes/(app)/+page.svelte @@ -1,13 +1,22 @@ -{#if hasRecentProjects} +{#if !loggedIn} +

Loading...

+{:else if hasRecentProjects}

You have no recent projects!

From d0fbd1cd9c37126bfecd5f6422d63d7e301c2f92 Mon Sep 17 00:00:00 2001 From: estib Date: Wed, 30 Jul 2025 12:22:59 +0200 Subject: [PATCH 6/8] fix(web): check for logged in user by USER_SERVICE, not token Update project reviews and rules pages to check for logged-in user by USER_SERVICE and redirect home if not logged in, rather than by auth token. Also add refreshUser method to userService.ts to refresh user state and handle errors. --- apps/web/src/lib/user/userService.ts | 10 ++++++++++ .../[ownerSlug]/[projectSlug]/reviews/+page.svelte | 9 +++++---- .../src/routes/(app)/[ownerSlug]/rules/+page.svelte | 9 +++++---- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/apps/web/src/lib/user/userService.ts b/apps/web/src/lib/user/userService.ts index a19b4ec198..b3923de15c 100644 --- a/apps/web/src/lib/user/userService.ts +++ b/apps/web/src/lib/user/userService.ts @@ -73,6 +73,16 @@ export class UserService { return user; } + async refreshUser() { + try { + const user = await this.fetchUser(); + this.user.set(user); + this.error.set(undefined); + } catch (error) { + this.error.set(error); + } + } + clearUser() { this.user.set(undefined); } diff --git a/apps/web/src/routes/(app)/[ownerSlug]/[projectSlug]/reviews/+page.svelte b/apps/web/src/routes/(app)/[ownerSlug]/[projectSlug]/reviews/+page.svelte index c601423c37..15b1e0a2cd 100644 --- a/apps/web/src/routes/(app)/[ownerSlug]/[projectSlug]/reviews/+page.svelte +++ b/apps/web/src/routes/(app)/[ownerSlug]/[projectSlug]/reviews/+page.svelte @@ -1,9 +1,9 @@ + + + GitButler | Login + + + + + + + + + diff --git a/apps/web/src/routes/(app)/login/confirm-password/+page.svelte b/apps/web/src/routes/(app)/login/confirm-password/+page.svelte new file mode 100644 index 0000000000..3ff7423003 --- /dev/null +++ b/apps/web/src/routes/(app)/login/confirm-password/+page.svelte @@ -0,0 +1,177 @@ + + + + GitButler | Confirm Password + + + + + + + + + diff --git a/apps/web/src/routes/(app)/login/reset-password/+page.svelte b/apps/web/src/routes/(app)/login/reset-password/+page.svelte new file mode 100644 index 0000000000..308c4e00d2 --- /dev/null +++ b/apps/web/src/routes/(app)/login/reset-password/+page.svelte @@ -0,0 +1,153 @@ + + + + GitButler | Login + + + + + + + + + diff --git a/apps/web/src/routes/(app)/profile/+page.svelte b/apps/web/src/routes/(app)/profile/+page.svelte index 4046ef458b..219e362291 100644 --- a/apps/web/src/routes/(app)/profile/+page.svelte +++ b/apps/web/src/routes/(app)/profile/+page.svelte @@ -232,12 +232,7 @@
- {#if !$token} - -

Who this?

-

Log into your butler account, create one or do whatever you please.

-
- {:else if !$user?.id} + {#if !$token || !$user?.id}

Loading...

{:else}

My Preferences

diff --git a/apps/web/src/routes/(app)/profile/finalize/+page.svelte b/apps/web/src/routes/(app)/profile/finalize/+page.svelte new file mode 100644 index 0000000000..03ac9eefa9 --- /dev/null +++ b/apps/web/src/routes/(app)/profile/finalize/+page.svelte @@ -0,0 +1,215 @@ + + + + GitButler | Login + + +
+
+ + {#if !userLogin} +
+ + +
+ {/if} + {#if !userEmail} +
+ + +
+ {/if} +
+
+ +
+ (agreeToTerms = e.currentTarget.checked)} + /> +

+ I agree to GitButler's + Terms and Conditions + and + Privacy Policy +

+
+
+ + + + {#if error} +
+ + {error} + +
+ {/if} + + {#if message} +
{message}
+ {/if} +
+
+ + diff --git a/apps/web/src/routes/(app)/signup/+page.svelte b/apps/web/src/routes/(app)/signup/+page.svelte new file mode 100644 index 0000000000..d7ae5eb3ed --- /dev/null +++ b/apps/web/src/routes/(app)/signup/+page.svelte @@ -0,0 +1,195 @@ + + + + GitButler | Sign Up + + + + + + + + + diff --git a/apps/web/src/routes/(app)/signup/resend-confirmation/+page.svelte b/apps/web/src/routes/(app)/signup/resend-confirmation/+page.svelte new file mode 100644 index 0000000000..bed40afabb --- /dev/null +++ b/apps/web/src/routes/(app)/signup/resend-confirmation/+page.svelte @@ -0,0 +1,175 @@ + + + + GitButler | Sign Up + + +{#if banner} + +{/if} + +
+ +

Resend Confirmation Email

+ {#if email} +

+ We will send a confirmation email to {email}. +

+ {:else} +

Please provide your email address to resend the confirmation email.

+ +
+ + +
+ {/if} + + + + {#if error} +
+ + {error} + +
+ {/if} + + {#if notice} +
+ + {notice} + +
+ {/if} +
+
+ + From a26bbfd9ce703cf99d896c98630b1ef74e7b051a Mon Sep 17 00:00:00 2001 From: estib Date: Mon, 16 Jun 2025 17:29:33 +0200 Subject: [PATCH 8/8] refactor(web): update root layout for auth/login routes Update root layout logic: - Refactor authentication + token restore on navigation. - Use webRoutes for logic around the home page for login state check, not just token. --- apps/web/src/routes/+layout.svelte | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/web/src/routes/+layout.svelte b/apps/web/src/routes/+layout.svelte index 9c38201fe9..73713963b9 100644 --- a/apps/web/src/routes/+layout.svelte +++ b/apps/web/src/routes/+layout.svelte @@ -15,7 +15,6 @@ import { provide } from '@gitbutler/shared/context'; import { WebRoutesService, WEB_ROUTES_SERVICE } from '@gitbutler/shared/routing/webRoutes.svelte'; import { type Snippet } from 'svelte'; - import { get } from 'svelte/store'; import '$lib/styles/global.css'; interface Props { @@ -30,17 +29,17 @@ const authService = new AuthService(); provide(AUTH_SERVICE, authService); - let token = $state(); + const persistedToken = authService.token; $effect(() => { - token = get(authService.tokenReadable) || page.url.searchParams.get('gb_access_token'); - if (token) { - authService.setToken(token); - - if (page.url.searchParams.has('gb_access_token')) { - page.url.searchParams.delete('gb_access_token'); - goto(`?${page.url.searchParams.toString()}`); + if (page.url.searchParams.has('gb_access_token')) { + const token = page.url.searchParams.get('gb_access_token'); + if (token && token !== persistedToken.current) { + authService.setToken(token); } + + page.url.searchParams.delete('gb_access_token'); + goto(`?${page.url.searchParams.toString()}`); } }); @@ -49,7 +48,7 @@ window.location.href = jsonLinks.legal.privacyPolicy.url; } - if (!token && page.route.id === '/(app)/home') { + if (!persistedToken.current && page.route.id === '/(app)/home') { goto('/'); } }); @@ -69,7 +68,7 @@ {/if} -{#if (page.route.id === '/(app)' && !token) || page.route.id === '/(app)/home'} +{#if (page.route.id === '/(app)' && !persistedToken.current) || page.route.id === '/(app)/home'}