@jskit-ai/workspaces-web 0.1.41 → 0.1.43

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.
@@ -1,18 +1,27 @@
1
1
  <template>
2
- <v-card rounded="lg" elevation="1" border>
3
- <v-card-item>
4
- <v-card-title class="text-h6">Workspace settings</v-card-title>
5
- <v-card-subtitle>These values apply to everyone in this workspace.</v-card-subtitle>
6
- </v-card-item>
7
- <v-divider />
8
- <v-card-text class="pt-4">
2
+ <v-sheet rounded="lg" border class="workspace-settings-panel">
3
+ <header class="workspace-settings-panel__header">
4
+ <h2 class="workspace-settings-panel__title">Workspace settings</h2>
5
+ <p class="text-body-2 text-medium-emphasis mb-0">These values apply to everyone in this workspace.</p>
6
+ </header>
7
+
8
+ <div class="workspace-settings-panel__body">
9
9
  <template v-if="showSkeleton">
10
10
  <v-skeleton-loader type="text@2, list-item-two-line@4, button" />
11
11
  </template>
12
12
 
13
- <p v-else-if="addEdit.loadError" class="text-body-2 text-medium-emphasis mb-4">
14
- {{ addEdit.loadError }}
15
- </p>
13
+ <div v-else-if="addEdit.loadError" class="workspace-settings-panel__state">
14
+ <p class="text-body-2 text-medium-emphasis mb-4">{{ addEdit.loadError }}</p>
15
+ <v-btn
16
+ v-if="addEdit.canRetryLoad"
17
+ color="primary"
18
+ variant="tonal"
19
+ :loading="addEdit.isFetching"
20
+ @click="addEdit.refresh"
21
+ >
22
+ Retry
23
+ </v-btn>
24
+ </div>
16
25
 
17
26
  <p v-else-if="!addEdit.canView" class="text-body-2 text-medium-emphasis mb-4">
18
27
  You do not have permission to view workspace settings.
@@ -162,8 +171,8 @@
162
171
  </v-row>
163
172
  </v-form>
164
173
  </template>
165
- </v-card-text>
166
- </v-card>
174
+ </div>
175
+ </v-sheet>
167
176
  </template>
168
177
 
169
178
  <script setup>
@@ -259,3 +268,37 @@ const addEdit = useAddEdit({
259
268
 
260
269
  const showSkeleton = computed(() => Boolean(addEdit.isInitialLoading));
261
270
  </script>
271
+
272
+ <style scoped>
273
+ .workspace-settings-panel {
274
+ overflow: hidden;
275
+ }
276
+
277
+ .workspace-settings-panel__header {
278
+ padding: 1rem 1rem 0;
279
+ }
280
+
281
+ .workspace-settings-panel__title {
282
+ font-size: 1rem;
283
+ font-weight: 650;
284
+ line-height: 1.2;
285
+ margin: 0 0 0.25rem;
286
+ }
287
+
288
+ .workspace-settings-panel__body {
289
+ padding: 1rem;
290
+ }
291
+
292
+ .workspace-settings-panel__state {
293
+ margin-inline: auto;
294
+ max-width: 30rem;
295
+ padding: 1.5rem 1rem;
296
+ text-align: center;
297
+ }
298
+
299
+ @media (max-width: 640px) {
300
+ .workspace-settings-panel__body :deep(.v-btn) {
301
+ min-height: 48px;
302
+ }
303
+ }
304
+ </style>
@@ -163,13 +163,14 @@ const workspaceInvitesEnabled = computed(() => bootstrapModel.workspaceInvitesEn
163
163
 
164
164
  const isBootstrapping = computed(() => Boolean(bootstrapView.isLoading));
165
165
  const isRefreshingBootstrap = computed(() => Boolean(bootstrapView.isRefetching));
166
+ const bootstrapLoadError = computed(() => String(bootstrapView.loadError || "").trim());
166
167
  const canCreateWorkspace = computed(() => bootstrapModel.workspaceAllowSelfCreate === true);
167
168
  const isCreatingWorkspace = computed(() => Boolean(createWorkspaceCommand.isRunning.value));
168
169
 
169
170
  function reportFeedback({
170
171
  message,
171
172
  severity = "error",
172
- channel = "banner",
173
+ channel = "",
173
174
  dedupeKey = ""
174
175
  } = {}) {
175
176
  const normalizedMessage = String(message || "").trim();
@@ -180,6 +181,7 @@ function reportFeedback({
180
181
  errorRuntime.report({
181
182
  source: "users-web.workspaces-view",
182
183
  message: normalizedMessage,
184
+ intent: "action-feedback",
183
185
  severity,
184
186
  channel,
185
187
  dedupeKey: dedupeKey || `users-web.workspaces-view:${severity}:${normalizedMessage}`,
@@ -233,7 +235,6 @@ async function openWorkspace(workspaceSlug) {
233
235
  reportFeedback({
234
236
  message: "Workspace surface is not configured.",
235
237
  severity: "error",
236
- channel: "banner",
237
238
  dedupeKey: "users-web.workspaces-view:workspace-surface-missing"
238
239
  });
239
240
  return;
@@ -258,7 +259,6 @@ async function openWorkspace(workspaceSlug) {
258
259
  reportFeedback({
259
260
  message: String(error?.message || "Unable to open workspace."),
260
261
  severity: "error",
261
- channel: "banner",
262
262
  dedupeKey: `users-web.workspaces-view:open-workspace:${normalizedSlug}`
263
263
  });
264
264
  } finally {
@@ -301,7 +301,6 @@ async function respondToInvite(invite, decision) {
301
301
  reportFeedback({
302
302
  message: "Invitation refused.",
303
303
  severity: "success",
304
- channel: "snackbar",
305
304
  dedupeKey: `users-web.workspaces-view:invite-refused:${token}`
306
305
  });
307
306
  } catch (error) {
@@ -310,7 +309,6 @@ async function respondToInvite(invite, decision) {
310
309
  error?.message || (normalizedDecision === "accept" ? "Unable to accept invite." : "Unable to refuse invite.")
311
310
  ),
312
311
  severity: "error",
313
- channel: "banner",
314
312
  dedupeKey: `users-web.workspaces-view:invite-${normalizedDecision}:${token}`
315
313
  });
316
314
  } finally {
@@ -341,7 +339,6 @@ async function createWorkspace() {
341
339
  reportFeedback({
342
340
  message: "Workspace name is required.",
343
341
  severity: "error",
344
- channel: "banner",
345
342
  dedupeKey: "users-web.workspaces-view:create-workspace-name-required"
346
343
  });
347
344
  return;
@@ -361,12 +358,15 @@ async function createWorkspace() {
361
358
  reportFeedback({
362
359
  message: String(error?.message || "Unable to create workspace."),
363
360
  severity: "error",
364
- channel: "banner",
365
361
  dedupeKey: "users-web.workspaces-view:create-workspace-error"
366
362
  });
367
363
  }
368
364
  }
369
365
 
366
+ async function refreshBootstrap() {
367
+ return bootstrapView.refresh();
368
+ }
369
+
370
370
  watch(
371
371
  () => bootstrapView.resource.data.value,
372
372
  async (payload) => {
@@ -401,20 +401,31 @@ watch(
401
401
  <template>
402
402
  <section class="workspaces-view py-6">
403
403
  <v-container class="mx-auto" max-width="860">
404
- <v-card rounded="lg" border elevation="1">
405
- <v-card-item>
406
- <v-card-title class="text-h6">You are logged in</v-card-title>
407
- <v-card-subtitle>Select a workspace or respond to invitations.</v-card-subtitle>
408
- </v-card-item>
409
- <v-divider />
410
-
411
- <v-card-text class="pt-4">
404
+ <v-sheet rounded="lg" border class="workspaces-view__panel">
405
+ <header class="workspaces-view__header">
406
+ <h1 class="workspaces-view__title">You are logged in</h1>
407
+ <p class="text-body-2 text-medium-emphasis mb-0">Select a workspace or respond to invitations.</p>
408
+ </header>
409
+
410
+ <div class="workspaces-view__body">
412
411
  <v-progress-linear v-if="!isBootstrapping && isRefreshingBootstrap" indeterminate class="mb-4" />
413
412
  <v-row>
414
413
  <v-col cols="12" :md="workspaceInvitesEnabled ? 6 : 12">
415
414
  <template v-if="isBootstrapping">
416
415
  <v-skeleton-loader type="text, list-item-avatar-two-line@3" />
417
416
  </template>
417
+ <div v-else-if="bootstrapLoadError" class="workspaces-view__state">
418
+ <h2 class="text-h6 mb-2">Unable to load workspaces</h2>
419
+ <p class="text-body-2 text-medium-emphasis mb-4">{{ bootstrapLoadError }}</p>
420
+ <v-btn
421
+ color="primary"
422
+ variant="tonal"
423
+ :loading="isRefreshingBootstrap"
424
+ @click="refreshBootstrap"
425
+ >
426
+ Retry
427
+ </v-btn>
428
+ </div>
418
429
  <template v-else>
419
430
  <div class="text-subtitle-2 mb-2">Your workspaces</div>
420
431
  <template v-if="workspaceItems.length === 0">
@@ -539,8 +550,47 @@ watch(
539
550
  </template>
540
551
  </v-col>
541
552
  </v-row>
542
- </v-card-text>
543
- </v-card>
553
+ </div>
554
+ </v-sheet>
544
555
  </v-container>
545
556
  </section>
546
557
  </template>
558
+
559
+ <style scoped>
560
+ .workspaces-view__panel {
561
+ overflow: hidden;
562
+ }
563
+
564
+ .workspaces-view__header {
565
+ padding: 1rem 1rem 0;
566
+ }
567
+
568
+ .workspaces-view__title {
569
+ font-size: clamp(1.35rem, 2vw, 1.85rem);
570
+ font-weight: 650;
571
+ letter-spacing: -0.02em;
572
+ line-height: 1.15;
573
+ margin: 0 0 0.35rem;
574
+ }
575
+
576
+ .workspaces-view__body {
577
+ padding: 1rem;
578
+ }
579
+
580
+ .workspaces-view__state {
581
+ margin-inline: auto;
582
+ max-width: 30rem;
583
+ padding: 2rem 1rem;
584
+ text-align: center;
585
+ }
586
+
587
+ @media (max-width: 640px) {
588
+ .workspaces-view {
589
+ padding-block: 0.75rem !important;
590
+ }
591
+
592
+ .workspaces-view__body :deep(.v-btn) {
593
+ min-height: 48px;
594
+ }
595
+ }
596
+ </style>
@@ -1,9 +1,6 @@
1
- const DEFAULT_COG_LINK_COMPONENT_TOKEN = "local.main.ui.surface-aware-menu-link-item";
2
-
3
1
  const ADMIN_COG_OUTLET = Object.freeze({
4
2
  target: "admin-cog:primary-menu",
5
- defaultLinkComponentToken: DEFAULT_COG_LINK_COMPONENT_TOKEN,
6
3
  ariaLabel: "Admin cog"
7
4
  });
8
5
 
9
- export { DEFAULT_COG_LINK_COMPONENT_TOKEN, ADMIN_COG_OUTLET };
6
+ export { ADMIN_COG_OUTLET };
@@ -18,17 +18,36 @@ const normalizedMessage = computed(() => String(props.message || "").trim() || "
18
18
  </script>
19
19
 
20
20
  <template>
21
- <v-card rounded="lg" elevation="1" border>
22
- <v-card-item>
23
- <template #prepend>
24
- <v-icon :icon="mdiAlertCircleOutline" color="error" />
25
- </template>
26
- <v-card-title class="text-h5">Unavailable</v-card-title>
27
- <v-card-subtitle>{{ normalizedSurfaceLabel }} surface.</v-card-subtitle>
28
- </v-card-item>
29
- <v-divider />
30
- <v-card-text class="d-flex flex-column ga-4">
31
- <p class="text-medium-emphasis mb-0">{{ normalizedMessage }}</p>
32
- </v-card-text>
33
- </v-card>
21
+ <v-sheet rounded="lg" border class="workspace-unavailable-panel">
22
+ <div class="workspace-unavailable-panel__header">
23
+ <v-icon :icon="mdiAlertCircleOutline" color="error" />
24
+ <div>
25
+ <h1 class="workspace-unavailable-panel__title">Unavailable</h1>
26
+ <p class="text-body-2 text-medium-emphasis mb-0">{{ normalizedSurfaceLabel }} surface.</p>
27
+ </div>
28
+ </div>
29
+ <p class="text-body-2 text-medium-emphasis mb-0">{{ normalizedMessage }}</p>
30
+ </v-sheet>
34
31
  </template>
32
+
33
+ <style scoped>
34
+ .workspace-unavailable-panel {
35
+ display: grid;
36
+ gap: 1rem;
37
+ padding: 1rem;
38
+ }
39
+
40
+ .workspace-unavailable-panel__header {
41
+ align-items: flex-start;
42
+ display: flex;
43
+ gap: 0.75rem;
44
+ }
45
+
46
+ .workspace-unavailable-panel__title {
47
+ font-size: clamp(1.35rem, 2vw, 1.85rem);
48
+ font-weight: 650;
49
+ letter-spacing: -0.02em;
50
+ line-height: 1.15;
51
+ margin: 0 0 0.25rem;
52
+ }
53
+ </style>
@@ -1,11 +1,7 @@
1
1
  <script setup>
2
- // To redirect this settings shell to a child page, uncomment and edit the example below.
3
- // import { redirectToChild } from "@jskit-ai/kernel/client/pageRedirects";
4
- // definePage({
5
- // redirect: redirectToChild("your_child_segment")
6
- // });
2
+ import WorkspaceSettingsClientElement from "@jskit-ai/workspaces-web/client/components/WorkspaceSettingsClientElement";
7
3
  </script>
8
4
 
9
5
  <template>
10
- <div />
6
+ <WorkspaceSettingsClientElement />
11
7
  </template>
@@ -5,28 +5,66 @@ import { RouterView } from "vue-router";
5
5
 
6
6
  <template>
7
7
  <section class="settings-shell d-flex flex-column ga-4">
8
- <v-card rounded="lg" elevation="1" border>
9
- <v-card-item>
10
- <v-card-title>Workspace settings</v-card-title>
11
- <v-card-subtitle>Configure the current workspace and its members.</v-card-subtitle>
12
- </v-card-item>
13
- <v-divider />
14
- <v-card-text class="pt-4">
15
- <v-row no-gutters>
16
- <v-col cols="12" md="3" lg="2" class="pr-md-4 mb-4 mb-md-0">
17
- <v-list nav density="comfortable" rounded="lg" border>
18
- <ShellOutlet
19
- target="admin-settings:primary-menu"
20
- default-link-component-token="local.main.ui.surface-aware-menu-link-item"
21
- />
22
- </v-list>
23
- </v-col>
24
-
25
- <v-col cols="12" md="9" lg="10">
26
- <RouterView />
27
- </v-col>
28
- </v-row>
29
- </v-card-text>
30
- </v-card>
8
+ <header>
9
+ <p class="text-overline text-medium-emphasis mb-1">Settings</p>
10
+ <h1 class="settings-shell__title">Workspace settings</h1>
11
+ <p class="text-body-2 text-medium-emphasis mb-0">Configure the current workspace and its members.</p>
12
+ </header>
13
+
14
+ <v-sheet rounded="lg" border class="settings-shell__panel">
15
+ <nav class="settings-shell__nav" aria-label="Workspace settings sections">
16
+ <v-list nav density="comfortable" class="settings-shell__nav-list">
17
+ <ShellOutlet target="admin-settings:primary-menu" />
18
+ </v-list>
19
+ </nav>
20
+ <main class="settings-shell__content">
21
+ <RouterView />
22
+ </main>
23
+ </v-sheet>
31
24
  </section>
32
25
  </template>
26
+
27
+ <style scoped>
28
+ .settings-shell__title {
29
+ font-size: clamp(1.35rem, 2vw, 1.85rem);
30
+ font-weight: 650;
31
+ letter-spacing: -0.02em;
32
+ line-height: 1.15;
33
+ margin: 0 0 0.35rem;
34
+ }
35
+
36
+ .settings-shell__panel {
37
+ display: grid;
38
+ gap: 1rem;
39
+ grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr);
40
+ padding: 1rem;
41
+ }
42
+
43
+ .settings-shell__nav-list {
44
+ padding: 0;
45
+ }
46
+
47
+ .settings-shell__content {
48
+ min-width: 0;
49
+ }
50
+
51
+ @media (max-width: 960px) {
52
+ .settings-shell__panel {
53
+ grid-template-columns: 1fr;
54
+ }
55
+
56
+ .settings-shell__nav {
57
+ overflow-x: auto;
58
+ }
59
+
60
+ .settings-shell__nav-list {
61
+ display: flex;
62
+ gap: 0.5rem;
63
+ min-width: max-content;
64
+ }
65
+
66
+ .settings-shell__nav-list :deep(.v-list-item) {
67
+ min-height: 48px;
68
+ }
69
+ }
70
+ </style>
@@ -11,19 +11,67 @@ const { workspaceUnavailable, workspaceUnavailableMessage } = useWorkspaceNotFou
11
11
  :message="workspaceUnavailableMessage"
12
12
  surface-label="Admin"
13
13
  />
14
- <v-card v-else rounded="lg" elevation="1" border>
15
- <v-card-item>
16
- <template #prepend>
17
- <v-chip color="primary" size="small" label>Admin</v-chip>
18
- </template>
19
- <v-card-title class="text-h5">Workspace Admin</v-card-title>
20
- <v-card-subtitle>Privileged workspace workflows.</v-card-subtitle>
21
- </v-card-item>
22
- <v-divider />
23
- <v-card-text class="d-flex flex-column ga-4">
24
- <p class="text-medium-emphasis mb-0">
25
- Use this area for workspace administration modules.
14
+ <section v-else class="workspace-admin-screen d-flex flex-column ga-4">
15
+ <header class="workspace-admin-screen__header">
16
+ <div>
17
+ <p class="text-overline text-medium-emphasis mb-1">Admin</p>
18
+ <h1 class="workspace-admin-screen__title">Workspace Admin</h1>
19
+ <p class="text-body-2 text-medium-emphasis mb-0">Manage members and workspace settings.</p>
20
+ </div>
21
+ <div class="workspace-admin-screen__actions">
22
+ <v-btn color="primary" variant="flat" to="./members">Members</v-btn>
23
+ <v-btn color="primary" variant="tonal" to="./workspace/settings">Settings</v-btn>
24
+ </div>
25
+ </header>
26
+
27
+ <v-sheet rounded="lg" border class="workspace-admin-screen__panel">
28
+ <h2 class="text-h6 mb-2">Admin tasks</h2>
29
+ <p class="text-body-2 text-medium-emphasis mb-0">
30
+ Review workspace access, members, and operational settings from this surface.
26
31
  </p>
27
- </v-card-text>
28
- </v-card>
32
+ </v-sheet>
33
+ </section>
29
34
  </template>
35
+
36
+ <style scoped>
37
+ .workspace-admin-screen__header {
38
+ align-items: flex-start;
39
+ display: flex;
40
+ gap: 1rem;
41
+ justify-content: space-between;
42
+ }
43
+
44
+ .workspace-admin-screen__title {
45
+ font-size: clamp(1.5rem, 2.5vw, 2.25rem);
46
+ font-weight: 700;
47
+ letter-spacing: -0.03em;
48
+ line-height: 1.1;
49
+ margin: 0 0 0.4rem;
50
+ }
51
+
52
+ .workspace-admin-screen__actions {
53
+ display: flex;
54
+ flex-wrap: wrap;
55
+ gap: 0.5rem;
56
+ justify-content: flex-end;
57
+ }
58
+
59
+ .workspace-admin-screen__panel {
60
+ padding: 1rem;
61
+ }
62
+
63
+ @media (max-width: 640px) {
64
+ .workspace-admin-screen__header {
65
+ flex-direction: column;
66
+ }
67
+
68
+ .workspace-admin-screen__actions {
69
+ width: 100%;
70
+ }
71
+
72
+ .workspace-admin-screen__actions :deep(.v-btn) {
73
+ min-height: 48px;
74
+ flex: 1 1 10rem;
75
+ }
76
+ }
77
+ </style>
@@ -11,17 +11,36 @@ const { workspaceUnavailable, workspaceUnavailableMessage } = useWorkspaceNotFou
11
11
  :message="workspaceUnavailableMessage"
12
12
  surface-label="App"
13
13
  />
14
- <v-card v-else rounded="lg" elevation="1" border>
15
- <v-card-item>
16
- <template #prepend>
17
- <v-chip color="primary" size="small" label>App</v-chip>
18
- </template>
19
- <v-card-title class="text-h5">Workspace Home</v-card-title>
20
- <v-card-subtitle>Primary in-workspace surface.</v-card-subtitle>
21
- </v-card-item>
22
- <v-divider />
23
- <v-card-text class="d-flex flex-column ga-4">
24
- <p class="text-medium-emphasis mb-0">Replace this page with your workspace dashboard modules.</p>
25
- </v-card-text>
26
- </v-card>
14
+ <section v-else class="workspace-home-screen d-flex flex-column ga-4">
15
+ <header>
16
+ <p class="text-overline text-medium-emphasis mb-1">Workspace</p>
17
+ <h1 class="workspace-home-screen__title">Workspace Home</h1>
18
+ <p class="text-body-2 text-medium-emphasis mb-0">The current workspace is active.</p>
19
+ </header>
20
+
21
+ <v-sheet rounded="lg" border class="workspace-home-screen__panel">
22
+ <h2 class="text-h6 mb-2">No workspace activity yet</h2>
23
+ <p class="text-body-2 text-medium-emphasis mb-0">
24
+ Activity from workspace workflows will appear here.
25
+ </p>
26
+ </v-sheet>
27
+ </section>
27
28
  </template>
29
+
30
+ <style scoped>
31
+ .workspace-home-screen__title {
32
+ font-size: clamp(1.5rem, 2.5vw, 2.25rem);
33
+ font-weight: 700;
34
+ letter-spacing: -0.03em;
35
+ line-height: 1.1;
36
+ margin: 0 0 0.4rem;
37
+ }
38
+
39
+ .workspace-home-screen__panel {
40
+ margin-inline: auto;
41
+ max-width: 34rem;
42
+ padding: 2rem 1.25rem;
43
+ text-align: center;
44
+ width: 100%;
45
+ }
46
+ </style>
@@ -16,6 +16,7 @@ test("workspaces-web exports are explicit and aligned with production usage", ()
16
16
  requiredExports: [
17
17
  "./client",
18
18
  "./client/components/AccountSettingsInvitesSection",
19
+ "./client/components/WorkspaceSettingsClientElement",
19
20
  "./client/providers/WorkspacesWebClientProvider",
20
21
  "./client/composables/useWorkspaceRouteContext"
21
22
  ]