ta_data_mas 0.0.2

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 (219) hide show
  1. package/README.md +42 -0
  2. package/apps/api/.prettierrc +4 -0
  3. package/apps/api/README.md +98 -0
  4. package/apps/api/drizzle/0000_right_drax.sql +87 -0
  5. package/apps/api/drizzle/meta/0000_snapshot.json +588 -0
  6. package/apps/api/drizzle/meta/_journal.json +13 -0
  7. package/apps/api/drizzle.config.ts +12 -0
  8. package/apps/api/eslint.config.mjs +35 -0
  9. package/apps/api/nest-cli.json +8 -0
  10. package/apps/api/package.json +99 -0
  11. package/apps/api/src/app.controller.spec.ts +25 -0
  12. package/apps/api/src/app.controller.ts +9 -0
  13. package/apps/api/src/app.module.ts +37 -0
  14. package/apps/api/src/app.service.ts +8 -0
  15. package/apps/api/src/auth/auth.controller.ts +113 -0
  16. package/apps/api/src/auth/auth.module.ts +15 -0
  17. package/apps/api/src/auth/auth.service.ts +309 -0
  18. package/apps/api/src/auth/decorators/current-user-decorator.ts +10 -0
  19. package/apps/api/src/auth/decorators/require-org-role-decorator.ts +7 -0
  20. package/apps/api/src/auth/dto/login.dto.ts +10 -0
  21. package/apps/api/src/auth/dto/register.dto.ts +14 -0
  22. package/apps/api/src/auth/guards/jwt-auth-guard.ts +40 -0
  23. package/apps/api/src/auth/guards/org-role.guards.ts +60 -0
  24. package/apps/api/src/db/db.module.ts +9 -0
  25. package/apps/api/src/db/drizzle.service.ts +16 -0
  26. package/apps/api/src/db/schema/index.ts +6 -0
  27. package/apps/api/src/db/schema/organizations.ts +30 -0
  28. package/apps/api/src/db/schema/projects.ts +26 -0
  29. package/apps/api/src/db/schema/query-history.ts +16 -0
  30. package/apps/api/src/db/schema/storage-buckets.ts +17 -0
  31. package/apps/api/src/db/schema/storage-objects.ts +18 -0
  32. package/apps/api/src/db/schema/users.ts +14 -0
  33. package/apps/api/src/main.ts +51 -0
  34. package/apps/api/src/members/dto/invite-member-dto.ts +6 -0
  35. package/apps/api/src/members/dto/update-role.dto.ts +8 -0
  36. package/apps/api/src/members/invite.service.ts +147 -0
  37. package/apps/api/src/members/members.controller.ts +56 -0
  38. package/apps/api/src/members/members.module.ts +13 -0
  39. package/apps/api/src/members/members.service.ts +85 -0
  40. package/apps/api/src/orgs/dto/create-org.dto.ts +8 -0
  41. package/apps/api/src/orgs/orgs.controller.ts +27 -0
  42. package/apps/api/src/orgs/orgs.module.ts +12 -0
  43. package/apps/api/src/orgs/orgs.service.ts +86 -0
  44. package/apps/api/src/project-api/project-api.controller.ts +95 -0
  45. package/apps/api/src/project-api/project-api.module.ts +12 -0
  46. package/apps/api/src/project-api/project-api.service.ts +196 -0
  47. package/apps/api/src/project-api/project-key.guard.ts +45 -0
  48. package/apps/api/src/project-api/query-parser.ts +129 -0
  49. package/apps/api/src/project-auth/dto/magic-link.dto.ts +7 -0
  50. package/apps/api/src/project-auth/dto/signin.dto.ts +10 -0
  51. package/apps/api/src/project-auth/dto/signup.dto.ts +11 -0
  52. package/apps/api/src/project-auth/project-auth-dashboard.controller.ts +35 -0
  53. package/apps/api/src/project-auth/project-auth.controller.ts +111 -0
  54. package/apps/api/src/project-auth/project-auth.module.ts +13 -0
  55. package/apps/api/src/project-auth/project-auth.service.ts +440 -0
  56. package/apps/api/src/projects/dto/create-project.dto.ts +9 -0
  57. package/apps/api/src/projects/projects.controller.ts +34 -0
  58. package/apps/api/src/projects/projects.module.ts +12 -0
  59. package/apps/api/src/projects/projects.service.ts +136 -0
  60. package/apps/api/src/realtime/realtime-socket.type.ts +14 -0
  61. package/apps/api/src/realtime/realtime.controller.ts +63 -0
  62. package/apps/api/src/realtime/realtime.gateway.ts +150 -0
  63. package/apps/api/src/realtime/realtime.module.ts +14 -0
  64. package/apps/api/src/realtime/realtime.service.ts +101 -0
  65. package/apps/api/src/realtime/trigger.service.ts +95 -0
  66. package/apps/api/src/sql-editor/dto/execute-query.dto.ts +7 -0
  67. package/apps/api/src/sql-editor/sql-editor.controller.ts +28 -0
  68. package/apps/api/src/sql-editor/sql-editor.module.ts +12 -0
  69. package/apps/api/src/sql-editor/sql-editor.service.ts +154 -0
  70. package/apps/api/src/storage/project-storage.controller.ts +140 -0
  71. package/apps/api/src/storage/storage.controller.ts +118 -0
  72. package/apps/api/src/storage/storage.module.ts +15 -0
  73. package/apps/api/src/storage/storage.service.ts +191 -0
  74. package/apps/api/src/storage/uploadthing.ts +23 -0
  75. package/apps/api/src/table-editor/dto/alter-table.dto.ts +15 -0
  76. package/apps/api/src/table-editor/dto/create-table.dto.ts +54 -0
  77. package/apps/api/src/table-editor/table-editor.controller.ts +117 -0
  78. package/apps/api/src/table-editor/table-editor.module.ts +12 -0
  79. package/apps/api/src/table-editor/table-editor.service.ts +366 -0
  80. package/apps/api/test/app.e2e-spec.ts +29 -0
  81. package/apps/api/test/jest-e2e.json +9 -0
  82. package/apps/api/tsconfig.build.json +4 -0
  83. package/apps/api/tsconfig.json +25 -0
  84. package/apps/web/.env.example +12 -0
  85. package/apps/web/AGENTS.md +9 -0
  86. package/apps/web/CLAUDE.md +1 -0
  87. package/apps/web/README.md +36 -0
  88. package/apps/web/components.json +25 -0
  89. package/apps/web/eslint.config.mjs +18 -0
  90. package/apps/web/next.config.ts +11 -0
  91. package/apps/web/package.json +48 -0
  92. package/apps/web/postcss.config.mjs +7 -0
  93. package/apps/web/public/file.svg +1 -0
  94. package/apps/web/public/globe.svg +1 -0
  95. package/apps/web/public/logo.svg +4 -0
  96. package/apps/web/public/next.svg +1 -0
  97. package/apps/web/public/vercel.svg +1 -0
  98. package/apps/web/public/window.svg +1 -0
  99. package/apps/web/src/app/(dashboard)/layout.tsx +33 -0
  100. package/apps/web/src/app/(dashboard)/organizations/(list)/layout.tsx +7 -0
  101. package/apps/web/src/app/(dashboard)/organizations/(list)/new/page.tsx +26 -0
  102. package/apps/web/src/app/(dashboard)/organizations/(list)/page.tsx +44 -0
  103. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/api/page.tsx +13 -0
  104. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/layout.tsx +19 -0
  105. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/page.tsx +10 -0
  106. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/providers/page.tsx +19 -0
  107. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/url-configuration/page.tsx +24 -0
  108. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/users/page.tsx +13 -0
  109. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/database/page.tsx +29 -0
  110. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/realtime/page.tsx +28 -0
  111. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/sql/page.tsx +21 -0
  112. package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/storage/page.tsx +28 -0
  113. package/apps/web/src/app/(dashboard)/organizations/[slug]/layout.tsx +20 -0
  114. package/apps/web/src/app/(dashboard)/organizations/[slug]/projects/page.tsx +62 -0
  115. package/apps/web/src/app/(dashboard)/organizations/[slug]/settings/members/page.tsx +28 -0
  116. package/apps/web/src/app/(dashboard)/organizations/[slug]/settings/page.tsx +10 -0
  117. package/apps/web/src/app/(dashboard)/settings/page.tsx +12 -0
  118. package/apps/web/src/app/dashboard/page.tsx +14 -0
  119. package/apps/web/src/app/favicon.ico +0 -0
  120. package/apps/web/src/app/globals.css +115 -0
  121. package/apps/web/src/app/layout.tsx +36 -0
  122. package/apps/web/src/app/login/page.tsx +199 -0
  123. package/apps/web/src/app/page.tsx +100 -0
  124. package/apps/web/src/app/register/page.tsx +218 -0
  125. package/apps/web/src/components/app-sidebar.tsx +301 -0
  126. package/apps/web/src/components/navbar.tsx +63 -0
  127. package/apps/web/src/components/topNav.tsx +68 -0
  128. package/apps/web/src/components/ui/avatar.tsx +112 -0
  129. package/apps/web/src/components/ui/badge.tsx +48 -0
  130. package/apps/web/src/components/ui/button.tsx +66 -0
  131. package/apps/web/src/components/ui/card.tsx +102 -0
  132. package/apps/web/src/components/ui/checkbox.tsx +32 -0
  133. package/apps/web/src/components/ui/dialog.tsx +168 -0
  134. package/apps/web/src/components/ui/dropdown-menu.tsx +269 -0
  135. package/apps/web/src/components/ui/field.tsx +238 -0
  136. package/apps/web/src/components/ui/input.tsx +19 -0
  137. package/apps/web/src/components/ui/label.tsx +24 -0
  138. package/apps/web/src/components/ui/resizable.tsx +50 -0
  139. package/apps/web/src/components/ui/scroll-area.tsx +55 -0
  140. package/apps/web/src/components/ui/select.tsx +192 -0
  141. package/apps/web/src/components/ui/separator.tsx +28 -0
  142. package/apps/web/src/components/ui/sheet.tsx +147 -0
  143. package/apps/web/src/components/ui/sidebar.tsx +703 -0
  144. package/apps/web/src/components/ui/skeleton.tsx +13 -0
  145. package/apps/web/src/components/ui/switch.tsx +33 -0
  146. package/apps/web/src/components/ui/table.tsx +116 -0
  147. package/apps/web/src/components/ui/tooltip.tsx +57 -0
  148. package/apps/web/src/features/api-docs/api-docs-client.tsx +227 -0
  149. package/apps/web/src/features/api-docs/api-docs-helpers.server.ts +86 -0
  150. package/apps/web/src/features/auth/action.ts +67 -0
  151. package/apps/web/src/features/auth/client.schema.ts +13 -0
  152. package/apps/web/src/features/auth/constants.ts +6 -0
  153. package/apps/web/src/features/auth/cookies.ts +86 -0
  154. package/apps/web/src/features/auth/server.schema.ts +11 -0
  155. package/apps/web/src/features/auth/sign-out.ts +17 -0
  156. package/apps/web/src/features/members/action.ts +66 -0
  157. package/apps/web/src/features/members/client.schema.ts +11 -0
  158. package/apps/web/src/features/members/constants.ts +8 -0
  159. package/apps/web/src/features/members/members-client.tsx +198 -0
  160. package/apps/web/src/features/members/members-helpers.server.ts +21 -0
  161. package/apps/web/src/features/members/server.schema.ts +19 -0
  162. package/apps/web/src/features/organization/action.ts +45 -0
  163. package/apps/web/src/features/organization/client.schema.ts +5 -0
  164. package/apps/web/src/features/organization/constants.tsx +3 -0
  165. package/apps/web/src/features/organization/create-org-form.tsx +66 -0
  166. package/apps/web/src/features/organization/organization-helpers.server.ts +35 -0
  167. package/apps/web/src/features/organization/server.schema.ts +10 -0
  168. package/apps/web/src/features/project-auth/project-auth-action.ts +78 -0
  169. package/apps/web/src/features/project-auth/project-auth-client.schema.ts +18 -0
  170. package/apps/web/src/features/project-auth/project-auth-constants.ts +8 -0
  171. package/apps/web/src/features/project-auth/project-auth-helpers.server.ts +39 -0
  172. package/apps/web/src/features/project-auth/project-auth-providers.tsx +222 -0
  173. package/apps/web/src/features/project-auth/project-auth-server.schema.ts +22 -0
  174. package/apps/web/src/features/project-auth/project-auth-shell.tsx +106 -0
  175. package/apps/web/src/features/project-auth/project-auth-url-config.tsx +151 -0
  176. package/apps/web/src/features/project-auth/project-auth-users.tsx +138 -0
  177. package/apps/web/src/features/projects/action.ts +46 -0
  178. package/apps/web/src/features/projects/client.schema.ts +9 -0
  179. package/apps/web/src/features/projects/constants.ts +6 -0
  180. package/apps/web/src/features/projects/create-project-modal.tsx +117 -0
  181. package/apps/web/src/features/projects/project-helpers.server.ts +38 -0
  182. package/apps/web/src/features/projects/server.schema.ts +10 -0
  183. package/apps/web/src/features/realtime/realtime-client.tsx +294 -0
  184. package/apps/web/src/features/realtime/realtime-helpers.server.ts +40 -0
  185. package/apps/web/src/features/sql-editor/sql-editor-client.tsx +324 -0
  186. package/apps/web/src/features/sql-editor/sql-editor-helpers.server.ts +22 -0
  187. package/apps/web/src/features/storage/storage-client.tsx +500 -0
  188. package/apps/web/src/features/storage/storage-helpers.server.ts +36 -0
  189. package/apps/web/src/features/table-editor/action.ts +97 -0
  190. package/apps/web/src/features/table-editor/add-column-dialog.tsx +184 -0
  191. package/apps/web/src/features/table-editor/client.schema.ts +36 -0
  192. package/apps/web/src/features/table-editor/create-table-drawer.tsx +542 -0
  193. package/apps/web/src/features/table-editor/server.schema.ts +41 -0
  194. package/apps/web/src/features/table-editor/table-editor-client.tsx +737 -0
  195. package/apps/web/src/features/table-editor/table-editor-helpers.server.ts +39 -0
  196. package/apps/web/src/hooks/use-mobile.ts +19 -0
  197. package/apps/web/src/lib/axios.ts +8 -0
  198. package/apps/web/src/lib/utils.ts +6 -0
  199. package/apps/web/src/proxy.ts +72 -0
  200. package/apps/web/src/server-utils/utils.ts +10 -0
  201. package/apps/web/tsconfig.json +34 -0
  202. package/package.json +22 -0
  203. package/packages/apiDatabase-js/package.json +39 -0
  204. package/packages/apiDatabase-js/src/auth/index.ts +121 -0
  205. package/packages/apiDatabase-js/src/client.ts +25 -0
  206. package/packages/apiDatabase-js/src/db/index.ts +15 -0
  207. package/packages/apiDatabase-js/src/db/query-builder.ts +194 -0
  208. package/packages/apiDatabase-js/src/index.ts +16 -0
  209. package/packages/apiDatabase-js/src/realtime/index.ts +84 -0
  210. package/packages/apiDatabase-js/src/storage/index.ts +130 -0
  211. package/packages/apiDatabase-js/tsconfig.json +13 -0
  212. package/packages/apiDatabase-js/tsdown.config.ts +20 -0
  213. package/packages/constants/package.json +6 -0
  214. package/packages/constants/src/index.ts +76 -0
  215. package/packages/tsconfig.json +13 -0
  216. package/packages/tsdown.config.ts +20 -0
  217. package/packages/types/package.json +6 -0
  218. package/packages/types/src/index.ts +254 -0
  219. package/pnpm-workspace.yaml +10 -0
@@ -0,0 +1,222 @@
1
+ "use client";
2
+
3
+ import { useState, useTransition } from "react";
4
+ import { Badge } from "@/components/ui/badge";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Input } from "@/components/ui/input";
7
+ import { Label } from "@/components/ui/label";
8
+ import type { ProjectOAuthSettings } from "@apiDatabase/types";
9
+ import { ChevronRight, Mail } from "lucide-react";
10
+ import { cn } from "@/lib/utils";
11
+
12
+ function GithubIcon({ className }: { className?: string }) {
13
+ return (
14
+ <svg
15
+ className={className}
16
+ viewBox="0 0 24 24"
17
+ fill="currentColor"
18
+ aria-hidden
19
+ >
20
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
21
+ </svg>
22
+ );
23
+ }
24
+
25
+ type ProviderId = "email" | "google" | "github";
26
+
27
+ const providers: Array<{
28
+ id: ProviderId;
29
+ name: string;
30
+ icon: React.ReactNode;
31
+ }> = [
32
+ { id: "email", name: "Email", icon: <Mail className="size-5" /> },
33
+ {
34
+ id: "google",
35
+ name: "Google",
36
+ icon: <span className="text-base font-semibold text-blue-600">G</span>,
37
+ },
38
+ { id: "github", name: "GitHub", icon: <GithubIcon className="size-5" /> },
39
+ ];
40
+
41
+ function isProviderEnabled(
42
+ id: ProviderId,
43
+ settings: ProjectOAuthSettings,
44
+ ): boolean {
45
+ if (id === "email") return true;
46
+ if (id === "google") return Boolean(settings.googleClientId);
47
+ return Boolean(settings.githubClientId);
48
+ }
49
+
50
+ export function ProjectAuthProviders({
51
+ orgSlug,
52
+ projectSlug,
53
+ initialSettings,
54
+ }: {
55
+ orgSlug: string;
56
+ projectSlug: string;
57
+ initialSettings: ProjectOAuthSettings;
58
+ }) {
59
+ const [isPending, startTransition] = useTransition();
60
+ const [settings, setSettings] =
61
+ useState<ProjectOAuthSettings>(initialSettings);
62
+ const [selected, setSelected] = useState<ProviderId | null>(null);
63
+ const [message, setMessage] = useState<string | null>(null);
64
+
65
+ function save() {
66
+ setMessage(null);
67
+ startTransition(async () => {
68
+ const res = await fetch(
69
+ `${process.env.NEXT_PUBLIC_API_URL}/orgs/${orgSlug}/projects/${projectSlug}/auth/settings`,
70
+ {
71
+ method: "POST",
72
+ headers: { "Content-Type": "application/json" },
73
+ credentials: "include",
74
+ body: JSON.stringify(settings),
75
+ },
76
+ );
77
+
78
+ if (!res.ok) {
79
+ setMessage("Save failed");
80
+ return;
81
+ }
82
+
83
+ setMessage("Saved");
84
+ });
85
+ }
86
+
87
+ return (
88
+ <div className="space-y-6 p-6">
89
+ <div>
90
+ <h1 className="text-2xl font-medium">Sign In / Providers</h1>
91
+ <p className="mt-1 text-sm text-muted-foreground">
92
+ Configure authentication providers for this project.
93
+ </p>
94
+ </div>
95
+
96
+ <div className="rounded-lg border divide-y">
97
+ {providers.map((provider) => {
98
+ const enabled = isProviderEnabled(provider.id, settings);
99
+ const isOpen = selected === provider.id;
100
+
101
+ return (
102
+ <div key={provider.id}>
103
+ <button
104
+ type="button"
105
+ onClick={() => setSelected(isOpen ? null : provider.id)}
106
+ className="flex w-full items-center gap-4 px-4 py-4 text-left hover:bg-muted/40"
107
+ >
108
+ <div className="flex size-9 items-center justify-center rounded-md border bg-background">
109
+ {provider.icon}
110
+ </div>
111
+ <div className="min-w-0 flex-1">
112
+ <p className="font-medium">{provider.name}</p>
113
+ </div>
114
+ <Badge
115
+ variant={enabled ? "default" : "secondary"}
116
+ className={cn(
117
+ enabled && "bg-emerald-600 text-white hover:bg-emerald-600",
118
+ )}
119
+ >
120
+ {enabled ? "Enabled" : "Disabled"}
121
+ </Badge>
122
+ <ChevronRight
123
+ className={cn(
124
+ "size-4 text-muted-foreground transition-transform",
125
+ isOpen && "rotate-90",
126
+ )}
127
+ />
128
+ </button>
129
+
130
+ {isOpen && provider.id === "email" && (
131
+ <div className="border-t bg-muted/20 px-4 py-4 text-sm text-muted-foreground">
132
+ Email + password and magic link are always enabled for this
133
+ project. No extra configuration needed.
134
+ </div>
135
+ )}
136
+
137
+ {isOpen && provider.id === "google" && (
138
+ <div className="space-y-4 border-t bg-muted/20 px-4 py-4">
139
+ <div className="grid gap-4 md:grid-cols-2">
140
+ <div className="space-y-2">
141
+ <Label htmlFor="googleClientId">Client ID</Label>
142
+ <Input
143
+ id="googleClientId"
144
+ value={settings.googleClientId ?? ""}
145
+ onChange={(e) =>
146
+ setSettings((s) => ({
147
+ ...s,
148
+ googleClientId: e.target.value,
149
+ }))
150
+ }
151
+ placeholder="Google OAuth client ID"
152
+ />
153
+ </div>
154
+ <div className="space-y-2">
155
+ <Label htmlFor="googleClientSecret">Client Secret</Label>
156
+ <Input
157
+ id="googleClientSecret"
158
+ type="password"
159
+ value={settings.googleClientSecret ?? ""}
160
+ onChange={(e) =>
161
+ setSettings((s) => ({
162
+ ...s,
163
+ googleClientSecret: e.target.value,
164
+ }))
165
+ }
166
+ placeholder="Google OAuth client secret"
167
+ />
168
+ </div>
169
+ </div>
170
+ <Button type="button" onClick={save} disabled={isPending}>
171
+ {isPending ? "Saving…" : "Save Google settings"}
172
+ </Button>
173
+ </div>
174
+ )}
175
+
176
+ {isOpen && provider.id === "github" && (
177
+ <div className="space-y-4 border-t bg-muted/20 px-4 py-4">
178
+ <div className="grid gap-4 md:grid-cols-2">
179
+ <div className="space-y-2">
180
+ <Label htmlFor="githubClientId">Client ID</Label>
181
+ <Input
182
+ id="githubClientId"
183
+ value={settings.githubClientId ?? ""}
184
+ onChange={(e) =>
185
+ setSettings((s) => ({
186
+ ...s,
187
+ githubClientId: e.target.value,
188
+ }))
189
+ }
190
+ placeholder="GitHub OAuth client ID"
191
+ />
192
+ </div>
193
+ <div className="space-y-2">
194
+ <Label htmlFor="githubClientSecret">Client Secret</Label>
195
+ <Input
196
+ id="githubClientSecret"
197
+ type="password"
198
+ value={settings.githubClientSecret ?? ""}
199
+ onChange={(e) =>
200
+ setSettings((s) => ({
201
+ ...s,
202
+ githubClientSecret: e.target.value,
203
+ }))
204
+ }
205
+ placeholder="GitHub OAuth client secret"
206
+ />
207
+ </div>
208
+ </div>
209
+ <Button type="button" onClick={save} disabled={isPending}>
210
+ {isPending ? "Saving…" : "Save GitHub settings"}
211
+ </Button>
212
+ </div>
213
+ )}
214
+ </div>
215
+ );
216
+ })}
217
+ </div>
218
+
219
+ {message && <p className="text-sm">{message}</p>}
220
+ </div>
221
+ );
222
+ }
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ import { PROJECT_AUTH_INTENT } from "./project-auth-constants";
3
+ import {
4
+ githubOAuthSchema,
5
+ googleOAuthSchema,
6
+ siteUrlSchema,
7
+ } from "./project-auth-client.schema";
8
+
9
+ export const projectAuthServerSchema = z.discriminatedUnion("intent", [
10
+ z.object({
11
+ intent: z.literal(PROJECT_AUTH_INTENT.SAVE_GOOGLE),
12
+ ...googleOAuthSchema.shape,
13
+ }),
14
+ z.object({
15
+ intent: z.literal(PROJECT_AUTH_INTENT.SAVE_GITHUB),
16
+ ...githubOAuthSchema.shape,
17
+ }),
18
+ z.object({
19
+ intent: z.literal(PROJECT_AUTH_INTENT.SAVE_SITE_URL),
20
+ ...siteUrlSchema.shape,
21
+ }),
22
+ ]);
@@ -0,0 +1,106 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const manageItems = [{ label: "Users", segment: "users" }] as const;
8
+
9
+ const configItems = [
10
+ { label: "Sign In / Providers", segment: "providers" },
11
+ { label: "URL Configuration", segment: "url-configuration" },
12
+ ] as const;
13
+
14
+ function NavLink({
15
+ href,
16
+ active,
17
+ children,
18
+ }: {
19
+ href: string;
20
+ active: boolean;
21
+ children: React.ReactNode;
22
+ }) {
23
+ return (
24
+ <Link
25
+ href={href}
26
+ className={cn(
27
+ "block shrink-0 whitespace-nowrap rounded-md px-3 py-2 text-sm transition-colors",
28
+ active
29
+ ? "bg-muted font-medium text-foreground"
30
+ : "text-muted-foreground hover:bg-muted/60 hover:text-foreground",
31
+ )}
32
+ >
33
+ {children}
34
+ </Link>
35
+ );
36
+ }
37
+
38
+ export function ProjectAuthShell({
39
+ orgSlug,
40
+ projectSlug,
41
+ children,
42
+ }: {
43
+ orgSlug: string;
44
+ projectSlug: string;
45
+ children: React.ReactNode;
46
+ }) {
47
+ const pathname = usePathname();
48
+ const base = `/organizations/${orgSlug}/${projectSlug}/auth`;
49
+
50
+ function isActive(segment: string) {
51
+ return (
52
+ pathname === `${base}/${segment}` ||
53
+ pathname.startsWith(`${base}/${segment}/`)
54
+ );
55
+ }
56
+
57
+ return (
58
+ <div className="flex h-full min-h-0 w-full flex-col md:flex-row">
59
+ <aside className="flex w-full shrink-0 flex-col border-b bg-background md:w-56 md:border-b-0 md:border-r">
60
+ <div className="border-b px-4 py-3 md:py-4">
61
+ <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
62
+ Authentication
63
+ </p>
64
+ </div>
65
+
66
+ <nav className="flex gap-1 overflow-x-auto p-2 md:flex-col md:gap-0 md:space-y-6 md:overflow-visible md:p-3">
67
+ <div className="flex shrink-0 gap-1 md:block md:space-y-1">
68
+ <p className="hidden px-3 py-2 text-xs font-medium uppercase tracking-wide text-muted-foreground md:block">
69
+ Manage
70
+ </p>
71
+ <div className="flex gap-1 md:block md:space-y-1">
72
+ {manageItems.map(({ label, segment }) => (
73
+ <NavLink
74
+ key={segment}
75
+ href={`${base}/${segment}`}
76
+ active={isActive(segment)}
77
+ >
78
+ {label}
79
+ </NavLink>
80
+ ))}
81
+ </div>
82
+ </div>
83
+
84
+ <div className="flex shrink-0 gap-1 md:block md:space-y-1">
85
+ <p className="hidden px-3 py-2 text-xs font-medium uppercase tracking-wide text-muted-foreground md:block">
86
+ Configuration
87
+ </p>
88
+ <div className="flex gap-1 md:block md:space-y-1">
89
+ {configItems.map(({ label, segment }) => (
90
+ <NavLink
91
+ key={segment}
92
+ href={`${base}/${segment}`}
93
+ active={isActive(segment)}
94
+ >
95
+ {label}
96
+ </NavLink>
97
+ ))}
98
+ </div>
99
+ </div>
100
+ </nav>
101
+ </aside>
102
+
103
+ <div className="min-w-0 flex-1 overflow-y-auto">{children}</div>
104
+ </div>
105
+ );
106
+ }
@@ -0,0 +1,151 @@
1
+ "use client";
2
+
3
+ import { useActionState } from "react";
4
+ import { zodResolver } from "@hookform/resolvers/zod";
5
+ import { Controller, useForm } from "react-hook-form";
6
+ import type { z } from "zod";
7
+ import { Button } from "@/components/ui/button";
8
+ import {
9
+ Field,
10
+ FieldError,
11
+ FieldGroup,
12
+ FieldLabel,
13
+ } from "@/components/ui/field";
14
+ import { Input } from "@/components/ui/input";
15
+ import { Check, Copy } from "lucide-react";
16
+ import { useState } from "react";
17
+ import { projectAuthAction } from "./project-auth-action";
18
+ import { siteUrlSchema } from "./project-auth-client.schema";
19
+ import { PROJECT_AUTH_INTENT } from "./project-auth-constants";
20
+
21
+ type SiteUrlValues = z.infer<typeof siteUrlSchema>;
22
+
23
+ function CopyRow({ value }: { value: string }) {
24
+ const [copied, setCopied] = useState(false);
25
+
26
+ async function copy() {
27
+ await navigator.clipboard.writeText(value);
28
+ setCopied(true);
29
+ setTimeout(() => setCopied(false), 1500);
30
+ }
31
+
32
+ return (
33
+ <div className="flex flex-col gap-2 px-4 py-3 sm:flex-row sm:items-center">
34
+ <code className="min-w-0 flex-1 truncate rounded-md border bg-muted/30 px-3 py-2 text-sm">
35
+ {value}
36
+ </code>
37
+ <Button type="button" variant="ghost" size="sm" onClick={copy}>
38
+ {copied ? <Check className="size-4" /> : <Copy className="size-4" />}
39
+ </Button>
40
+ </div>
41
+ );
42
+ }
43
+
44
+ export function ProjectAuthUrlConfig({
45
+ orgSlug,
46
+ projectSlug,
47
+ initialSiteUrl,
48
+ apiBaseUrl,
49
+ }: {
50
+ orgSlug: string;
51
+ projectSlug: string;
52
+ initialSiteUrl: string;
53
+ apiBaseUrl: string;
54
+ }) {
55
+ const boundAction = projectAuthAction.bind(null, { orgSlug, projectSlug });
56
+ const [state, formAction, isPending] = useActionState(boundAction, {});
57
+
58
+ const form = useForm<SiteUrlValues>({
59
+ resolver: zodResolver(siteUrlSchema),
60
+ defaultValues: { siteUrl: initialSiteUrl },
61
+ });
62
+
63
+ const redirectUrls = [
64
+ `${apiBaseUrl}/projects/${projectSlug}/auth/google/callback`,
65
+ `${apiBaseUrl}/projects/${projectSlug}/auth/github/callback`,
66
+ `${apiBaseUrl}/projects/${projectSlug}/auth/magic-link/verify`,
67
+ ];
68
+
69
+ return (
70
+ <div className="mx-auto w-full max-w-4xl space-y-8 p-4 sm:p-6">
71
+ <div>
72
+ <h1 className="text-2xl font-medium">URL Configuration</h1>
73
+ <p className="mt-1 text-sm text-muted-foreground">
74
+ Configure site URL and redirect URLs used by auth providers.
75
+ </p>
76
+ </div>
77
+
78
+ <section className="space-y-4">
79
+ <div>
80
+ <h2 className="text-base font-medium">Site URL</h2>
81
+ <p className="text-sm text-muted-foreground">
82
+ Where users land after OAuth sign-in (your app URL).
83
+ </p>
84
+ </div>
85
+
86
+ <form
87
+ action={formAction}
88
+ noValidate
89
+ onSubmitCapture={async (e) => {
90
+ const ok = await form.trigger(undefined, { shouldFocus: true });
91
+ if (!ok) e.preventDefault();
92
+ }}
93
+ className="max-w-xl space-y-4"
94
+ >
95
+ {state.error && (
96
+ <p className="text-sm text-destructive">{state.error}</p>
97
+ )}
98
+ {state.success && (
99
+ <p className="text-sm text-emerald-600">{state.success}</p>
100
+ )}
101
+
102
+ <FieldGroup>
103
+ <Controller
104
+ name="siteUrl"
105
+ control={form.control}
106
+ render={({ field, fieldState }) => (
107
+ <Field data-invalid={fieldState.invalid}>
108
+ <FieldLabel htmlFor="siteUrl">Site URL</FieldLabel>
109
+ <Input
110
+ {...field}
111
+ id="siteUrl"
112
+ placeholder="http://localhost:3001"
113
+ aria-invalid={fieldState.invalid}
114
+ />
115
+ {fieldState.invalid && (
116
+ <FieldError errors={[fieldState.error]} />
117
+ )}
118
+ </Field>
119
+ )}
120
+ />
121
+ </FieldGroup>
122
+
123
+ <Button
124
+ type="submit"
125
+ name="intent"
126
+ value={PROJECT_AUTH_INTENT.SAVE_SITE_URL}
127
+ disabled={isPending}
128
+ >
129
+ {isPending ? "Saving…" : "Save changes"}
130
+ </Button>
131
+ </form>
132
+ </section>
133
+
134
+ <section className="space-y-3">
135
+ <div>
136
+ <h2 className="text-base font-medium">Redirect URLs</h2>
137
+ <p className="text-sm text-muted-foreground">
138
+ Add these callback URLs in your Google and GitHub OAuth app
139
+ settings.
140
+ </p>
141
+ </div>
142
+
143
+ <div className="rounded-lg border divide-y">
144
+ {redirectUrls.map((url) => (
145
+ <CopyRow key={url} value={url} />
146
+ ))}
147
+ </div>
148
+ </section>
149
+ </div>
150
+ );
151
+ }
@@ -0,0 +1,138 @@
1
+ "use client";
2
+
3
+ import { useMemo, useState } from "react";
4
+ import { Avatar, AvatarFallback } from "@/components/ui/avatar";
5
+ import { Badge } from "@/components/ui/badge";
6
+ import { Input } from "@/components/ui/input";
7
+ import {
8
+ Table,
9
+ TableBody,
10
+ TableCell,
11
+ TableHead,
12
+ TableHeader,
13
+ TableRow,
14
+ } from "@/components/ui/table";
15
+ import type { ProjectAuthUser } from "@apiDatabase/types";
16
+ import { Mail } from "lucide-react";
17
+
18
+ function GithubIcon({ className }: { className?: string }) {
19
+ return (
20
+ <svg
21
+ className={className}
22
+ viewBox="0 0 24 24"
23
+ fill="currentColor"
24
+ aria-hidden
25
+ >
26
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
27
+ </svg>
28
+ );
29
+ }
30
+
31
+ function ProviderIcon({ provider }: { provider: ProjectAuthUser["provider"] }) {
32
+ if (provider === "github") return <GithubIcon className="size-4" />;
33
+ if (provider === "google") {
34
+ return <span className="text-xs font-semibold text-blue-600">G</span>;
35
+ }
36
+ return <Mail className="size-4" />;
37
+ }
38
+
39
+ function providerType(provider: ProjectAuthUser["provider"]) {
40
+ return provider === "email" ? "Email" : "Social";
41
+ }
42
+
43
+ export function ProjectAuthUsers({ users }: { users: ProjectAuthUser[] }) {
44
+ const [query, setQuery] = useState("");
45
+
46
+ const filtered = useMemo(() => {
47
+ const q = query.trim().toLowerCase();
48
+ if (!q) return users;
49
+ return users.filter(
50
+ (u) =>
51
+ u.email.toLowerCase().includes(q) || u.id.toLowerCase().includes(q),
52
+ );
53
+ }, [query, users]);
54
+
55
+ return (
56
+ <div className="space-y-6 p-6">
57
+ <div>
58
+ <h1 className="text-2xl font-medium">Users</h1>
59
+ <p className="mt-1 text-sm text-muted-foreground">
60
+ End-users who signed up to apps built on this project.
61
+ </p>
62
+ </div>
63
+
64
+ <div className="flex flex-wrap items-center gap-3">
65
+ <Input
66
+ value={query}
67
+ onChange={(e) => setQuery(e.target.value)}
68
+ placeholder="Search email, user ID"
69
+ className="max-w-sm"
70
+ />
71
+ </div>
72
+
73
+ <div className="rounded-lg border">
74
+ <Table>
75
+ <TableHeader>
76
+ <TableRow>
77
+ <TableHead className="w-10" />
78
+ <TableHead>UID</TableHead>
79
+ <TableHead>Email</TableHead>
80
+ <TableHead>Providers</TableHead>
81
+ <TableHead>Provider type</TableHead>
82
+ <TableHead>Created at</TableHead>
83
+ </TableRow>
84
+ </TableHeader>
85
+ <TableBody>
86
+ {filtered.length === 0 ? (
87
+ <TableRow>
88
+ <TableCell
89
+ colSpan={6}
90
+ className="py-10 text-center text-muted-foreground"
91
+ >
92
+ {users.length === 0
93
+ ? "No users yet. Sign up from the SDK demo to populate this table."
94
+ : "No users match your search."}
95
+ </TableCell>
96
+ </TableRow>
97
+ ) : (
98
+ filtered.map((user) => (
99
+ <TableRow key={user.id}>
100
+ <TableCell>
101
+ <Avatar className="size-8">
102
+ <AvatarFallback className="text-xs">
103
+ {user.email.charAt(0).toUpperCase()}
104
+ </AvatarFallback>
105
+ </Avatar>
106
+ </TableCell>
107
+ <TableCell className="max-w-[140px] truncate font-mono text-xs text-muted-foreground">
108
+ {user.id}
109
+ </TableCell>
110
+ <TableCell className="font-medium">{user.email}</TableCell>
111
+ <TableCell>
112
+ <div className="flex items-center gap-2">
113
+ <ProviderIcon provider={user.provider} />
114
+ <span className="capitalize">{user.provider}</span>
115
+ {user.emailVerified && (
116
+ <Badge variant="secondary" className="text-xs">
117
+ verified
118
+ </Badge>
119
+ )}
120
+ </div>
121
+ </TableCell>
122
+ <TableCell>{providerType(user.provider)}</TableCell>
123
+ <TableCell className="text-muted-foreground">
124
+ {new Date(user.createdAt).toLocaleString()}
125
+ </TableCell>
126
+ </TableRow>
127
+ ))
128
+ )}
129
+ </TableBody>
130
+ </Table>
131
+ </div>
132
+
133
+ <p className="text-sm text-muted-foreground">
134
+ Total: {filtered.length} user{filtered.length === 1 ? "" : "s"}
135
+ </p>
136
+ </div>
137
+ );
138
+ }
@@ -0,0 +1,46 @@
1
+ "use server";
2
+
3
+ import { revalidatePath } from "next/cache";
4
+ import { projectsServerSchema } from "./server.schema";
5
+ import { PROJECTS_INTENT } from "./constants";
6
+ import { COOKIE_KEYS } from "@apiDatabase/constants";
7
+ import { retrieveTokenFromCookie } from "@/server-utils/utils";
8
+ import apiClient from "@/lib/axios";
9
+
10
+ export type ProjectsActionState = {
11
+ error?: string;
12
+ success?: string;
13
+ };
14
+
15
+ export async function projectsAction(
16
+ { slug }: { slug: string },
17
+ _prev: ProjectsActionState,
18
+ formData: FormData,
19
+ ): Promise<ProjectsActionState> {
20
+ const raw = Object.fromEntries(formData);
21
+ const parsed = projectsServerSchema.safeParse(raw);
22
+
23
+ if (!parsed.success) {
24
+ return { error: parsed.error.flatten().formErrors[0] ?? "Invalid input" };
25
+ }
26
+
27
+ const token = await retrieveTokenFromCookie();
28
+ const { intent, ...data } = parsed.data;
29
+
30
+ try {
31
+ switch (intent) {
32
+ case PROJECTS_INTENT.CREATE: {
33
+ await apiClient.post(
34
+ `/orgs/${slug}/projects`,
35
+ { name: (data as { name: string }).name },
36
+ { headers: { Cookie: `${COOKIE_KEYS.ACCESS_TOKEN}=${token}` } },
37
+ );
38
+ revalidatePath(`/organizations/${slug}/projects`);
39
+ return { success: "Project created!" };
40
+ }
41
+ }
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ } catch (err: any) {
44
+ return { error: err.response?.data?.message ?? "Something went wrong" };
45
+ }
46
+ }