Skip to content

Commit 10b4648

Browse files
Add stylized email using react-email
1 parent effae67 commit 10b4648

File tree

15 files changed

+1176
-138
lines changed

15 files changed

+1176
-138
lines changed

packages/web/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"build": "next build",
88
"start": "next start",
99
"lint": "next lint",
10-
"test": "vitest"
10+
"test": "vitest",
11+
"dev:emails": "email dev --dir ./src/emails"
1112
},
1213
"dependencies": {
1314
"@auth/prisma-adapter": "^2.7.4",
@@ -56,6 +57,8 @@
5657
"@radix-ui/react-toast": "^1.2.2",
5758
"@radix-ui/react-toggle": "^1.1.0",
5859
"@radix-ui/react-tooltip": "^1.1.4",
60+
"@react-email/components": "^0.0.33",
61+
"@react-email/render": "^1.0.5",
5962
"@replit/codemirror-lang-csharp": "^6.2.0",
6063
"@replit/codemirror-lang-nix": "^6.0.1",
6164
"@replit/codemirror-lang-solidity": "^6.0.2",
@@ -142,6 +145,7 @@
142145
"jsdom": "^25.0.1",
143146
"npm-run-all": "^4.1.5",
144147
"postcss": "^8",
148+
"react-email": "3.0.3",
145149
"tailwindcss": "^3.4.1",
146150
"tsx": "^4.19.2",
147151
"typescript": "^5",

packages/web/src/app/[domain]/components/navigationMenu.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ import { Button } from "@/components/ui/button";
22
import { NavigationMenu as NavigationMenuBase, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, navigationMenuTriggerStyle } from "@/components/ui/navigation-menu";
33
import Link from "next/link";
44
import { Separator } from "@/components/ui/separator";
5-
import Image from "next/image";
6-
import logoDark from "@/public/sb_logo_dark_small.png";
7-
import logoLight from "@/public/sb_logo_light_small.png";
85
import { SettingsDropdown } from "./settingsDropdown";
96
import { GitHubLogoIcon, DiscordLogoIcon } from "@radix-ui/react-icons";
107
import { redirect } from "next/navigation";
118
import { OrgSelector } from "./orgSelector";
129
import { getSubscriptionData } from "@/actions";
1310
import { isServiceError } from "@/lib/utils";
11+
import { SourcebotLogo } from "@/app/components/sourcebotLogo";
12+
1413
const SOURCEBOT_DISCORD_URL = "https://discord.gg/6Fhp27x7Pb";
1514
const SOURCEBOT_GITHUB_URL = "https://github.com/sourcebot-dev/sourcebot";
1615

@@ -31,17 +30,9 @@ export const NavigationMenu = async ({
3130
href={`/${domain}`}
3231
className="mr-3 cursor-pointer"
3332
>
34-
<Image
35-
src={logoDark}
36-
className="h-11 w-auto hidden dark:block"
37-
alt={"Sourcebot logo"}
38-
priority={true}
39-
/>
40-
<Image
41-
src={logoLight}
42-
className="h-11 w-auto block dark:hidden"
43-
alt={"Sourcebot logo"}
44-
priority={true}
33+
<SourcebotLogo
34+
className="h-11"
35+
size="small"
4536
/>
4637
</Link>
4738

packages/web/src/app/[domain]/components/payWall/paywallCard.tsx

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
22
import { Check } from "lucide-react"
33
import { EnterpriseContactUsButton } from "./enterpriseContactUsButton"
44
import { CheckoutButton } from "./checkoutButton"
5-
import Image from "next/image";
6-
import logoDark from "@/public/sb_logo_dark_large.png";
7-
import logoLight from "@/public/sb_logo_light_large.png";
5+
import { SourcebotLogo } from "@/app/components/sourcebotLogo";
86

97
const teamFeatures = [
108
"Index hundreds of repos from multiple code hosts (GitHub, GitLab, Gerrit, Gitea, etc.). Self-hosted code sources supported",
@@ -24,17 +22,9 @@ export async function PaywallCard({ domain }: { domain: string }) {
2422
return (
2523
<div className="max-w-4xl mx-auto px-4 py-8">
2624
<div className="max-h-44 w-auto mb-4 flex justify-center">
27-
<Image
28-
src={logoDark}
29-
className="h-18 md:h-40 w-auto hidden dark:block"
30-
alt={"Sourcebot logo"}
31-
priority={true}
32-
/>
33-
<Image
34-
src={logoLight}
35-
className="h-18 md:h-40 w-auto block dark:hidden"
36-
alt={"Sourcebot logo"}
37-
priority={true}
25+
<SourcebotLogo
26+
className="h-18 md:h-40"
27+
size="large"
3828
/>
3929
</div>
4030
<h2 className="text-3xl font-bold text-center mb-8 text-primary">

packages/web/src/app/[domain]/page.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { listRepositories } from "@/lib/server/searchService";
22
import { isServiceError } from "@/lib/utils";
3-
import Image from "next/image";
43
import { Suspense } from "react";
5-
import logoDark from "@/public/sb_logo_dark_large.png";
6-
import logoLight from "@/public/sb_logo_light_large.png";
74
import { NavigationMenu } from "./components/navigationMenu";
85
import { RepositoryCarousel } from "./components/repositoryCarousel";
96
import { SearchBar } from "./components/searchBar";
@@ -14,6 +11,7 @@ import Link from "next/link";
1411
import { getOrgFromDomain } from "@/data/org";
1512
import { PageNotFound } from "./components/pageNotFound";
1613
import { Footer } from "./components/footer";
14+
import { SourcebotLogo } from "../components/sourcebotLogo";
1715

1816

1917
export default async function Home({ params: { domain } }: { params: { domain: string } }) {
@@ -30,17 +28,8 @@ export default async function Home({ params: { domain } }: { params: { domain: s
3028
<UpgradeToast />
3129
<div className="flex flex-col justify-center items-center mt-8 mb-8 md:mt-18 w-full px-5">
3230
<div className="max-h-44 w-auto">
33-
<Image
34-
src={logoDark}
35-
className="h-18 md:h-40 w-auto hidden dark:block"
36-
alt={"Sourcebot logo"}
37-
priority={true}
38-
/>
39-
<Image
40-
src={logoLight}
41-
className="h-18 md:h-40 w-auto block dark:hidden"
42-
alt={"Sourcebot logo"}
43-
priority={true}
31+
<SourcebotLogo
32+
className="h-18 md:h-40 w-auto"
4433
/>
4534
</div>
4635
<SearchBar
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import logoDarkLarge from "@/public/sb_logo_dark_large.png";
2+
import logoLightLarge from "@/public/sb_logo_light_large.png";
3+
import logoDarkSmall from "@/public/sb_logo_dark_small.png";
4+
import logoLightSmall from "@/public/sb_logo_light_small.png";
5+
import Image from "next/image";
6+
import { cn } from "@/lib/utils";
7+
8+
interface SourcebotLogoProps {
9+
className?: string;
10+
size?: "small" | "large";
11+
}
12+
13+
export const SourcebotLogo = ({ className, size = "large" }: SourcebotLogoProps) => {
14+
return (
15+
<>
16+
<Image
17+
src={size === "large" ? logoDarkLarge : logoDarkSmall}
18+
className={cn("h-16 w-auto hidden dark:block", className)}
19+
alt={"Sourcebot logo"}
20+
priority={true}
21+
/>
22+
<Image
23+
src={size === "large" ? logoLightLarge : logoLightSmall}
24+
className={cn("h-16 w-auto block dark:hidden", className)}
25+
alt={"Sourcebot logo"}
26+
priority={true}
27+
/>
28+
</>
29+
)
30+
}

packages/web/src/app/login/components/credentialsForm.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ import { zodResolver } from "@hookform/resolvers/zod";
88
import { z } from "zod";
99
import { signIn } from "next-auth/react";
1010
import { verifyCredentialsRequestSchema } from "@/lib/schemas";
11+
import { useState } from "react";
12+
import { Loader2 } from "lucide-react";
1113

1214
interface CredentialsFormProps {
1315
callbackUrl?: string;
1416
}
1517

1618
export const CredentialsForm = ({ callbackUrl }: CredentialsFormProps) => {
19+
const [isLoading, setIsLoading] = useState(false);
1720
const form = useForm<z.infer<typeof verifyCredentialsRequestSchema>>({
1821
resolver: zodResolver(verifyCredentialsRequestSchema),
1922
defaultValues: {
@@ -23,10 +26,14 @@ export const CredentialsForm = ({ callbackUrl }: CredentialsFormProps) => {
2326
});
2427

2528
const onSubmit = (values: z.infer<typeof verifyCredentialsRequestSchema>) => {
29+
setIsLoading(true);
2630
signIn("credentials", {
2731
email: values.email,
2832
password: values.password,
2933
redirectTo: callbackUrl ?? "/"
34+
})
35+
.finally(() => {
36+
setIsLoading(false);
3037
});
3138
}
3239

@@ -66,7 +73,9 @@ export const CredentialsForm = ({ callbackUrl }: CredentialsFormProps) => {
6673
type="submit"
6774
className="w-full"
6875
variant="outline"
76+
disabled={isLoading}
6977
>
78+
{isLoading ? <Loader2 className="animate-spin mr-2" /> : ""}
7079
Sign in with credentials
7180
</Button>
7281
</form>

packages/web/src/app/login/components/loginForm.tsx

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
'use client';
22

33
import { Button } from "@/components/ui/button";
4-
import logoDark from "@/public/sb_logo_dark_large.png";
5-
import logoLight from "@/public/sb_logo_light_large.png";
64
import googleLogo from "@/public/google.svg";
75
import Image from "next/image";
86
import { signIn } from "next-auth/react";
@@ -11,6 +9,7 @@ import { Card } from "@/components/ui/card";
119
import { cn, getCodeHostIcon } from "@/lib/utils";
1210
import { MagicLinkForm } from "./magicLinkForm";
1311
import { CredentialsForm } from "./credentialsForm";
12+
import { SourcebotLogo } from "@/app/components/sourcebotLogo";
1413

1514
interface LoginFormProps {
1615
callbackUrl?: string;
@@ -45,20 +44,9 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps
4544
return (
4645
<div className="flex flex-col items-center justify-center">
4746
<div className="mb-6 flex flex-col items-center">
48-
<div>
49-
<Image
50-
src={logoDark}
51-
className="h-16 w-auto hidden dark:block"
52-
alt={"Sourcebot logo"}
53-
priority={true}
54-
/>
55-
<Image
56-
src={logoLight}
57-
className="h-16 w-auto block dark:hidden"
58-
alt={"Sourcebot logo"}
59-
priority={true}
60-
/>
61-
</div>
47+
<SourcebotLogo
48+
className="h-16"
49+
/>
6250
<h2 className="text-lg font-bold">Sign in to your account</h2>
6351
</div>
6452
<Card className="flex flex-col items-center border p-12 rounded-lg gap-6 w-[500px] bg-background">
@@ -73,6 +61,7 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps
7361
<>
7462
{enabledMethods.github && (
7563
<ProviderButton
64+
key="github"
7665
name="GitHub"
7766
logo={getCodeHostIcon("github")!}
7867
onClick={() => {
@@ -82,6 +71,7 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps
8271
)}
8372
{enabledMethods.google && (
8473
<ProviderButton
74+
key="google"
8575
name="Google"
8676
logo={{ src: googleLogo }}
8777
onClick={() => {
@@ -92,10 +82,10 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps
9282
</>
9383
] : []),
9484
...(enabledMethods.magicLink ? [
95-
<MagicLinkForm callbackUrl={callbackUrl} />
85+
<MagicLinkForm key="magic-link" callbackUrl={callbackUrl} />
9686
] : []),
9787
...(enabledMethods.credentials ? [
98-
<CredentialsForm callbackUrl={callbackUrl} />
88+
<CredentialsForm key="credentials" callbackUrl={callbackUrl} />
9989
] : [])
10090
]}
10191
/>
@@ -132,7 +122,7 @@ const DividerSet = ({ children }: { children: React.ReactNode[] }) => {
132122
return (
133123
<Fragment key={index}>
134124
{child}
135-
{index < children.length - 1 && <Divider />}
125+
{index < children.length - 1 && <Divider key={`divider-${index}`} />}
136126
</Fragment>
137127
)
138128
})

packages/web/src/app/login/components/magicLinkForm.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { useForm } from "react-hook-form";
77
import { zodResolver } from "@hookform/resolvers/zod";
88
import { z } from "zod";
99
import { signIn } from "next-auth/react";
10-
11-
10+
import { useState } from "react";
11+
import { Loader2 } from "lucide-react";
1212
const magicLinkSchema = z.object({
1313
email: z.string().email(),
1414
});
@@ -18,6 +18,7 @@ interface MagicLinkFormProps {
1818
}
1919

2020
export const MagicLinkForm = ({ callbackUrl }: MagicLinkFormProps) => {
21+
const [isLoading, setIsLoading] = useState(false);
2122
const magicLinkForm = useForm<z.infer<typeof magicLinkSchema>>({
2223
resolver: zodResolver(magicLinkSchema),
2324
defaultValues: {
@@ -26,7 +27,11 @@ export const MagicLinkForm = ({ callbackUrl }: MagicLinkFormProps) => {
2627
});
2728

2829
const onSignIn = (values: z.infer<typeof magicLinkSchema>) => {
29-
signIn("nodemailer", { email: values.email, redirectTo: callbackUrl ?? "/" });
30+
setIsLoading(true);
31+
signIn("nodemailer", { email: values.email, redirectTo: callbackUrl ?? "/" })
32+
.finally(() => {
33+
setIsLoading(false);
34+
});
3035
}
3136

3237
return (
@@ -54,7 +59,9 @@ export const MagicLinkForm = ({ callbackUrl }: MagicLinkFormProps) => {
5459
type="submit"
5560
className="w-full"
5661
variant="outline"
62+
disabled={isLoading}
5763
>
64+
{isLoading ? <Loader2 className="animate-spin mr-2" /> : ""}
5865
Sign in with magic link
5966
</Button>
6067
</form>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { SourcebotLogo } from "@/app/components/sourcebotLogo";
2+
3+
export default function VerifyPage() {
4+
5+
return (
6+
<div className="flex flex-col items-center justify-center h-screen">
7+
<SourcebotLogo
8+
className="mb-2 h-16"
9+
size="small"
10+
/>
11+
<h1 className="text-2xl font-bold mb-2">Verify your email</h1>
12+
<p className="text-sm text-muted-foreground">
13+
{`We've sent a magic link to your email. Please check your inbox.`}
14+
</p>
15+
</div>
16+
)
17+
}

packages/web/src/app/onboard/components/orgCreateForm.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@ import { isServiceError } from "@/lib/utils"
88
import { useForm } from "react-hook-form"
99
import { z } from "zod"
1010
import { zodResolver } from "@hookform/resolvers/zod"
11-
import logoDark from "@/public/sb_logo_dark_large.png";
12-
import logoLight from "@/public/sb_logo_light_large.png";
13-
import Image from "next/image";
1411
import { useState } from "react";
12+
import { SourcebotLogo } from "@/app/components/sourcebotLogo"
1513

1614
const onboardingFormSchema = z.object({
1715
name: z.string()
@@ -64,17 +62,8 @@ export function OrgCreateForm({ setOrgCreateData }: OrgCreateFormProps) {
6462
return (
6563
<div className="space-y-6">
6664
<div className="flex justify-center">
67-
<Image
68-
src={logoDark}
69-
className="h-16 w-auto hidden dark:block"
70-
alt={"Sourcebot logo"}
71-
priority={true}
72-
/>
73-
<Image
74-
src={logoLight}
75-
className="h-16 w-auto block dark:hidden"
76-
alt={"Sourcebot logo"}
77-
priority={true}
65+
<SourcebotLogo
66+
className="h-16"
7867
/>
7968
</div>
8069
<h1 className="text-2xl font-bold">Let&apos;s create your organization</h1>

0 commit comments

Comments
 (0)