diff --git a/apps/desktop/src/components/AiCredentialCheck.svelte b/apps/desktop/src/components/AiCredentialCheck.svelte index 54e9279d8e..ab63d74429 100644 --- a/apps/desktop/src/components/AiCredentialCheck.svelte +++ b/apps/desktop/src/components/AiCredentialCheck.svelte @@ -1,10 +1,9 @@
-

Before we begin

+

Before we begin

diff --git a/apps/desktop/src/components/BaseBranchSwitch.svelte b/apps/desktop/src/components/BaseBranchSwitch.svelte index 9ab06df094..e41307ff2c 100644 --- a/apps/desktop/src/components/BaseBranchSwitch.svelte +++ b/apps/desktop/src/components/BaseBranchSwitch.svelte @@ -1,9 +1,8 @@ -

Clone a repository

+

Clone a repository

Clone URL
diff --git a/apps/desktop/src/components/CommitSigningForm.svelte b/apps/desktop/src/components/CommitSigningForm.svelte index 7997ea9ce1..8a66617153 100644 --- a/apps/desktop/src/components/CommitSigningForm.svelte +++ b/apps/desktop/src/components/CommitSigningForm.svelte @@ -1,11 +1,19 @@ diff --git a/apps/desktop/src/components/Welcome.svelte b/apps/desktop/src/components/Welcome.svelte index 00b39cf059..fcbfa5e57a 100644 --- a/apps/desktop/src/components/Welcome.svelte +++ b/apps/desktop/src/components/Welcome.svelte @@ -40,7 +40,7 @@
-

Welcome to GitButler

+

Welcome to GitButler

-

Set up Claude Code

+

Set up Claude Code

- + chipToasts.error('Please use a valid image file')} + />
@@ -304,56 +286,6 @@ gap: 24px; } - .hidden-input { - z-index: var(--z-ground); - position: absolute; - width: 100%; - height: 100%; - cursor: pointer; - opacity: 0; - } - - .profile-pic-wrapper { - position: relative; - width: 100px; - height: 100px; - overflow: hidden; - border-radius: var(--radius-m); - background-color: var(--clr-scale-pop-70); - transition: opacity var(--transition-medium); - - &:hover, - &:focus-within { - & .profile-pic__edit-label { - opacity: 1; - } - - & .profile-pic { - opacity: 0.8; - } - } - } - - .profile-pic { - width: 100%; - height: 100%; - - object-fit: cover; - background-color: var(--clr-scale-pop-70); - } - - .profile-pic__edit-label { - position: absolute; - bottom: 8px; - left: 8px; - padding: 4px 6px; - border-radius: var(--radius-m); - background-color: var(--clr-scale-ntrl-20); - color: var(--clr-core-ntrl-100); - opacity: 0; - transition: opacity var(--transition-medium); - } - .contact-info { display: flex; flex: 1; diff --git a/apps/desktop/src/lib/notifications/toasts.ts b/apps/desktop/src/lib/notifications/toasts.ts index aacaddc3aa..a95cd7529e 100644 --- a/apps/desktop/src/lib/notifications/toasts.ts +++ b/apps/desktop/src/lib/notifications/toasts.ts @@ -8,7 +8,7 @@ import { } from '$lib/error/parser'; import posthog from 'posthog-js'; import { writable, type Writable } from 'svelte/store'; -import type { MessageStyle } from '$components/InfoMessage.svelte'; +import type { MessageStyle } from '@gitbutler/ui'; type ExtraAction = { label: string; diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js index f0f8abc1f6..50e78f37b0 100644 --- a/apps/web/postcss.config.js +++ b/apps/web/postcss.config.js @@ -5,7 +5,7 @@ import postcssNesting from 'postcss-nesting'; import pxToRem from 'postcss-pxtorem'; import path from 'path'; -const mediaQueriesCssPath = path.resolve('src/lib/styles/media-queries.css'); +const mediaQueriesCssPath = path.resolve('src/styles/media-queries.css'); /** @type {import('postcss-load-config').Config} */ const config = { diff --git a/apps/web/src/lib/images/blank-chat.svg b/apps/web/src/lib/assets/blank-chat.svg similarity index 100% rename from apps/web/src/lib/images/blank-chat.svg rename to apps/web/src/lib/assets/blank-chat.svg diff --git a/apps/web/src/lib/images/github.svg b/apps/web/src/lib/assets/github.svg similarity index 100% rename from apps/web/src/lib/images/github.svg rename to apps/web/src/lib/assets/github.svg diff --git a/apps/web/src/lib/assets/splash-illustrations/new-project.svg b/apps/web/src/lib/assets/splash-illustrations/new-project.svg new file mode 100644 index 0000000000..33ef5e25ac --- /dev/null +++ b/apps/web/src/lib/assets/splash-illustrations/new-project.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/lib/assets/splash-illustrations/walkin.svg b/apps/web/src/lib/assets/splash-illustrations/walkin.svg new file mode 100644 index 0000000000..a3f5775c7f --- /dev/null +++ b/apps/web/src/lib/assets/splash-illustrations/walkin.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/lib/auth/RedirectIfLoggedIn.svelte b/apps/web/src/lib/auth/RedirectIfLoggedIn.svelte new file mode 100644 index 0000000000..0b0a8e3d19 --- /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..e173a5c285 --- /dev/null +++ b/apps/web/src/lib/auth/RedirectIfNotFinalized.svelte @@ -0,0 +1,28 @@ + diff --git a/apps/web/src/lib/auth/RedirectToProfileIfLoggedIn.svelte b/apps/web/src/lib/auth/RedirectToProfileIfLoggedIn.svelte new file mode 100644 index 0000000000..12e9775435 --- /dev/null +++ b/apps/web/src/lib/auth/RedirectToProfileIfLoggedIn.svelte @@ -0,0 +1,18 @@ + diff --git a/apps/web/src/lib/components/ChatComponent.svelte b/apps/web/src/lib/components/ChatComponent.svelte index 6ec454ffc7..a1976e5938 100644 --- a/apps/web/src/lib/components/ChatComponent.svelte +++ b/apps/web/src/lib/components/ChatComponent.svelte @@ -1,10 +1,10 @@ - -
-

{title}

-
- {@render children()} -
-
- - diff --git a/apps/web/src/lib/components/Footer.svelte b/apps/web/src/lib/components/Footer.svelte deleted file mode 100644 index 1c85bfb52f..0000000000 --- a/apps/web/src/lib/components/Footer.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/apps/web/src/lib/components/GitbutlerLogoLink.svelte b/apps/web/src/lib/components/GitbutlerLogoLink.svelte new file mode 100644 index 0000000000..37b8b57ac6 --- /dev/null +++ b/apps/web/src/lib/components/GitbutlerLogoLink.svelte @@ -0,0 +1,65 @@ + + +{#snippet logoContent()} + {#if !markOnly} + GitButler + {/if} +
+ + + +
+{/snippet} + +{#if disabled} + +{:else} + +{/if} + + diff --git a/apps/web/src/lib/components/HeaderAuthSection.svelte b/apps/web/src/lib/components/HeaderAuthSection.svelte new file mode 100644 index 0000000000..e1ca5d12a8 --- /dev/null +++ b/apps/web/src/lib/components/HeaderAuthSection.svelte @@ -0,0 +1,35 @@ + + +{#if $user && !hideIfUserAuthenticated} + +{:else if !$user} + +{/if} + + diff --git a/apps/web/src/lib/components/HomePage.svelte b/apps/web/src/lib/components/HomePage.svelte deleted file mode 100644 index 42154f46df..0000000000 --- a/apps/web/src/lib/components/HomePage.svelte +++ /dev/null @@ -1,6 +0,0 @@ - - - -
Home Page
diff --git a/apps/web/src/lib/components/Navigation.svelte b/apps/web/src/lib/components/Navigation.svelte index 13ae54e897..a2ca72dedd 100644 --- a/apps/web/src/lib/components/Navigation.svelte +++ b/apps/web/src/lib/components/Navigation.svelte @@ -1,211 +1,78 @@ - - - (isCtxMenuOpen = isOpen)} -> - - { - goto('/profile'); - }} - /> - {#if $token && $featureShowOrganizations} - goto('/organizations')} /> - {/if} - - - { - // TODO: implement theme switching - }} - /> - { - // TODO: implement theme switching - }} - /> - { - // TODO: implement theme switching - }} - /> - + {/each} +
+ {/if} +
- - - - + + {/if} + diff --git a/apps/web/src/lib/components/UserAuthAvatar.svelte b/apps/web/src/lib/components/UserAuthAvatar.svelte new file mode 100644 index 0000000000..82acb13cc5 --- /dev/null +++ b/apps/web/src/lib/components/UserAuthAvatar.svelte @@ -0,0 +1,50 @@ + + + + + + + diff --git a/apps/web/src/lib/components/UserDashboard.svelte b/apps/web/src/lib/components/UserDashboard.svelte deleted file mode 100644 index 29bf547b79..0000000000 --- a/apps/web/src/lib/components/UserDashboard.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - - - GitButler | User - - -{#if !$token} -

Unauthorized

-{:else if !$user?.id} -

Loading...

-{:else} -
-

Welcome to GitButler Cloud

-
-{/if} - - diff --git a/apps/web/src/lib/components/UserPage.svelte b/apps/web/src/lib/components/UserPage.svelte deleted file mode 100644 index 9257c9aea0..0000000000 --- a/apps/web/src/lib/components/UserPage.svelte +++ /dev/null @@ -1,57 +0,0 @@ - - -{#if !$token} -

Unauthorized

-{:else if !$user?.id} -

Loading...

-{:else} -
-
- {#if $user?.picture} - User Avatar - {/if} -
{$user?.name}
-
-
Email: {$user?.email}
-
Joined: {$user?.created_at}
-
Supporter: {$user?.supporter}
-
-{/if} - - diff --git a/apps/web/src/lib/components/auth/OAuthButtons.svelte b/apps/web/src/lib/components/auth/OAuthButtons.svelte new file mode 100644 index 0000000000..4465e2932c --- /dev/null +++ b/apps/web/src/lib/components/auth/OAuthButtons.svelte @@ -0,0 +1,112 @@ + + + + + diff --git a/apps/web/src/lib/components/auth/PasswordConfirmation.svelte b/apps/web/src/lib/components/auth/PasswordConfirmation.svelte new file mode 100644 index 0000000000..0d2f3d2fa9 --- /dev/null +++ b/apps/web/src/lib/components/auth/PasswordConfirmation.svelte @@ -0,0 +1,115 @@ + + +
+ { + passwordTouched = true; + }} + /> + { + passwordConfirmationTouched = true; + }} + onblur={() => { + passwordConfirmationTouched = true; + }} + /> +
+ + diff --git a/apps/web/src/lib/components/auth/UsernameTextbox.svelte b/apps/web/src/lib/components/auth/UsernameTextbox.svelte new file mode 100644 index 0000000000..9972ade9ca --- /dev/null +++ b/apps/web/src/lib/components/auth/UsernameTextbox.svelte @@ -0,0 +1,135 @@ + + + diff --git a/apps/web/src/lib/components/marketing/Footer.svelte b/apps/web/src/lib/components/marketing/Footer.svelte new file mode 100644 index 0000000000..87942c09f2 --- /dev/null +++ b/apps/web/src/lib/components/marketing/Footer.svelte @@ -0,0 +1,424 @@ + + + + + diff --git a/apps/web/src/lib/components/marketing/Header.svelte b/apps/web/src/lib/components/marketing/Header.svelte new file mode 100644 index 0000000000..fa80bff7bd --- /dev/null +++ b/apps/web/src/lib/components/marketing/Header.svelte @@ -0,0 +1,125 @@ + + + +{#snippet link(props: { + href: string; + label: string; + icon?: keyof typeof iconsJson; + target?: string; + rel?: string; +})} + + {props.label} + {#if props.icon} + + {/if} + +{/snippet} + +
+ + + + + +
+ + diff --git a/apps/web/src/lib/components/marketing/ReleaseCard.svelte b/apps/web/src/lib/components/marketing/ReleaseCard.svelte new file mode 100644 index 0000000000..c2f8987ecd --- /dev/null +++ b/apps/web/src/lib/components/marketing/ReleaseCard.svelte @@ -0,0 +1,241 @@ + + +
+
+

{release.version}

+ + {new Date(release.released_at).toLocaleDateString('en-GB', { + day: 'numeric', + month: 'short', + year: 'numeric' + })} + +
+ +
+ {#if release.notes} +
+ {@html marked(release.notes)} +
+ {/if} + + {#if showDownloadLinks && release.builds && release.builds.length > 0} + {#if !downloadLinksVisible} + + {/if} + + {#if downloadLinksVisible} + + {/if} + {/if} +
+
+ + diff --git a/apps/web/src/lib/components/service/FullscreenIllustrationCard.svelte b/apps/web/src/lib/components/service/FullscreenIllustrationCard.svelte new file mode 100644 index 0000000000..f1a0001acb --- /dev/null +++ b/apps/web/src/lib/components/service/FullscreenIllustrationCard.svelte @@ -0,0 +1,122 @@ + + +
+
+
+
+

+ {@render title()} +

+ + {@render children()} +
+ + +
+ +
+ {@html illustration ?? walkininSvg} +
+
+
+ + diff --git a/apps/web/src/lib/components/service/FullscreenUtilityCard.svelte b/apps/web/src/lib/components/service/FullscreenUtilityCard.svelte new file mode 100644 index 0000000000..4f9d295347 --- /dev/null +++ b/apps/web/src/lib/components/service/FullscreenUtilityCard.svelte @@ -0,0 +1,90 @@ + + +
+ +

{title}

+ + {@render children()} + + + +
+ + diff --git a/apps/web/src/lib/data/links.json b/apps/web/src/lib/data/links.json index 7d7ccdd73e..15002b0a4f 100644 --- a/apps/web/src/lib/data/links.json +++ b/apps/web/src/lib/data/links.json @@ -20,6 +20,11 @@ "url": "https://app.gitbutler.com/downloads/release/linux/x86_64/deb", "label": "Linux DEB" }, + "linuxRpm": { + "id": "linuxRpm", + "url": "https://app.gitbutler.com/downloads/release/linux/x86_64/rpm", + "label": "Linux RPM" + }, "windowsMsi": { "id": "windowsMsi", "url": "https://app.gitbutler.com/downloads/release/windows/x86_64/msi", @@ -27,15 +32,25 @@ } }, "resources": { - "github": { - "id": "github", - "url": "https://github.com/gitbutlerapp/gitbutler", - "label": "GitHub" + "downloads": { + "id": "downloads", + "url": "/downloads", + "label": "Downloads" }, "documentation": { "id": "documentation", "url": "https://docs.gitbutler.com", - "label": "Documentation" + "label": "Docs" + }, + "source": { + "id": "source", + "url": "https://github.com/gitbutlerapp/gitbutler", + "label": "Source" + }, + "jobs": { + "id": "jobs", + "url": "https://jobs.gitbutler.com", + "label": "Jobs" }, "blog": { "id": "blog", @@ -59,6 +74,11 @@ "url": "https://x.com/gitbutler", "label": "X" }, + "bluesky": { + "id": "bluesky", + "url": "https://bsky.app/profile/gitbutler.com", + "label": "Bluesky" + }, "instagram": { "id": "instagram", "url": "https://www.instagram.com/gitbutler", @@ -94,6 +114,11 @@ "id": "youtube-demo", "url": "https://youtu.be/agfyTN3HpRM?si=jVyMeMrWIaMa0Jdp", "label": "YouTube Demo" + }, + "support": { + "id": "help", + "url": "https://github.com/gitbutlerapp/gitbutler/issues/new?template=BLANK_ISSUE", + "label": "Submit a ticket" } } } diff --git a/apps/web/src/lib/data/os-icons.json b/apps/web/src/lib/data/os-icons.json new file mode 100644 index 0000000000..3d0ba24d56 --- /dev/null +++ b/apps/web/src/lib/data/os-icons.json @@ -0,0 +1,5 @@ +{ + "macos": "M14.5124 1C14.5609 1 14.6094 1 14.6607 1C14.7798 2.41044 14.2185 3.46432 13.5363 4.22751C12.867 4.98542 11.9504 5.7205 10.468 5.60897C10.3691 4.21872 10.9313 3.24301 11.6125 2.48158C12.2443 1.77197 13.4026 1.14052 14.5124 1Z M19 15.6805C19 15.6946 19 15.7069 19 15.7201C18.5834 16.9303 17.9891 17.9675 17.2639 18.93C16.6019 19.8038 15.7906 20.9798 14.3421 20.9798C13.0904 20.9798 12.259 20.2078 10.9761 20.1868C9.61914 20.1657 8.87289 20.8323 7.63218 21C7.49025 21 7.34832 21 7.20915 21C6.29807 20.8735 5.5628 20.1815 5.02715 19.5579C3.44765 17.7154 2.22708 15.3354 2 12.2897C2 11.9911 2 11.6934 2 11.3948C2.09614 9.21499 3.20042 7.44272 4.66821 6.58381C5.44285 6.12712 6.50776 5.73807 7.69353 5.91196C8.20171 5.98748 8.72089 6.15435 9.17597 6.31946C9.60724 6.47842 10.1466 6.76033 10.6575 6.7454C11.0036 6.73574 11.3479 6.56273 11.6968 6.44065C12.7186 6.08673 13.7203 5.68098 15.0407 5.87156C16.6275 6.10166 17.7538 6.77789 18.4497 7.82124C17.1073 8.64063 16.0461 9.87542 16.2274 11.9841C16.3886 13.8995 17.5496 15.0201 19 15.6805Z", + "windows": "M8.53359 4.68442L2.75 5.72786V10.5553L8.53355 10.4673L8.53359 4.68442ZM19.25 11.6954L9.85695 11.55V17.5536L19.25 19.25V11.6954ZM8.53359 11.532L2.75004 11.4432V16.2698L8.53359 17.314V11.532ZM19.25 2.75L9.85695 4.44478V10.4484L19.25 10.3039V2.75Z", + "linux": "M13.6372 18.7335C13.7705 18.936 13.632 19.2506 13.3746 19.2499H8.63011C8.37922 19.2505 8.23119 18.9405 8.36753 18.7335C9.5959 16.8297 12.4089 16.8297 13.6372 18.7335ZM18.5951 19.2499H15.4887C15.3581 19.2497 15.2345 19.1591 15.1904 19.0313C13.8185 14.9973 8.12488 15.1777 6.81431 19.0313C6.77023 19.1591 6.64666 19.2497 6.51605 19.2499H3.40485C2.85633 19.2594 2.57308 18.5904 2.89081 18.1774C4.88527 15.7575 5.29045 11.6426 5.29045 8.68997C5.29045 5.55268 7.79631 2.75 11.002 2.75C14.2076 2.75 16.7135 5.55268 16.7135 8.68997C16.7135 11.7738 17.1908 15.6075 19.1139 18.1791C19.437 18.6018 19.1245 19.2591 18.5951 19.2499ZM7.8289 9.67996C7.8289 10.2739 8.37584 10.7731 8.96654 10.6509C9.33585 10.5745 9.641 10.2572 9.71446 9.8731C9.82601 9.28986 9.38428 8.68997 8.78083 8.68997C8.24655 8.68997 7.8289 9.15708 7.8289 9.67996ZM14.1084 12.3546C13.9579 12.0415 13.5583 11.9029 13.2572 12.0592L11.002 13.2324L8.74751 12.0592C8.44623 11.9028 8.04636 12.0417 7.89594 12.355C7.74551 12.6683 7.87905 13.0842 8.18032 13.2406L10.7188 14.5606C10.8949 14.6521 11.1098 14.6521 11.286 14.5606L13.8244 13.2406C14.1388 13.0774 14.2591 12.6675 14.1084 12.3546ZM14.175 9.67996C14.175 9.09285 13.6364 8.58508 13.0374 8.70899C12.6681 8.78539 12.363 9.10274 12.2895 9.48682C12.1791 10.0641 12.6119 10.67 13.2231 10.67C13.7574 10.67 14.175 10.2028 14.175 9.67996Z" +} diff --git a/apps/web/src/lib/meta/opengraph.ts b/apps/web/src/lib/meta/opengraph.ts index c142189f0c..f831a7230f 100644 --- a/apps/web/src/lib/meta/opengraph.ts +++ b/apps/web/src/lib/meta/opengraph.ts @@ -93,5 +93,7 @@ export async function fillMeta(html: string, url: string) { } } + // Default fallback for non-review pages + metaTags = metaTags.replaceAll('%image%', `${env.PUBLIC_APP_HOST}og-image.png`); return html.replace('%metatags%', metaTags); } diff --git a/apps/web/src/lib/store.ts b/apps/web/src/lib/store.ts index 327e340fd6..b325f72a37 100644 --- a/apps/web/src/lib/store.ts +++ b/apps/web/src/lib/store.ts @@ -1,5 +1,3 @@ -import * as jsonLinks from '$lib/data/links.json'; import { writable } from 'svelte/store'; -export const targetDownload = writable(jsonLinks.downloads.appleSilicon); export const latestClientVersion = writable('...'); diff --git a/apps/web/src/lib/styles/global.css b/apps/web/src/lib/styles/global.css deleted file mode 100644 index 97210bc1cd..0000000000 --- a/apps/web/src/lib/styles/global.css +++ /dev/null @@ -1,36 +0,0 @@ -@import '@gitbutler/ui/main.css'; - -:root { - --layout-col-gap: 20px; - --off-white: #f5f5f3; - --clr-bg: var(--off-white); -} - -body { - min-height: 100vh; - background: var(--clr-bg-2); -} - -body:has(.marketing-page) { - --off-white: #f5f5f3; - --clr-bg: var(--off-white); - /* gray */ - --clr-white: #ffffff; - --clr-black: #000000; - --clr-dark-gray: #707070; - --clr-gray: #d0cfcb; - --clr-light-gray: #f1f1ed; - /* accent */ - --clr-accent: #97eae5; -} - -body:has(.marketing-page)::-webkit-scrollbar { - /* For vertical scrollbars */ - width: 8px; - /* For horizontal scrollbars */ - height: 8px; -} - -body:has(.marketing-page)::-webkit-scrollbar-thumb { - background: color-mix(in srgb, var(--clr-accent) 96%, var(--clr-black)); -} diff --git a/apps/web/src/lib/user/userService.ts b/apps/web/src/lib/user/userService.ts index c175cbfdb5..c3798f04c7 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/lib/utils/releaseUtils.ts b/apps/web/src/lib/utils/releaseUtils.ts new file mode 100644 index 0000000000..8011b9bcc3 --- /dev/null +++ b/apps/web/src/lib/utils/releaseUtils.ts @@ -0,0 +1,79 @@ +import { getValidReleases, type Build, type Release } from '$lib/types/releases'; + +const API_BASE_URL = 'https://app.gitbutler.com/api/downloads'; + +/** + * Process builds by filtering out .zip files, removing duplicates, and sorting by platform + */ +export function processBuilds(builds: Build[]): Build[] { + return builds + .filter((build) => !build.url.endsWith('.zip')) + .filter((build, index, self) => self.findIndex((b) => b.url === build.url) === index) + .sort((a, b) => b.platform.localeCompare(a.platform)); +} + +/** + * Find a specific build based on OS, architecture, and optional file includes criteria + */ +export function findBuild( + builds: Build[], + os: string, + arch?: string, + fileIncludes?: string +): Build | undefined { + return builds.find( + (build: Build) => + build.os === os && + (!arch || build.arch === arch) && + (!fileIncludes || build.file.includes(fileIncludes)) + ); +} + +/** + * Create standardized build mapping for the latest release with common platform configurations + */ +export function createLatestReleaseBuilds(latestRelease: Release): { + [key: string]: Build | undefined; +} { + return { + darwin_x86_64: findBuild(latestRelease.builds, 'darwin', 'x86_64'), + darwin_aarch64: findBuild(latestRelease.builds, 'darwin', 'aarch64'), + windows_x86_64: findBuild(latestRelease.builds, 'windows', 'x86_64'), + linux_appimage: findBuild(latestRelease.builds, 'linux', undefined, 'AppImage'), + linux_deb: findBuild(latestRelease.builds, 'linux', undefined, 'deb'), + linux_rpm: findBuild(latestRelease.builds, 'linux', undefined, 'rpm') + }; +} + +/** + * Process all releases by applying processBuilds to each release's builds array + */ +export function processAllReleases(releases: Release[]): Release[] { + return releases.map((release) => ({ + ...release, + builds: processBuilds(release.builds) + })); +} + +/** + * Fetch releases from the GitButler API + */ +export async function fetchReleases( + limit: number = 10, + channel: 'release' | 'nightly' = 'release' +): Promise { + const response = await fetch(`${API_BASE_URL}?limit=${limit}&channel=${channel}`); + const data = await response.json(); + return getValidReleases(data); +} + +/** + * Fetch and process releases from the GitButler API + */ +export async function fetchAndProcessReleases( + limit: number = 10, + channel: 'release' | 'nightly' = 'release' +): Promise { + const releases = await fetchReleases(limit, channel); + return processAllReleases(releases); +} diff --git a/apps/web/src/lib/youtube.ts b/apps/web/src/lib/youtube.ts new file mode 100644 index 0000000000..525f5d5f59 --- /dev/null +++ b/apps/web/src/lib/youtube.ts @@ -0,0 +1,204 @@ +export interface YouTubeVideo { + id: string; + title: string; + description: string; + thumbnail: string; + publishedAt: string; + channelTitle: string; + videoId: string; + url: string; +} + +export interface YouTubePlaylist { + id: string; + title: string; + description: string; + videos: YouTubeVideo[]; +} + +/** + * Extract playlist ID from YouTube playlist URL + */ +export function extractPlaylistId(url: string): string | null { + try { + const urlObj = new URL(url); + return urlObj.searchParams.get('list'); + } catch { + return null; + } +} + +/** + * Get YouTube video URL from video ID + */ +export function getVideoUrl(videoId: string): string { + return `https://www.youtube.com/watch?v=${videoId}`; +} + +/** + * Get YouTube video embed URL from video ID + */ +export function getEmbedUrl(videoId: string): string { + return `https://www.youtube.com/embed/${videoId}`; +} + +/** + * Get high-quality YouTube thumbnail URL + * maxresdefault.jpg (1280x720) - highest quality, may not exist for all videos + * hqdefault.jpg (480x360) - high quality, more reliable + * mqdefault.jpg (320x180) - medium quality (default) + */ +export function getHighQualityThumbnail(videoId: string): string { + // Try maxres first for highest quality + return `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`; +} + +/** + * Get fallback thumbnail URL if high quality fails + */ +export function getFallbackThumbnail(videoId: string): string { + return `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`; +} + +/** + * Fetches videos from a YouTube playlist without requiring an API key + * Uses a combination of RSS feed and fallback data + */ +export async function fetchPlaylistVideos(playlistId: string): Promise { + try { + // Try to fetch from YouTube RSS feed (works without API key but has limitations) + const rssUrl = `https://www.youtube.com/feeds/videos.xml?playlist_id=${playlistId}`; + + // Use a CORS proxy to access the RSS feed + const proxyUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(rssUrl)}`; + const response = await fetch(proxyUrl, { + // Add timeout to prevent hanging + signal: AbortSignal.timeout(5000) + }); + + if (response.ok) { + const data = await response.json(); + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(data.contents, 'text/xml'); + + const entries = Array.from(xmlDoc.querySelectorAll('entry')).slice(0, 10); + const playlistTitle = xmlDoc.querySelector('title')?.textContent || 'YouTube Playlist'; + const channelTitle = xmlDoc.querySelector('author > name')?.textContent || 'YouTube Channel'; + + const videos: YouTubeVideo[] = entries + .map((entry) => { + // Try multiple ways to get videoId for better compatibility + const videoId = + entry.querySelector('yt\\:videoId')?.textContent || + entry.querySelector('videoId')?.textContent || + entry.querySelector('id')?.textContent?.split(':').pop() || + null; + + if (!videoId) return null; + + const title = entry.querySelector('title')?.textContent || 'Untitled Video'; + const description = + entry.querySelector('media\\:description')?.textContent || + entry.querySelector('description')?.textContent || + ''; + const publishedAt = + entry.querySelector('published')?.textContent || new Date().toISOString(); + + return { + id: videoId, + title, + description, + thumbnail: getHighQualityThumbnail(videoId), + publishedAt, + channelTitle, + videoId, + url: getVideoUrl(videoId) + }; + }) + .filter((video): video is YouTubeVideo => video !== null); + + return { + id: playlistId, + title: playlistTitle, + description: 'GitButler Feature Updates and Tutorials', + videos + }; + } + } catch (error) { + console.warn('Failed to fetch from RSS feed:', error); + } + + // Fallback to hardcoded playlist data for the specific GitButler playlist + return getGitButlerPlaylistFallback(playlistId); +} + +/** + * Fallback data with actual GitButler video information + * This can be updated manually when new videos are added to the playlist + */ +function getGitButlerPlaylistFallback(playlistId: string): YouTubePlaylist { + const videos: YouTubeVideo[] = [ + { + id: '1', + title: 'GitButler: A New Way to Git', + description: + 'Introducing GitButler - a Git client that makes complex Git workflows simple and visual.', + thumbnail: 'https://img.youtube.com/vi/A8-aLZ8e5tw/mqdefault.jpg', + publishedAt: '2024-03-15T10:00:00Z', + channelTitle: 'GitButler', + videoId: 'A8-aLZ8e5tw', + url: getVideoUrl('A8-aLZ8e5tw') + }, + { + id: '2', + title: 'Virtual Branches Explained', + description: + 'Learn about GitButlers virtual branches feature that lets you work on multiple features simultaneously.', + thumbnail: 'https://img.youtube.com/vi/ChNLvCmJFss/mqdefault.jpg', + publishedAt: '2024-03-20T14:30:00Z', + channelTitle: 'GitButler', + videoId: 'ChNLvCmJFss', + url: getVideoUrl('ChNLvCmJFss') + }, + { + id: '3', + title: 'Getting Started with GitButler', + description: + 'A quick tutorial on how to get started with GitButler and set up your first project.', + thumbnail: 'https://img.youtube.com/vi/YjCY-3rBd5g/mqdefault.jpg', + publishedAt: '2024-03-25T16:45:00Z', + channelTitle: 'GitButler', + videoId: 'YjCY-3rBd5g', + url: getVideoUrl('YjCY-3rBd5g') + }, + { + id: '4', + title: 'Advanced GitButler Features', + description: + 'Explore advanced features like AI commit messages, hunk management, and workflow automation.', + thumbnail: 'https://img.youtube.com/vi/Qz8Bz9QvVpU/mqdefault.jpg', + publishedAt: '2024-04-01T12:15:00Z', + channelTitle: 'GitButler', + videoId: 'Qz8Bz9QvVpU', + url: getVideoUrl('Qz8Bz9QvVpU') + }, + { + id: '5', + title: 'GitButler vs Traditional Git', + description: + 'Compare GitButler with traditional Git workflows and see why teams are switching.', + thumbnail: 'https://img.youtube.com/vi/dQw4w9WgXcQ/mqdefault.jpg', + publishedAt: '2024-04-05T09:30:00Z', + channelTitle: 'GitButler', + videoId: 'dQw4w9WgXcQ', + url: getVideoUrl('dQw4w9WgXcQ') + } + ]; + + return { + id: playlistId, + title: 'GitButler Feature Updates', + description: 'Latest GitButler tutorials, feature demonstrations, and updates', + videos + }; +} diff --git a/apps/web/src/params/ownerSlug.ts b/apps/web/src/params/ownerSlug.ts new file mode 100644 index 0000000000..b09aa7f0d9 --- /dev/null +++ b/apps/web/src/params/ownerSlug.ts @@ -0,0 +1,8 @@ +/** + * Parameter matcher for owner slugs. + * Matches URL-safe strings that can be used as owner identifiers. + * Allows letters, numbers, hyphens, underscores, and dots. + */ +export function match(param: string): param is string { + return /^[a-zA-Z0-9_.-]+$/.test(param); +} diff --git a/apps/web/src/routes/(app)/+layout.svelte b/apps/web/src/routes/(app)/+layout.svelte index 2f55f15357..aedce5ebf9 100644 --- a/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/web/src/routes/(app)/+layout.svelte @@ -1,9 +1,10 @@ -
- {#if !isCommitPage} - - {/if} + +
+
{@render children?.()}
-
+
+ diff --git a/apps/web/src/routes/(app)/+page.svelte b/apps/web/src/routes/(app)/+page.svelte index 29fae729f3..3cd7f98b05 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!

{:else} -
+ + + {/if} 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 1b633973d6..50a08065c3 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 | Downloads - - -
-

Latest Release

-
-
-
- GitButler -
-
-
-
{latestRelease?.version}
-
{latestRelease?.released_at.substring(0, 10)}
-
-
-
-
-
- - Windows -
-
- {#if latestReleaseBuilds?.['windows_x86_64']} - Download Windows (MSI) - {/if} -
-
-
-
- - macOS -
-
- {#if latestReleaseBuilds?.['darwin_x86_64']} - Download Intel - {/if} - {#if latestReleaseBuilds?.['darwin_aarch64']} - Download Apple Silicon - {/if} -
-
-
-
-
-
- - Linux -
-
- {#if latestReleaseBuilds?.['linux_appimage']} - Download AppImage - {/if} - {#if latestReleaseBuilds?.['linux_deb']} - Download Deb - {/if} - {#if latestReleaseBuilds?.['linux_rpm']} - Download RPM - {/if} -
-
-
-
-
-
-
- -

All Recent Releases

-
-
-

Stable Releases

- {#each releases as release} -
-
- Version: {release.version} - {release.sha.substring(0, 6)} -
-
Released: {new Date(release.released_at).toLocaleString()}
- {#if release.notes} -
{@html marked(release.notes)}
- {/if} -
- {#each release.builds as build} -
  • {build.platform}
  • - {/each} -
    -
    -
    - {/each} -
    -
    -

    Nightly Releases

    -
    - These are nightly builds that are automatically built from the master branch each night and - may be unstable. -
    - {#each nightlies as release} -
    -
    - Version: {release.version} - {release.sha.substring(0, 6)} -
    -
    Released: {new Date(release.released_at).toLocaleString()}
    - {#if release.notes} -
    {@html marked(release.notes)}
    - {/if} -
    - {#each release.builds as build} -
  • {build.platform}
  • - {/each} -
    -
    -
    - {/each} -
    -
    -
    - - diff --git a/apps/web/src/routes/(app)/downloads/+page.ts b/apps/web/src/routes/(app)/downloads/+page.ts deleted file mode 100644 index 3599ccee55..0000000000 --- a/apps/web/src/routes/(app)/downloads/+page.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { getValidReleases, type Build, type Release } from '$lib/types/releases'; -import type { PageLoad } from './$types'; - -function processBuilds(builds: Build[]) { - return builds - .filter((build) => !build.url.endsWith('.zip')) - .filter((build, index, self) => self.findIndex((b) => b.url === build.url) === index) - .sort((a, b) => b.platform.localeCompare(a.platform)); -} - -function findBuild(builds: Build[], os: string, arch?: string, fileIncludes?: string) { - return builds.find( - (build: any) => - build.os === os && - (!arch || build.arch === arch) && - (!fileIncludes || build.file.includes(fileIncludes)) - ); -} - -// eslint-disable-next-line func-style -export const load: PageLoad = async () => { - let releases: Release[] = []; - let nightlies: Release[] = []; - let latestReleaseBuilds: { [key: string]: Build | unknown } = {}; - - const releaseResponse = await fetch( - 'https://app.gitbutler.com/api/downloads?limit=10&channel=release' - ); - releases = getValidReleases(await releaseResponse.json()); - const latestRelease = releases[0]; - - releases.forEach((release) => { - release.builds = processBuilds(release.builds); - }); - - latestReleaseBuilds = { - darwin_x86_64: findBuild(latestRelease.builds, 'darwin', 'x86_64'), - darwin_aarch64: findBuild(latestRelease.builds, 'darwin', 'aarch64'), - windows_x86_64: findBuild(latestRelease.builds, 'windows', 'x86_64'), - linux_appimage: findBuild(latestRelease.builds, 'linux', undefined, 'AppImage'), - linux_deb: findBuild(latestRelease.builds, 'linux', undefined, 'deb'), - linux_rpm: findBuild(latestRelease.builds, 'linux', undefined, 'rpm') - }; - - const nightlyResponse = await fetch( - 'https://app.gitbutler.com/api/downloads?limit=15&channel=nightly' - ); - nightlies = getValidReleases(await nightlyResponse.json()); - nightlies.forEach((nightlyRelease) => { - nightlyRelease.builds = processBuilds(nightlyRelease.builds); - }); - - return { - nightlies, - releases, - latestRelease, - latestReleaseBuilds - }; -}; diff --git a/apps/web/src/routes/(app)/loggedin/+page.svelte b/apps/web/src/routes/(app)/loggedin/+page.svelte new file mode 100644 index 0000000000..aa9e1ab9a7 --- /dev/null +++ b/apps/web/src/routes/(app)/loggedin/+page.svelte @@ -0,0 +1,17 @@ + + + + GitButler | Logged in + + + +

    You can now close this window and return to your client.

    +
    diff --git a/apps/web/src/routes/(app)/login/+page.svelte b/apps/web/src/routes/(app)/login/+page.svelte new file mode 100644 index 0000000000..646e92b13a --- /dev/null +++ b/apps/web/src/routes/(app)/login/+page.svelte @@ -0,0 +1,219 @@ + + + + GitButler | Login + + + + + + {#snippet title()} + Login + to GitButler + {/snippet} + +
    +
    + + + + +
    + + {#if confirmationSent} + + {#snippet content()} +

    Confirmation email sent! Please check your inbox.

    + {/snippet} +
    + {:else if error} +
    + + {#snippet content()} + {#if errorCode === 'email_not_verified'} + {#if !resendDisabled} +

    + Verify your email before logging in. Check your inbox or . +

    + {:else} +

    + Verify your email before logging in. You can resend the confirmation email in {resendCountdown} + seconds. +

    + {/if} + {:else} +

    {error}

    + {/if} + {/snippet} +
    +
    + {/if} + + + + +
    + + {#snippet footer()} + + {/snippet} +
    + + 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..2c1aa64ff5 --- /dev/null +++ b/apps/web/src/routes/(app)/login/confirm-password/+page.svelte @@ -0,0 +1,104 @@ + + + + GitButler | Confirm Password + + + + + +
    + + + {#if error} + + {#snippet content()} + {error} + {/snippet} + + {/if} + + {#if message} + + {#snippet content()} + {message} + {/snippet} + + {/if} + + +
    +
    + + diff --git a/apps/web/src/routes/(app)/login/forgot-password/+page.svelte b/apps/web/src/routes/(app)/login/forgot-password/+page.svelte new file mode 100644 index 0000000000..e6cb4a3731 --- /dev/null +++ b/apps/web/src/routes/(app)/login/forgot-password/+page.svelte @@ -0,0 +1,77 @@ + + + + GitButler | Forgot Password + + + + + + {#if isLinkSent} +

    + We've sent a password reset link to: {sentToEmail} +
    + Click the link in your email to reset your password. +

    + {:else} +
    + + + {#if error} + + {#snippet content()} + {error} + {/snippet} + + {/if} + + +
    + {/if} +
    + + diff --git a/apps/web/src/routes/(app)/profile/+page.svelte b/apps/web/src/routes/(app)/profile/+page.svelte index 06b5d78f52..dec8fe4081 100644 --- a/apps/web/src/routes/(app)/profile/+page.svelte +++ b/apps/web/src/routes/(app)/profile/+page.svelte @@ -1,15 +1,20 @@ @@ -230,825 +69,313 @@ GitButler | User -
    -
    - {#if !$token} - -

    Who this?

    -

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

    -
    - {:else if !$user?.id} -

    Loading...

    - {:else} -

    My Preferences

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

    It looks like you're not logged in

    +

    + Please log in to access your profile +

    +
    +{:else} +
    +
    + - -
    - + {#if recentProjects.current.length > 0} + -
    -
    -
    - - e.key === 'Enter' && updateName()} - /> -
    -
    - - -
    -
    -
    -
    -
    + + {#snippet children(notificationSettings)} + + {/snippet} + - {#if $user?.supporter} -
    -
    -

    🎉 Thank you for being a supporter!

    -

    - Your support helps us build a better GitButler. We appreciate your contribution. -

    - -
    -
    + {/if} -

    Contact Info

    - -
    -
    -
    - - -
    - -
    - - -
    - -
    - - -
    + + {#if $user} + + {#snippet title()} + Signing out + {/snippet} + {#snippet caption()} + Ready to take a break? Click here to log out and unwind. + {/snippet} + {#snippet actions()} + + {/snippet} + + {/if} +
    -
    - - -
    +
    +
    + +
    -
    - -
    +
    +
    +
    + - +

    + Get the desktop app for Mac, Windows, and Linux. +

    -
    - - -

    Notification settings

    - - - {#snippet children(notificationSettings)} - -
    -
    - -
    - -
    - -
    -
    - -
    + -
    - -
    +
    -
    - -
    - -
    - -
    -
    -
    - {/snippet} -
    - -

    SSH Keys

    - - -
    - {#if loadingSshKeys} -
    Loading SSH keys...
    - {:else if sshKeys.length === 0} -
    No SSH keys added yet
    - {:else} - {#each sshKeys as key} -
    -
    - {key.name} - {key.fingerprint} -
    - -
    - {/each} - {/if} - - - - +

    + Get the app for + + other platforms + + ↗ +

    -
    - - +
    - - - {#if showSshKeyTokenModal} -
    -
    -

    Add Your SSH Key

    -

    - Run the following command in your terminal to add your SSH key to GitButler: -

    -
    - ssh git@ssh.gitbutler.com add/{sshKeyToken} - -
    -

    This token will expire after use or in 5 minutes.

    -
    - -
    -
    -
    + {#if $user?.supporter} + {/if} -

    Experimental settings

    - - - {#snippet title()}Organizations{/snippet} - {#snippet caption()} - Organizations are a way of linking together projects. - {/snippet} - {#snippet actions()} - ($featureShowOrganizations = !$featureShowOrganizations)} - /> - {/snippet} - - - - {#snippet title()}User / Organization / Project Pages{/snippet} - {#snippet caption()} - This will show the stub landing pages for orgs, users and projects. - {/snippet} - {#snippet actions()} - ($featureShowProjectPage = !$featureShowProjectPage)} - /> - {/snippet} - - {/if} + +
    -
    +{/if} diff --git a/apps/web/src/routes/(app)/profile/components/ExperimentalSettings.svelte b/apps/web/src/routes/(app)/profile/components/ExperimentalSettings.svelte new file mode 100644 index 0000000000..8857b2b45c --- /dev/null +++ b/apps/web/src/routes/(app)/profile/components/ExperimentalSettings.svelte @@ -0,0 +1,51 @@ + + + + +
    +

    Experimental

    +

    + These settings enable experimental features that are still in development. +
    + They may be unstable or incomplete. Use them at your own risk. +

    +
    + +
    + + {#snippet title()}Organizations{/snippet} + {#snippet caption()} + Organizations are a way of linking together projects. + {/snippet} + {#snippet actions()} + ($featureShowOrganizations = !$featureShowOrganizations)} + /> + {/snippet} + + + + {#snippet title()}User / Organization / Project Pages{/snippet} + {#snippet caption()} + This will show the stub landing pages for orgs, users and projects. + {/snippet} + {#snippet actions()} + ($featureShowProjectPage = !$featureShowProjectPage)} + /> + {/snippet} + +
    + + diff --git a/apps/web/src/routes/(app)/profile/components/NotificationSettings.svelte b/apps/web/src/routes/(app)/profile/components/NotificationSettings.svelte new file mode 100644 index 0000000000..1a820afeef --- /dev/null +++ b/apps/web/src/routes/(app)/profile/components/NotificationSettings.svelte @@ -0,0 +1,211 @@ + + + + +
    +

    Notification settings

    +

    + Manage your email notification preferences for various activities within GitButler. +

    +
    + +
    + + {#snippet title()} + Chat message mention emails + {/snippet} + {#snippet caption()} + Emails when you are mentioned in a message. + {/snippet} + {#snippet actions()} + + updateReceiveChatMentionEmails(!notificationSettings.receiveChatMentionEmails)} + /> + {/snippet} + + + + {#snippet title()} + Chat message reply emails + {/snippet} + {#snippet caption()} + Emails when you receive a reply to a chat message. + {/snippet} + {#snippet actions()} + updateReceiveChatReplyEmails(!notificationSettings.receiveChatReplyEmails)} + /> + {/snippet} + + + + {#snippet title()} + Issue creation emails + {/snippet} + {#snippet caption()} + Emails for new issues created in changes you are involved in. + {/snippet} + {#snippet actions()} + + updateReceiveIssueCreationEmails(!notificationSettings.receiveIssueCreationEmails)} + /> + {/snippet} + + + + {#snippet title()} + Issue status emails + {/snippet} + {#snippet caption()} + Emails for status updates of issues in changes you are involved in. + {/snippet} + {#snippet actions()} + + updateReceiveIssueResolutionEmails(!notificationSettings.receiveIssueResolutionEmails)} + /> + {/snippet} + + + + {#snippet title()} + Branch version update emails + {/snippet} + {#snippet caption()} + Emails when a new review branch version is created. + {/snippet} + {#snippet actions()} + + updateReceiveReviewBranchEmails(!notificationSettings.receiveReviewBranchEmails)} + /> + {/snippet} + + + + {#snippet title()} + Change status update emails + {/snippet} + {#snippet caption()} + Emails for updates on the review status of changes you are involved in. + {/snippet} + {#snippet actions()} + updateReceiveSignOffEmails(!notificationSettings.receiveSignOffEmails)} + /> + {/snippet} + +
    + + diff --git a/apps/web/src/routes/(app)/profile/components/ProfileHeader.svelte b/apps/web/src/routes/(app)/profile/components/ProfileHeader.svelte new file mode 100644 index 0000000000..f351a68796 --- /dev/null +++ b/apps/web/src/routes/(app)/profile/components/ProfileHeader.svelte @@ -0,0 +1,241 @@ + + +
    + +
    + { + // TODO: Add toast notification for invalid file type + }} + /> + +
    + e.key === 'Enter' && updateName()} + /> + +
    +
    +
    + + +
    + + + + + + + +
    +
    + + + {#snippet title()} + Share my email + {/snippet} + {#snippet caption()} + Allow other users to see your email address. + {/snippet} + {#snippet actions()} + (emailShareValue = !emailShareValue)} + /> + {/snippet} + + + +
    + +
    +
    +
    + + diff --git a/apps/web/src/routes/(app)/profile/components/SshKeysSection.svelte b/apps/web/src/routes/(app)/profile/components/SshKeysSection.svelte new file mode 100644 index 0000000000..674e58f90a --- /dev/null +++ b/apps/web/src/routes/(app)/profile/components/SshKeysSection.svelte @@ -0,0 +1,346 @@ + + + + + +
    + {#if loadingSshKeys} +
    Loading SSH keys...
    + {:else if sshKeys.length === 0} +
    No SSH keys added yet
    + {:else} + {#each sshKeys as key} +
    +
    + {key.name} + {key.fingerprint} +
    + +
    + {/each} + {/if} + + + + +
    +
    + + + + + +{#if showSshKeyTokenModal} +
    +
    +

    Add Your SSH Key

    +

    + Run the following command in your terminal to add your SSH key to GitButler: +

    +
    + ssh git@ssh.gitbutler.com add/{sshKeyToken} + +
    +

    This token will expire after use or in 5 minutes.

    +
    + +
    +
    +
    +{/if} + + diff --git a/apps/web/src/routes/(app)/profile/components/SupporterCard.svelte b/apps/web/src/routes/(app)/profile/components/SupporterCard.svelte new file mode 100644 index 0000000000..78ab8eb05b --- /dev/null +++ b/apps/web/src/routes/(app)/profile/components/SupporterCard.svelte @@ -0,0 +1,30 @@ + + +
    +

    🎉 Thank you for being a supporter!

    +

    + Your support helps us build a better GitButler.
    We appreciate your contribution. +

    + +
    + + 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..080e2ee1bf --- /dev/null +++ b/apps/web/src/routes/(app)/profile/finalize/+page.svelte @@ -0,0 +1,134 @@ + + + + GitButler | Finalize Account + + + + {#snippet title()} + Almost done! + {/snippet} + +
    +

    + We need these details to set up your account properly. +

    + {#if !userLogin} + + {/if} + {#if !userEmail} + + {/if} + + {#if error} + + {#snippet content()} + {error} + {/snippet} + + {/if} + + {#if message} + + {#snippet content()} + {message} + {/snippet} + + {/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..4072c66b47 --- /dev/null +++ b/apps/web/src/routes/(app)/signup/+page.svelte @@ -0,0 +1,166 @@ + + + + GitButler | Sign Up + + + + + + {#snippet title()} + {#if !successMessage} + Sign Up + for GitButler + {:else} + 🚀 Check your email for confirmation instructions + {/if} + {/snippet} + + {#if !successMessage} +
    +
    + + + +
    + + {#if error} + + {#snippet content()} + {error} + {/snippet} + + {/if} + + + + + + {/if} + + {#snippet footer()} + + {/snippet} +
    + + 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..1d4bae74a7 --- /dev/null +++ b/apps/web/src/routes/(app)/signup/resend-confirmation/+page.svelte @@ -0,0 +1,91 @@ + + + + GitButler | Resend Confirmation + + + + {#if email} +

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

    + {:else} +
    + + + {#if error} + + {#snippet content()} + {error} + {/snippet} + + {/if} + + {#if message} + + {#snippet content()} + {message} + {/snippet} + + {/if} + + {#if banner} + + {#snippet content()} + {banner} + {/snippet} + + {/if} + + +
    + {/if} +
    diff --git a/apps/web/src/routes/(home)/HomePage.svelte b/apps/web/src/routes/(home)/HomePage.svelte new file mode 100644 index 0000000000..d742d5d75b --- /dev/null +++ b/apps/web/src/routes/(home)/HomePage.svelte @@ -0,0 +1,25 @@ + + + + + + + + + +