@lobb-js/lobb-ext-auth 0.10.4 → 0.11.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 (56) hide show
  1. package/README.md +1 -0
  2. package/dist/auth.d.ts +2 -1
  3. package/dist/auth.js +23 -4
  4. package/dist/index.js +41 -2
  5. package/dist/lib/components/pages/settings/index.svelte +1 -1
  6. package/dist/lib/components/pages/settings/pages/activityFeed.svelte +1 -1
  7. package/dist/lib/components/pages/settings/pages/rolesAndPermissions.svelte +1 -1
  8. package/dist/lib/components/pages/settings/pages/users.svelte +1 -1
  9. package/dist/lib/components/pages/userSettings/index.svelte +45 -32
  10. package/dist/onStartup.js +17 -2
  11. package/extensions/auth/collections/collections.ts +2 -0
  12. package/extensions/auth/collections/shares.ts +60 -0
  13. package/extensions/auth/config/extensionConfigSchema.d.ts +41 -0
  14. package/extensions/auth/config/permissionsAction/create.d.ts +18 -0
  15. package/extensions/auth/config/permissionsAction/delete.d.ts +3 -0
  16. package/extensions/auth/config/permissionsAction/read.d.ts +11 -0
  17. package/extensions/auth/config/permissionsAction/update.d.ts +18 -0
  18. package/extensions/auth/index.ts +0 -2
  19. package/extensions/auth/permissions.d.ts +2 -0
  20. package/extensions/auth/permissions.ts +34 -0
  21. package/extensions/auth/studio/auth.ts +25 -5
  22. package/extensions/auth/studio/index.ts +44 -2
  23. package/extensions/auth/studio/lib/components/pages/settings/index.svelte +1 -1
  24. package/extensions/auth/studio/lib/components/pages/settings/pages/activityFeed.svelte +1 -1
  25. package/extensions/auth/studio/lib/components/pages/settings/pages/rolesAndPermissions.svelte +1 -1
  26. package/extensions/auth/studio/lib/components/pages/settings/pages/users.svelte +1 -1
  27. package/extensions/auth/studio/lib/components/pages/userSettings/index.svelte +45 -32
  28. package/extensions/auth/studio/onStartup.ts +14 -2
  29. package/extensions/auth/tests/collections/shares.test.ts +657 -0
  30. package/extensions/auth/tests/configs/auth.ts +17 -0
  31. package/extensions/auth/tests/controllers/me.test.ts +104 -0
  32. package/extensions/auth/tests/permissions.test.ts +127 -0
  33. package/extensions/auth/tests/workflows/shareIntersection.test.ts +158 -0
  34. package/extensions/auth/workflows/baseWorkflow.ts +48 -26
  35. package/extensions/auth/workflows/currentUserPermissionsWorkflow.ts +32 -0
  36. package/extensions/auth/workflows/index.ts +12 -0
  37. package/extensions/auth/workflows/meAliasWorkflows.ts +26 -0
  38. package/extensions/auth/workflows/policiesWorkflows.ts +64 -117
  39. package/extensions/auth/workflows/shareIntersection.ts +64 -0
  40. package/extensions/auth/workflows/sharesWorkflows.ts +135 -0
  41. package/extensions/auth/workflows/utils.ts +132 -224
  42. package/package.json +4 -6
  43. package/dist/lib/components/pages/userSettings/components/account.svelte +0 -106
  44. package/dist/lib/components/pages/userSettings/components/account.svelte.d.ts +0 -14
  45. package/dist/lib/components/pages/userSettings/components/profile.svelte +0 -87
  46. package/dist/lib/components/pages/userSettings/components/profile.svelte.d.ts +0 -14
  47. package/dist/tests/login.spec.d.ts +0 -1
  48. package/dist/tests/login.spec.js +0 -27
  49. package/dist/tests/package.json +0 -1
  50. package/dist/tests/playwright.config.cjs +0 -27
  51. package/dist/tests/playwright.config.d.cts +0 -2
  52. package/extensions/auth/studio/lib/components/pages/userSettings/components/account.svelte +0 -106
  53. package/extensions/auth/studio/lib/components/pages/userSettings/components/profile.svelte +0 -87
  54. package/extensions/auth/studio/tests/login.spec.ts +0 -34
  55. package/extensions/auth/studio/tests/package.json +0 -1
  56. package/extensions/auth/studio/tests/playwright.config.cjs +0 -27
@@ -1,286 +1,194 @@
1
1
  import type { EventContext, Filter, Lobb } from "@lobb-js/core";
2
+ import type { Context } from "hono";
2
3
  import type {
3
4
  CollectionPermissionActionsKeys,
4
5
  ExtensionConfig,
6
+ PermissionsConfig,
5
7
  User,
6
8
  } from "../config/extensionConfigSchema.ts";
7
- import { LobbError } from "@lobb-js/core";
9
+ import { isActionAllowed } from "../permissions.ts";
10
+
11
+ interface HandlePolicyOutput {
12
+ filter?: Filter;
13
+ payload?: Record<string, unknown>;
14
+ }
8
15
 
9
16
  interface HandlePolicyProps {
10
17
  ctx: EventContext;
11
18
  collectionName: string;
12
19
  action: CollectionPermissionActionsKeys;
13
- user: User | undefined;
14
- input: any | undefined;
15
- role?: string;
20
+ permissions: PermissionsConfig | undefined;
16
21
  lobb: Lobb;
17
- extensionConfig: ExtensionConfig;
18
- }
19
-
20
- interface HandlePolicyOutput {
22
+ // Used by both filter-template rendering (for reads) and as the `user`
23
+ // argument passed into guard / mutate callbacks (for writes).
24
+ user?: User | undefined;
25
+ // Payload for create/update — passed to fields allowlist, guard, mutate.
21
26
  payload?: Record<string, unknown>;
22
- filter?: Filter;
27
+ // Target id for findOne/update/delete — used by the self-read bypass.
28
+ recordId?: string | number;
29
+ // Query filter for findAll — used by the self-read bypass.
30
+ filter?: Record<string, unknown>;
23
31
  }
24
32
 
33
+ // Permission decision for a request, regardless of where the permissions
34
+ // came from (role config or share's embedded snapshot). Walks the tree,
35
+ // throws FORBIDDEN if denied, returns void when unconditionally allowed,
36
+ // returns { filter? } for conditional reads, returns { payload? } when
37
+ // create/update mutators were applied. Static features (filter, fields
38
+ // allowlist) work for both roles and shares; dynamic features (payload
39
+ // guard, mutators) only fire when their function values exist in the
40
+ // permissions object — for shares they're absent (stripped by JSON
41
+ // serialisation) so those branches naturally no-op.
25
42
  export function handlePolicy({
26
43
  ctx,
27
44
  collectionName,
28
45
  action,
29
- user,
30
- input,
46
+ permissions,
31
47
  lobb,
32
- role = "public",
33
- extensionConfig,
48
+ user,
49
+ payload,
50
+ recordId,
51
+ filter,
34
52
  }: HandlePolicyProps): HandlePolicyOutput | void {
35
- if (role === "admin") {
36
- return;
37
- }
38
-
39
- const config = extensionConfig;
40
- const currentRole = config.roles?.[role];
41
-
42
- if (typeof currentRole === "undefined") {
43
- if (role === "public") {
44
- throw new ctx.LobbError({
45
- code: "FORBIDDEN",
46
- message: "You do not have permission to perform this action.",
47
- });
53
+ // Self-row bypass: an authenticated user can always read/update/delete
54
+ // their own auth_users row regardless of role. Share requests have
55
+ // `user === undefined`, so they never hit this branch — they only get
56
+ // what their embedded permissions explicitly grant.
57
+ if (user && collectionName === "auth_users") {
58
+ if (action === "read" && filter && Object.keys(filter).length === 1 && filter.id === user.id) {
59
+ return;
60
+ }
61
+ if ((action === "update" || action === "delete") && recordId !== undefined && recordId === user.id) {
62
+ return;
48
63
  }
49
- throw new ctx.LobbError({
50
- code: "FORBIDDEN",
51
- message: "Your role is not recognized by the system.",
52
- });
53
- }
54
-
55
- const currentRolePermission = currentRole.permissions;
56
-
57
- if (currentRolePermission === true) {
58
- return;
59
64
  }
60
65
 
61
- const collectionPermission = currentRolePermission[collectionName];
62
-
63
- if (typeof collectionPermission === "undefined") {
66
+ if (!isActionAllowed(collectionName, action, permissions)) {
64
67
  throw new ctx.LobbError({
65
68
  code: "FORBIDDEN",
66
69
  message: "You do not have permission to perform this action.",
67
70
  });
68
71
  }
69
72
 
70
- if (collectionPermission === true) {
71
- return;
72
- }
73
-
74
- const permissionAction = collectionPermission[action];
73
+ const collPerm = permissions === true ? true : permissions![collectionName];
74
+ const actionPerm = collPerm === true ? true : collPerm![action];
75
75
 
76
- if (typeof permissionAction === "undefined") {
77
- throw new ctx.LobbError({
78
- code: "FORBIDDEN",
79
- message: "You do not have permission to perform this action.",
80
- });
81
- }
82
-
83
- if (typeof permissionAction === "boolean") {
84
- if (permissionAction === false) {
85
- throw new ctx.LobbError({
86
- code: "FORBIDDEN",
87
- message: "You do not have permission to perform this action.",
88
- });
76
+ if (action === "read") {
77
+ if (
78
+ typeof actionPerm === "object" && actionPerm !== null && actionPerm.filter
79
+ ) {
80
+ return {
81
+ filter: lobb.utils.renderTemplateDeep(actionPerm.filter, { user }),
82
+ };
89
83
  }
90
-
91
84
  return;
92
85
  }
93
86
 
94
- if (action === "create") {
95
- runGuard(user?.role, input.collectionName, "create", {
96
- payload: input.data,
97
- user: user,
98
- }, extensionConfig);
99
-
100
- handleFields(user?.role, input.collectionName, "create", input.data, extensionConfig);
101
-
102
- const data = handleMutate(
103
- user?.role,
104
- input.collectionName,
105
- "create",
106
- input.data,
107
- user,
108
- extensionConfig,
109
- );
110
-
111
- return {
112
- payload: data,
113
- };
114
- } else if (action === "read") {
115
- const readFilter = getFilter(user?.role, input.collectionName, "read", extensionConfig);
116
- const filter = lobb.utils.renderTemplateDeep(
117
- readFilter,
118
- {
119
- user,
120
- },
121
- );
122
-
123
- return {
124
- filter,
125
- };
126
- } else if (action === "update") {
127
- runGuard(user?.role, input.collectionName, "update", {
128
- payload: input.data,
129
- user: user,
130
- }, extensionConfig);
131
-
132
- handleFields(user?.role, input.collectionName, "update", input.data, extensionConfig);
133
-
134
- const data = handleMutate(
135
- user?.role,
136
- input.collectionName,
137
- "update",
138
- input.data,
139
- user,
140
- extensionConfig,
141
- );
142
-
143
- return {
144
- payload: data,
145
- };
146
- } else if (action === "delete") {
147
- } else {
148
- throw new Error(
149
- `The (${action}) action is not implemented in the (auth) extension (handlePolicy) function`,
150
- );
151
- }
152
- }
153
-
154
- export function getFilter(
155
- role: string = "public",
156
- collectionName: string,
157
- action: "read",
158
- extensionConfig: ExtensionConfig,
159
- ) {
160
- const config = extensionConfig;
161
- const permissions = config.roles[role]?.permissions;
162
-
163
- if (permissions && permissions !== true) {
164
- const collectionPermission = permissions[collectionName];
165
- if (collectionPermission && collectionPermission !== true) {
166
- const permission = collectionPermission[action];
167
- if (permission && permission !== true) {
168
- return permission.filter;
87
+ if (action === "create" || action === "update") {
88
+ // Field allowlist — static, works for both roles and shares.
89
+ if (actionPerm.fields && payload) {
90
+ const allowedFields = Object.keys(actionPerm.fields);
91
+ for (const fieldName of Object.keys(payload)) {
92
+ if (!allowedFields.includes(fieldName)) {
93
+ throw new ctx.LobbError({
94
+ code: "FORBIDDEN",
95
+ message:
96
+ `You do not have permission to modify the field (${fieldName}).`,
97
+ });
98
+ }
169
99
  }
170
100
  }
171
- }
172
- }
173
101
 
174
- export function runGuard(
175
- role: string = "public",
176
- collectionName: string,
177
- action: "create" | "update",
178
- context: { payload: Record<string, unknown>; user?: User },
179
- extensionConfig: ExtensionConfig,
180
- ) {
181
- const config = extensionConfig;
182
- const permissions = config.roles[role]?.permissions;
183
-
184
- if (permissions && permissions !== true) {
185
- const collectionPermission = permissions[collectionName];
186
- if (collectionPermission && collectionPermission !== true) {
187
- const permission = collectionPermission[action];
188
- if (permission && permission !== true) {
189
- if (permission.payloadGuard) {
190
- const result = permission.payloadGuard(context);
191
- if (!result) {
192
- throw new LobbError({
193
- code: "FORBIDDEN",
194
- message: "You do not have permission to perform this action.",
195
- });
196
- }
197
- }
102
+ // Payload guard — dynamic function; absent for shares.
103
+ if (actionPerm.payloadGuard && payload) {
104
+ const allowed = actionPerm.payloadGuard({ payload, user });
105
+ if (!allowed) {
106
+ throw new ctx.LobbError({
107
+ code: "FORBIDDEN",
108
+ message: "You do not have permission to perform this action.",
109
+ });
198
110
  }
199
111
  }
200
- }
201
- }
202
112
 
203
- export function handleFields(
204
- role: string = "public",
205
- collectionName: string,
206
- action: "create" | "update",
207
- payload: Record<string, unknown>,
208
- extensionConfig: ExtensionConfig,
209
- ) {
210
- const config = extensionConfig;
211
- const permissions = config.roles[role]?.permissions;
212
-
213
- if (permissions && permissions !== true) {
214
- const collectionPermission = permissions[collectionName];
215
- if (collectionPermission && collectionPermission !== true) {
216
- const permission = collectionPermission[action];
217
- if (permission && permission !== true) {
218
- if (permission.fields) {
219
- const fields = permission.fields;
220
- const allowedFields = Object.keys(fields);
221
- const existingFields = Object.keys(payload);
222
- // add the handler logic of the fields
223
- for (const fieldName of existingFields) {
224
- if (!allowedFields.includes(fieldName)) {
225
- throw new LobbError({
226
- code: "FORBIDDEN",
227
- message:
228
- `You do not have permission to modify the field (${fieldName}).`,
229
- });
230
- }
231
- }
232
- }
113
+ // Mutators — dynamic functions; absent for shares.
114
+ if (actionPerm.mutate && payload) {
115
+ const mutated: Record<string, unknown> = structuredClone(payload);
116
+ for (
117
+ const [fieldName, mutateFn] of Object.entries(actionPerm.mutate) as [
118
+ string,
119
+ (
120
+ ctx: { value: unknown; payload: Record<string, unknown>; user?: User },
121
+ ) => unknown,
122
+ ][]
123
+ ) {
124
+ mutated[fieldName] = mutateFn({
125
+ value: payload[fieldName],
126
+ payload,
127
+ user,
128
+ });
233
129
  }
130
+ return { payload: mutated };
234
131
  }
235
132
  }
133
+
134
+ // delete and other actions with conditional perms have nothing extra to apply.
236
135
  }
237
136
 
238
- export function handleMutate(
239
- role: string = "public",
240
- collectionName: string,
241
- action: "create" | "update",
242
- payload: Record<string, unknown>,
243
- user: User | undefined,
137
+ interface ResolvedPermissions {
138
+ permissions: PermissionsConfig | undefined;
139
+ user: User | undefined;
140
+ }
141
+
142
+ // Picks the permission set for a request based on its bearer credential:
143
+ // share token → share's embedded permissions; otherwise the user's role
144
+ // in the extension config. Admin gets `permissions: true` which downstream
145
+ // (handlePolicy, handleReadFields) treats as unconditional pass-through.
146
+ // Throws FORBIDDEN when the role is unknown.
147
+ export function resolveRequestPermissions(
148
+ context: Context,
244
149
  extensionConfig: ExtensionConfig,
245
- ) {
246
- const config = extensionConfig;
247
- const permissions = config.roles[role]?.permissions;
248
- const returnedPayload: Record<string, unknown> = structuredClone(payload);
150
+ ctx: EventContext,
151
+ ): ResolvedPermissions {
152
+ const share = context.get("auth_share") as
153
+ | { permissions: PermissionsConfig }
154
+ | undefined;
155
+ if (share) {
156
+ return { permissions: share.permissions, user: undefined };
157
+ }
249
158
 
250
- if (permissions && permissions !== true) {
251
- const collectionPermission = permissions[collectionName];
252
- if (collectionPermission && collectionPermission !== true) {
253
- const permission = collectionPermission[action];
254
- if (permission && permission !== true) {
255
- if (permission.mutate) {
256
- for (
257
- const [fieldName, mutateFn] of Object.entries(permission.mutate)
258
- ) {
259
- returnedPayload[fieldName] = mutateFn({
260
- value: payload[fieldName],
261
- payload,
262
- user,
263
- });
264
- }
265
- }
266
- }
159
+ const user = context.get("auth_user") as User | undefined;
160
+ const role = user?.role ?? "public";
161
+
162
+ if (role === "admin") {
163
+ return { permissions: true, user };
164
+ }
165
+
166
+ const currentRole = extensionConfig.roles?.[role];
167
+ if (typeof currentRole === "undefined") {
168
+ if (role === "public") {
169
+ throw new ctx.LobbError({
170
+ code: "FORBIDDEN",
171
+ message: "You do not have permission to perform this action.",
172
+ });
267
173
  }
174
+ throw new ctx.LobbError({
175
+ code: "FORBIDDEN",
176
+ message: "Your role is not recognized by the system.",
177
+ });
268
178
  }
269
179
 
270
- return returnedPayload;
180
+ return { permissions: currentRole.permissions, user };
271
181
  }
272
182
 
183
+ // Post-read field allowlist. Filters out fields not in the role's
184
+ // `read.fields` allowlist. Static feature — works for both roles and shares.
273
185
  export function handleReadFields(
274
- role = "public",
186
+ permissions: PermissionsConfig | undefined,
275
187
  collectionName: string,
276
188
  payload: Record<string, unknown> | Record<string, unknown>[],
277
- extensionConfig: ExtensionConfig,
278
189
  ) {
279
- // filter the payload based on fields
280
- const config = extensionConfig;
281
190
  let clonedPayload = structuredClone(payload);
282
191
 
283
- const permissions = config.roles[role]?.permissions;
284
192
  if (permissions && typeof permissions !== "boolean") {
285
193
  const collectionPermission = permissions[collectionName];
286
194
  if (collectionPermission && typeof collectionPermission !== "boolean") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobb-js/lobb-ext-auth",
3
- "version": "0.10.4",
3
+ "version": "0.11.1",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -18,9 +18,8 @@
18
18
  }
19
19
  },
20
20
  "scripts": {
21
- "test": "bun run test:lobb && bun run test:studio",
21
+ "test": "bun test extensions/auth/tests",
22
22
  "test:lobb": "bun test extensions/auth/tests",
23
- "test:studio": "bun --bun playwright test --config extensions/auth/studio/tests/playwright.config.cjs",
24
23
  "dev": "bun run --watch lobb.ts",
25
24
  "dev:studio": "vite dev",
26
25
  "build": "vite build",
@@ -33,14 +32,13 @@
33
32
  "package": "svelte-package --input extensions/auth/studio"
34
33
  },
35
34
  "dependencies": {
36
- "@lobb-js/core": "^0.31.9",
35
+ "@lobb-js/core": "^0.32.1",
37
36
  "argon2": "^0.40.3",
38
37
  "hono": "^4.7.0"
39
38
  },
40
39
  "devDependencies": {
41
- "@lobb-js/studio": "^0.28.5",
40
+ "@lobb-js/studio": "^0.29.1",
42
41
  "@lucide/svelte": "^0.563.1",
43
- "@playwright/test": "^1.58.2",
44
42
  "@sveltejs/adapter-node": "^5.5.4",
45
43
  "@sveltejs/kit": "^2.60.1",
46
44
  "@sveltejs/package": "^2.5.7",
@@ -1,106 +0,0 @@
1
- <script lang="ts">
2
- import type { ExtensionProps } from "@lobb-js/studio";
3
- import { onMount } from "svelte";
4
-
5
- const { utils }: ExtensionProps = $props();
6
-
7
- let user = $state({
8
- old_password: "",
9
- new_password: "",
10
- });
11
- const icons = utils.components.Icons;
12
-
13
- // @ts-ignore: Im sure the session object exist
14
- const currentUserId = utils.ctx.extensions.auth.session.user.id;
15
- let loaded = $state(false);
16
- onMount(async () => {
17
- const response = await utils.lobb.findOne("auth_users", currentUserId);
18
- const result = await response.json();
19
- user = result.data;
20
- loaded = true;
21
- });
22
-
23
- async function onUpdateCLick() {
24
- if (!user.new_password) {
25
- utils.toast.error("Make sure you fill the new password");
26
- return;
27
- } else if (!user.old_password) {
28
- utils.toast.error("Make sure you fill the old password");
29
- return;
30
- }
31
-
32
- const result = await utils.lobb.request({
33
- method: "POST",
34
- route: "/api/extensions/auth/change_password",
35
- payload: {
36
- old_password: user.old_password,
37
- new_password: user.new_password,
38
- },
39
- });
40
-
41
- if (result.status >= 400) {
42
- return;
43
- }
44
-
45
- utils.toast.success("Account updated successfully.");
46
- }
47
- </script>
48
-
49
- {#if loaded}
50
- <div class="flex max-w-[40rem] flex-1 flex-col gap-4 overflow-y-visible">
51
- <div>
52
- <div class="text-lg font-semibold">Account</div>
53
- <div class="text-sm text-muted-foreground">
54
- Update your account settings.
55
- </div>
56
- </div>
57
- <utils.components.Separator />
58
- <!-- TODO: add prefered language here too -->
59
- <div class="flex flex-col gap-2">
60
- <div class="text-sm">Change Password</div>
61
- <div class="flex gap-4">
62
- <utils.components.Input
63
- bind:value={user.old_password}
64
- placeholder="Old password"
65
- type="password"
66
- />
67
- <utils.components.Input
68
- bind:value={user.new_password}
69
- placeholder="New password"
70
- type="password"
71
- />
72
- </div>
73
- <div class="text-xs text-muted-foreground">
74
- This is where you can change your password.
75
- </div>
76
- </div>
77
- <utils.components.Button
78
- class="self-start"
79
- onclick={onUpdateCLick}
80
- Icon={icons.Pencil}
81
- >
82
- Update Account
83
- </utils.components.Button>
84
- </div>
85
- {:else}
86
- <div class="flex max-w-[40rem] flex-1 flex-col gap-8 overflow-y-visible">
87
- <div class="grid gap-2">
88
- <utils.components.Skeleton class="h-8 w-full max-w-60" />
89
- <utils.components.Skeleton class="h-4 w-full max-w-96" />
90
- </div>
91
- <utils.components.Separator />
92
- <div class="flex flex-col gap-2">
93
- <utils.components.Skeleton class="h-4 w-full max-w-36" />
94
- <utils.components.Skeleton class="h-8 w-full max-w-60" />
95
- <utils.components.Skeleton class="h-4 w-full max-w-96" />
96
- </div>
97
- <div class="flex flex-col gap-2">
98
- <utils.components.Skeleton class="h-4 w-full max-w-36" />
99
- <div class="flex gap-4">
100
- <utils.components.Skeleton class="h-8 w-full max-w-60" />
101
- <utils.components.Skeleton class="h-8 w-full max-w-60" />
102
- </div>
103
- <utils.components.Skeleton class="h-4 w-full max-w-96" />
104
- </div>
105
- </div>
106
- {/if}
@@ -1,14 +0,0 @@
1
- import { SvelteComponentTyped } from "svelte";
2
- declare const __propDef: {
3
- props: Record<string, never>;
4
- events: {
5
- [evt: string]: CustomEvent<any>;
6
- };
7
- slots: {};
8
- };
9
- export type AccountProps = typeof __propDef.props;
10
- export type AccountEvents = typeof __propDef.events;
11
- export type AccountSlots = typeof __propDef.slots;
12
- export default class Account extends SvelteComponentTyped<AccountProps, AccountEvents, AccountSlots> {
13
- }
14
- export {};
@@ -1,87 +0,0 @@
1
- <script lang="ts">
2
- import type { ExtensionProps } from "@lobb-js/studio";
3
- import { onMount } from "svelte";
4
-
5
- const { utils }: ExtensionProps = $props();
6
-
7
- let user = $state({
8
- name: "",
9
- });
10
- const icons = utils.components.Icons;
11
-
12
- // @ts-ignore: Im sure the session object exist
13
- const currentUserId = utils.ctx.extensions.auth.session.user.id;
14
- let loaded = $state(false);
15
- onMount(async () => {
16
- const response = await utils.lobb.findOne("auth_users", currentUserId);
17
- const result = await response.json();
18
- user = result.data;
19
- loaded = true;
20
- });
21
-
22
- async function onUpdateCLick() {
23
- // TODO: add the logic of updating some of the current user's information
24
- const updateObject: any = {};
25
- updateObject["name"] = user.name;
26
- const response = await utils.lobb.updateOne(
27
- "auth_users",
28
- currentUserId,
29
- updateObject,
30
- );
31
- if (response.status >= 400) {
32
- const result = await response.json();
33
- utils.toast.error(result.message);
34
- return;
35
- }
36
- utils.toast.success("Profile updated successfully.");
37
- }
38
- </script>
39
-
40
- {#if loaded}
41
- <div class="flex max-w-[40rem] flex-1 flex-col gap-4 overflow-y-visible">
42
- <div>
43
- <div class="text-lg font-semibold">Profile</div>
44
- <div class="text-sm text-muted-foreground">
45
- Edit information that others will see.
46
- </div>
47
- </div>
48
- <utils.components.Separator />
49
- <!-- TODO: you should add name and other stuff like phone number and stuff like that -->
50
- <div class="flex flex-col gap-2">
51
- <div class="text-sm">Full Name</div>
52
- <utils.components.Input
53
- placeholder="Full Name"
54
- bind:value={user.name}
55
- />
56
- <div class="text-xs text-muted-foreground">
57
- This is your public display name. It can be your real name or a
58
- pseudonym.
59
- </div>
60
- </div>
61
- <utils.components.Button
62
- class="self-start"
63
- onclick={onUpdateCLick}
64
- Icon={icons.Pencil}
65
- >
66
- Update Profile
67
- </utils.components.Button>
68
- </div>
69
- {:else}
70
- <div class="flex max-w-[40rem] flex-1 flex-col gap-8 overflow-y-visible">
71
- <div class="grid gap-2">
72
- <utils.components.Skeleton class="h-8 w-full max-w-60" />
73
- <utils.components.Skeleton class="h-4 w-full max-w-96" />
74
- </div>
75
- <utils.components.Separator />
76
- <div class="flex flex-col gap-2">
77
- <utils.components.Skeleton class="h-4 w-full max-w-36" />
78
- <utils.components.Skeleton class="h-8 w-full max-w-60" />
79
- <utils.components.Skeleton class="h-4 w-full max-w-96" />
80
- </div>
81
- <div class="flex flex-col gap-2">
82
- <utils.components.Skeleton class="h-4 w-full max-w-36" />
83
- <utils.components.Skeleton class="h-8 w-full max-w-60" />
84
- <utils.components.Skeleton class="h-4 w-full max-w-96" />
85
- </div>
86
- </div>
87
- {/if}
@@ -1,14 +0,0 @@
1
- import { SvelteComponentTyped } from "svelte";
2
- declare const __propDef: {
3
- props: Record<string, never>;
4
- events: {
5
- [evt: string]: CustomEvent<any>;
6
- };
7
- slots: {};
8
- };
9
- export type ProfileProps = typeof __propDef.props;
10
- export type ProfileEvents = typeof __propDef.events;
11
- export type ProfileSlots = typeof __propDef.slots;
12
- export default class Profile extends SvelteComponentTyped<ProfileProps, ProfileEvents, ProfileSlots> {
13
- }
14
- export {};