@antelopejs/dms-saas 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,7 +30,9 @@ This package lives in `packages/dms-saas` of the `AntelopeJS/dms-saas`
30
30
  workspace. From that directory run `pnpm install`,
31
31
  `pnpm --dir frontend-vue install`, `pnpm build`, `pnpm typecheck`, and
32
32
  `pnpm test:frontend`. The frontend check generates a temporary workspace, then
33
- builds client, SSR, and email bundles and runs Vue typechecking.
33
+ builds client, SSR, and email bundles and runs Vue typechecking. To run the
34
+ playground frontend during backend development, use `pnpm frontend:dev`; this
35
+ dispatches through the core CLI as `ajs dms dev`.
34
36
 
35
37
  ## Completing a registration that entered without a workspace
36
38
 
@@ -138,7 +138,7 @@ const isRegistrationClosed =
138
138
  <i18n-t keypath="saas.register.legal_acceptance" tag="span">
139
139
  <template #terms_and_conditions>
140
140
  <DmsLink
141
- to="/legal/terms-and-conditions"
141
+ to="/terms-and-conditions"
142
142
  target="_blank"
143
143
  class="underline"
144
144
  >
@@ -147,7 +147,7 @@ const isRegistrationClosed =
147
147
  </template>
148
148
  <template #privacy_policy>
149
149
  <DmsLink
150
- to="/legal/privacy-policy"
150
+ to="/privacy-policy"
151
151
  target="_blank"
152
152
  class="underline"
153
153
  >
@@ -1,247 +1,497 @@
1
1
  <script setup lang="ts">
2
- import { computed, onMounted, ref, watch } from "vue";
2
+ import { computed, onMounted, ref, watch } from 'vue'
3
3
 
4
4
  const props = defineProps<{
5
- collapsed?: boolean;
6
- }>();
5
+ collapsed?: boolean
6
+ }>()
7
7
 
8
8
  interface DmsSaasPublicRuntimeConfig {
9
- admissionMode?: "open" | "invitation-only";
9
+ admissionMode?: 'open' | 'invitation-only'
10
10
  }
11
11
 
12
- const toast = useToast();
13
- const { t } = useI18n();
14
- const { resolveApiError } = useApiErrorMessage();
12
+ const toast = useToast()
13
+ const { t } = useI18n()
14
+ const { resolveApiError } = useApiErrorMessage()
15
15
 
16
16
  // Platform owners are not tenant members by default, so an owner can legitimately
17
17
  // have no workspace at all — the switcher must still offer them a way in.
18
- const isOwner = useIsOwner();
19
- const config = useDmsRuntimeConfig();
18
+ const isOwner = useIsOwner()
19
+ const config = useDmsRuntimeConfig()
20
20
  const canCreateWorkspace = computed(
21
- () =>
22
- isOwner.value ||
23
- (config.public.dmsSaas as DmsSaasPublicRuntimeConfig | undefined)
24
- ?.admissionMode !== "invitation-only",
25
- );
26
-
27
- const SEARCH_THRESHOLD = 6;
28
- const HOME_ROUTE = "/";
29
-
30
- const { workspaces, refresh } = useMyWorkspaces();
31
- const isLoading = ref(true);
32
- const isOpen = ref(false);
33
- const isCreateOpen = ref(false);
34
- const searchQuery = ref("");
35
- const switchingId = ref<string | null>(null);
21
+ () =>
22
+ isOwner.value ||
23
+ (config.public.dmsSaas as DmsSaasPublicRuntimeConfig | undefined)
24
+ ?.admissionMode !== 'invitation-only',
25
+ )
26
+
27
+ const SEARCH_THRESHOLD = 6
28
+ const HOME_ROUTE = '/'
29
+
30
+ const { workspaces, refresh } = useMyWorkspaces()
31
+ const isLoading = ref(true)
32
+ const isOpen = ref(false)
33
+ const isCreateOpen = ref(false)
34
+ const searchQuery = ref('')
35
+ const switchingId = ref<string | null>(null)
36
36
 
37
37
  const current = computed<MyWorkspace | undefined>(
38
- () => workspaces.value.find((w) => w.isCurrent) ?? workspaces.value[0],
39
- );
38
+ () => workspaces.value.find((w) => w.isCurrent) ?? workspaces.value[0],
39
+ )
40
40
 
41
- const showSearch = computed(() => workspaces.value.length >= SEARCH_THRESHOLD);
41
+ const showSearch = computed(() => workspaces.value.length >= SEARCH_THRESHOLD)
42
42
 
43
- const isVisible = computed(() => !!current.value || isOwner.value);
43
+ const isVisible = computed(() => !!current.value || isOwner.value)
44
44
 
45
45
  const triggerLabel = computed(
46
- () => current.value?.name ?? t("saas.workspaces.none"),
47
- );
46
+ () => current.value?.name ?? t('saas.workspaces.none'),
47
+ )
48
48
 
49
49
  const triggerCaption = computed(
50
- () => current.value?.planName ?? t("saas.workspaces.current_label"),
51
- );
50
+ () => current.value?.planName ?? t('saas.workspaces.current_label'),
51
+ )
52
52
 
53
53
  const filtered = computed<MyWorkspace[]>(() => {
54
- const needle = searchQuery.value.trim().toLowerCase();
55
- if (!needle) return workspaces.value;
56
- return workspaces.value.filter((w) => w.name.toLowerCase().includes(needle));
57
- });
54
+ const needle = searchQuery.value.trim().toLowerCase()
55
+ if (!needle) return workspaces.value
56
+ return workspaces.value.filter((w) => w.name.toLowerCase().includes(needle))
57
+ })
58
58
 
59
59
  watch(isOpen, (open) => {
60
- if (!open) searchQuery.value = "";
61
- });
60
+ if (!open) searchQuery.value = ''
61
+ })
62
62
 
63
63
  const popoverPlacement = computed(() =>
64
- props.collapsed
65
- ? { side: "right" as const, align: "start" as const, sideOffset: 12 }
66
- : { side: "bottom" as const, align: "start" as const, sideOffset: 6 },
67
- );
64
+ props.collapsed
65
+ ? { side: 'right' as const, align: 'start' as const, sideOffset: 12 }
66
+ : { side: 'bottom' as const, align: 'start' as const, sideOffset: 6 },
67
+ )
68
68
 
69
69
  function initialOf(name: string): string {
70
- return (name.trim()[0] ?? "?").toUpperCase();
70
+ return (name.trim()[0] ?? '?').toUpperCase()
71
71
  }
72
72
 
73
73
  async function load(): Promise<void> {
74
- try {
75
- await refresh();
76
- } catch {
77
- // Errors are swallowed on purpose: useMyWorkspaces already decides what the
78
- // list becomes, and the sidebar is no place for an error card.
79
- } finally {
80
- isLoading.value = false;
81
- }
74
+ try {
75
+ await refresh()
76
+ } catch {
77
+ // Errors are swallowed on purpose: useMyWorkspaces already decides what the
78
+ // list becomes, and the sidebar is no place for an error card.
79
+ } finally {
80
+ isLoading.value = false
81
+ }
82
82
  }
83
83
 
84
84
  async function selectWorkspace(workspace: MyWorkspace): Promise<void> {
85
- if (workspace.isCurrent || switchingId.value) return;
86
- switchingId.value = workspace._id;
87
- try {
88
- // Reloads the page on success, so the switching state never resets.
89
- await useTenantSwitch(workspace._id);
90
- } catch (error) {
91
- switchingId.value = null;
92
- toast.add({
93
- title: resolveApiError(error, "saas.workspaces.switch_error"),
94
- color: "error",
95
- icon: "i-ph-warning-circle",
96
- });
97
- }
85
+ if (workspace.isCurrent || switchingId.value) return
86
+ switchingId.value = workspace._id
87
+ try {
88
+ // Reloads the page on success, so the switching state never resets.
89
+ await useTenantSwitch(workspace._id)
90
+ } catch (error) {
91
+ switchingId.value = null
92
+ toast.add({
93
+ title: resolveApiError(error, 'saas.workspaces.switch_error'),
94
+ color: 'error',
95
+ icon: 'i-ph-warning-circle',
96
+ })
97
+ }
98
98
  }
99
99
 
100
100
  function openCreateModal(): void {
101
- isOpen.value = false;
102
- isCreateOpen.value = true;
101
+ isOpen.value = false
102
+ isCreateOpen.value = true
103
103
  }
104
104
 
105
105
  async function onWorkspaceCreated(tenantId: string): Promise<void> {
106
- isCreateOpen.value = false;
107
- try {
108
- // Lands on the DMS homepage rather than reloading the settings screen the
109
- // creation was started from — that screen belongs to the previous tenant.
110
- await useTenantSwitch(tenantId, HOME_ROUTE);
111
- } catch (error) {
112
- toast.add({
113
- title: resolveApiError(error, "saas.workspaces.switch_error"),
114
- color: "error",
115
- icon: "i-ph-warning-circle",
116
- });
117
- load();
118
- }
119
- }
120
-
121
- onMounted(load);
106
+ isCreateOpen.value = false
107
+ try {
108
+ // Lands on the DMS homepage rather than reloading the settings screen the
109
+ // creation was started from — that screen belongs to the previous tenant.
110
+ await useTenantSwitch(tenantId, HOME_ROUTE)
111
+ } catch (error) {
112
+ toast.add({
113
+ title: resolveApiError(error, 'saas.workspaces.switch_error'),
114
+ color: 'error',
115
+ icon: 'i-ph-warning-circle',
116
+ })
117
+ load()
118
+ }
119
+ }
120
+
121
+ onMounted(load)
122
122
  </script>
123
123
 
124
124
  <template>
125
- <USkeleton
126
- v-if="isLoading"
127
- :class="collapsed ? 'mx-auto size-9 rounded-md' : 'h-[46px] w-full rounded-md'"
128
- />
129
- <UPopover
130
- v-else-if="isVisible"
131
- v-model:open="isOpen"
132
- :content="popoverPlacement"
133
- >
134
- <UTooltip
135
- :text="triggerLabel"
136
- :disabled="!collapsed"
137
- :content="{ side: 'right' }"
138
- >
139
- <button
140
- type="button"
141
- class="border-default bg-elevated hover:border-accented flex w-full items-center rounded-md border text-left transition-colors"
142
- :class="collapsed ? 'mx-auto size-9 justify-center' : 'gap-2.5 px-2.5 py-2'"
143
- :aria-label="$t('saas.workspaces.switcher_title')"
144
- >
145
- <span
146
- class="bg-primary/10 text-primary grid size-7 shrink-0 place-items-center rounded-md text-xs font-semibold"
147
- >
148
- <UIcon v-if="!current" name="i-ph-buildings" class="size-4" />
149
- <template v-else>{{ initialOf(current.name) }}</template>
150
- </span>
151
- <template v-if="!collapsed">
152
- <span class="min-w-0 flex-1">
153
- <span class="text-muted block truncate text-[10px] font-medium uppercase tracking-wide">
154
- {{ triggerCaption }}
155
- </span>
156
- <span class="text-highlighted block truncate text-xs font-medium">
157
- {{ triggerLabel }}
158
- </span>
159
- </span>
160
- <UIcon
161
- name="i-ph-caret-up-down"
162
- class="text-dimmed size-4 shrink-0"
163
- />
164
- </template>
165
- </button>
166
- </UTooltip>
167
-
168
- <template #content>
169
- <div class="flex w-64 flex-col gap-1 p-2">
170
- <p class="text-muted px-2 pt-1 text-xs font-medium">
171
- {{ $t("saas.workspaces.switcher_title") }}
172
- </p>
173
- <UInput
174
- v-if="showSearch"
175
- v-model="searchQuery"
176
- size="sm"
177
- icon="i-ph-magnifying-glass"
178
- :placeholder="$t('saas.workspaces.select')"
179
- />
180
- <ul v-if="filtered.length" class="m-0 max-h-64 list-none overflow-y-auto p-0">
181
- <li v-for="workspace in filtered" :key="workspace._id">
182
- <button
183
- type="button"
184
- class="hover:bg-elevated flex w-full cursor-pointer items-center gap-2.5 rounded-md px-2 py-1.5 text-left text-sm transition-colors"
185
- :class="{ 'cursor-default': workspace.isCurrent || !!switchingId }"
186
- @click="selectWorkspace(workspace)"
187
- >
188
- <span
189
- class="bg-primary/10 text-primary grid size-6 shrink-0 place-items-center rounded-md text-[10px] font-semibold"
190
- >
191
- {{ initialOf(workspace.name) }}
192
- </span>
193
- <span
194
- class="min-w-0 flex-1 truncate"
195
- :class="workspace.isCurrent ? 'text-highlighted font-medium' : 'text-default'"
196
- >
197
- {{ workspace.name }}
198
- </span>
199
- <span
200
- v-if="workspace.planName"
201
- class="text-muted shrink-0 text-[10px] font-medium uppercase tracking-wide"
202
- >
203
- {{ workspace.planName }}
204
- </span>
205
- <UIcon
206
- v-if="switchingId === workspace._id"
207
- name="i-ph-spinner"
208
- class="text-muted size-4 shrink-0 animate-spin"
209
- />
210
- <UIcon
211
- v-else-if="workspace.isCurrent"
212
- name="i-ph-check"
213
- class="text-primary size-4 shrink-0"
214
- />
215
- </button>
216
- </li>
217
- </ul>
218
- <p v-else-if="workspaces.length" class="text-muted px-2 py-1.5 text-sm">
219
- {{ $t("saas.workspaces.no_results") }}
220
- </p>
221
- <USeparator v-if="workspaces.length" />
222
- <UButton
223
- v-if="canCreateWorkspace"
224
- variant="ghost"
225
- color="neutral"
226
- icon="i-ph-plus"
227
- size="sm"
228
- block
229
- class="justify-start"
230
- @click="openCreateModal"
231
- >
232
- {{ $t("saas.workspaces.create.button") }}
233
- </UButton>
234
- </div>
235
- </template>
236
- </UPopover>
237
-
238
- <UModal
239
- v-model:open="isCreateOpen"
240
- :title="$t('saas.workspaces.create.title')"
241
- :description="$t('saas.workspaces.create.self_serve.description')"
242
- >
243
- <template #body>
244
- <DmsSaasWorkspaceCreateModal :on-success-callback="onWorkspaceCreated" />
245
- </template>
246
- </UModal>
125
+ <USkeleton
126
+ v-if="isLoading"
127
+ :class="
128
+ collapsed ? 'mx-auto size-9 rounded-md' : 'h-[46px] w-full rounded-md'
129
+ "
130
+ />
131
+ <UPopover
132
+ v-else-if="isVisible"
133
+ v-model:open="isOpen"
134
+ :content="popoverPlacement"
135
+ :ui="{ content: 'workspace-switcher-popover' }"
136
+ >
137
+ <UTooltip
138
+ :text="triggerLabel"
139
+ :disabled="!collapsed"
140
+ :content="{ side: 'right' }"
141
+ >
142
+ <button
143
+ type="button"
144
+ class="workspace-switcher-trigger"
145
+ :class="{ 'workspace-switcher-trigger--collapsed': collapsed }"
146
+ :aria-label="$t('saas.workspaces.switcher_title')"
147
+ >
148
+ <span class="workspace-switcher-avatar">
149
+ <template v-if="current">{{ initialOf(current.name) }}</template>
150
+ <svg v-if="!current" viewBox="0 0 24 24" aria-hidden="true">
151
+ <path
152
+ d="M3 21h18M5 21V5l7-3 7 3v16M9 9h.01M15 9h.01M9 13h.01M15 13h.01M9 17h.01M15 17h.01"
153
+ />
154
+ </svg>
155
+ </span>
156
+ <template v-if="!collapsed">
157
+ <span class="min-w-0 flex-1">
158
+ <span class="workspace-switcher-plan">
159
+ {{ triggerCaption }}
160
+ </span>
161
+ <span class="workspace-switcher-name">
162
+ {{ triggerLabel }}
163
+ </span>
164
+ </span>
165
+ <span class="workspace-switcher-chevron" aria-hidden="true">
166
+ <svg viewBox="0 0 24 24">
167
+ <path d="m7 15 5 5 5-5" />
168
+ <path d="m7 9 5-5 5 5" />
169
+ </svg>
170
+ </span>
171
+ </template>
172
+ </button>
173
+ </UTooltip>
174
+
175
+ <template #content>
176
+ <div class="workspace-switcher-menu">
177
+ <p class="workspace-switcher-menu-title">
178
+ {{ $t('saas.workspaces.switcher_title') }}
179
+ </p>
180
+ <input
181
+ v-if="showSearch"
182
+ v-model="searchQuery"
183
+ class="workspace-switcher-search"
184
+ type="search"
185
+ :placeholder="$t('saas.workspaces.select')"
186
+ />
187
+ <ul
188
+ v-if="filtered.length"
189
+ class="m-0 max-h-64 list-none overflow-y-auto p-0"
190
+ >
191
+ <li v-for="workspace in filtered" :key="workspace._id">
192
+ <button
193
+ type="button"
194
+ class="workspace-switcher-row"
195
+ :class="{
196
+ 'workspace-switcher-row--current': workspace.isCurrent,
197
+ 'cursor-default': workspace.isCurrent || !!switchingId,
198
+ }"
199
+ @click="selectWorkspace(workspace)"
200
+ >
201
+ <span class="workspace-switcher-row-avatar">
202
+ {{ initialOf(workspace.name) }}
203
+ </span>
204
+ <span
205
+ class="workspace-switcher-row-name"
206
+ :class="{
207
+ 'workspace-switcher-row-name--current': workspace.isCurrent,
208
+ }"
209
+ >
210
+ {{ workspace.name }}
211
+ </span>
212
+ <span
213
+ v-if="workspace.planName"
214
+ class="workspace-switcher-row-plan"
215
+ >
216
+ {{ workspace.planName }}
217
+ </span>
218
+ <span
219
+ v-if="switchingId === workspace._id"
220
+ class="workspace-switcher-spinner"
221
+ aria-hidden="true"
222
+ />
223
+ <span
224
+ v-else-if="workspace.isCurrent"
225
+ class="workspace-switcher-check"
226
+ aria-hidden="true"
227
+ >
228
+ <svg viewBox="0 0 24 24"><path d="m4 12 5 5L20 6" /></svg>
229
+ </span>
230
+ </button>
231
+ </li>
232
+ </ul>
233
+ <p v-else-if="workspaces.length" class="workspace-switcher-empty">
234
+ {{ $t('saas.workspaces.no_results') }}
235
+ </p>
236
+ <div v-if="workspaces.length" class="workspace-switcher-separator" />
237
+ <button
238
+ v-if="canCreateWorkspace"
239
+ type="button"
240
+ class="workspace-switcher-create"
241
+ @click="openCreateModal"
242
+ >
243
+ + {{ $t('saas.workspaces.create.button') }}
244
+ </button>
245
+ </div>
246
+ </template>
247
+ </UPopover>
248
+
249
+ <UModal
250
+ v-model:open="isCreateOpen"
251
+ :title="$t('saas.workspaces.create.title')"
252
+ :description="$t('saas.workspaces.create.self_serve.description')"
253
+ >
254
+ <template #body>
255
+ <DmsSaasWorkspaceCreateModal :on-success-callback="onWorkspaceCreated" />
256
+ </template>
257
+ </UModal>
247
258
  </template>
259
+
260
+ <style scoped>
261
+ :global(.workspace-switcher-popover) {
262
+ padding: 0;
263
+ border: 0;
264
+ border-radius: 0;
265
+ background: transparent;
266
+ box-shadow: none;
267
+ }
268
+
269
+ .workspace-switcher-trigger {
270
+ display: flex;
271
+ align-items: center;
272
+ gap: 10px;
273
+ width: 100%;
274
+ padding: 9px 10px;
275
+ border: 1px solid rgba(120, 140, 200, 0.1);
276
+ border-radius: 6px;
277
+ background: #15151b;
278
+ color: #f1f1f3;
279
+ text-align: left;
280
+ transition: border-color 150ms ease;
281
+ }
282
+
283
+ .workspace-switcher-trigger:hover {
284
+ border-color: rgba(140, 160, 220, 0.18);
285
+ }
286
+
287
+ .workspace-switcher-trigger--collapsed {
288
+ width: 36px;
289
+ height: 36px;
290
+ justify-content: center;
291
+ padding: 4px;
292
+ }
293
+
294
+ .workspace-switcher-avatar,
295
+ .workspace-switcher-row-avatar {
296
+ display: inline-flex;
297
+ flex-shrink: 0;
298
+ align-items: center;
299
+ justify-content: center;
300
+ color: #fff;
301
+ font-weight: 700;
302
+ background: linear-gradient(135deg, #8b5cf6, #7c3aed);
303
+ }
304
+
305
+ .workspace-switcher-avatar {
306
+ width: 26px;
307
+ height: 26px;
308
+ border-radius: 6px;
309
+ font-size: 13px;
310
+ }
311
+
312
+ .workspace-switcher-avatar svg,
313
+ .workspace-switcher-chevron svg,
314
+ .workspace-switcher-check svg {
315
+ width: 100%;
316
+ height: 100%;
317
+ fill: none;
318
+ stroke: currentColor;
319
+ stroke-linecap: round;
320
+ stroke-linejoin: round;
321
+ stroke-width: 1.8;
322
+ }
323
+
324
+ .workspace-switcher-avatar svg {
325
+ width: 16px;
326
+ height: 16px;
327
+ }
328
+
329
+ .workspace-switcher-trigger:not(.workspace-switcher-trigger--collapsed)
330
+ > span:nth-child(2) {
331
+ display: flex;
332
+ min-width: 0;
333
+ flex-direction: column;
334
+ line-height: 1.25;
335
+ }
336
+
337
+ .workspace-switcher-plan,
338
+ .workspace-switcher-row-plan {
339
+ color: #a78bfa;
340
+ font-family: 'JetBrains Mono', ui-monospace, monospace;
341
+ letter-spacing: 0.08em;
342
+ text-transform: uppercase;
343
+ }
344
+
345
+ .workspace-switcher-plan {
346
+ font-size: 10px;
347
+ }
348
+
349
+ .workspace-switcher-name {
350
+ overflow: hidden;
351
+ color: #f1f1f3;
352
+ font-family: 'Space Grotesk', Inter, ui-sans-serif, sans-serif;
353
+ font-size: 13.5px;
354
+ font-weight: 600;
355
+ letter-spacing: -0.01em;
356
+ text-overflow: ellipsis;
357
+ white-space: nowrap;
358
+ }
359
+
360
+ .workspace-switcher-chevron {
361
+ display: inline-flex;
362
+ width: 16px;
363
+ height: 16px;
364
+ margin-left: auto;
365
+ flex-shrink: 0;
366
+ color: #71717a;
367
+ }
368
+
369
+ .workspace-switcher-menu {
370
+ display: flex;
371
+ width: 224px;
372
+ flex-direction: column;
373
+ gap: 2px;
374
+ padding: 5px;
375
+ border: 1px solid rgba(140, 160, 220, 0.18);
376
+ border-radius: 8px;
377
+ background: #15151b;
378
+ box-shadow: 0 16px 48px -16px rgb(0 0 0 / 70%);
379
+ }
380
+
381
+ .workspace-switcher-menu-title,
382
+ .workspace-switcher-empty {
383
+ color: #71717a;
384
+ }
385
+
386
+ .workspace-switcher-menu-title {
387
+ margin: 0;
388
+ padding: 5px 9px 3px;
389
+ font-size: 10.5px;
390
+ }
391
+
392
+ .workspace-switcher-search {
393
+ width: 100%;
394
+ padding: 6px 8px;
395
+ border: 1px solid rgba(140, 160, 220, 0.18);
396
+ border-radius: 6px;
397
+ background: #0a0b12;
398
+ color: #f1f1f3;
399
+ font-size: 12px;
400
+ }
401
+
402
+ .workspace-switcher-row {
403
+ display: flex;
404
+ width: 100%;
405
+ align-items: center;
406
+ gap: 9px;
407
+ padding: 8px 9px;
408
+ border: 0;
409
+ border-radius: 6px;
410
+ background: transparent;
411
+ color: #9b9ba4;
412
+ font-size: 13px;
413
+ text-align: left;
414
+ }
415
+
416
+ .workspace-switcher-row:hover {
417
+ background: #1f1f24;
418
+ color: #f1f1f3;
419
+ }
420
+
421
+ .workspace-switcher-row--current {
422
+ background: color-mix(in oklab, #8b5cf6 12%, transparent);
423
+ color: #f1f1f3;
424
+ }
425
+
426
+ .workspace-switcher-row-avatar {
427
+ width: 22px;
428
+ height: 22px;
429
+ border-radius: 5px;
430
+ font-size: 11px;
431
+ }
432
+
433
+ .workspace-switcher-row-name {
434
+ min-width: 0;
435
+ flex: 1;
436
+ overflow: hidden;
437
+ text-overflow: ellipsis;
438
+ white-space: nowrap;
439
+ }
440
+
441
+ .workspace-switcher-row-name--current {
442
+ color: #f1f1f3;
443
+ }
444
+
445
+ .workspace-switcher-row-plan {
446
+ flex-shrink: 0;
447
+ font-size: 9.5px;
448
+ white-space: nowrap;
449
+ }
450
+
451
+ .workspace-switcher-check {
452
+ display: inline-flex;
453
+ width: 16px;
454
+ height: 16px;
455
+ flex-shrink: 0;
456
+ color: #a78bfa;
457
+ }
458
+
459
+ .workspace-switcher-spinner {
460
+ width: 14px;
461
+ height: 14px;
462
+ flex-shrink: 0;
463
+ border: 2px solid #71717a;
464
+ border-top-color: #a78bfa;
465
+ border-radius: 50%;
466
+ animation: workspace-switcher-spin 700ms linear infinite;
467
+ }
468
+
469
+ .workspace-switcher-separator {
470
+ height: 1px;
471
+ margin: 4px 2px;
472
+ background: rgba(120, 140, 200, 0.06);
473
+ }
474
+
475
+ .workspace-switcher-create {
476
+ justify-content: flex-start;
477
+ width: 100%;
478
+ padding: 8px 9px;
479
+ border: 0;
480
+ background: transparent;
481
+ color: #a78bfa;
482
+ font-size: 13px;
483
+ text-align: left;
484
+ }
485
+
486
+ .workspace-switcher-empty {
487
+ margin: 0;
488
+ padding: 8px 9px;
489
+ font-size: 13px;
490
+ }
491
+
492
+ @keyframes workspace-switcher-spin {
493
+ to {
494
+ transform: rotate(360deg);
495
+ }
496
+ }
497
+ </style>
@@ -448,7 +448,7 @@ onMounted(async () => {
448
448
  <i18n-t keypath="saas.register.legal_acceptance" tag="span">
449
449
  <template #terms_and_conditions>
450
450
  <DmsLink
451
- to="/legal/terms-and-conditions"
451
+ to="/terms-and-conditions"
452
452
  target="_blank"
453
453
  class="text-primary underline"
454
454
  >
@@ -457,7 +457,7 @@ onMounted(async () => {
457
457
  </template>
458
458
  <template #privacy_policy>
459
459
  <DmsLink
460
- to="/legal/privacy-policy"
460
+ to="/privacy-policy"
461
461
  target="_blank"
462
462
  class="text-primary underline"
463
463
  >
@@ -12,6 +12,9 @@ const EXEMPT_PREFIXES = [
12
12
  BILLING_PATH,
13
13
  "/register",
14
14
  "/legal",
15
+ "/terms-of-use",
16
+ "/terms-and-conditions",
17
+ "/privacy-policy",
15
18
  ];
16
19
 
17
20
  function isExempt(path: string): boolean {
@@ -6,9 +6,9 @@ const TERMS_OF_USE_LABEL = "saas.legal.terms_of_use";
6
6
  const TERMS_AND_CONDITIONS_LABEL = "saas.legal.terms_and_conditions";
7
7
  const PRIVACY_POLICY_LABEL = "saas.legal.privacy_policy";
8
8
 
9
- const TERMS_OF_USE_ROUTE = "/legal/terms-of-use";
10
- const TERMS_AND_CONDITIONS_ROUTE = "/legal/terms-and-conditions";
11
- const PRIVACY_POLICY_ROUTE = "/legal/privacy-policy";
9
+ const TERMS_OF_USE_ROUTE = "/terms-of-use";
10
+ const TERMS_AND_CONDITIONS_ROUTE = "/terms-and-conditions";
11
+ const PRIVACY_POLICY_ROUTE = "/privacy-policy";
12
12
 
13
13
  const ORDER_TERMS_OF_USE = 100;
14
14
  const ORDER_TERMS_AND_CONDITIONS = 200;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms-saas",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -42,9 +42,9 @@
42
42
  "zod": "^3.24.4"
43
43
  },
44
44
  "devDependencies": {
45
- "@antelopejs/core": ">=1.7.0 <2",
45
+ "@antelopejs/core": "1.8.0",
46
46
  "@antelopejs/dms": ">=0.3.5 <1.0.0",
47
- "@antelopejs/dms-frontend": ">=0.2.0 <1.0.0",
47
+ "@antelopejs/dms-frontend": "0.2.0",
48
48
  "@antelopejs/file-storage-local": "0.1.4",
49
49
  "@antelopejs/mongodb": "1.3.0",
50
50
  "@antelopejs/tooling-configs": ">=0.0.6 <1.0.0",
@@ -72,7 +72,7 @@
72
72
  },
73
73
  "scripts": {
74
74
  "build": "pnpm --filter @antelopejs/interface-dms-saas build && rimraf dist && tsc -p tsconfig.build.json",
75
- "test:frontend": "ajs-dms verify-source --layer node_modules/@antelopejs/dms/frontend-vue --module frontend-vue",
75
+ "test:frontend": "ajs dms verify-source --layer node_modules/@antelopejs/dms/frontend-vue --module frontend-vue",
76
76
  "frontend:dev": "cd playground && pnpm frontend:dev",
77
77
  "dev": "ajs project run -w -p playground",
78
78
  "stripe:webhooks": "stripe listen --forward-to localhost:5010/api/saas/webhooks/stripe",