create-pilotprojects-app 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -12
- package/package.json +1 -1
- package/templates/.prettierrc.cjs +10 -0
- package/templates/apps/mobile/.eslintrc.cjs +6 -0
- package/templates/apps/mobile/app/(auth)/login.tsx +18 -9
- package/templates/apps/mobile/app/(tabs)/index.tsx +2 -4
- package/templates/apps/mobile/app/_layout.tsx +7 -5
- package/templates/apps/mobile/lib/supabase.ts +1 -1
- package/templates/apps/mobile/lib/trpc/client.ts +3 -6
- package/templates/apps/mobile/nativewind-env.d.ts +34 -0
- package/templates/apps/mobile/package.json +2 -1
- package/templates/apps/mobile/tailwind.config.ts +55 -0
- package/templates/apps/web/.eslintrc.cjs +2 -0
- package/templates/apps/web/app/(auth)/login/page.tsx +10 -5
- package/templates/apps/web/app/(dashboard)/dashboard/page.tsx +3 -4
- package/templates/apps/web/app/(dashboard)/layout.tsx +5 -4
- package/templates/apps/web/app/api/trpc/[trpc]/route.ts +6 -5
- package/templates/apps/web/lib/trpc/client.tsx +1 -2
- package/templates/apps/web/lib/trpc/server.ts +6 -6
- package/templates/apps/web/package.json +14 -6
- package/templates/packages/api/.eslintrc.cjs +2 -0
- package/templates/packages/api/src/index.ts +3 -1
- package/templates/packages/api/src/trpc.ts +3 -2
- package/templates/packages/auth/.eslintrc.cjs +2 -0
- package/templates/packages/auth/src/middleware.ts +10 -14
- package/templates/packages/ui/.eslintrc.cjs +6 -0
- package/templates/packages/ui/src/native/button.tsx +17 -15
- package/templates/packages/ui/src/native/card.tsx +18 -10
- package/templates/packages/ui/src/native/input.tsx +6 -4
- package/templates/packages/ui/src/native/nativewind-env.d.ts +28 -0
- package/templates/packages/ui/src/native/styled.ts +24 -0
- package/templates/packages/ui/src/web/button.tsx +10 -11
- package/templates/packages/ui/src/web/input.tsx +1 -1
- package/templates/packages/ui/tsconfig.json +2 -1
- package/templates/packages/validators/.eslintrc.cjs +2 -0
package/README.md
CHANGED
|
@@ -55,18 +55,18 @@ The CLI is interactive — it asks a few questions then scaffolds, installs depe
|
|
|
55
55
|
|
|
56
56
|
## Interactive prompts
|
|
57
57
|
|
|
58
|
-
| Prompt | Options
|
|
59
|
-
| ------------------------------ |
|
|
60
|
-
| Project name | Any lowercase slug
|
|
61
|
-
| Package scope | `@<name>`
|
|
62
|
-
| Apps to scaffold | Web / Mobile / Both
|
|
63
|
-
| Environments | development / uat / production
|
|
64
|
-
| Include design system? | Yes / No
|
|
65
|
-
| Include Sentry? | Yes / No
|
|
66
|
-
| Include Resend (email)? | Yes / No
|
|
67
|
-
| Include PostHog? | Yes / No
|
|
68
|
-
| Google Analytics? _(web only)_ | Yes / No
|
|
69
|
-
| Package manager | pnpm / npm / yarn
|
|
58
|
+
| Prompt | Options | Default |
|
|
59
|
+
| ------------------------------ | -------------------------------------------------------- | ----------------- |
|
|
60
|
+
| Project name | Any lowercase slug | — |
|
|
61
|
+
| Package scope | `@<name>` | `@<project-name>` |
|
|
62
|
+
| Apps to scaffold | Web / Mobile / Both | Both |
|
|
63
|
+
| Environments | development / uat / production _(local always included)_ | production |
|
|
64
|
+
| Include design system? | Yes / No | Yes |
|
|
65
|
+
| Include Sentry? | Yes / No | Yes |
|
|
66
|
+
| Include Resend (email)? | Yes / No | Yes |
|
|
67
|
+
| Include PostHog? | Yes / No | No |
|
|
68
|
+
| Google Analytics? _(web only)_ | Yes / No | No |
|
|
69
|
+
| Package manager | pnpm / npm / yarn | pnpm |
|
|
70
70
|
|
|
71
71
|
---
|
|
72
72
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @type {import("prettier").Config} */
|
|
2
|
+
// Template files reference {{PLACEHOLDER}} modules that can't be resolved.
|
|
3
|
+
// Disabling prettier-plugin-tailwindcss here prevents class-sort config loading errors.
|
|
4
|
+
module.exports = {
|
|
5
|
+
semi: true,
|
|
6
|
+
singleQuote: false,
|
|
7
|
+
tabWidth: 2,
|
|
8
|
+
trailingComma: "all",
|
|
9
|
+
printWidth: 100,
|
|
10
|
+
};
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
import { Button } from "{{PACKAGE_SCOPE}}/ui/native/button";
|
|
2
|
+
import { Input } from "{{PACKAGE_SCOPE}}/ui/native/input";
|
|
1
3
|
import { useState } from "react";
|
|
2
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
View,
|
|
6
|
+
Text,
|
|
7
|
+
KeyboardAvoidingView,
|
|
8
|
+
Platform,
|
|
9
|
+
ScrollView,
|
|
10
|
+
StyleSheet,
|
|
11
|
+
TouchableOpacity,
|
|
12
|
+
} from "react-native";
|
|
3
13
|
|
|
4
14
|
import { supabase } from "@/lib/supabase";
|
|
5
|
-
import { Button } from "{{PACKAGE_SCOPE}}/ui/native/button";
|
|
6
|
-
import { Input } from "{{PACKAGE_SCOPE}}/ui/native/input";
|
|
7
15
|
|
|
8
16
|
export default function LoginScreen() {
|
|
9
17
|
const [email, setEmail] = useState("");
|
|
@@ -26,16 +34,13 @@ export default function LoginScreen() {
|
|
|
26
34
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
27
35
|
className="flex-1 bg-background"
|
|
28
36
|
>
|
|
29
|
-
<ScrollView
|
|
30
|
-
contentContainerStyle={{ flexGrow: 1 }}
|
|
31
|
-
keyboardShouldPersistTaps="handled"
|
|
32
|
-
>
|
|
37
|
+
<ScrollView contentContainerStyle={styles.scrollContent} keyboardShouldPersistTaps="handled">
|
|
33
38
|
<View className="flex-1 items-center justify-center px-6 py-16">
|
|
34
39
|
<View className="mb-10 items-center gap-2">
|
|
35
40
|
<View className="h-16 w-16 items-center justify-center rounded-2xl bg-primary">
|
|
36
41
|
<Text className="text-2xl font-bold text-primary-foreground">A</Text>
|
|
37
42
|
</View>
|
|
38
|
-
<Text className="text-2xl font-bold text-foreground">{{PROJECT_NAME}}</Text>
|
|
43
|
+
<Text className="text-2xl font-bold text-foreground">{{ PROJECT_NAME }}</Text>
|
|
39
44
|
<Text className="text-sm text-muted-foreground">Sign in to continue</Text>
|
|
40
45
|
</View>
|
|
41
46
|
|
|
@@ -74,7 +79,7 @@ export default function LoginScreen() {
|
|
|
74
79
|
</View>
|
|
75
80
|
|
|
76
81
|
<View className="mt-8 flex-row items-center gap-1">
|
|
77
|
-
<Text className="text-sm text-muted-foreground">Don't have an account
|
|
82
|
+
<Text className="text-sm text-muted-foreground">{"Don't have an account?"}</Text>
|
|
78
83
|
<TouchableOpacity>
|
|
79
84
|
<Text className="text-sm font-medium text-primary">Sign up</Text>
|
|
80
85
|
</TouchableOpacity>
|
|
@@ -84,3 +89,7 @@ export default function LoginScreen() {
|
|
|
84
89
|
</KeyboardAvoidingView>
|
|
85
90
|
);
|
|
86
91
|
}
|
|
92
|
+
|
|
93
|
+
const styles = StyleSheet.create({
|
|
94
|
+
scrollContent: { flexGrow: 1 },
|
|
95
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { Card, CardHeader, CardTitle, CardContent } from "{{PACKAGE_SCOPE}}/ui/native/card";
|
|
1
2
|
import { View, Text, ScrollView } from "react-native";
|
|
2
3
|
|
|
3
4
|
import { trpc } from "@/lib/trpc/client";
|
|
4
|
-
import { Card, CardHeader, CardTitle, CardContent } from "{{PACKAGE_SCOPE}}/ui/native/card";
|
|
5
5
|
|
|
6
6
|
export default function HomeScreen() {
|
|
7
7
|
const { data: user, isLoading } = trpc.user.me.useQuery();
|
|
@@ -18,9 +18,7 @@ export default function HomeScreen() {
|
|
|
18
18
|
<CardTitle>Welcome back</CardTitle>
|
|
19
19
|
</CardHeader>
|
|
20
20
|
<CardContent>
|
|
21
|
-
<Text className="text-muted-foreground">
|
|
22
|
-
{user?.displayName ?? user?.email}
|
|
23
|
-
</Text>
|
|
21
|
+
<Text className="text-muted-foreground">{user?.displayName ?? user?.email}</Text>
|
|
24
22
|
</CardContent>
|
|
25
23
|
</Card>
|
|
26
24
|
)}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "../global.css";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
import { Stack, useRouter, useSegments } from "expo-router";
|
|
5
|
-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
6
3
|
import * as Sentry from "@sentry/react-native";
|
|
4
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
|
+
import { Stack, useRouter, useSegments } from "expo-router";
|
|
6
|
+
import { useEffect, useState } from "react";
|
|
7
7
|
|
|
8
8
|
import { supabase } from "@/lib/supabase";
|
|
9
9
|
import { trpc, makeTRPCClient } from "@/lib/trpc/client";
|
|
@@ -25,7 +25,9 @@ function AuthGate({ children }: { children: React.ReactNode }) {
|
|
|
25
25
|
const segments = useSegments();
|
|
26
26
|
|
|
27
27
|
useEffect(() => {
|
|
28
|
-
const {
|
|
28
|
+
const {
|
|
29
|
+
data: { subscription },
|
|
30
|
+
} = supabase.auth.onAuthStateChange((_event, session) => {
|
|
29
31
|
const inAuthGroup = segments[0] === "(auth)";
|
|
30
32
|
|
|
31
33
|
if (!session && !inAuthGroup) {
|
|
@@ -36,7 +38,7 @@ function AuthGate({ children }: { children: React.ReactNode }) {
|
|
|
36
38
|
});
|
|
37
39
|
|
|
38
40
|
return () => subscription.unsubscribe();
|
|
39
|
-
}, [segments]);
|
|
41
|
+
}, [segments, router]);
|
|
40
42
|
|
|
41
43
|
return <>{children}</>;
|
|
42
44
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { AppRouter } from "{{PACKAGE_SCOPE}}/api";
|
|
2
2
|
import { createTRPCClient, httpBatchLink } from "@trpc/client";
|
|
3
|
+
import { createTRPCReact } from "@trpc/react-query";
|
|
3
4
|
import superjson from "superjson";
|
|
4
5
|
|
|
5
|
-
import type { AppRouter } from "{{PACKAGE_SCOPE}}/api";
|
|
6
|
-
|
|
7
6
|
import { supabase } from "../supabase";
|
|
8
7
|
|
|
9
8
|
// Used for hooks: trpc.user.me.useQuery() etc.
|
|
@@ -20,9 +19,7 @@ export function makeTRPCClient() {
|
|
|
20
19
|
const {
|
|
21
20
|
data: { session },
|
|
22
21
|
} = await supabase.auth.getSession();
|
|
23
|
-
return session?.access_token
|
|
24
|
-
? { Authorization: `Bearer ${session.access_token}` }
|
|
25
|
-
: {};
|
|
22
|
+
return session?.access_token ? { Authorization: `Bearer ${session.access_token}` } : {};
|
|
26
23
|
},
|
|
27
24
|
}),
|
|
28
25
|
],
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/// <reference types="nativewind/types" />
|
|
2
|
+
/// <reference types="react-native-css-interop/types" />
|
|
3
|
+
|
|
4
|
+
// Ensure className is always available on core RN components regardless of
|
|
5
|
+
// whether the react-native-css-interop reference resolves in pnpm's isolated store.
|
|
6
|
+
import "react-native";
|
|
7
|
+
declare module "react-native" {
|
|
8
|
+
interface ViewProps {
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
interface TextProps {
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
interface TextInputProps {
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
interface ImageProps {
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
interface ScrollViewProps {
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
interface TouchableOpacityProps {
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
interface PressableProps {
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.
|
|
32
|
+
|
|
33
|
+
// Allow CSS file imports (processed by NativeWind/Metro at build time)
|
|
34
|
+
declare module "*.css" {}
|
|
@@ -39,9 +39,10 @@
|
|
|
39
39
|
"zod": "^3.23.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
+
"{{PACKAGE_SCOPE}}/config": "workspace:*",
|
|
42
43
|
"@babel/core": "^7.24.0",
|
|
43
44
|
"@types/react": "~18.3.0",
|
|
44
|
-
"eslint": "^8
|
|
45
|
+
"eslint": "^8",
|
|
45
46
|
"tailwindcss": "^3.4.0",
|
|
46
47
|
"typescript": "^5.5.0"
|
|
47
48
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Config } from "tailwindcss";
|
|
2
|
+
|
|
3
|
+
// Mobile uses hex values directly — react-native-css-interop@0.1.x cannot resolve
|
|
4
|
+
// hsl(var(--token)) at runtime on native. The preset's CSS-variable-based colors
|
|
5
|
+
// are overridden here with equivalent hex values that NativeWind can generate static styles from.
|
|
6
|
+
export default {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
8
|
+
presets: [require("nativewind/preset")],
|
|
9
|
+
content: [
|
|
10
|
+
"./app/**/*.{ts,tsx}",
|
|
11
|
+
"./components/**/*.{ts,tsx}",
|
|
12
|
+
"../../packages/ui/src/native/**/*.{ts,tsx}",
|
|
13
|
+
],
|
|
14
|
+
theme: {
|
|
15
|
+
extend: {
|
|
16
|
+
colors: {
|
|
17
|
+
background: "#ffffff",
|
|
18
|
+
foreground: "#0f172a",
|
|
19
|
+
primary: {
|
|
20
|
+
DEFAULT: "#3b82f6",
|
|
21
|
+
foreground: "#f8fafc",
|
|
22
|
+
},
|
|
23
|
+
secondary: {
|
|
24
|
+
DEFAULT: "#f1f5f9",
|
|
25
|
+
foreground: "#1e293b",
|
|
26
|
+
},
|
|
27
|
+
destructive: {
|
|
28
|
+
DEFAULT: "#ef4444",
|
|
29
|
+
foreground: "#f8fafc",
|
|
30
|
+
},
|
|
31
|
+
muted: {
|
|
32
|
+
DEFAULT: "#f1f5f9",
|
|
33
|
+
foreground: "#64748b",
|
|
34
|
+
},
|
|
35
|
+
accent: {
|
|
36
|
+
DEFAULT: "#f1f5f9",
|
|
37
|
+
foreground: "#1e293b",
|
|
38
|
+
},
|
|
39
|
+
card: {
|
|
40
|
+
DEFAULT: "#ffffff",
|
|
41
|
+
foreground: "#0f172a",
|
|
42
|
+
},
|
|
43
|
+
border: "#e2e8f0",
|
|
44
|
+
input: "#e2e8f0",
|
|
45
|
+
ring: "#3b82f6",
|
|
46
|
+
},
|
|
47
|
+
borderRadius: {
|
|
48
|
+
sm: "4px",
|
|
49
|
+
md: "6px",
|
|
50
|
+
lg: "8px",
|
|
51
|
+
xl: "12px",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
} satisfies Config;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { useRouter } from "next/navigation";
|
|
5
|
-
|
|
6
3
|
import { createBrowserClient } from "{{PACKAGE_SCOPE}}/auth/client";
|
|
7
4
|
import { Button } from "{{PACKAGE_SCOPE}}/ui/web/button";
|
|
5
|
+
import {
|
|
6
|
+
Card,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardHeader,
|
|
10
|
+
CardTitle,
|
|
11
|
+
} from "{{PACKAGE_SCOPE}}/ui/web/card";
|
|
8
12
|
import { Input } from "{{PACKAGE_SCOPE}}/ui/web/input";
|
|
9
|
-
import {
|
|
13
|
+
import { useRouter } from "next/navigation";
|
|
14
|
+
import { useState } from "react";
|
|
10
15
|
|
|
11
16
|
export default function LoginPage() {
|
|
12
17
|
const router = useRouter();
|
|
@@ -39,7 +44,7 @@ export default function LoginPage() {
|
|
|
39
44
|
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary text-lg font-bold text-primary-foreground">
|
|
40
45
|
A
|
|
41
46
|
</div>
|
|
42
|
-
<span className="text-xl font-semibold text-foreground">{{PROJECT_NAME}}</span>
|
|
47
|
+
<span className="text-xl font-semibold text-foreground">{{ PROJECT_NAME }}</span>
|
|
43
48
|
</div>
|
|
44
49
|
|
|
45
50
|
<Card className="w-full max-w-sm shadow-md">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { api } from "@/lib/trpc/server";
|
|
2
1
|
import { Card, CardContent, CardHeader, CardTitle } from "{{PACKAGE_SCOPE}}/ui/web/card";
|
|
3
2
|
|
|
3
|
+
import { api } from "@/lib/trpc/server";
|
|
4
|
+
|
|
4
5
|
export default async function DashboardPage() {
|
|
5
6
|
const user = await api.user.me();
|
|
6
7
|
|
|
@@ -11,9 +12,7 @@ export default async function DashboardPage() {
|
|
|
11
12
|
<CardTitle>Welcome back</CardTitle>
|
|
12
13
|
</CardHeader>
|
|
13
14
|
<CardContent>
|
|
14
|
-
<p className="text-muted-foreground">
|
|
15
|
-
{user.displayName ?? user.email}
|
|
16
|
-
</p>
|
|
15
|
+
<p className="text-muted-foreground">{user.displayName ?? user.email}</p>
|
|
17
16
|
</CardContent>
|
|
18
17
|
</Card>
|
|
19
18
|
</main>
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { redirect } from "next/navigation";
|
|
2
|
-
import { cookies } from "next/headers";
|
|
3
|
-
|
|
4
1
|
import { createServerClient } from "{{PACKAGE_SCOPE}}/auth/server";
|
|
2
|
+
import { cookies } from "next/headers";
|
|
3
|
+
import { redirect } from "next/navigation";
|
|
5
4
|
|
|
6
5
|
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
7
6
|
const cookieStore = await cookies();
|
|
8
7
|
const supabase = createServerClient(cookieStore);
|
|
9
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
data: { user },
|
|
10
|
+
} = await supabase.auth.getUser();
|
|
10
11
|
|
|
11
12
|
if (!user) redirect("/login");
|
|
12
13
|
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
|
2
|
-
import { cookies } from "next/headers";
|
|
3
|
-
|
|
4
1
|
import { appRouter } from "{{PACKAGE_SCOPE}}/api";
|
|
5
|
-
import { db } from "{{PACKAGE_SCOPE}}/db";
|
|
6
2
|
import { createServerClient } from "{{PACKAGE_SCOPE}}/auth/server";
|
|
3
|
+
import { db } from "{{PACKAGE_SCOPE}}/db";
|
|
4
|
+
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
|
5
|
+
import { cookies } from "next/headers";
|
|
7
6
|
|
|
8
7
|
async function createContext() {
|
|
9
8
|
const cookieStore = await cookies();
|
|
10
9
|
const supabase = createServerClient(cookieStore);
|
|
11
|
-
const {
|
|
10
|
+
const {
|
|
11
|
+
data: { session },
|
|
12
|
+
} = await supabase.auth.getSession();
|
|
12
13
|
|
|
13
14
|
return {
|
|
14
15
|
db,
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { createCallerFactory } from "
|
|
2
|
-
import { cookies } from "next/headers";
|
|
3
|
-
|
|
4
|
-
import { appRouter } from "{{PACKAGE_SCOPE}}/api";
|
|
5
|
-
import { db } from "{{PACKAGE_SCOPE}}/db";
|
|
1
|
+
import { appRouter, createCallerFactory } from "{{PACKAGE_SCOPE}}/api";
|
|
6
2
|
import { createServerClient } from "{{PACKAGE_SCOPE}}/auth/server";
|
|
3
|
+
import { db } from "{{PACKAGE_SCOPE}}/db";
|
|
4
|
+
import { cookies } from "next/headers";
|
|
7
5
|
|
|
8
6
|
const createCaller = createCallerFactory(appRouter);
|
|
9
7
|
|
|
10
8
|
export const api = createCaller(async () => {
|
|
11
9
|
const cookieStore = await cookies();
|
|
12
10
|
const supabase = createServerClient(cookieStore);
|
|
13
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
data: { session },
|
|
13
|
+
} = await supabase.auth.getSession();
|
|
14
14
|
|
|
15
15
|
return {
|
|
16
16
|
db,
|
|
@@ -4,41 +4,49 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "next build",
|
|
7
|
-
"dev": "next dev
|
|
8
|
-
"lint": "
|
|
7
|
+
"dev": "next dev",
|
|
8
|
+
"lint": "eslint app lib --ext .ts,.tsx",
|
|
9
9
|
"type-check": "tsc --noEmit",
|
|
10
10
|
"start": "next start"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"{{PACKAGE_SCOPE}}/api": "workspace:*",
|
|
14
14
|
"{{PACKAGE_SCOPE}}/auth": "workspace:*",
|
|
15
|
+
"{{PACKAGE_SCOPE}}/email": "workspace:*",
|
|
15
16
|
"{{PACKAGE_SCOPE}}/ui": "workspace:*",
|
|
16
17
|
"{{PACKAGE_SCOPE}}/validators": "workspace:*",
|
|
18
|
+
"@radix-ui/react-slot": "^1.1.0",
|
|
19
|
+
"@sentry/nextjs": "^8.30.0",
|
|
17
20
|
"@supabase/supabase-js": "^2.45.0",
|
|
18
21
|
"@supabase/ssr": "^0.5.0",
|
|
19
22
|
"@tanstack/react-query": "^5.56.0",
|
|
20
23
|
"@trpc/client": "^11.0.0",
|
|
21
24
|
"@trpc/react-query": "^11.0.0",
|
|
22
25
|
"@trpc/server": "^11.0.0",
|
|
23
|
-
"
|
|
26
|
+
"class-variance-authority": "^0.7.0",
|
|
27
|
+
"clsx": "^2.1.0",
|
|
24
28
|
"next": "15.0.0",
|
|
29
|
+
"next-themes": "^0.3.0",
|
|
25
30
|
"posthog-js": "^1.161.0",
|
|
26
31
|
"posthog-node": "^4.2.0",
|
|
27
32
|
"react": "^18.3.0",
|
|
28
33
|
"react-dom": "^18.3.0",
|
|
29
34
|
"superjson": "^2.2.0",
|
|
35
|
+
"tailwind-merge": "^2.5.0",
|
|
30
36
|
"zod": "^3.23.0",
|
|
31
37
|
"@next/third-parties": "^14.2.0"
|
|
32
38
|
},
|
|
33
39
|
"devDependencies": {
|
|
40
|
+
"{{PACKAGE_SCOPE}}/config": "workspace:*",
|
|
41
|
+
"{{PACKAGE_SCOPE}}/db": "workspace:*",
|
|
34
42
|
"@types/node": "^20.0.0",
|
|
35
43
|
"@types/react": "^18.3.0",
|
|
36
44
|
"@types/react-dom": "^18.3.0",
|
|
37
|
-
"eslint": "^8.57.0",
|
|
38
|
-
"eslint-config-next": "15.0.0",
|
|
39
|
-
"tailwindcss": "^3.4.0",
|
|
40
45
|
"autoprefixer": "^10.4.0",
|
|
46
|
+
"eslint": "^8",
|
|
41
47
|
"postcss": "^8.4.0",
|
|
48
|
+
"tailwindcss": "^3.4.0",
|
|
49
|
+
"tailwindcss-animate": "^1.0.7",
|
|
42
50
|
"typescript": "^5.5.0"
|
|
43
51
|
}
|
|
44
52
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export { appRouter } from "./root";
|
|
2
|
+
export { createCallerFactory } from "./trpc";
|
|
2
3
|
export type { AppRouter } from "./root";
|
|
3
4
|
export type { TRPCContext, AuthedContext } from "./trpc";
|
|
4
5
|
|
|
5
6
|
import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
|
|
7
|
+
|
|
6
8
|
import type { AppRouter } from "./root";
|
|
7
9
|
|
|
8
|
-
export type RouterInputs
|
|
10
|
+
export type RouterInputs = inferRouterInputs<AppRouter>;
|
|
9
11
|
export type RouterOutputs = inferRouterOutputs<AppRouter>;
|
|
@@ -29,8 +29,9 @@ const t = initTRPC.context<TRPCContext>().create({
|
|
|
29
29
|
},
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
export const createTRPCRouter
|
|
33
|
-
export const
|
|
32
|
+
export const createTRPCRouter = t.router;
|
|
33
|
+
export const createCallerFactory = t.createCallerFactory;
|
|
34
|
+
export const publicProcedure = t.procedure;
|
|
34
35
|
|
|
35
36
|
const enforceAuth = t.middleware(({ ctx, next }) => {
|
|
36
37
|
if (!ctx.session || !ctx.user) {
|
|
@@ -2,23 +2,19 @@ import { createServerClient, type CookieOptions } from "@supabase/ssr";
|
|
|
2
2
|
import { NextResponse, type NextRequest } from "next/server";
|
|
3
3
|
|
|
4
4
|
export async function updateSession(request: NextRequest) {
|
|
5
|
-
|
|
5
|
+
const response = NextResponse.next({ request });
|
|
6
6
|
|
|
7
|
-
const supabase = createServerClient(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
request.cookies.set(name, value);
|
|
16
|
-
response.cookies.set(name, value, options);
|
|
17
|
-
});
|
|
18
|
-
},
|
|
7
|
+
const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
|
|
8
|
+
cookies: {
|
|
9
|
+
getAll: () => request.cookies.getAll(),
|
|
10
|
+
setAll: (cookiesToSet: { name: string; value: string; options: CookieOptions }[]) => {
|
|
11
|
+
cookiesToSet.forEach(({ name, value, options }) => {
|
|
12
|
+
request.cookies.set(name, value);
|
|
13
|
+
response.cookies.set(name, value, options);
|
|
14
|
+
});
|
|
19
15
|
},
|
|
20
16
|
},
|
|
21
|
-
);
|
|
17
|
+
});
|
|
22
18
|
|
|
23
19
|
await supabase.auth.getUser();
|
|
24
20
|
return response;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { type PressableProps } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { cn } from "../lib/utils";
|
|
5
5
|
|
|
6
|
+
import { StyledPressable, StyledText } from "./styled";
|
|
7
|
+
|
|
6
8
|
interface ButtonProps extends PressableProps {
|
|
7
9
|
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost";
|
|
8
10
|
size?: "default" | "sm" | "lg";
|
|
@@ -21,37 +23,37 @@ function Button({
|
|
|
21
23
|
...props
|
|
22
24
|
}: ButtonProps) {
|
|
23
25
|
return (
|
|
24
|
-
<
|
|
26
|
+
<StyledPressable
|
|
25
27
|
disabled={disabled}
|
|
26
28
|
className={cn(
|
|
27
29
|
"flex-row items-center justify-center rounded-md",
|
|
28
|
-
variant === "default"
|
|
30
|
+
variant === "default" && "bg-primary",
|
|
29
31
|
variant === "destructive" && "bg-destructive",
|
|
30
|
-
variant === "outline"
|
|
31
|
-
variant === "secondary"
|
|
32
|
-
variant === "ghost"
|
|
32
|
+
variant === "outline" && "border-input bg-background border",
|
|
33
|
+
variant === "secondary" && "bg-secondary",
|
|
34
|
+
variant === "ghost" && "bg-transparent",
|
|
33
35
|
size === "default" && "h-10 px-4",
|
|
34
|
-
size === "sm"
|
|
35
|
-
size === "lg"
|
|
36
|
+
size === "sm" && "h-9 px-3",
|
|
37
|
+
size === "lg" && "h-11 px-8",
|
|
36
38
|
disabled && "opacity-50",
|
|
37
39
|
className,
|
|
38
40
|
)}
|
|
39
41
|
{...props}
|
|
40
42
|
>
|
|
41
|
-
<
|
|
43
|
+
<StyledText
|
|
42
44
|
className={cn(
|
|
43
45
|
"text-sm font-medium",
|
|
44
|
-
variant === "default"
|
|
46
|
+
variant === "default" && "text-primary-foreground",
|
|
45
47
|
variant === "destructive" && "text-destructive-foreground",
|
|
46
|
-
variant === "outline"
|
|
47
|
-
variant === "secondary"
|
|
48
|
-
variant === "ghost"
|
|
48
|
+
variant === "outline" && "text-foreground",
|
|
49
|
+
variant === "secondary" && "text-secondary-foreground",
|
|
50
|
+
variant === "ghost" && "text-foreground",
|
|
49
51
|
textClassName,
|
|
50
52
|
)}
|
|
51
53
|
>
|
|
52
54
|
{children}
|
|
53
|
-
</
|
|
54
|
-
</
|
|
55
|
+
</StyledText>
|
|
56
|
+
</StyledPressable>
|
|
55
57
|
);
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -1,41 +1,49 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { type ViewProps } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { cn } from "../lib/utils";
|
|
5
5
|
|
|
6
|
+
import { StyledView, StyledText } from "./styled";
|
|
7
|
+
|
|
6
8
|
function Card({ className, ...props }: ViewProps & { className?: string }) {
|
|
7
9
|
return (
|
|
8
|
-
<
|
|
9
|
-
className={cn("
|
|
10
|
+
<StyledView
|
|
11
|
+
className={cn("border-border bg-card rounded-lg border p-4 shadow-sm", className)}
|
|
10
12
|
{...props}
|
|
11
13
|
/>
|
|
12
14
|
);
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
function CardHeader({ className, ...props }: ViewProps & { className?: string }) {
|
|
16
|
-
return <
|
|
18
|
+
return <StyledView className={cn("mb-3 flex-col gap-1", className)} {...props} />;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
function CardTitle({ children, className }: { children: React.ReactNode; className?: string }) {
|
|
20
22
|
return (
|
|
21
|
-
<
|
|
23
|
+
<StyledText className={cn("text-card-foreground text-xl font-semibold", className)}>
|
|
22
24
|
{children}
|
|
23
|
-
</
|
|
25
|
+
</StyledText>
|
|
24
26
|
);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
function CardDescription({
|
|
29
|
+
function CardDescription({
|
|
30
|
+
children,
|
|
31
|
+
className,
|
|
32
|
+
}: {
|
|
33
|
+
children: React.ReactNode;
|
|
34
|
+
className?: string;
|
|
35
|
+
}) {
|
|
28
36
|
return (
|
|
29
|
-
<
|
|
37
|
+
<StyledText className={cn("text-muted-foreground text-sm", className)}>{children}</StyledText>
|
|
30
38
|
);
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
function CardContent({ className, ...props }: ViewProps & { className?: string }) {
|
|
34
|
-
return <
|
|
42
|
+
return <StyledView className={cn("mt-2", className)} {...props} />;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
function CardFooter({ className, ...props }: ViewProps & { className?: string }) {
|
|
38
|
-
return <
|
|
46
|
+
return <StyledView className={cn("mt-4 flex-row items-center", className)} {...props} />;
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter };
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { type TextInputProps } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { cn } from "../lib/utils";
|
|
5
5
|
|
|
6
|
+
import { StyledTextInput } from "./styled";
|
|
7
|
+
|
|
6
8
|
interface InputProps extends TextInputProps {
|
|
7
9
|
className?: string;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
function Input({ className, ...props }: InputProps) {
|
|
11
13
|
return (
|
|
12
|
-
<
|
|
14
|
+
<StyledTextInput
|
|
13
15
|
className={cn(
|
|
14
|
-
"h-10 w-full rounded-md border
|
|
16
|
+
"border-input bg-background text-foreground placeholder:text-muted-foreground h-10 w-full rounded-md border px-3 py-2 text-sm",
|
|
15
17
|
props.editable === false && "opacity-50",
|
|
16
18
|
className,
|
|
17
19
|
)}
|
|
18
20
|
placeholderTextColor="#64748b"
|
|
19
21
|
{...props}
|
|
20
22
|
/>
|
|
21
|
-
);
|
|
23
|
+
) as React.ReactElement;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export { Input };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Augment React Native props with className for NativeWind/react-native-css-interop.
|
|
2
|
+
// This mirrors what react-native-css-interop/types adds, scoped to this package
|
|
3
|
+
// so it is always in scope regardless of which tsconfig compiles these files.
|
|
4
|
+
import "react-native";
|
|
5
|
+
|
|
6
|
+
declare module "react-native" {
|
|
7
|
+
interface ViewProps {
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
interface TextProps {
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
interface TextInputProps {
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
interface ImageProps {
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
interface ScrollViewProps {
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
interface TouchableOpacityProps {
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
interface PressableProps {
|
|
26
|
+
className?: string;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
Pressable,
|
|
6
|
+
TextInput,
|
|
7
|
+
ScrollView,
|
|
8
|
+
type ViewProps,
|
|
9
|
+
type TextProps,
|
|
10
|
+
type PressableProps,
|
|
11
|
+
type TextInputProps,
|
|
12
|
+
type ScrollViewProps,
|
|
13
|
+
} from "react-native";
|
|
14
|
+
|
|
15
|
+
// NativeWind adds className at runtime via css-interop, but the stock React Native
|
|
16
|
+
// types don't declare it. Cast here once so every native UI component can use className
|
|
17
|
+
// without per-site suppressions.
|
|
18
|
+
type WithClassName<T> = T & { className?: string };
|
|
19
|
+
|
|
20
|
+
export const StyledView = View as React.ComponentType<WithClassName<ViewProps>>;
|
|
21
|
+
export const StyledText = Text as React.ComponentType<WithClassName<TextProps>>;
|
|
22
|
+
export const StyledPressable = Pressable as React.ComponentType<WithClassName<PressableProps>>;
|
|
23
|
+
export const StyledTextInput = TextInput as React.ComponentType<WithClassName<TextInputProps>>;
|
|
24
|
+
export const StyledScrollView = ScrollView as React.ComponentType<WithClassName<ScrollViewProps>>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
1
|
import { Slot } from "@radix-ui/react-slot";
|
|
3
2
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import * as React from "react";
|
|
4
4
|
|
|
5
5
|
import { cn } from "../lib/utils";
|
|
6
6
|
|
|
@@ -9,18 +9,18 @@ const buttonVariants = cva(
|
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
12
|
-
default:
|
|
12
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
13
13
|
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
14
|
-
outline:
|
|
15
|
-
secondary:
|
|
16
|
-
ghost:
|
|
17
|
-
link:
|
|
14
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
15
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
16
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
17
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
18
18
|
},
|
|
19
19
|
size: {
|
|
20
20
|
default: "h-10 px-4 py-2",
|
|
21
|
-
sm:
|
|
22
|
-
lg:
|
|
23
|
-
icon:
|
|
21
|
+
sm: "h-9 rounded-md px-3",
|
|
22
|
+
lg: "h-11 rounded-md px-8",
|
|
23
|
+
icon: "h-10 w-10",
|
|
24
24
|
},
|
|
25
25
|
},
|
|
26
26
|
defaultVariants: {
|
|
@@ -31,8 +31,7 @@ const buttonVariants = cva(
|
|
|
31
31
|
);
|
|
32
32
|
|
|
33
33
|
export interface ButtonProps
|
|
34
|
-
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
35
|
-
VariantProps<typeof buttonVariants> {
|
|
34
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
36
35
|
asChild?: boolean;
|
|
37
36
|
}
|
|
38
37
|
|
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
|
|
3
3
|
import { cn } from "../lib/utils";
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
|
6
6
|
|
|
7
7
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
8
8
|
({ className, type, ...props }, ref) => (
|