create-pilotprojects-app 0.2.1 → 0.3.1

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.
Files changed (35) hide show
  1. package/README.md +25 -2
  2. package/package.json +1 -1
  3. package/templates/.prettierrc.cjs +10 -0
  4. package/templates/apps/mobile/.eslintrc.cjs +6 -0
  5. package/templates/apps/mobile/app/(auth)/login.tsx +18 -9
  6. package/templates/apps/mobile/app/(tabs)/index.tsx +2 -4
  7. package/templates/apps/mobile/app/_layout.tsx +7 -5
  8. package/templates/apps/mobile/lib/supabase.ts +1 -1
  9. package/templates/apps/mobile/lib/trpc/client.ts +3 -6
  10. package/templates/apps/mobile/nativewind-env.d.ts +34 -0
  11. package/templates/apps/mobile/package.json +2 -1
  12. package/templates/apps/mobile/tailwind.config.ts +55 -0
  13. package/templates/apps/web/.eslintrc.cjs +2 -0
  14. package/templates/apps/web/app/(auth)/login/page.tsx +10 -5
  15. package/templates/apps/web/app/(dashboard)/dashboard/page.tsx +3 -4
  16. package/templates/apps/web/app/(dashboard)/layout.tsx +5 -4
  17. package/templates/apps/web/app/api/trpc/[trpc]/route.ts +6 -5
  18. package/templates/apps/web/lib/trpc/client.tsx +1 -2
  19. package/templates/apps/web/lib/trpc/server.ts +6 -6
  20. package/templates/apps/web/package.json +14 -6
  21. package/templates/packages/api/.eslintrc.cjs +2 -0
  22. package/templates/packages/api/src/index.ts +3 -1
  23. package/templates/packages/api/src/trpc.ts +3 -2
  24. package/templates/packages/auth/.eslintrc.cjs +2 -0
  25. package/templates/packages/auth/src/middleware.ts +10 -14
  26. package/templates/packages/ui/.eslintrc.cjs +6 -0
  27. package/templates/packages/ui/src/native/button.tsx +17 -15
  28. package/templates/packages/ui/src/native/card.tsx +18 -10
  29. package/templates/packages/ui/src/native/input.tsx +6 -4
  30. package/templates/packages/ui/src/native/nativewind-env.d.ts +28 -0
  31. package/templates/packages/ui/src/native/styled.ts +24 -0
  32. package/templates/packages/ui/src/web/button.tsx +10 -11
  33. package/templates/packages/ui/src/web/input.tsx +1 -1
  34. package/templates/packages/ui/tsconfig.json +2 -1
  35. package/templates/packages/validators/.eslintrc.cjs +2 -0
package/README.md CHANGED
@@ -75,11 +75,19 @@ The CLI is interactive — it asks a few questions then scaffolds, installs depe
75
75
  ```bash
76
76
  cd my-project
77
77
 
78
- # Copy and fill in environment files
79
- cp apps/web/.env.development.example apps/web/.env.development
78
+ # Copy local env files and fill in values
79
+ cp apps/web/.env.local.example apps/web/.env.local
80
80
  # Add: SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY,
81
81
  # DATABASE_URL, RESEND_API_KEY, SENTRY_DSN
82
82
 
83
+ # For each additional environment you selected:
84
+ # cp apps/web/.env.development.example apps/web/.env.development
85
+ # cp apps/web/.env.uat.example apps/web/.env.uat
86
+ # cp apps/web/.env.production.example apps/web/.env.production
87
+
88
+ # Mobile (if selected):
89
+ cp apps/mobile/.env.local.example apps/mobile/.env.local
90
+
83
91
  # Start local Supabase (requires Supabase CLI)
84
92
  supabase start
85
93
 
@@ -94,6 +102,21 @@ pnpm dev
94
102
 
95
103
  ---
96
104
 
105
+ ## Environments
106
+
107
+ `local` is always included. The environments prompt lets you pick additional ones:
108
+
109
+ | Environment | Web env file | Mobile `APP_ENV` | EAS profile |
110
+ | ------------- | ------------------ | ---------------- | ------------------------------------- |
111
+ | `local` | `.env.local` | `local` | — |
112
+ | `development` | `.env.development` | `development` | `development` (dev client, simulator) |
113
+ | `uat` | `.env.uat` | `uat` | `uat` (internal distribution) |
114
+ | `production` | `.env.production` | `production` | `production` (App Store / Play Store) |
115
+
116
+ The mobile `app.config.ts` and `eas.json` are generated to include **only** the environments you selected.
117
+
118
+ ---
119
+
97
120
  ## Tech stack
98
121
 
99
122
  | Layer | Choice |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pilotprojects-app",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "CLI to scaffold the Pilotprojects monorepo boilerplate",
5
5
  "keywords": [
6
6
  "create",
@@ -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
+ };
@@ -0,0 +1,6 @@
1
+ /** @type {import("eslint").Linter.Config} */
2
+ module.exports = {
3
+ extends: [require.resolve("{{PACKAGE_SCOPE}}/config/eslint/react-native")],
4
+ // react-native ships Flow-typed source; eslint-plugin-import can't parse it
5
+ rules: { "import/namespace": "off" },
6
+ };
@@ -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 { View, Text, KeyboardAvoidingView, Platform, ScrollView, TouchableOpacity } from "react-native";
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?</Text>
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 { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
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,5 +1,5 @@
1
- import { createClient } from "@supabase/supabase-js";
2
1
  import AsyncStorage from "@react-native-async-storage/async-storage";
2
+ import { createClient } from "@supabase/supabase-js";
3
3
 
4
4
  export const supabase = createClient(
5
5
  process.env.EXPO_PUBLIC_SUPABASE_URL!,
@@ -1,9 +1,8 @@
1
- import { createTRPCReact } from "@trpc/react-query";
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.57.0",
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;
@@ -0,0 +1,2 @@
1
+ /** @type {import("eslint").Linter.Config} */
2
+ module.exports = { extends: [require.resolve("{{PACKAGE_SCOPE}}/config/eslint/base")] };
@@ -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 { Card, CardContent, CardDescription, CardHeader, CardTitle } from "{{PACKAGE_SCOPE}}/ui/web/card";
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 { data: { user } } = await supabase.auth.getUser();
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 { data: { session } } = await supabase.auth.getSession();
10
+ const {
11
+ data: { session },
12
+ } = await supabase.auth.getSession();
12
13
 
13
14
  return {
14
15
  db,
@@ -1,7 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { createTRPCReact } from "@trpc/react-query";
4
-
5
3
  import type { AppRouter } from "{{PACKAGE_SCOPE}}/api";
4
+ import { createTRPCReact } from "@trpc/react-query";
6
5
 
7
6
  export const trpc = createTRPCReact<AppRouter>();
@@ -1,16 +1,16 @@
1
- import { createCallerFactory } from "@trpc/server";
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 { data: { session } } = await supabase.auth.getSession();
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 --turbo",
8
- "lint": "next 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
- "@sentry/nextjs": "^8.30.0",
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
  }
@@ -0,0 +1,2 @@
1
+ /** @type {import("eslint").Linter.Config} */
2
+ module.exports = { extends: [require.resolve("{{PACKAGE_SCOPE}}/config/eslint/base")] };
@@ -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 = inferRouterInputs<AppRouter>;
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 = t.router;
33
- export const publicProcedure = t.procedure;
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) {
@@ -0,0 +1,2 @@
1
+ /** @type {import("eslint").Linter.Config} */
2
+ module.exports = { extends: [require.resolve("{{PACKAGE_SCOPE}}/config/eslint/base")] };
@@ -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
- let response = NextResponse.next({ request });
5
+ const response = NextResponse.next({ request });
6
6
 
7
- const supabase = createServerClient(
8
- process.env.SUPABASE_URL!,
9
- process.env.SUPABASE_ANON_KEY!,
10
- {
11
- cookies: {
12
- getAll: () => request.cookies.getAll(),
13
- setAll: (cookiesToSet: { name: string; value: string; options: CookieOptions }[]) => {
14
- cookiesToSet.forEach(({ name, value, options }) => {
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;
@@ -0,0 +1,6 @@
1
+ /** @type {import("eslint").Linter.Config} */
2
+ module.exports = {
3
+ extends: [require.resolve("{{PACKAGE_SCOPE}}/config/eslint/base")],
4
+ // react-native ships Flow-typed source; eslint-plugin-import can't parse it
5
+ rules: { "import/namespace": "off" },
6
+ };
@@ -1,8 +1,10 @@
1
1
  import * as React from "react";
2
- import { Pressable, Text, type PressableProps } from "react-native";
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
- <Pressable
26
+ <StyledPressable
25
27
  disabled={disabled}
26
28
  className={cn(
27
29
  "flex-row items-center justify-center rounded-md",
28
- variant === "default" && "bg-primary",
30
+ variant === "default" && "bg-primary",
29
31
  variant === "destructive" && "bg-destructive",
30
- variant === "outline" && "border border-input bg-background",
31
- variant === "secondary" && "bg-secondary",
32
- variant === "ghost" && "bg-transparent",
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" && "h-9 px-3",
35
- size === "lg" && "h-11 px-8",
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
- <Text
43
+ <StyledText
42
44
  className={cn(
43
45
  "text-sm font-medium",
44
- variant === "default" && "text-primary-foreground",
46
+ variant === "default" && "text-primary-foreground",
45
47
  variant === "destructive" && "text-destructive-foreground",
46
- variant === "outline" && "text-foreground",
47
- variant === "secondary" && "text-secondary-foreground",
48
- variant === "ghost" && "text-foreground",
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
- </Text>
54
- </Pressable>
55
+ </StyledText>
56
+ </StyledPressable>
55
57
  );
56
58
  }
57
59
 
@@ -1,41 +1,49 @@
1
1
  import * as React from "react";
2
- import { View, Text, type ViewProps } from "react-native";
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
- <View
9
- className={cn("rounded-lg border border-border bg-card p-4 shadow-sm", className)}
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 <View className={cn("mb-3 flex-col gap-1", className)} {...props} />;
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
- <Text className={cn("text-xl font-semibold text-card-foreground", className)}>
23
+ <StyledText className={cn("text-card-foreground text-xl font-semibold", className)}>
22
24
  {children}
23
- </Text>
25
+ </StyledText>
24
26
  );
25
27
  }
26
28
 
27
- function CardDescription({ children, className }: { children: React.ReactNode; className?: string }) {
29
+ function CardDescription({
30
+ children,
31
+ className,
32
+ }: {
33
+ children: React.ReactNode;
34
+ className?: string;
35
+ }) {
28
36
  return (
29
- <Text className={cn("text-sm text-muted-foreground", className)}>{children}</Text>
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 <View className={cn("mt-2", className)} {...props} />;
42
+ return <StyledView className={cn("mt-2", className)} {...props} />;
35
43
  }
36
44
 
37
45
  function CardFooter({ className, ...props }: ViewProps & { className?: string }) {
38
- return <View className={cn("mt-4 flex-row items-center", className)} {...props} />;
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 { TextInput, type TextInputProps } from "react-native";
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
- <TextInput
14
+ <StyledTextInput
13
15
  className={cn(
14
- "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground",
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: "bg-primary text-primary-foreground hover:bg-primary/90",
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: "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",
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: "h-9 rounded-md px-3",
22
- lg: "h-11 rounded-md px-8",
23
- icon: "h-10 w-10",
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 interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
5
+ export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
6
6
 
7
7
  const Input = React.forwardRef<HTMLInputElement, InputProps>(
8
8
  ({ className, type, ...props }, ref) => (
@@ -4,5 +4,6 @@
4
4
  "jsx": "react-jsx",
5
5
  "lib": ["ES2022", "dom"]
6
6
  },
7
- "include": ["src"]
7
+ "include": ["src"],
8
+ "files": ["src/native/nativewind-env.d.ts"]
8
9
  }
@@ -0,0 +1,2 @@
1
+ /** @type {import("eslint").Linter.Config} */
2
+ module.exports = { extends: [require.resolve("{{PACKAGE_SCOPE}}/config/eslint/base")] };