create-aphex 0.1.3 → 0.2.0
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/dist/index.js +9 -88
- package/package.json +4 -4
- package/templates/base/.dockerignore +0 -44
- package/templates/base/.env.example +0 -38
- package/templates/base/CHANGELOG.md +0 -124
- package/templates/base/Dockerfile +0 -69
- package/templates/base/README.md +0 -147
- package/templates/base/aphex.config.ts +0 -45
- package/templates/base/docker-compose.yml +0 -31
- package/templates/base/drizzle/0000_tiny_redwing.sql +0 -226
- package/templates/base/drizzle/meta/0000_snapshot.json +0 -1379
- package/templates/base/drizzle/meta/_journal.json +0 -13
- package/templates/base/drizzle.config.ts +0 -17
- package/templates/base/package.json +0 -81
- package/templates/base/prod.docker-compose.yml +0 -61
- package/templates/base/src/app.css +0 -32
- package/templates/base/src/app.d.ts +0 -26
- package/templates/base/src/app.html +0 -11
- package/templates/base/src/hooks.server.ts +0 -52
- package/templates/base/src/lib/assets/favicon.svg +0 -5
- package/templates/base/src/lib/auth-client.ts +0 -19
- package/templates/base/src/lib/index.ts +0 -1
- package/templates/base/src/lib/schemaTypes/index.ts +0 -11
- package/templates/base/src/lib/schemaTypes/post.ts +0 -47
- package/templates/base/src/lib/server/auth/better-auth/instance.ts +0 -183
- package/templates/base/src/lib/server/auth/index.ts +0 -25
- package/templates/base/src/lib/server/auth/instance.ts +0 -7
- package/templates/base/src/lib/server/auth/service.ts +0 -486
- package/templates/base/src/lib/server/cache/index.ts +0 -10
- package/templates/base/src/lib/server/db/auth-schema.ts +0 -86
- package/templates/base/src/lib/server/db/cms-schema.ts +0 -23
- package/templates/base/src/lib/server/db/index.ts +0 -55
- package/templates/base/src/lib/server/db/schema.ts +0 -3
- package/templates/base/src/lib/server/email/components/EmailLayout.svelte +0 -24
- package/templates/base/src/lib/server/email/components/EmailVerification.svelte +0 -26
- package/templates/base/src/lib/server/email/components/Invitation.svelte +0 -30
- package/templates/base/src/lib/server/email/components/PasswordReset.svelte +0 -28
- package/templates/base/src/lib/server/email/index.ts +0 -28
- package/templates/base/src/lib/server/email/invitation-hook.ts +0 -56
- package/templates/base/src/lib/server/email/renderer.ts +0 -13
- package/templates/base/src/lib/server/email/templates/email-verification.ts +0 -8
- package/templates/base/src/lib/server/email/templates/invitation.ts +0 -8
- package/templates/base/src/lib/server/email/templates/password-reset.ts +0 -7
- package/templates/base/src/lib/server/services/organization.ts +0 -68
- package/templates/base/src/lib/server/storage/index.ts +0 -28
- package/templates/base/src/lib/stores/activeTab.svelte.ts +0 -14
- package/templates/base/src/lib/utils/imageBuilder.ts +0 -3
- package/templates/base/src/lib/utils/slug.ts +0 -33
- package/templates/base/src/routes/(protected)/admin/+layout.server.ts +0 -86
- package/templates/base/src/routes/(protected)/admin/+layout.svelte +0 -78
- package/templates/base/src/routes/(protected)/admin/+page.server.ts +0 -60
- package/templates/base/src/routes/(protected)/admin/+page.svelte +0 -34
- package/templates/base/src/routes/(protected)/admin/organizations/+page.server.ts +0 -39
- package/templates/base/src/routes/(protected)/admin/organizations/+page.svelte +0 -31
- package/templates/base/src/routes/(protected)/admin/organizations/_components/CreateOrganization.svelte +0 -141
- package/templates/base/src/routes/(protected)/admin/organizations/_components/OrganizationsList.svelte +0 -102
- package/templates/base/src/routes/(protected)/admin/settings/+layout.server.ts +0 -42
- package/templates/base/src/routes/(protected)/admin/settings/+layout.svelte +0 -116
- package/templates/base/src/routes/(protected)/admin/settings/+page.server.ts +0 -16
- package/templates/base/src/routes/(protected)/admin/settings/+page.svelte +0 -32
- package/templates/base/src/routes/(protected)/admin/settings/_components/AccountSettings.svelte +0 -177
- package/templates/base/src/routes/(protected)/admin/settings/_components/ApiKeysSettings.svelte +0 -472
- package/templates/base/src/routes/(protected)/admin/settings/_components/OrganizationsSettings.svelte +0 -182
- package/templates/base/src/routes/(protected)/admin/settings/account/+page.server.ts +0 -25
- package/templates/base/src/routes/(protected)/admin/settings/account/+page.svelte +0 -23
- package/templates/base/src/routes/(protected)/admin/settings/api-keys/+page.server.ts +0 -54
- package/templates/base/src/routes/(protected)/admin/settings/api-keys/+page.svelte +0 -14
- package/templates/base/src/routes/(protected)/admin/settings/members/+page.server.ts +0 -31
- package/templates/base/src/routes/(protected)/admin/settings/members/+page.svelte +0 -338
- package/templates/base/src/routes/(protected)/admin/settings/roles/+page.server.ts +0 -17
- package/templates/base/src/routes/(protected)/admin/settings/roles/+page.svelte +0 -339
- package/templates/base/src/routes/+layout.svelte +0 -12
- package/templates/base/src/routes/+page.svelte +0 -2
- package/templates/base/src/routes/api/[...slug]/+server.ts +0 -35
- package/templates/base/src/routes/api/instance-settings/+server.ts +0 -70
- package/templates/base/src/routes/api/invitations/+server.ts +0 -54
- package/templates/base/src/routes/api/invitations/[id]/accept/+server.ts +0 -54
- package/templates/base/src/routes/api/invitations/[id]/reject/+server.ts +0 -49
- package/templates/base/src/routes/api/settings/api-keys/+server.ts +0 -57
- package/templates/base/src/routes/api/settings/api-keys/[id]/+server.ts +0 -53
- package/templates/base/src/routes/god-mode/+layout.server.ts +0 -32
- package/templates/base/src/routes/god-mode/+layout.svelte +0 -155
- package/templates/base/src/routes/god-mode/+page.server.ts +0 -10
- package/templates/base/src/routes/god-mode/+page.svelte +0 -32
- package/templates/base/src/routes/god-mode/_components/CreateOrganization.svelte +0 -141
- package/templates/base/src/routes/god-mode/_components/OrganizationsList.svelte +0 -169
- package/templates/base/src/routes/god-mode/organizations/+page.server.ts +0 -43
- package/templates/base/src/routes/god-mode/organizations/+page.svelte +0 -74
- package/templates/base/src/routes/invitations/+page.server.ts +0 -48
- package/templates/base/src/routes/invitations/+page.svelte +0 -264
- package/templates/base/src/routes/invite/[token]/+page.server.ts +0 -59
- package/templates/base/src/routes/invite/[token]/+page.svelte +0 -109
- package/templates/base/src/routes/login/+page.server.ts +0 -14
- package/templates/base/src/routes/login/+page.svelte +0 -332
- package/templates/base/src/routes/media/[id]/[filename]/+server.ts +0 -3
- package/templates/base/src/routes/reset-password/[token]/+page.server.ts +0 -15
- package/templates/base/src/routes/reset-password/[token]/+page.svelte +0 -179
- package/templates/base/src/routes/verify-email/+page.svelte +0 -41
- package/templates/base/static/images/aphex-darkmode.png +0 -0
- package/templates/base/static/images/aphex-lightmode.png +0 -0
- package/templates/base/static/robots.txt +0 -3
- package/templates/base/svelte.config.js +0 -19
- package/templates/base/tsconfig.json +0 -20
- package/templates/base/vite.config.ts +0 -146
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { PageData } from './$types';
|
|
3
|
-
import AccountSettings from '../_components/AccountSettings.svelte';
|
|
4
|
-
|
|
5
|
-
let { data }: { data: PageData } = $props();
|
|
6
|
-
</script>
|
|
7
|
-
|
|
8
|
-
<svelte:head>
|
|
9
|
-
<title>Aphex CMS - Account</title>
|
|
10
|
-
</svelte:head>
|
|
11
|
-
|
|
12
|
-
<div class="grid gap-6">
|
|
13
|
-
<div class="hidden sm:block">
|
|
14
|
-
<h2 class="text-xl font-semibold">Profile</h2>
|
|
15
|
-
<p class="text-muted-foreground text-sm">Manage your personal account settings.</p>
|
|
16
|
-
</div>
|
|
17
|
-
|
|
18
|
-
<AccountSettings
|
|
19
|
-
user={data.user}
|
|
20
|
-
userPreferences={data.userPreferences}
|
|
21
|
-
hasChildOrganizations={data.hasChildOrganizations}
|
|
22
|
-
/>
|
|
23
|
-
</div>
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type { PageServerLoad } from './$types';
|
|
2
|
-
import { drizzleDb } from '$lib/server/db';
|
|
3
|
-
import { apikey } from '$lib/server/db/auth-schema';
|
|
4
|
-
import { eq } from 'drizzle-orm';
|
|
5
|
-
import { error } from '@sveltejs/kit';
|
|
6
|
-
import { hasCapability } from '@aphexcms/cms-core';
|
|
7
|
-
|
|
8
|
-
export const load: PageServerLoad = async ({ locals }) => {
|
|
9
|
-
const auth = locals.auth;
|
|
10
|
-
|
|
11
|
-
if (!auth || auth.type !== 'session') {
|
|
12
|
-
throw new Error('No session found');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (!hasCapability(auth, 'apiKey.manage')) {
|
|
16
|
-
throw error(403, 'You do not have permission to manage API keys');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const userApiKeys = await drizzleDb.query.apikey.findMany({
|
|
20
|
-
where: eq(apikey.referenceId, auth.user.id),
|
|
21
|
-
columns: {
|
|
22
|
-
id: true,
|
|
23
|
-
name: true,
|
|
24
|
-
metadata: true,
|
|
25
|
-
expiresAt: true,
|
|
26
|
-
lastRequest: true,
|
|
27
|
-
createdAt: true
|
|
28
|
-
},
|
|
29
|
-
orderBy: (apikey, { desc }) => [desc(apikey.createdAt)]
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const apiKeysWithPermissions = userApiKeys
|
|
33
|
-
.map((key) => {
|
|
34
|
-
let metadata: any = key.metadata;
|
|
35
|
-
if (typeof metadata === 'string') {
|
|
36
|
-
metadata = JSON.parse(metadata);
|
|
37
|
-
if (typeof metadata === 'string') {
|
|
38
|
-
metadata = JSON.parse(metadata);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
metadata = metadata || null;
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
...key,
|
|
45
|
-
permissions: metadata?.permissions || [],
|
|
46
|
-
organizationId: metadata?.organizationId
|
|
47
|
-
};
|
|
48
|
-
})
|
|
49
|
-
.filter((key) => auth.type === 'session' && key.organizationId === auth.organizationId);
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
apiKeys: apiKeysWithPermissions
|
|
53
|
-
};
|
|
54
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { PageData } from './$types';
|
|
3
|
-
import ApiKeysSettings from '../_components/ApiKeysSettings.svelte';
|
|
4
|
-
|
|
5
|
-
let { data }: { data: PageData } = $props();
|
|
6
|
-
</script>
|
|
7
|
-
|
|
8
|
-
<svelte:head>
|
|
9
|
-
<title>Aphex CMS - API Keys</title>
|
|
10
|
-
</svelte:head>
|
|
11
|
-
|
|
12
|
-
<div class="grid gap-6">
|
|
13
|
-
<ApiKeysSettings apiKeys={data.apiKeys} organizationRole={data.user.organizationRole} />
|
|
14
|
-
</div>
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { PageServerLoad } from './$types';
|
|
2
|
-
|
|
3
|
-
export const load: PageServerLoad = async ({ locals }) => {
|
|
4
|
-
const auth = locals.auth;
|
|
5
|
-
|
|
6
|
-
if (!auth || auth.type !== 'session') {
|
|
7
|
-
throw new Error('No session found');
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const { databaseAdapter, rolesService } = locals.aphexCMS;
|
|
11
|
-
let pendingInvitations: any[] = [];
|
|
12
|
-
|
|
13
|
-
if (auth.organizationId) {
|
|
14
|
-
const invitations = await databaseAdapter.findOrganizationInvitations(auth.organizationId);
|
|
15
|
-
pendingInvitations = invitations.filter(
|
|
16
|
-
(inv: any) => !inv.acceptedAt && inv.expiresAt && inv.expiresAt > new Date()
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Load roles so the invite dropdown reflects custom roles defined for this
|
|
21
|
-
// org. Owners are excluded — ownership is transferred, not invited.
|
|
22
|
-
const allRoles = auth.organizationId ? await rolesService.listRoles(auth.organizationId) : [];
|
|
23
|
-
const inviteRoles = allRoles
|
|
24
|
-
.filter((r) => r.name !== 'owner')
|
|
25
|
-
.map((r) => ({ name: r.name, description: r.description }));
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
pendingInvitations,
|
|
29
|
-
inviteRoles
|
|
30
|
-
};
|
|
31
|
-
};
|
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import * as Card from '@aphexcms/ui/shadcn/card';
|
|
3
|
-
import * as Select from '@aphexcms/ui/shadcn/select';
|
|
4
|
-
import * as Avatar from '@aphexcms/ui/shadcn/avatar';
|
|
5
|
-
import { Button } from '@aphexcms/ui/shadcn/button';
|
|
6
|
-
import { Input } from '@aphexcms/ui/shadcn/input';
|
|
7
|
-
import { Badge } from '@aphexcms/ui/shadcn/badge';
|
|
8
|
-
import { confirmDialog, usePermissions } from '@aphexcms/cms-core/client';
|
|
9
|
-
import { invalidateAll } from '$app/navigation';
|
|
10
|
-
import { Mail, Crown, Shield, Edit, Eye, Users, Send } from '@lucide/svelte';
|
|
11
|
-
import { organizations } from '@aphexcms/cms-core/client';
|
|
12
|
-
import { toast } from 'svelte-sonner';
|
|
13
|
-
import type { PageData } from './$types';
|
|
14
|
-
|
|
15
|
-
let { data }: { data: PageData } = $props();
|
|
16
|
-
|
|
17
|
-
const activeOrganization = $derived(data.activeOrganization);
|
|
18
|
-
const currentUserId = $derived(data.user.id);
|
|
19
|
-
const pendingInvitations = $derived((data as any).pendingInvitations ?? []);
|
|
20
|
-
const inviteRoles = $derived(
|
|
21
|
-
((data as any).inviteRoles ?? []) as Array<{ name: string; description: string | null }>
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
let inviteEmail = $state('');
|
|
25
|
-
let inviteRole = $state<string>('editor');
|
|
26
|
-
let isInviting = $state(false);
|
|
27
|
-
|
|
28
|
-
// Ensure the selected invite role always exists in the available set.
|
|
29
|
-
// Falls back to the first option if the previously-selected role was
|
|
30
|
-
// deleted while the page was open.
|
|
31
|
-
$effect(() => {
|
|
32
|
-
if (inviteRoles.length > 0 && !inviteRoles.some((r) => r.name === inviteRole)) {
|
|
33
|
-
inviteRole = inviteRoles[0].name;
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const currentUserRole = $derived(
|
|
38
|
-
activeOrganization?.members.find((m: any) => m.userId === currentUserId)?.role
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
// Per-capability gates — replaces the coarse "owner/admin" role check.
|
|
42
|
-
// Split per action so custom roles with just `member.invite` can invite
|
|
43
|
-
// but not remove, etc.
|
|
44
|
-
const perms = usePermissions();
|
|
45
|
-
const canInvite = $derived(perms.can('member.invite'));
|
|
46
|
-
const canRemoveMembers = $derived(perms.can('member.remove'));
|
|
47
|
-
|
|
48
|
-
// function getRoleIcon(role: string) {
|
|
49
|
-
// switch (role) {
|
|
50
|
-
// case 'owner':
|
|
51
|
-
// return Crown;
|
|
52
|
-
// case 'admin':
|
|
53
|
-
// return Shield;
|
|
54
|
-
// case 'editor':
|
|
55
|
-
// return Edit;
|
|
56
|
-
// case 'viewer':
|
|
57
|
-
// return Eye;
|
|
58
|
-
// default:
|
|
59
|
-
// return Users;
|
|
60
|
-
// }
|
|
61
|
-
// }
|
|
62
|
-
|
|
63
|
-
function getRoleBadgeVariant(role: string): 'default' | 'secondary' | 'outline' {
|
|
64
|
-
switch (role) {
|
|
65
|
-
case 'owner':
|
|
66
|
-
return 'default';
|
|
67
|
-
case 'admin':
|
|
68
|
-
return 'secondary';
|
|
69
|
-
default:
|
|
70
|
-
return 'outline';
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function getInitials(name: string | null, email: string): string {
|
|
75
|
-
if (name) {
|
|
76
|
-
return name
|
|
77
|
-
.split(' ')
|
|
78
|
-
.map((w) => w[0])
|
|
79
|
-
.join('')
|
|
80
|
-
.toUpperCase()
|
|
81
|
-
.slice(0, 2);
|
|
82
|
-
}
|
|
83
|
-
return email[0].toUpperCase();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function inviteMember() {
|
|
87
|
-
if (!inviteEmail.trim()) return;
|
|
88
|
-
|
|
89
|
-
isInviting = true;
|
|
90
|
-
try {
|
|
91
|
-
const result = await organizations.inviteMember({
|
|
92
|
-
email: inviteEmail.trim(),
|
|
93
|
-
role: inviteRole
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
if (!result.success) {
|
|
97
|
-
throw new Error(result.error || result.message || 'Failed to invite member');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
toast.success(`Invitation sent to ${inviteEmail.trim()}`);
|
|
101
|
-
inviteEmail = '';
|
|
102
|
-
inviteRole = 'editor';
|
|
103
|
-
await invalidateAll();
|
|
104
|
-
} catch (error) {
|
|
105
|
-
toast.error(error instanceof Error ? error.message : 'Failed to invite member');
|
|
106
|
-
} finally {
|
|
107
|
-
isInviting = false;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async function cancelInvitation(invitationId: string, email: string) {
|
|
112
|
-
const confirmCancelInvitation = await confirmDialog({
|
|
113
|
-
title: `Cancel invitation for ${email}?`,
|
|
114
|
-
description:
|
|
115
|
-
'The user will not be able to join the organization if the invitation is revoked. They can still re-join with a new invitation later.',
|
|
116
|
-
confirmText: 'Revoke',
|
|
117
|
-
variant: 'destructive'
|
|
118
|
-
});
|
|
119
|
-
if (!confirmCancelInvitation) return;
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
const result = await organizations.cancelInvitation({ invitationId });
|
|
123
|
-
if (!result.success) throw new Error(result.error || 'Failed to cancel invitation');
|
|
124
|
-
toast.success(`Invitation for ${email} revoked`);
|
|
125
|
-
await invalidateAll();
|
|
126
|
-
} catch (error) {
|
|
127
|
-
toast.error(error instanceof Error ? error.message : 'Failed to cancel invitation');
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async function removeMember(userId: string, userName: string) {
|
|
132
|
-
const confirmed = await confirmDialog({
|
|
133
|
-
title: `Remove ${userName}?`,
|
|
134
|
-
description: `${userName} will lose access to this organization. They can be re-invited later.`,
|
|
135
|
-
confirmText: 'Remove',
|
|
136
|
-
variant: 'destructive'
|
|
137
|
-
});
|
|
138
|
-
if (!confirmed) return;
|
|
139
|
-
|
|
140
|
-
try {
|
|
141
|
-
const result = await organizations.removeMember({ userId });
|
|
142
|
-
if (!result.success) throw new Error(result.error || 'Failed to remove member');
|
|
143
|
-
toast.success(`${userName} has been removed`);
|
|
144
|
-
await invalidateAll();
|
|
145
|
-
} catch (error) {
|
|
146
|
-
toast.error(error instanceof Error ? error.message : 'Failed to remove member');
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
</script>
|
|
150
|
-
|
|
151
|
-
<svelte:head>
|
|
152
|
-
<title>Aphex CMS - Members</title>
|
|
153
|
-
</svelte:head>
|
|
154
|
-
|
|
155
|
-
<div class="grid gap-6">
|
|
156
|
-
<div class="hidden sm:block">
|
|
157
|
-
<h2 class="text-xl font-semibold">Members</h2>
|
|
158
|
-
<p class="text-muted-foreground text-sm">
|
|
159
|
-
Manage members and invitations for your organization.
|
|
160
|
-
</p>
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
{#if !activeOrganization}
|
|
164
|
-
<Card.Root>
|
|
165
|
-
<Card.Content class="py-12 text-center">
|
|
166
|
-
<Users class="text-muted-foreground mx-auto mb-3 h-10 w-10" />
|
|
167
|
-
<p class="text-muted-foreground text-lg">No active organization</p>
|
|
168
|
-
</Card.Content>
|
|
169
|
-
</Card.Root>
|
|
170
|
-
{:else}
|
|
171
|
-
<!-- Invite Member -->
|
|
172
|
-
{#if canInvite}
|
|
173
|
-
<Card.Root>
|
|
174
|
-
<Card.Header>
|
|
175
|
-
<Card.Title class="flex items-center gap-2">
|
|
176
|
-
<Send class="h-4 w-4" />
|
|
177
|
-
Invite Member
|
|
178
|
-
</Card.Title>
|
|
179
|
-
<Card.Description>
|
|
180
|
-
Send an invite to add someone to {activeOrganization.name}
|
|
181
|
-
</Card.Description>
|
|
182
|
-
</Card.Header>
|
|
183
|
-
<Card.Content>
|
|
184
|
-
<div class="flex items-end gap-3">
|
|
185
|
-
<div class="flex-1">
|
|
186
|
-
<Input
|
|
187
|
-
id="invite-email"
|
|
188
|
-
type="email"
|
|
189
|
-
bind:value={inviteEmail}
|
|
190
|
-
placeholder="email@example.com"
|
|
191
|
-
/>
|
|
192
|
-
</div>
|
|
193
|
-
<div class="w-[160px]">
|
|
194
|
-
<Select.Root type="single" name="role" bind:value={inviteRole}>
|
|
195
|
-
<Select.Trigger>
|
|
196
|
-
<span class="capitalize">{inviteRole}</span>
|
|
197
|
-
</Select.Trigger>
|
|
198
|
-
<Select.Content>
|
|
199
|
-
{#each inviteRoles as option (option.name)}
|
|
200
|
-
<Select.Item value={option.name} label={option.name}>
|
|
201
|
-
<div>
|
|
202
|
-
<div class="font-medium capitalize">{option.name}</div>
|
|
203
|
-
{#if option.description}
|
|
204
|
-
<div class="text-muted-foreground text-xs">{option.description}</div>
|
|
205
|
-
{/if}
|
|
206
|
-
</div>
|
|
207
|
-
</Select.Item>
|
|
208
|
-
{/each}
|
|
209
|
-
</Select.Content>
|
|
210
|
-
</Select.Root>
|
|
211
|
-
</div>
|
|
212
|
-
<Button onclick={inviteMember} disabled={isInviting}>
|
|
213
|
-
{isInviting ? 'Sending...' : 'Send Invite'}
|
|
214
|
-
</Button>
|
|
215
|
-
</div>
|
|
216
|
-
</Card.Content>
|
|
217
|
-
</Card.Root>
|
|
218
|
-
{/if}
|
|
219
|
-
|
|
220
|
-
<!-- Pending Invitations -->
|
|
221
|
-
{#if pendingInvitations.length > 0}
|
|
222
|
-
<Card.Root class="border-dashed">
|
|
223
|
-
<Card.Header>
|
|
224
|
-
<div class="flex items-center gap-2">
|
|
225
|
-
<Card.Title>Pending Invites</Card.Title>
|
|
226
|
-
<Badge variant="secondary" class="text-xs">{pendingInvitations.length}</Badge>
|
|
227
|
-
</div>
|
|
228
|
-
<Card.Description>Invitations that haven't been accepted yet</Card.Description>
|
|
229
|
-
</Card.Header>
|
|
230
|
-
<Card.Content>
|
|
231
|
-
<div class="divide-y">
|
|
232
|
-
{#each pendingInvitations as invitation, i (invitation.id)}
|
|
233
|
-
<div
|
|
234
|
-
class="flex items-center justify-between gap-4 {i > 0 ? 'pt-3' : ''} {i <
|
|
235
|
-
pendingInvitations.length - 1
|
|
236
|
-
? 'pb-3'
|
|
237
|
-
: ''}"
|
|
238
|
-
>
|
|
239
|
-
<div class="flex items-center gap-3">
|
|
240
|
-
<div
|
|
241
|
-
class="border-muted-foreground/25 flex h-9 w-9 items-center justify-center rounded-full border border-dashed"
|
|
242
|
-
>
|
|
243
|
-
<Mail class="text-muted-foreground h-4 w-4" />
|
|
244
|
-
</div>
|
|
245
|
-
<div>
|
|
246
|
-
<p class="text-sm font-medium">{invitation.email}</p>
|
|
247
|
-
<p class="text-muted-foreground text-xs">
|
|
248
|
-
Invited as <span class="capitalize">{invitation.role}</span>
|
|
249
|
-
</p>
|
|
250
|
-
</div>
|
|
251
|
-
</div>
|
|
252
|
-
{#if canInvite}
|
|
253
|
-
<Button
|
|
254
|
-
variant="outline"
|
|
255
|
-
size="sm"
|
|
256
|
-
onclick={() => cancelInvitation(invitation.id, invitation.email)}
|
|
257
|
-
>
|
|
258
|
-
Revoke
|
|
259
|
-
</Button>
|
|
260
|
-
{/if}
|
|
261
|
-
</div>
|
|
262
|
-
{/each}
|
|
263
|
-
</div>
|
|
264
|
-
</Card.Content>
|
|
265
|
-
</Card.Root>
|
|
266
|
-
{/if}
|
|
267
|
-
|
|
268
|
-
<!-- Members -->
|
|
269
|
-
<Card.Root>
|
|
270
|
-
<Card.Header>
|
|
271
|
-
<div class="flex items-center gap-2">
|
|
272
|
-
<Card.Title>Members</Card.Title>
|
|
273
|
-
<Badge variant="secondary" class="text-xs">
|
|
274
|
-
{activeOrganization.members.length}
|
|
275
|
-
</Badge>
|
|
276
|
-
</div>
|
|
277
|
-
<Card.Description>People with access to this organization</Card.Description>
|
|
278
|
-
</Card.Header>
|
|
279
|
-
<Card.Content>
|
|
280
|
-
<div class="divide-y">
|
|
281
|
-
{#each activeOrganization.members as member, i (member.id)}
|
|
282
|
-
{@const isCurrentUser = member.userId === currentUserId}
|
|
283
|
-
{@const canRemove = canRemoveMembers && !isCurrentUser && member.role !== 'owner'}
|
|
284
|
-
|
|
285
|
-
<div
|
|
286
|
-
class="flex items-center justify-between gap-4 {i > 0 ? 'pt-3' : ''} {i <
|
|
287
|
-
activeOrganization.members.length - 1
|
|
288
|
-
? 'pb-3'
|
|
289
|
-
: ''}"
|
|
290
|
-
>
|
|
291
|
-
<div class="flex items-center gap-3">
|
|
292
|
-
<Avatar.Root class="h-9 w-9">
|
|
293
|
-
{#if member.user.image}
|
|
294
|
-
<Avatar.Image
|
|
295
|
-
src={member.user.image}
|
|
296
|
-
alt={member.user.name || member.user.email}
|
|
297
|
-
/>
|
|
298
|
-
{/if}
|
|
299
|
-
<Avatar.Fallback class="text-xs">
|
|
300
|
-
{getInitials(member.user.name, member.user.email)}
|
|
301
|
-
</Avatar.Fallback>
|
|
302
|
-
</Avatar.Root>
|
|
303
|
-
<div class="min-w-0">
|
|
304
|
-
<p class="truncate text-sm font-medium">
|
|
305
|
-
{member.user.name || member.user.email}
|
|
306
|
-
{#if isCurrentUser}
|
|
307
|
-
<span class="text-muted-foreground font-normal">(You)</span>
|
|
308
|
-
{/if}
|
|
309
|
-
</p>
|
|
310
|
-
{#if member.user.name}
|
|
311
|
-
<p class="text-muted-foreground truncate text-xs">
|
|
312
|
-
{member.user.email}
|
|
313
|
-
</p>
|
|
314
|
-
{/if}
|
|
315
|
-
</div>
|
|
316
|
-
</div>
|
|
317
|
-
<div class="flex items-center gap-2">
|
|
318
|
-
<Badge variant={getRoleBadgeVariant(member.role)} class="capitalize">
|
|
319
|
-
{member.role}
|
|
320
|
-
</Badge>
|
|
321
|
-
{#if canRemove}
|
|
322
|
-
<Button
|
|
323
|
-
variant="outline"
|
|
324
|
-
size="sm"
|
|
325
|
-
onclick={() =>
|
|
326
|
-
removeMember(member.userId, member.user.name || member.user.email)}
|
|
327
|
-
>
|
|
328
|
-
Remove
|
|
329
|
-
</Button>
|
|
330
|
-
{/if}
|
|
331
|
-
</div>
|
|
332
|
-
</div>
|
|
333
|
-
{/each}
|
|
334
|
-
</div>
|
|
335
|
-
</Card.Content>
|
|
336
|
-
</Card.Root>
|
|
337
|
-
{/if}
|
|
338
|
-
</div>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { PageServerLoad } from './$types';
|
|
2
|
-
import { error } from '@sveltejs/kit';
|
|
3
|
-
|
|
4
|
-
export const load: PageServerLoad = async ({ locals }) => {
|
|
5
|
-
const auth = locals.auth;
|
|
6
|
-
if (!auth || auth.type !== 'session') {
|
|
7
|
-
throw error(401, 'Not authenticated');
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const { rolesService } = locals.aphexCMS;
|
|
11
|
-
const roles = await rolesService.listRoles(auth.organizationId);
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
roles,
|
|
15
|
-
canManageRoles: auth.capabilities?.includes('role.manage') ?? false
|
|
16
|
-
};
|
|
17
|
-
};
|