@aphexcms/cms-core 0.1.0 → 0.1.3
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/package.json +22 -5
- package/src/api/assets.ts +0 -75
- package/src/api/client.ts +0 -150
- package/src/api/documents.ts +0 -102
- package/src/api/index.ts +0 -7
- package/src/api/organizations.ts +0 -154
- package/src/api/types.ts +0 -34
- package/src/app.d.ts +0 -19
- package/src/auth/MULTI_TENANCY_PLAN.md +0 -1183
- package/src/auth/auth-errors.ts +0 -23
- package/src/auth/auth-hooks.ts +0 -132
- package/src/auth/provider.ts +0 -25
- package/src/client/index.ts +0 -47
- package/src/components/AdminApp.svelte +0 -1078
- package/src/components/admin/AdminLayout.svelte +0 -115
- package/src/components/admin/DocumentEditor.svelte +0 -795
- package/src/components/admin/DocumentTypesList.svelte +0 -97
- package/src/components/admin/ObjectModal.svelte +0 -135
- package/src/components/admin/SchemaField.svelte +0 -171
- package/src/components/admin/fields/ArrayField.svelte +0 -266
- package/src/components/admin/fields/BooleanField.svelte +0 -35
- package/src/components/admin/fields/ImageField.svelte +0 -284
- package/src/components/admin/fields/NumberField.svelte +0 -82
- package/src/components/admin/fields/ReferenceField.svelte +0 -260
- package/src/components/admin/fields/SlugField.svelte +0 -74
- package/src/components/admin/fields/StringField.svelte +0 -40
- package/src/components/admin/fields/TextareaField.svelte +0 -40
- package/src/components/fields/index.ts +0 -9
- package/src/components/index.ts +0 -16
- package/src/components/layout/OrganizationSwitcher.svelte +0 -218
- package/src/components/layout/Sidebar.svelte +0 -88
- package/src/components/layout/sidebar/AppSidebar.svelte +0 -63
- package/src/components/layout/sidebar/NavMain.svelte +0 -95
- package/src/components/layout/sidebar/NavSecondary.svelte +0 -69
- package/src/components/layout/sidebar/NavUser.svelte +0 -85
- package/src/config.ts +0 -18
- package/src/db/adapters/index.ts +0 -3
- package/src/db/index.ts +0 -5
- package/src/db/interfaces/asset.ts +0 -61
- package/src/db/interfaces/document.ts +0 -53
- package/src/db/interfaces/index.ts +0 -98
- package/src/db/interfaces/organization.ts +0 -51
- package/src/db/interfaces/schema.ts +0 -13
- package/src/db/interfaces/user.ts +0 -16
- package/src/db/utils/reference-resolver.ts +0 -119
- package/src/define.ts +0 -7
- package/src/email/index.ts +0 -5
- package/src/email/interfaces/email.ts +0 -45
- package/src/engine.ts +0 -85
- package/src/field-validation/rule.ts +0 -287
- package/src/field-validation/utils.ts +0 -91
- package/src/hooks.ts +0 -142
- package/src/index.ts +0 -5
- package/src/lib/is-mobile.svelte.ts +0 -9
- package/src/lib/utils.ts +0 -13
- package/src/plugins/README.md +0 -154
- package/src/routes/assets-by-id.ts +0 -161
- package/src/routes/assets-cdn.ts +0 -185
- package/src/routes/assets.ts +0 -116
- package/src/routes/documents-by-id.ts +0 -188
- package/src/routes/documents-publish.ts +0 -211
- package/src/routes/documents.ts +0 -172
- package/src/routes/index.ts +0 -13
- package/src/routes/organizations-by-id.ts +0 -258
- package/src/routes/organizations-invitations.ts +0 -183
- package/src/routes/organizations-members.ts +0 -301
- package/src/routes/organizations-switch.ts +0 -74
- package/src/routes/organizations.ts +0 -146
- package/src/routes/schemas-by-type.ts +0 -35
- package/src/routes/schemas.ts +0 -19
- package/src/routes-exports.ts +0 -42
- package/src/schema-context.svelte.ts +0 -24
- package/src/schema-utils/cleanup.ts +0 -116
- package/src/schema-utils/index.ts +0 -4
- package/src/schema-utils/utils.ts +0 -47
- package/src/schema-utils/validator.ts +0 -58
- package/src/server/index.ts +0 -40
- package/src/services/asset-service.ts +0 -256
- package/src/services/index.ts +0 -6
- package/src/storage/adapters/index.ts +0 -2
- package/src/storage/adapters/local-storage-adapter.ts +0 -215
- package/src/storage/index.ts +0 -8
- package/src/storage/interfaces/index.ts +0 -2
- package/src/storage/interfaces/storage.ts +0 -114
- package/src/storage/providers/storage.ts +0 -83
- package/src/types/asset.ts +0 -81
- package/src/types/auth.ts +0 -80
- package/src/types/config.ts +0 -45
- package/src/types/document.ts +0 -38
- package/src/types/index.ts +0 -8
- package/src/types/organization.ts +0 -119
- package/src/types/schemas.ts +0 -151
- package/src/types/sidebar.ts +0 -37
- package/src/types/user.ts +0 -17
- package/src/utils/content-hash.ts +0 -75
- package/src/utils/image-url.ts +0 -204
- package/src/utils/index.ts +0 -12
- package/src/utils/slug.ts +0 -33
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { Input } from '@aphex/ui/shadcn/input';
|
|
3
|
-
import type { Field } from '../../../types/schemas.js';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
field: Field;
|
|
7
|
-
value: any;
|
|
8
|
-
onUpdate: (value: any) => void;
|
|
9
|
-
validationClasses?: string;
|
|
10
|
-
onBlur?: (event: any) => void;
|
|
11
|
-
onFocus?: (event: any) => void;
|
|
12
|
-
readonly?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
let {
|
|
16
|
-
field,
|
|
17
|
-
value,
|
|
18
|
-
onUpdate,
|
|
19
|
-
validationClasses,
|
|
20
|
-
onBlur,
|
|
21
|
-
onFocus,
|
|
22
|
-
readonly = false
|
|
23
|
-
}: Props = $props();
|
|
24
|
-
|
|
25
|
-
function handleInputChange(event: Event) {
|
|
26
|
-
const target = event.target as HTMLInputElement;
|
|
27
|
-
onUpdate(target.value);
|
|
28
|
-
}
|
|
29
|
-
</script>
|
|
30
|
-
|
|
31
|
-
<Input
|
|
32
|
-
id={field.name}
|
|
33
|
-
value={value || ''}
|
|
34
|
-
placeholder={field.title}
|
|
35
|
-
oninput={handleInputChange}
|
|
36
|
-
onblur={onBlur}
|
|
37
|
-
onfocus={onFocus}
|
|
38
|
-
class={validationClasses}
|
|
39
|
-
disabled={readonly}
|
|
40
|
-
/>
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { Textarea } from '@aphex/ui/shadcn/textarea';
|
|
3
|
-
import type { Field } from '../../../types/schemas.js';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
field: Field;
|
|
7
|
-
value: any;
|
|
8
|
-
onUpdate: (value: any) => void;
|
|
9
|
-
validationClasses?: string;
|
|
10
|
-
onBlur?: (event: any) => void;
|
|
11
|
-
onFocus?: (event: any) => void;
|
|
12
|
-
readonly?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
let {
|
|
16
|
-
field,
|
|
17
|
-
value,
|
|
18
|
-
onUpdate,
|
|
19
|
-
validationClasses,
|
|
20
|
-
onBlur,
|
|
21
|
-
onFocus,
|
|
22
|
-
readonly = false
|
|
23
|
-
}: Props = $props();
|
|
24
|
-
|
|
25
|
-
function handleInputChange(event: Event) {
|
|
26
|
-
const target = event.target as HTMLTextAreaElement;
|
|
27
|
-
onUpdate(target.value);
|
|
28
|
-
}
|
|
29
|
-
</script>
|
|
30
|
-
|
|
31
|
-
<Textarea
|
|
32
|
-
id={field.name}
|
|
33
|
-
value={value || ''}
|
|
34
|
-
placeholder={field.title}
|
|
35
|
-
oninput={handleInputChange}
|
|
36
|
-
onblur={onBlur}
|
|
37
|
-
onfocus={onFocus}
|
|
38
|
-
class="min-h-[100px] {validationClasses}"
|
|
39
|
-
disabled={readonly}
|
|
40
|
-
/>
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Field components re-export
|
|
2
|
-
export { default as StringField } from '../admin/fields/StringField.svelte';
|
|
3
|
-
export { default as TextareaField } from '../admin/fields/TextareaField.svelte';
|
|
4
|
-
export { default as NumberField } from '../admin/fields/NumberField.svelte';
|
|
5
|
-
export { default as BooleanField } from '../admin/fields/BooleanField.svelte';
|
|
6
|
-
export { default as SlugField } from '../admin/fields/SlugField.svelte';
|
|
7
|
-
export { default as ImageField } from '../admin/fields/ImageField.svelte';
|
|
8
|
-
export { default as ArrayField } from '../admin/fields/ArrayField.svelte';
|
|
9
|
-
export { default as ReferenceField } from '../admin/fields/ReferenceField.svelte';
|
package/src/components/index.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// Aphex CMS Components
|
|
2
|
-
// All admin interface components -- is this being used?
|
|
3
|
-
|
|
4
|
-
// Main admin app
|
|
5
|
-
export { default as AdminApp } from './AdminApp.svelte';
|
|
6
|
-
|
|
7
|
-
// Sidebar
|
|
8
|
-
export { default as Sidebar } from './layout/Sidebar.svelte';
|
|
9
|
-
|
|
10
|
-
// Admin components (will be migrated from your current structure)
|
|
11
|
-
export { default as DocumentEditor } from './admin/DocumentEditor.svelte';
|
|
12
|
-
export { default as DocumentTypesList } from './admin/DocumentTypesList.svelte';
|
|
13
|
-
export { default as SchemaField } from './admin/SchemaField.svelte';
|
|
14
|
-
|
|
15
|
-
// Field components
|
|
16
|
-
export * from './fields/index.js';
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { organizations } from '../../api/index.js';
|
|
3
|
-
import { invalidateAll, goto } from '$app/navigation';
|
|
4
|
-
import { page } from '$app/state';
|
|
5
|
-
import { SvelteURLSearchParams } from 'svelte/reactivity';
|
|
6
|
-
import {
|
|
7
|
-
DropdownMenu,
|
|
8
|
-
DropdownMenuContent,
|
|
9
|
-
DropdownMenuItem,
|
|
10
|
-
DropdownMenuLabel,
|
|
11
|
-
DropdownMenuSeparator,
|
|
12
|
-
DropdownMenuTrigger
|
|
13
|
-
} from '@aphex/ui/shadcn/dropdown-menu';
|
|
14
|
-
import {
|
|
15
|
-
SidebarMenu,
|
|
16
|
-
SidebarMenuItem,
|
|
17
|
-
SidebarMenuButton,
|
|
18
|
-
useSidebar
|
|
19
|
-
} from '@aphex/ui/shadcn/sidebar';
|
|
20
|
-
import type { SidebarOrganization } from '../../types/sidebar.js';
|
|
21
|
-
|
|
22
|
-
type Props = {
|
|
23
|
-
organizations?: SidebarOrganization[];
|
|
24
|
-
activeOrganization?: SidebarOrganization;
|
|
25
|
-
canCreateOrganization?: boolean;
|
|
26
|
-
onOpenChange?: (open: boolean) => void;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
let {
|
|
30
|
-
organizations: orgs = [],
|
|
31
|
-
activeOrganization,
|
|
32
|
-
canCreateOrganization = false,
|
|
33
|
-
onOpenChange
|
|
34
|
-
}: Props = $props();
|
|
35
|
-
|
|
36
|
-
const sidebar = useSidebar();
|
|
37
|
-
|
|
38
|
-
let isSwitching = $state(false);
|
|
39
|
-
|
|
40
|
-
// Set initial orgId in URL if not present
|
|
41
|
-
$effect(() => {
|
|
42
|
-
if (activeOrganization && !page.url.searchParams.has('orgId')) {
|
|
43
|
-
const params = new SvelteURLSearchParams(page.url.searchParams);
|
|
44
|
-
params.set('orgId', activeOrganization.id);
|
|
45
|
-
goto(`${page.url.pathname}?${params.toString()}`, { replaceState: true });
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
async function handleSwitchOrganization(org: SidebarOrganization) {
|
|
50
|
-
if (org.id === activeOrganization?.id) return;
|
|
51
|
-
|
|
52
|
-
isSwitching = true;
|
|
53
|
-
try {
|
|
54
|
-
// Switch organization on the server
|
|
55
|
-
await organizations.switch({ organizationId: org.id });
|
|
56
|
-
|
|
57
|
-
// Update URL with new orgId and clear editor state
|
|
58
|
-
const params = new SvelteURLSearchParams();
|
|
59
|
-
params.set('orgId', org.id);
|
|
60
|
-
|
|
61
|
-
// Preserve docType if present, but clear editor-specific params
|
|
62
|
-
const currentDocType = page.url.searchParams.get('docType');
|
|
63
|
-
if (currentDocType) {
|
|
64
|
-
params.set('docType', currentDocType);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
await goto(`${page.url.pathname}?${params.toString()}`, { replaceState: false });
|
|
68
|
-
|
|
69
|
-
// Invalidate all data to refetch with new organization context
|
|
70
|
-
await invalidateAll();
|
|
71
|
-
} catch (error) {
|
|
72
|
-
console.error('Failed to switch organization:', error);
|
|
73
|
-
// TODO: Show error toast
|
|
74
|
-
} finally {
|
|
75
|
-
isSwitching = false;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function getOrganizationInitials(name: string): string {
|
|
80
|
-
return name
|
|
81
|
-
.split(' ')
|
|
82
|
-
.map((word) => word[0])
|
|
83
|
-
.join('')
|
|
84
|
-
.toUpperCase()
|
|
85
|
-
.slice(0, 2);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function getRoleLabel(role: string): string {
|
|
89
|
-
return role.charAt(0).toUpperCase() + role.slice(1);
|
|
90
|
-
}
|
|
91
|
-
</script>
|
|
92
|
-
|
|
93
|
-
<SidebarMenu>
|
|
94
|
-
<SidebarMenuItem>
|
|
95
|
-
<DropdownMenu {onOpenChange}>
|
|
96
|
-
<DropdownMenuTrigger>
|
|
97
|
-
{#snippet child({ props })}
|
|
98
|
-
<SidebarMenuButton
|
|
99
|
-
{...props}
|
|
100
|
-
size="lg"
|
|
101
|
-
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
102
|
-
disabled={isSwitching}
|
|
103
|
-
>
|
|
104
|
-
{#if activeOrganization}
|
|
105
|
-
<div
|
|
106
|
-
class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg text-sm font-semibold"
|
|
107
|
-
>
|
|
108
|
-
{getOrganizationInitials(activeOrganization.name)}
|
|
109
|
-
</div>
|
|
110
|
-
<div class="grid flex-1 text-left text-sm leading-tight">
|
|
111
|
-
<span class="truncate font-medium">
|
|
112
|
-
{activeOrganization.name}
|
|
113
|
-
</span>
|
|
114
|
-
<span class="text-muted-foreground truncate text-xs">
|
|
115
|
-
{getRoleLabel(activeOrganization.role)}
|
|
116
|
-
</span>
|
|
117
|
-
</div>
|
|
118
|
-
{:else}
|
|
119
|
-
<div
|
|
120
|
-
class="bg-muted flex aspect-square size-8 items-center justify-center rounded-lg"
|
|
121
|
-
>
|
|
122
|
-
<span class="text-muted-foreground text-xs">?</span>
|
|
123
|
-
</div>
|
|
124
|
-
<span class="text-muted-foreground">No organization</span>
|
|
125
|
-
{/if}
|
|
126
|
-
<svg
|
|
127
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
128
|
-
width="16"
|
|
129
|
-
height="16"
|
|
130
|
-
viewBox="0 0 24 24"
|
|
131
|
-
fill="none"
|
|
132
|
-
stroke="currentColor"
|
|
133
|
-
stroke-width="2"
|
|
134
|
-
stroke-linecap="round"
|
|
135
|
-
stroke-linejoin="round"
|
|
136
|
-
class="ml-auto"
|
|
137
|
-
>
|
|
138
|
-
<path d="m7 15 5 5 5-5" />
|
|
139
|
-
<path d="m7 9 5-5 5 5" />
|
|
140
|
-
</svg>
|
|
141
|
-
</SidebarMenuButton>
|
|
142
|
-
{/snippet}
|
|
143
|
-
</DropdownMenuTrigger>
|
|
144
|
-
<DropdownMenuContent
|
|
145
|
-
class="w-[--bits-dropdown-menu-anchor-width] min-w-56 rounded-lg"
|
|
146
|
-
align="start"
|
|
147
|
-
side={sidebar.isMobile ? 'bottom' : 'right'}
|
|
148
|
-
sideOffset={4}
|
|
149
|
-
>
|
|
150
|
-
<DropdownMenuLabel class="text-muted-foreground text-xs">Organizations</DropdownMenuLabel>
|
|
151
|
-
{#each orgs as org, index (org.id)}
|
|
152
|
-
<DropdownMenuItem
|
|
153
|
-
onSelect={() => handleSwitchOrganization(org)}
|
|
154
|
-
class="gap-2 p-2"
|
|
155
|
-
disabled={isSwitching || org.id === activeOrganization?.id}
|
|
156
|
-
>
|
|
157
|
-
<div
|
|
158
|
-
class="flex size-6 items-center justify-center rounded-md border text-xs font-semibold"
|
|
159
|
-
>
|
|
160
|
-
{getOrganizationInitials(org.name)}
|
|
161
|
-
</div>
|
|
162
|
-
<div class="flex flex-1 flex-col">
|
|
163
|
-
<span class="text-sm">{org.name}</span>
|
|
164
|
-
<span class="text-muted-foreground text-xs">{getRoleLabel(org.role)}</span>
|
|
165
|
-
</div>
|
|
166
|
-
{#if org.id === activeOrganization?.id}
|
|
167
|
-
<svg
|
|
168
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
169
|
-
width="16"
|
|
170
|
-
height="16"
|
|
171
|
-
viewBox="0 0 24 24"
|
|
172
|
-
fill="none"
|
|
173
|
-
stroke="currentColor"
|
|
174
|
-
stroke-width="2"
|
|
175
|
-
stroke-linecap="round"
|
|
176
|
-
stroke-linejoin="round"
|
|
177
|
-
class="text-primary"
|
|
178
|
-
>
|
|
179
|
-
<path d="M20 6 9 17l-5-5" />
|
|
180
|
-
</svg>
|
|
181
|
-
{/if}
|
|
182
|
-
</DropdownMenuItem>
|
|
183
|
-
{/each}
|
|
184
|
-
<DropdownMenuSeparator />
|
|
185
|
-
<DropdownMenuItem onSelect={() => goto('/admin/organizations')} class="gap-2 p-2">
|
|
186
|
-
<button class="text-muted-foreground font-medium">View all organizations</button>
|
|
187
|
-
</DropdownMenuItem>
|
|
188
|
-
{#if canCreateOrganization}
|
|
189
|
-
<DropdownMenuSeparator />
|
|
190
|
-
<DropdownMenuItem class="gap-2 p-2">
|
|
191
|
-
<div class="flex size-6 items-center justify-center rounded-md border bg-transparent">
|
|
192
|
-
<svg
|
|
193
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
194
|
-
width="16"
|
|
195
|
-
height="16"
|
|
196
|
-
viewBox="0 0 24 24"
|
|
197
|
-
fill="none"
|
|
198
|
-
stroke="currentColor"
|
|
199
|
-
stroke-width="2"
|
|
200
|
-
stroke-linecap="round"
|
|
201
|
-
stroke-linejoin="round"
|
|
202
|
-
>
|
|
203
|
-
<path d="M5 12h14" />
|
|
204
|
-
<path d="M12 5v14" />
|
|
205
|
-
</svg>
|
|
206
|
-
</div>
|
|
207
|
-
<button
|
|
208
|
-
class="text-muted-foreground font-medium"
|
|
209
|
-
onclick={() => {
|
|
210
|
-
goto('/admin/organizations?action=create');
|
|
211
|
-
}}>Create organization</button
|
|
212
|
-
>
|
|
213
|
-
</DropdownMenuItem>
|
|
214
|
-
{/if}
|
|
215
|
-
</DropdownMenuContent>
|
|
216
|
-
</DropdownMenu>
|
|
217
|
-
</SidebarMenuItem>
|
|
218
|
-
</SidebarMenu>
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { SidebarProvider, SidebarInset, SidebarTrigger } from '@aphex/ui/shadcn/sidebar';
|
|
3
|
-
import { Separator } from '@aphex/ui/shadcn/separator';
|
|
4
|
-
import { Button } from '@aphex/ui/shadcn/button';
|
|
5
|
-
import { ModeWatcher } from 'mode-watcher';
|
|
6
|
-
import { Sun, Moon } from 'lucide-svelte';
|
|
7
|
-
import { toggleMode } from 'mode-watcher';
|
|
8
|
-
import { page } from '$app/state';
|
|
9
|
-
import type { SidebarData } from '../../types/sidebar.js';
|
|
10
|
-
import AppSidebar from './sidebar/AppSidebar.svelte';
|
|
11
|
-
import { activeTabState } from '$lib/stores/activeTab.svelte.js';
|
|
12
|
-
|
|
13
|
-
type Props = {
|
|
14
|
-
data: SidebarData;
|
|
15
|
-
onSignOut?: () => void | Promise<void>;
|
|
16
|
-
children: any;
|
|
17
|
-
enableGraphiQL?: boolean;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
let { data, onSignOut, children, enableGraphiQL = false }: Props = $props();
|
|
21
|
-
|
|
22
|
-
// Only show tabs on the main /admin page
|
|
23
|
-
const showTabs = $derived(page.url.pathname === '/admin');
|
|
24
|
-
</script>
|
|
25
|
-
|
|
26
|
-
<ModeWatcher />
|
|
27
|
-
<SidebarProvider class="h-screen">
|
|
28
|
-
<AppSidebar {data} {onSignOut} />
|
|
29
|
-
<SidebarInset class="flex h-full flex-col">
|
|
30
|
-
<header
|
|
31
|
-
class="flex h-16 shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12"
|
|
32
|
-
>
|
|
33
|
-
<div class="flex w-full items-center px-4" class:justify-between={showTabs}>
|
|
34
|
-
<!-- Left: Trigger and Separator -->
|
|
35
|
-
<div class="flex items-center gap-2">
|
|
36
|
-
<SidebarTrigger class="-ml-1" />
|
|
37
|
-
<Separator orientation="vertical" class="mr-2 h-4" />
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<!-- Center: Structure/Vision Tabs (only on /admin page) -->
|
|
41
|
-
{#if showTabs && activeTabState}
|
|
42
|
-
<div
|
|
43
|
-
class="bg-muted text-muted-foreground mx-auto inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]"
|
|
44
|
-
>
|
|
45
|
-
<button
|
|
46
|
-
onclick={() => {
|
|
47
|
-
if (activeTabState) activeTabState.value = 'structure';
|
|
48
|
-
}}
|
|
49
|
-
class="{activeTabState.value === 'structure'
|
|
50
|
-
? 'bg-background text-foreground shadow'
|
|
51
|
-
: 'text-muted-foreground'} ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
|
|
52
|
-
>
|
|
53
|
-
Structure
|
|
54
|
-
</button>
|
|
55
|
-
{#if enableGraphiQL}
|
|
56
|
-
<button
|
|
57
|
-
onclick={() => {
|
|
58
|
-
if (activeTabState) activeTabState.value = 'vision';
|
|
59
|
-
}}
|
|
60
|
-
class="{activeTabState.value === 'vision'
|
|
61
|
-
? 'bg-background text-foreground shadow'
|
|
62
|
-
: 'text-muted-foreground'} ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
|
|
63
|
-
>
|
|
64
|
-
Vision
|
|
65
|
-
</button>
|
|
66
|
-
{/if}
|
|
67
|
-
</div>
|
|
68
|
-
{/if}
|
|
69
|
-
|
|
70
|
-
<!-- Right: Theme Toggle -->
|
|
71
|
-
<div class:ml-auto={!showTabs}>
|
|
72
|
-
<Button onclick={toggleMode} variant="outline" size="icon">
|
|
73
|
-
<Sun
|
|
74
|
-
class="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
|
|
75
|
-
/>
|
|
76
|
-
<Moon
|
|
77
|
-
class="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
|
|
78
|
-
/>
|
|
79
|
-
<span class="sr-only">Toggle theme</span>
|
|
80
|
-
</Button>
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
|
-
</header>
|
|
84
|
-
<main class="flex flex-1 flex-col overflow-hidden pt-0">
|
|
85
|
-
{@render children()}
|
|
86
|
-
</main>
|
|
87
|
-
</SidebarInset>
|
|
88
|
-
</SidebarProvider>
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
Sidebar,
|
|
4
|
-
SidebarContent,
|
|
5
|
-
SidebarFooter,
|
|
6
|
-
SidebarHeader,
|
|
7
|
-
SidebarRail
|
|
8
|
-
} from '@aphex/ui/shadcn/sidebar';
|
|
9
|
-
import OrganizationSwitcher from '../OrganizationSwitcher.svelte';
|
|
10
|
-
import NavMain from './NavMain.svelte';
|
|
11
|
-
import NavUser from './NavUser.svelte';
|
|
12
|
-
import type { SidebarData } from '../../../types/sidebar.js';
|
|
13
|
-
import type { ComponentProps } from 'svelte';
|
|
14
|
-
|
|
15
|
-
type Props = ComponentProps<typeof Sidebar> & {
|
|
16
|
-
data: SidebarData;
|
|
17
|
-
onSignOut?: () => void | Promise<void>;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
let { data, onSignOut, ...restProps }: Props = $props();
|
|
21
|
-
|
|
22
|
-
// Convert navItems to the format expected by NavMain
|
|
23
|
-
const navMainItems = $derived(
|
|
24
|
-
data?.navItems?.map((item) => ({
|
|
25
|
-
title: item.label,
|
|
26
|
-
url: item.href,
|
|
27
|
-
icon: undefined, // We'll keep icons simple for now
|
|
28
|
-
isActive: false
|
|
29
|
-
})) || [
|
|
30
|
-
{
|
|
31
|
-
title: 'Content',
|
|
32
|
-
url: '/admin',
|
|
33
|
-
icon: undefined,
|
|
34
|
-
isActive: false
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
);
|
|
38
|
-
</script>
|
|
39
|
-
|
|
40
|
-
<Sidebar collapsible="icon" {...restProps}>
|
|
41
|
-
<SidebarHeader>
|
|
42
|
-
<!-- Organization Switcher -->
|
|
43
|
-
{#if data?.organizations && data.organizations.length > 0}
|
|
44
|
-
<OrganizationSwitcher
|
|
45
|
-
organizations={data.organizations}
|
|
46
|
-
activeOrganization={data.activeOrganization}
|
|
47
|
-
canCreateOrganization={data.user?.role === 'super_admin'}
|
|
48
|
-
/>
|
|
49
|
-
{/if}
|
|
50
|
-
</SidebarHeader>
|
|
51
|
-
|
|
52
|
-
<SidebarContent>
|
|
53
|
-
<NavMain items={navMainItems} />
|
|
54
|
-
</SidebarContent>
|
|
55
|
-
|
|
56
|
-
<SidebarFooter>
|
|
57
|
-
{#if data?.user}
|
|
58
|
-
<NavUser user={data.user} {onSignOut} />
|
|
59
|
-
{/if}
|
|
60
|
-
</SidebarFooter>
|
|
61
|
-
|
|
62
|
-
<SidebarRail />
|
|
63
|
-
</Sidebar>
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { goto } from '$app/navigation';
|
|
3
|
-
import { page } from '$app/state';
|
|
4
|
-
import * as Collapsible from '@aphex/ui/shadcn/collapsible';
|
|
5
|
-
import {
|
|
6
|
-
SidebarGroup,
|
|
7
|
-
SidebarGroupLabel,
|
|
8
|
-
SidebarMenu,
|
|
9
|
-
SidebarMenuItem,
|
|
10
|
-
SidebarMenuButton,
|
|
11
|
-
SidebarMenuSub,
|
|
12
|
-
SidebarMenuSubItem,
|
|
13
|
-
SidebarMenuSubButton
|
|
14
|
-
} from '@aphex/ui/shadcn/sidebar';
|
|
15
|
-
import { ChevronRight, type Icon as IconType } from 'lucide-svelte';
|
|
16
|
-
|
|
17
|
-
type NavItem = {
|
|
18
|
-
title: string;
|
|
19
|
-
url: string;
|
|
20
|
-
icon?: typeof IconType;
|
|
21
|
-
isActive?: boolean;
|
|
22
|
-
items?: {
|
|
23
|
-
title: string;
|
|
24
|
-
url: string;
|
|
25
|
-
}[];
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
type Props = {
|
|
29
|
-
items: NavItem[];
|
|
30
|
-
label?: string;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
let { items, label = 'Content' }: Props = $props();
|
|
34
|
-
|
|
35
|
-
let currentPath = $derived(page.url.pathname);
|
|
36
|
-
</script>
|
|
37
|
-
|
|
38
|
-
<SidebarGroup>
|
|
39
|
-
<SidebarGroupLabel>{label}</SidebarGroupLabel>
|
|
40
|
-
<SidebarMenu>
|
|
41
|
-
{#each items as item (item.title)}
|
|
42
|
-
{#if item.items && item.items.length > 0}
|
|
43
|
-
<Collapsible.Root open={item.isActive} class="group/collapsible">
|
|
44
|
-
{#snippet child({ props })}
|
|
45
|
-
<SidebarMenuItem {...props}>
|
|
46
|
-
<Collapsible.Trigger>
|
|
47
|
-
{#snippet child({ props })}
|
|
48
|
-
<SidebarMenuButton {...props} tooltipContent={item.title}>
|
|
49
|
-
{#if item.icon}
|
|
50
|
-
{@const Icon = item.icon}
|
|
51
|
-
<Icon class="h-4 w-4" />
|
|
52
|
-
{/if}
|
|
53
|
-
<span>{item.title}</span>
|
|
54
|
-
<ChevronRight
|
|
55
|
-
class="ml-auto h-4 w-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
|
|
56
|
-
/>
|
|
57
|
-
</SidebarMenuButton>
|
|
58
|
-
{/snippet}
|
|
59
|
-
</Collapsible.Trigger>
|
|
60
|
-
<Collapsible.Content>
|
|
61
|
-
<SidebarMenuSub>
|
|
62
|
-
{#each item.items as subItem (subItem.title)}
|
|
63
|
-
<SidebarMenuSubItem>
|
|
64
|
-
<SidebarMenuSubButton>
|
|
65
|
-
{#snippet child({ props })}
|
|
66
|
-
<a href={subItem.url} {...props}>
|
|
67
|
-
<span>{subItem.title}</span>
|
|
68
|
-
</a>
|
|
69
|
-
{/snippet}
|
|
70
|
-
</SidebarMenuSubButton>
|
|
71
|
-
</SidebarMenuSubItem>
|
|
72
|
-
{/each}
|
|
73
|
-
</SidebarMenuSub>
|
|
74
|
-
</Collapsible.Content>
|
|
75
|
-
</SidebarMenuItem>
|
|
76
|
-
{/snippet}
|
|
77
|
-
</Collapsible.Root>
|
|
78
|
-
{:else}
|
|
79
|
-
<SidebarMenuItem>
|
|
80
|
-
<SidebarMenuButton
|
|
81
|
-
onclick={() => goto(item.url)}
|
|
82
|
-
isActive={currentPath.startsWith(item.url)}
|
|
83
|
-
tooltipContent={item.title}
|
|
84
|
-
>
|
|
85
|
-
{#if item.icon}
|
|
86
|
-
{@const Icon = item.icon}
|
|
87
|
-
<Icon class="h-4 w-4" />
|
|
88
|
-
{/if}
|
|
89
|
-
<span>{item.title}</span>
|
|
90
|
-
</SidebarMenuButton>
|
|
91
|
-
</SidebarMenuItem>
|
|
92
|
-
{/if}
|
|
93
|
-
{/each}
|
|
94
|
-
</SidebarMenu>
|
|
95
|
-
</SidebarGroup>
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
SidebarGroup,
|
|
4
|
-
SidebarGroupContent,
|
|
5
|
-
SidebarMenu,
|
|
6
|
-
SidebarMenuItem,
|
|
7
|
-
SidebarMenuButton
|
|
8
|
-
} from '@aphex/ui/shadcn/sidebar';
|
|
9
|
-
import { Sun, Moon } from 'lucide-svelte';
|
|
10
|
-
import { toggleMode } from 'mode-watcher';
|
|
11
|
-
|
|
12
|
-
type Props = {
|
|
13
|
-
enableGraphiQL?: boolean;
|
|
14
|
-
activeTab?: 'structure' | 'vision';
|
|
15
|
-
onTabChange?: (tab: 'structure' | 'vision') => void;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
let { enableGraphiQL = false, activeTab = $bindable('structure'), onTabChange }: Props = $props();
|
|
19
|
-
|
|
20
|
-
function handleTabClick(tab: 'structure' | 'vision') {
|
|
21
|
-
activeTab = tab;
|
|
22
|
-
if (onTabChange) {
|
|
23
|
-
onTabChange(tab);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
</script>
|
|
27
|
-
|
|
28
|
-
<SidebarGroup class="mt-auto">
|
|
29
|
-
<SidebarGroupContent>
|
|
30
|
-
<SidebarMenu>
|
|
31
|
-
<!-- Structure Tab -->
|
|
32
|
-
<SidebarMenuItem>
|
|
33
|
-
<SidebarMenuButton
|
|
34
|
-
onclick={() => handleTabClick('structure')}
|
|
35
|
-
isActive={activeTab === 'structure'}
|
|
36
|
-
tooltipContent="Structure"
|
|
37
|
-
size="sm"
|
|
38
|
-
>
|
|
39
|
-
<span>Structure</span>
|
|
40
|
-
</SidebarMenuButton>
|
|
41
|
-
</SidebarMenuItem>
|
|
42
|
-
|
|
43
|
-
<!-- Vision Tab (if GraphiQL is enabled) -->
|
|
44
|
-
{#if enableGraphiQL}
|
|
45
|
-
<SidebarMenuItem>
|
|
46
|
-
<SidebarMenuButton
|
|
47
|
-
onclick={() => handleTabClick('vision')}
|
|
48
|
-
isActive={activeTab === 'vision'}
|
|
49
|
-
tooltipContent="Vision"
|
|
50
|
-
size="sm"
|
|
51
|
-
>
|
|
52
|
-
<span>Vision</span>
|
|
53
|
-
</SidebarMenuButton>
|
|
54
|
-
</SidebarMenuItem>
|
|
55
|
-
{/if}
|
|
56
|
-
|
|
57
|
-
<!-- Theme Toggle -->
|
|
58
|
-
<SidebarMenuItem>
|
|
59
|
-
<SidebarMenuButton onclick={toggleMode} tooltipContent="Toggle theme" size="sm">
|
|
60
|
-
<Sun class="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
|
61
|
-
<Moon
|
|
62
|
-
class="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
|
|
63
|
-
/>
|
|
64
|
-
<span>Theme</span>
|
|
65
|
-
</SidebarMenuButton>
|
|
66
|
-
</SidebarMenuItem>
|
|
67
|
-
</SidebarMenu>
|
|
68
|
-
</SidebarGroupContent>
|
|
69
|
-
</SidebarGroup>
|