Skip to content

Commit 9fb6941

Browse files
committed
chore: merge branch 'dev' into stage
2 parents da271b6 + ec53a1b commit 9fb6941

35 files changed

Lines changed: 2566 additions & 96 deletions

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
> [!note]
66
>
77
> - this setup is done for quickly spinning up an MVP with a waitlist
8-
> - more features will be added soon :: including authentication (better-auth), payments, feature flags, rate limiting, captchas, i18n, monitoring, testing, and etc
8+
> - more features will be added soon :: including payments, feature flags, rate limiting, captchas, i18n, monitoring, testing, and etc
99
> - documentation is yet to be developed
1010
1111
## what is vazen?
@@ -29,12 +29,14 @@
2929
- [**react.js v19**](https://react.dev/) :: latest react with react compiler enabled
3030
- [**typescript**](https://www.typescriptlang.org/) :: type-safe development
3131
- [**tailwind CSS v4**](https://tailwindcss.com/) :: utility-first CSS framework
32-
- [**oRPC**]() :: alternative to tRPC with openapi support out of the box
33-
- [**tanstack query**](https://orpc.unnoq.com/) :: for efficient query & api handling
32+
- [**oRPC**](https://orpc.unnoq.com/) :: alternative to tRPC with openapi support out of the box
33+
- [**tanstack query**](https://tanstack.com/query/latest) :: for efficient query & api handling
3434
- [**supabase**](https://supabase.com/) :: postgresql on supabase, i'm assuming you're just starting up, for growth you may move to aws RDS later
3535
- [**drizzle ORM**](https://orm.drizzle.team/) :: database interactions & cloudflare hyperdrive integration (turning regional database to distributed database by cashing reads)
36+
- [**better-auth**](https://better-auth.com/) :: the most comprehensive authentication framework
3637
- [**react-email**](https://react.email/) :: library for building email templates in react
3738
- [**posthog**](https://posthog.com/) :: web analytics
39+
- [**c15t**](https://c15t.com/) :: developer-first consent management (cookie compliance)
3840

3941
### CI/CD
4042

apps/web/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ NEXT_PUBLIC_POSTHOG_HOST=""
44

55
# server
66
NEXTJS_ENV=development
7-
7+
BETTER_AUTH_SECRET=

apps/web/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,24 @@
1313
"lint": "eslint --max-warnings 0 --report-unused-disable-directives-severity warn",
1414
"preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
1515
"start": "next start",
16-
"typegen:worker": "wrangler types",
16+
"typegen:worker": "wrangler types --env-interface CloudflareEnv",
1717
"typegen:worker:stage": "wrangler types --env-interface CloudflareEnv -e=stage"
1818
},
1919
"dependencies": {
20+
"@c15t/nextjs": "^1.8.1",
2021
"@opennextjs/cloudflare": "^1.11.0",
2122
"@orpc/client": "catalog:",
2223
"@orpc/server": "catalog:",
2324
"@orpc/tanstack-query": "catalog:",
2425
"@repo/analytics": "workspace:*",
2526
"@repo/api": "workspace:*",
27+
"@repo/auth": "workspace:*",
2628
"@repo/db": "workspace:*",
2729
"@repo/fonts": "workspace:*",
30+
"@repo/security": "workspace:*",
2831
"@repo/ui": "workspace:*",
2932
"@tanstack/react-query": "^5.90.5",
33+
"better-auth": "catalog:",
3034
"next": "catalog:",
3135
"react": "catalog:",
3236
"react-dom": "catalog:",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { getCloudflareContext } from "@opennextjs/cloudflare";
2+
import { toNextJsHandler } from "better-auth/next-js";
3+
import { auth } from "@repo/auth/server";
4+
5+
const { env } = await getCloudflareContext({ async: true });
6+
7+
export const { POST, GET } = toNextJsHandler(auth(env).handler);

apps/web/src/app/api/rpc/[[...rest]]/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import { router } from "@repo/api/router";
66
const handler = new RPCHandler(router);
77

88
async function handleRequest(request: Request) {
9-
const { env: CloudflareEnv } = getCloudflareContext();
9+
const { env: cloudflareEnv } = getCloudflareContext();
1010
const { response } = await handler.handle(request, {
1111
prefix: "/api/rpc",
12-
context: await createContext({ headers: request.headers, CloudflareEnv }),
12+
context: await createContext({ headers: request.headers, cloudflareEnv }),
1313
});
1414

1515
return response ?? new Response("Not found", { status: 404 });

apps/web/src/app/providers.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@ import { QueryClientProvider } from "@tanstack/react-query";
55
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
66
import { ThemeProvider } from "@repo/ui";
77
import { createQueryClient } from "@/lib/query/client";
8+
import { ConsentManagementProvider } from "@/components/providers/consent-manager";
89

910
export function Providers(props: { children: React.ReactNode }) {
1011
const [queryClient] = useState(() => createQueryClient());
1112

1213
return (
1314
<ThemeProvider>
14-
<QueryClientProvider client={queryClient}>
15-
{props.children}
16-
<ReactQueryDevtools />
17-
</QueryClientProvider>
15+
<ConsentManagementProvider>
16+
<QueryClientProvider client={queryClient}>
17+
{props.children}
18+
<ReactQueryDevtools />
19+
</QueryClientProvider>
20+
</ConsentManagementProvider>
1821
</ThemeProvider>
1922
);
2023
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"use client";
2+
3+
import { ClientSideOptionsProvider } from "@c15t/nextjs/client";
4+
import { usePostHog } from "@repo/analytics/posthog";
5+
6+
export function ConsentManagerClient({ children }: { children: React.ReactNode }) {
7+
const posthog = usePostHog();
8+
9+
return (
10+
<ClientSideOptionsProvider
11+
callbacks={{
12+
onConsentSet({ preferences }) {
13+
if (preferences.measurement) {
14+
posthog.opt_in_capturing();
15+
} else {
16+
posthog.opt_out_capturing();
17+
}
18+
},
19+
onError(error) {
20+
console.error("Consent error:", error);
21+
},
22+
}}
23+
>
24+
{children}
25+
</ClientSideOptionsProvider>
26+
);
27+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { ConsentManagerDialog, ConsentManagerProvider, CookieBanner } from "@c15t/nextjs";
2+
import type { ReactNode } from "react";
3+
import { ConsentManagerClient } from "./consent-manager.client";
4+
5+
export function ConsentManagementProvider({ children }: { children: ReactNode }) {
6+
return (
7+
<ConsentManagerProvider
8+
options={{
9+
mode: "offline",
10+
consentCategories: ["necessary", "measurement"],
11+
ignoreGeoLocation: true, // Useful for development to always view the banner.
12+
legalLinks: {
13+
privacyPolicy: {
14+
href: "/privacy",
15+
label: "Privacy Policy",
16+
},
17+
termsOfService: {
18+
href: "/terms",
19+
label: "Terms of Service",
20+
},
21+
},
22+
}}
23+
>
24+
<CookieBanner
25+
theme={{
26+
"banner.overlay": "!bg-black/30",
27+
"banner.root": "!font-geist",
28+
"banner.card": "!rounded-none !border",
29+
"banner.footer.accept-button": "!rounded-none",
30+
"banner.footer.reject-button": "!rounded-none",
31+
"banner.footer.customize-button": "!rounded-none ",
32+
}}
33+
legalLinks={["privacyPolicy", "termsOfService"]}
34+
scrollLock={true}
35+
trapFocus={true}
36+
/>
37+
<ConsentManagerDialog
38+
legalLinks={["privacyPolicy", "termsOfService"]}
39+
theme={{
40+
"dialog.root": "!rounded-none !font-geist",
41+
"dialog.footer": "!hidden",
42+
"widget.accordion.item": "!rounded-none",
43+
"widget.footer.reject-button": "!rounded-none",
44+
"widget.footer.accept-button": "!rounded-none",
45+
"widget.footer.save-button": "!rounded-none ",
46+
}}
47+
/>
48+
<ConsentManagerClient>{children}</ConsentManagerClient>
49+
</ConsentManagerProvider>
50+
);
51+
}

apps/web/src/lib/orpc.server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import { createContext } from "@repo/api/orpc";
55
import { router } from "@repo/api/router";
66
import "server-only";
77

8-
const { env: CloudflareEnv } = await getCloudflareContext({ async: true });
8+
const { env: cloudflareEnv } = await getCloudflareContext({ async: true });
99
globalThis.$client = createRouterClient(router, {
1010
context: async () => {
11-
const ctx = await createContext({ headers: await headers(), CloudflareEnv: CloudflareEnv });
11+
const ctx = await createContext({ headers: await headers(), cloudflareEnv });
1212
return ctx;
1313
},
1414
});

apps/web/src/proxy.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { securityHeadersMiddleware, securityHeadersOptions } from "@repo/security/security-headers";
2+
3+
const securityHeaders = securityHeadersMiddleware(securityHeadersOptions);
4+
5+
/**
6+
*
7+
* ### Best practises
8+
* @see https://vercel.com/blog/postmortem-on-next-js-middleware-bypass
9+
* - strictly do not use proxy for auth, do auth things it in data access layer
10+
* - use proxy.ts as just routing infrastructure
11+
*
12+
*/
13+
export default function proxy() {
14+
return securityHeaders();
15+
}

0 commit comments

Comments
 (0)