diff --git a/apps/web/src/app/auth/components/particles/index.ts b/apps/web/src/app/auth/components/particles/index.ts
new file mode 100644
index 00000000..cfcb4d89
--- /dev/null
+++ b/apps/web/src/app/auth/components/particles/index.ts
@@ -0,0 +1 @@
+export { Particles } from "./particles";
diff --git a/apps/web/src/app/auth/login/mouse-position.tsx b/apps/web/src/app/auth/components/particles/mouse-position.tsx
similarity index 100%
rename from apps/web/src/app/auth/login/mouse-position.tsx
rename to apps/web/src/app/auth/components/particles/mouse-position.tsx
diff --git a/apps/web/src/app/auth/login/particles.tsx b/apps/web/src/app/auth/components/particles/particles.tsx
similarity index 99%
rename from apps/web/src/app/auth/login/particles.tsx
rename to apps/web/src/app/auth/components/particles/particles.tsx
index aaba919e..6fabeae6 100644
--- a/apps/web/src/app/auth/login/particles.tsx
+++ b/apps/web/src/app/auth/components/particles/particles.tsx
@@ -12,7 +12,7 @@ interface ParticlesProps {
rgb?: string;
}
-export default function Particles({
+export function Particles({
className = "",
quantity = 30,
staticity = 50,
diff --git a/apps/web/src/app/auth/login/page.tsx b/apps/web/src/app/auth/login/page.tsx
index d86f7268..bf98d559 100644
--- a/apps/web/src/app/auth/login/page.tsx
+++ b/apps/web/src/app/auth/login/page.tsx
@@ -1,52 +1,11 @@
-import {
- Box,
- Button,
- Flex,
- Paper,
- Stack,
- Text,
- ThemeIcon,
- Title,
- useMantineColorScheme,
-} from "@mantine/core";
+import { Button, Flex, Paper, Stack, Text, Title } from "@mantine/core";
import { AuthProvider } from "@sweetr/graphql-types/frontend/graphql";
-import {
- IconBrandGithub,
- IconClock,
- IconFireExtinguisher,
- IconFlame,
- IconQuote,
-} from "@tabler/icons-react";
import { useAuthProviderQuery } from "../../../api/auth.api";
import { Logo } from "../../../components/logo";
-import { IconDeployment } from "../../../providers/icon.provider";
-import { useSearchParams } from "react-router";
-import Particles from "./particles";
+import { Link, useSearchParams } from "react-router";
+import { Particles } from "../components/particles";
import classes from "./page.module.css";
-const ELITE_BENCHMARKS = [
- {
- icon: IconDeployment,
- metric: "Deployment Frequency",
- benchmark: "On-demand, multiple deploys per day",
- },
- {
- icon: IconClock,
- metric: "Lead Time for Changes",
- benchmark: "Less than one hour from commit to production",
- },
- {
- icon: IconFireExtinguisher,
- metric: "Time to Restore Service",
- benchmark: "Less than one hour to recover from incidents",
- },
- {
- icon: IconFlame,
- metric: "Change Failure Rate",
- benchmark: "0–15% of changes cause failures",
- },
-];
-
export const LoginPage = () => {
const [searchParams] = useSearchParams({
redirectTo: "",
@@ -59,8 +18,6 @@ export const LoginPage = () => {
},
});
- const { colorScheme } = useMantineColorScheme();
-
document.body.style.backgroundColor = "#141517";
return (
@@ -76,7 +33,6 @@ export const LoginPage = () => {
radius="lg"
p={48}
maw={960}
- w="100%"
pos="relative"
style={{ zIndex: 1 }}
>
@@ -89,29 +45,39 @@ export const LoginPage = () => {
>
- Get started for free
+ Sweetr
- No credit card required
+ Login or signup with GitHub
- }
- bg="green.4"
- loaderProps={{ color: "black" }}
- c="black"
- >
- Continue with GitHub
-
-
- Works for login and signup. New teams will be guided through
- setup.
-
+
+
+
+
+
+
{
td="underline"
style={{ textUnderlineOffset: 3 }}
>
- Explore the sandbox instead.
+ Explore the sandbox instead
-
-
-
- Is your engineering team elite?
-
-
- According to the DORA research program, elite teams hit all four
- of these benchmarks. Most teams don't know where they stand.
-
-
-
- {ELITE_BENCHMARKS.map((item) => (
-
-
-
-
-
-
- {item.metric}
-
-
- {item.benchmark}
-
-
-
- ))}
-
-
-
- Sweetr tracks these metrics automatically from your Git data.
-
-
-
-
-
- Source: 2021 Accelerate State of DevOps Report (DORA / Google
- Cloud)
-
-
-
diff --git a/apps/web/src/app/auth/sign-up/page.module.css b/apps/web/src/app/auth/sign-up/page.module.css
new file mode 100644
index 00000000..25774738
--- /dev/null
+++ b/apps/web/src/app/auth/sign-up/page.module.css
@@ -0,0 +1,32 @@
+.page {
+ min-height: 100vh;
+ position: relative;
+ overflow: hidden;
+ background:
+ radial-gradient(
+ ellipse 600px 400px at 15% 10%,
+ rgba(64, 192, 87, 0.12) 0%,
+ transparent 100%
+ ),
+ radial-gradient(
+ ellipse 500px 500px at 85% 80%,
+ rgba(64, 192, 87, 0.08) 0%,
+ transparent 100%
+ ),
+ radial-gradient(
+ ellipse 300px 600px at 50% 50%,
+ rgba(64, 192, 87, 0.04) 0%,
+ transparent 100%
+ ),
+ radial-gradient(
+ circle 800px at 70% 20%,
+ rgba(34, 139, 230, 0.04) 0%,
+ transparent 100%
+ );
+}
+
+.particles {
+ position: absolute;
+ inset: 0;
+ z-index: 0;
+}
diff --git a/apps/web/src/app/auth/sign-up/page.tsx b/apps/web/src/app/auth/sign-up/page.tsx
new file mode 100644
index 00000000..e9268e1a
--- /dev/null
+++ b/apps/web/src/app/auth/sign-up/page.tsx
@@ -0,0 +1,197 @@
+import {
+ Accordion,
+ Anchor,
+ Box,
+ Button,
+ Divider,
+ Flex,
+ Paper,
+ Stack,
+ Text,
+ ThemeIcon,
+ Title,
+} from "@mantine/core";
+import {
+ IconBrandGithub,
+ IconCode,
+ IconLock,
+ IconLogout,
+ IconShieldCheck,
+} from "@tabler/icons-react";
+import { Logo } from "../../../components/logo";
+import { Link } from "react-router";
+import { Particles } from "../components/particles";
+import classes from "./page.module.css";
+import { installGithubAppUrl } from "../../../providers/github.provider";
+
+const FAQ_ITEMS = [
+ {
+ value: "source-code",
+ icon: IconCode,
+ color: "green",
+ question: "Does the GitHub app have access to my source code?",
+ answer: (
+ <>
+ No. Sweetr only asks for access to metadata about organization members
+ and their pull requests. You can check the{" "}
+
+ documentation
+ {" "}
+ for more details.
+ >
+ ),
+ },
+ {
+ value: "permissions",
+ icon: IconLock,
+ color: "blue",
+ question: "Who has permissions to see the data?",
+ answer:
+ "Sweetr inherits the exact permissions your team already has on GitHub. If a teammate can't see a private repository on GitHub, they won't see any of its data on Sweetr either.",
+ },
+ {
+ value: "security",
+ icon: IconShieldCheck,
+ color: "violet",
+ question: "Is my data secure?",
+ answer: (
+ <>
+ We follow stringent security best practices to protect our servers and
+ your data.{" "}
+
+ Learn more
+
+ .
+ >
+ ),
+ },
+ {
+ value: "uninstall",
+ icon: IconLogout,
+ color: "red",
+ question: "Can I uninstall it at any time?",
+ answer:
+ "Yes. You can uninstall the GitHub app from your organization settings at any time and revoke access in a single click to remove all data from our servers. No questions asked.",
+ },
+];
+
+export const SignUpPage = () => {
+ document.body.style.backgroundColor = "#141517";
+
+ return (
+
+
+
+
+
+
+
+ Start 14-day free trial
+
+ No credit card required
+
+
+ }
+ bg="green.4"
+ loaderProps={{ color: "black" }}
+ c="black"
+ >
+ Install GitHub App
+
+
+ Back to login
+
+
+
+
+
+ How it works
+
+
+ Sweetr is a GitHub app you install on your organization. Once
+ installed, we sync your data automatically. You'll see your team's
+ metrics within minutes, with zero configuration.
+
+
+
+
+
+ Common questions
+
+
+
+ {FAQ_ITEMS.map((item) => (
+
+
+
+
+ }
+ >
+
+ {item.question}
+
+
+
+
+ {item.answer}
+
+
+
+ ))}
+
+
+
+
+
+ );
+};
diff --git a/apps/web/src/routes.tsx b/apps/web/src/routes.tsx
index 4865c519..10d88086 100644
--- a/apps/web/src/routes.tsx
+++ b/apps/web/src/routes.tsx
@@ -1,7 +1,7 @@
import { createBrowserRouter, redirect } from "react-router";
import { PageNotFound } from "./app/404";
import { ErrorPage } from "./app/500";
-import { LoginPage } from "./app/auth/login/page";
+import { SignUpPage } from "./app/auth/sign-up/page";
import { AutomationsPage } from "./app/automations/page";
import { AutomationPrSizeLabelerPage } from "./app/automations/settings/pr-size-labeler/page";
import { AutomationIncidentDetectionPage } from "./app/automations/settings/incident-detection/page";
@@ -70,6 +70,7 @@ import { SystemsPullRequestsPage } from "./app/systems/pull-requests/page";
import { MetricsAndInsightsPage } from "./app/metrics-and-insights/page";
import { PrFlowPage } from "./app/metrics-and-insights/pr-flow/page";
import { CodeReviewEfficiencyPage } from "./app/metrics-and-insights/code-review-efficiency/page";
+import { LoginPage } from "./app/auth/login/page";
export const router = createBrowserRouter([
{
@@ -101,6 +102,10 @@ export const router = createBrowserRouter([
path: "/login",
element: ,
},
+ {
+ path: "/sign-up",
+ element: ,
+ },
],
},
// Sandbox entry point — starts MSW, sets fake auth, redirects to /
diff --git a/package-lock.json b/package-lock.json
index f9653572..35bc1f44 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,8 +16,8 @@
},
"devDependencies": {
"dotenv-cli": "^7.4.2",
- "prettier": "*",
- "turbo": "*"
+ "prettier": "latest",
+ "turbo": "latest"
},
"engines": {
"node": ">=22.12.0"
@@ -15083,7 +15083,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}