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,74 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { Switch } from '@aphexcms/ui/shadcn/switch';
|
|
3
|
-
import { Label } from '@aphexcms/ui/shadcn/label';
|
|
4
|
-
import { instance } from '@aphexcms/cms-core/client';
|
|
5
|
-
import CreateOrganization from '../_components/CreateOrganization.svelte';
|
|
6
|
-
import OrganizationsList from '../../(protected)/admin/organizations/_components/OrganizationsList.svelte';
|
|
7
|
-
import type { PageData } from './$types';
|
|
8
|
-
|
|
9
|
-
let { data }: { data: PageData } = $props();
|
|
10
|
-
|
|
11
|
-
let allowUserOrgCreation = $state(data.instanceSettings?.allowUserOrgCreation ?? false);
|
|
12
|
-
let saving = $state(false);
|
|
13
|
-
|
|
14
|
-
async function toggleOrgCreation(checked: boolean) {
|
|
15
|
-
saving = true;
|
|
16
|
-
try {
|
|
17
|
-
const result = await instance.updateSettings({ allowUserOrgCreation: checked });
|
|
18
|
-
if (result.success && result.data) {
|
|
19
|
-
allowUserOrgCreation = result.data.allowUserOrgCreation ?? false;
|
|
20
|
-
}
|
|
21
|
-
} catch (error) {
|
|
22
|
-
console.error('Failed to update instance settings:', error);
|
|
23
|
-
allowUserOrgCreation = !checked;
|
|
24
|
-
} finally {
|
|
25
|
-
saving = false;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
</script>
|
|
29
|
-
|
|
30
|
-
<svelte:head>
|
|
31
|
-
<title>Aphex CMS - Organizations</title>
|
|
32
|
-
</svelte:head>
|
|
33
|
-
|
|
34
|
-
<div class="space-y-8">
|
|
35
|
-
<!-- Header -->
|
|
36
|
-
<div class="hidden sm:block">
|
|
37
|
-
<h2 class="text-xl font-semibold">Organizations on this instance</h2>
|
|
38
|
-
<p class="text-muted-foreground text-sm">
|
|
39
|
-
See all organizations and control who can create them.
|
|
40
|
-
</p>
|
|
41
|
-
</div>
|
|
42
|
-
|
|
43
|
-
<!-- Org List -->
|
|
44
|
-
<div>
|
|
45
|
-
<div class="mb-4 flex items-center justify-between">
|
|
46
|
-
<h3 class="text-lg font-medium">
|
|
47
|
-
In this instance
|
|
48
|
-
<span class="text-muted-foreground ml-1">· {data.organizations.length}</span>
|
|
49
|
-
</h3>
|
|
50
|
-
<CreateOrganization />
|
|
51
|
-
</div>
|
|
52
|
-
|
|
53
|
-
<OrganizationsList orgs={data.organizations} />
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<!-- Toggle -->
|
|
57
|
-
<div class="flex items-center justify-between rounded-lg border p-4">
|
|
58
|
-
<div class="space-y-0.5">
|
|
59
|
-
<Label for="prevent-org-creation" class="text-base font-medium"
|
|
60
|
-
>Prevent anyone else from creating an organization.</Label
|
|
61
|
-
>
|
|
62
|
-
<p class="text-muted-foreground text-sm">
|
|
63
|
-
Toggling this on will let only you create organizations. You will have to invite users to
|
|
64
|
-
new organizations.
|
|
65
|
-
</p>
|
|
66
|
-
</div>
|
|
67
|
-
<Switch
|
|
68
|
-
id="prevent-org-creation"
|
|
69
|
-
checked={!allowUserOrgCreation}
|
|
70
|
-
onCheckedChange={(checked) => toggleOrgCreation(!checked)}
|
|
71
|
-
disabled={saving}
|
|
72
|
-
/>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { redirect } from '@sveltejs/kit';
|
|
2
|
-
import type { PageServerLoad } from './$types';
|
|
3
|
-
|
|
4
|
-
export const load: PageServerLoad = async ({ locals }) => {
|
|
5
|
-
const auth = locals.auth;
|
|
6
|
-
|
|
7
|
-
// Require authentication — redirect to login if no session
|
|
8
|
-
// Both 'session' and 'partial_session' users can view invitations
|
|
9
|
-
if (!auth || auth.type === 'api_key') {
|
|
10
|
-
throw redirect(302, '/login');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const { databaseAdapter } = locals.aphexCMS;
|
|
14
|
-
|
|
15
|
-
// Fetch all pending invitations for this user's email
|
|
16
|
-
const allInvitations = await databaseAdapter.findInvitationsByEmail(auth.user.email);
|
|
17
|
-
|
|
18
|
-
const now = new Date();
|
|
19
|
-
const pending = allInvitations
|
|
20
|
-
.filter((inv) => inv.acceptedAt === null && new Date(inv.expiresAt) > now)
|
|
21
|
-
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
22
|
-
|
|
23
|
-
// Enrich with organization names
|
|
24
|
-
const enriched = await Promise.all(
|
|
25
|
-
pending.map(async (inv) => {
|
|
26
|
-
const org = await databaseAdapter.findOrganizationById(inv.organizationId);
|
|
27
|
-
return {
|
|
28
|
-
id: inv.id,
|
|
29
|
-
organizationId: inv.organizationId,
|
|
30
|
-
organizationName: org?.name ?? 'Unknown',
|
|
31
|
-
organizationSlug: org?.slug ?? '',
|
|
32
|
-
role: inv.role,
|
|
33
|
-
email: inv.email,
|
|
34
|
-
expiresAt: inv.expiresAt,
|
|
35
|
-
createdAt: inv.createdAt
|
|
36
|
-
};
|
|
37
|
-
})
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
pendingInvitations: enriched,
|
|
42
|
-
hasOrganization: auth.type === 'session',
|
|
43
|
-
user: {
|
|
44
|
-
email: auth.user.email,
|
|
45
|
-
name: auth.user.name
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
};
|
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { goto, invalidateAll } from '$app/navigation';
|
|
3
|
-
import { resolve } from '$app/paths';
|
|
4
|
-
import { Button } from '@aphexcms/ui/shadcn/button';
|
|
5
|
-
import { Badge } from '@aphexcms/ui/shadcn/badge';
|
|
6
|
-
import { invitations, organizations } from '@aphexcms/cms-core/client';
|
|
7
|
-
import { authClient } from '$lib/auth-client';
|
|
8
|
-
import type { PageData } from './$types';
|
|
9
|
-
|
|
10
|
-
let { data }: { data: PageData } = $props();
|
|
11
|
-
|
|
12
|
-
let pendingInvitations = $state(data.pendingInvitations);
|
|
13
|
-
let acceptingId = $state<string | null>(null);
|
|
14
|
-
let rejectingId = $state<string | null>(null);
|
|
15
|
-
let error = $state<string | null>(null);
|
|
16
|
-
|
|
17
|
-
async function handleAccept(invitation: (typeof pendingInvitations)[0]) {
|
|
18
|
-
acceptingId = invitation.id;
|
|
19
|
-
error = null;
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
const result = await invitations.accept(invitation.id);
|
|
23
|
-
|
|
24
|
-
if (!result.success) {
|
|
25
|
-
throw new Error(result.error || 'Failed to accept invitation');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Switch to the newly joined org
|
|
29
|
-
await organizations.switch({ organizationId: invitation.organizationId });
|
|
30
|
-
await invalidateAll();
|
|
31
|
-
goto('/admin');
|
|
32
|
-
} catch (err) {
|
|
33
|
-
error = err instanceof Error ? err.message : 'Failed to accept invitation';
|
|
34
|
-
acceptingId = null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function handleReject(invitation: (typeof pendingInvitations)[0]) {
|
|
39
|
-
rejectingId = invitation.id;
|
|
40
|
-
error = null;
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
const result = await invitations.reject(invitation.id);
|
|
44
|
-
|
|
45
|
-
if (!result.success) {
|
|
46
|
-
throw new Error(result.error || 'Failed to decline invitation');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Remove from local state
|
|
50
|
-
pendingInvitations = pendingInvitations.filter((inv) => inv.id !== invitation.id);
|
|
51
|
-
} catch (err) {
|
|
52
|
-
error = err instanceof Error ? err.message : 'Failed to decline invitation';
|
|
53
|
-
} finally {
|
|
54
|
-
rejectingId = null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function timeUntilExpiry(date: Date | string) {
|
|
59
|
-
const diff = new Date(date).getTime() - Date.now();
|
|
60
|
-
const days = Math.ceil(diff / (1000 * 60 * 60 * 24));
|
|
61
|
-
if (days <= 0) return 'Expired';
|
|
62
|
-
if (days === 1) return '1 day left';
|
|
63
|
-
return `${days} days left`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function handleSignOut() {
|
|
67
|
-
await authClient.signOut();
|
|
68
|
-
goto(resolve('/login'));
|
|
69
|
-
}
|
|
70
|
-
</script>
|
|
71
|
-
|
|
72
|
-
<svelte:head>
|
|
73
|
-
<title>Aphex CMS - Invitations</title>
|
|
74
|
-
</svelte:head>
|
|
75
|
-
|
|
76
|
-
<div class="flex min-h-screen flex-col">
|
|
77
|
-
<!-- Top bar -->
|
|
78
|
-
<header class="flex items-center justify-between px-6 py-4">
|
|
79
|
-
<svg
|
|
80
|
-
class="h-8 w-8 fill-black dark:fill-white"
|
|
81
|
-
version="1.1"
|
|
82
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
83
|
-
viewBox="0 0 512 512"
|
|
84
|
-
>
|
|
85
|
-
<path
|
|
86
|
-
d="M0 0 C17.1474496 14.16905616 25.92496627 35.45820085 34.56640625 55.4140625 C35.34334316 57.19720923 36.12053079 58.98024674 36.89794922 60.76318359 C51.78722595 94.97195072 66.32335778 129.32882671 80.31640625 163.9140625 C80.6444928 164.72450012 80.97257935 165.53493774 81.31060791 166.36993408 C99.98913061 212.53216334 117.78569191 259.01293728 134.00390625 306.1015625 C134.36190811 307.13669983 134.36190811 307.13669983 134.72714233 308.19274902 C137.32766526 315.72906515 139.80262285 323.3004011 142.1171875 330.9296875 C142.43800545 331.95940926 142.43800545 331.95940926 142.76530457 333.00993347 C145.2565569 341.39135358 146.09964488 351.05219488 142.44140625 359.23046875 C139.92789443 362.992009 136.34564423 365.89944351 132.31640625 367.9140625 C124.8558052 368.61421121 118.36839373 368.62364314 112.31640625 363.9140625 C105.53664487 356.69699976 100.652558 348.68983785 95.94140625 340.0390625 C90.47619491 330.29913602 84.93335438 322.05287151 77.31640625 313.9140625 C75.27331056 311.57880549 73.26394666 309.2152832 71.25390625 306.8515625 C70.24519648 305.67152884 69.23608749 304.49183631 68.2265625 303.3125 C67.73816895 302.74112305 67.24977539 302.16974609 66.74658203 301.58105469 C65.40017275 300.01169952 64.04454028 298.45078423 62.6875 296.890625 C48.81582552 280.69615056 38.44623629 261.89870442 27.89111328 243.45849609 C-8.42360388 178.69858879 -8.42360388 178.69858879 -68.08203125 137.78515625 C-79.11186673 135.11741947 -89.899608 136.09230489 -99.68359375 141.9140625 C-115.46806791 152.47947666 -122.3076261 172.13261849 -126.43359375 189.7890625 C-126.73185059 191.06136719 -126.73185059 191.06136719 -127.03613281 192.359375 C-130.16760015 206.71148601 -131.06828893 221.08194412 -130.99609375 235.7265625 C-130.9939386 236.51966827 -130.99178345 237.31277405 -130.98956299 238.12991333 C-131.31394288 266.6844263 -131.31394288 266.6844263 -123.23828125 293.5859375 C-112.95312656 315.60340432 -111.26097095 345.34400812 -119.5 368.0390625 C-124.86779615 381.07766965 -132.78250397 390.79435644 -145.68359375 396.9140625 C-155.27731384 400.41943083 -164.20346687 401.56551213 -174.37109375 401.4140625 C-176.32958252 401.39702271 -176.32958252 401.39702271 -178.32763672 401.37963867 C-197.70054192 400.99323331 -215.16231823 397.0095896 -230.68359375 384.9140625 C-231.37066406 384.41519531 -232.05773437 383.91632813 -232.765625 383.40234375 C-244.80767188 373.97296976 -251.15090869 358.63090674 -253.68359375 343.9140625 C-253.9270261 339.66653687 -253.91464574 335.41789154 -253.93359375 331.1640625 C-253.96453125 329.45089844 -253.96453125 329.45089844 -253.99609375 327.703125 C-254.00125 326.58164062 -254.00640625 325.46015625 -254.01171875 324.3046875 C-254.02106445 323.29438477 -254.03041016 322.28408203 -254.04003906 321.24316406 C-253.25260131 313.88871121 -250.02140677 307.08532381 -247.0859375 300.359375 C-246.73986954 299.55749252 -246.39380157 298.75561005 -246.0372467 297.9294281 C-245.29136983 296.20150738 -244.54396437 294.47424582 -243.79516602 292.74758911 C-241.75797546 288.04789133 -239.73451175 283.34229198 -237.7109375 278.63671875 C-237.0834108 277.17808662 -237.0834108 277.17808662 -236.44320679 275.68998718 C-232.12759637 265.64489937 -227.89599701 255.56528394 -223.68359375 245.4765625 C-223.31303864 244.58933502 -222.94248352 243.70210754 -222.56069946 242.78799438 C-221.05624911 239.1852842 -219.55230259 235.58236459 -218.04930115 231.97904968 C-215.45620988 225.7624217 -212.85713855 219.54832537 -210.25390625 213.3359375 C-199.87599396 188.56875674 -189.53860671 163.78483086 -179.21582031 138.99462891 C-176.70326253 132.96093438 -174.18966694 126.92767262 -171.67578125 120.89453125 C-171.2632309 119.90436005 -170.85068054 118.91418884 -170.42562866 117.89401245 C-168.35461551 112.92351005 -166.28304289 107.95324201 -164.21044922 102.98339844 C-160.33953189 93.69973915 -156.47385511 84.41400336 -152.62658691 75.12051392 C-150.82992691 70.78139762 -149.03030349 66.44351312 -147.23046875 62.10571289 C-146.38199036 60.05847127 -145.53492768 58.01064218 -144.68945312 55.9621582 C-140.72547669 46.35880091 -136.72273166 36.78723512 -132.3203125 27.375 C-132.00310242 26.69433975 -131.68589233 26.0136795 -131.35906982 25.31239319 C-123.25124472 8.26154126 -109.39227096 -6.16254505 -91.68359375 -13.0859375 C-59.99340229 -22.6620319 -26.67593546 -20.59334511 0 0 Z "
|
|
87
|
-
transform="translate(308.68359375,64.0859375)"
|
|
88
|
-
/>
|
|
89
|
-
<path
|
|
90
|
-
d="M0 0 C18.24717952 9.79342293 31.4582424 26.28742033 37.6171875 45.97265625 C39.90862161 58.10790422 38.62948747 76.77026254 32.3125 87.5625 C31.23887442 89.06398132 30.13305826 90.54284625 29 92 C28.04867187 93.39798828 28.04867187 93.39798828 27.078125 94.82421875 C22.05477616 102.1158627 16.59613089 108.96607145 8 112 C-4.55244493 113.74100692 -12.66496084 107.03937869 -22.140625 99.87890625 C-25.14482573 97.61370274 -28.2068393 95.46853473 -31.33056641 93.37231445 C-44.35072813 84.61921656 -53.55106508 73.69367426 -57 58 C-59.27762162 41.63399726 -53.36243485 19.43955677 -44.1875 5.9375 C-32.8352893 -8.66127096 -15.43371672 -7.37348527 0 0 Z "
|
|
91
|
-
transform="translate(254,240)"
|
|
92
|
-
/>
|
|
93
|
-
</svg>
|
|
94
|
-
<button
|
|
95
|
-
class="text-muted-foreground hover:text-foreground text-sm transition-colors"
|
|
96
|
-
onclick={handleSignOut}
|
|
97
|
-
>
|
|
98
|
-
{data.user.email}
|
|
99
|
-
</button>
|
|
100
|
-
</header>
|
|
101
|
-
|
|
102
|
-
<!-- Content -->
|
|
103
|
-
<main class="flex flex-1 flex-col items-center justify-center px-4 pb-24">
|
|
104
|
-
{#if error}
|
|
105
|
-
<div
|
|
106
|
-
class="bg-destructive/10 text-destructive border-destructive/20 mb-6 w-full max-w-md rounded-lg border p-4"
|
|
107
|
-
>
|
|
108
|
-
<p class="text-sm">{error}</p>
|
|
109
|
-
</div>
|
|
110
|
-
{/if}
|
|
111
|
-
|
|
112
|
-
{#if pendingInvitations.length === 0}
|
|
113
|
-
<!-- Empty state with envelope illustration -->
|
|
114
|
-
<div class="flex flex-col items-center text-center">
|
|
115
|
-
<svg
|
|
116
|
-
class="mb-6 h-32 w-32"
|
|
117
|
-
viewBox="0 0 200 200"
|
|
118
|
-
fill="none"
|
|
119
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
120
|
-
>
|
|
121
|
-
<!-- Envelope body -->
|
|
122
|
-
<rect
|
|
123
|
-
x="40"
|
|
124
|
-
y="70"
|
|
125
|
-
width="120"
|
|
126
|
-
height="80"
|
|
127
|
-
rx="4"
|
|
128
|
-
fill="currentColor"
|
|
129
|
-
class="text-muted/30"
|
|
130
|
-
/>
|
|
131
|
-
<!-- Envelope flap (open) -->
|
|
132
|
-
<path d="M40 70 L100 30 L160 70" fill="currentColor" class="text-muted/20" />
|
|
133
|
-
<path
|
|
134
|
-
d="M40 70 L100 30 L160 70"
|
|
135
|
-
stroke="currentColor"
|
|
136
|
-
stroke-width="1.5"
|
|
137
|
-
class="text-muted/40"
|
|
138
|
-
fill="none"
|
|
139
|
-
/>
|
|
140
|
-
<!-- Letter paper -->
|
|
141
|
-
<rect
|
|
142
|
-
x="55"
|
|
143
|
-
y="45"
|
|
144
|
-
width="90"
|
|
145
|
-
height="65"
|
|
146
|
-
rx="2"
|
|
147
|
-
fill="currentColor"
|
|
148
|
-
class="text-muted/50"
|
|
149
|
-
/>
|
|
150
|
-
<!-- Letter lines -->
|
|
151
|
-
<rect x="65" y="58" width="40" height="3" rx="1.5" fill="hsl(var(--primary))" />
|
|
152
|
-
<line
|
|
153
|
-
x1="65"
|
|
154
|
-
y1="70"
|
|
155
|
-
x2="130"
|
|
156
|
-
y2="70"
|
|
157
|
-
stroke="currentColor"
|
|
158
|
-
stroke-width="2"
|
|
159
|
-
class="text-muted/30"
|
|
160
|
-
/>
|
|
161
|
-
<line
|
|
162
|
-
x1="65"
|
|
163
|
-
y1="78"
|
|
164
|
-
x2="125"
|
|
165
|
-
y2="78"
|
|
166
|
-
stroke="currentColor"
|
|
167
|
-
stroke-width="2"
|
|
168
|
-
class="text-muted/30"
|
|
169
|
-
/>
|
|
170
|
-
<line
|
|
171
|
-
x1="65"
|
|
172
|
-
y1="86"
|
|
173
|
-
x2="115"
|
|
174
|
-
y2="86"
|
|
175
|
-
stroke="currentColor"
|
|
176
|
-
stroke-width="2"
|
|
177
|
-
class="text-muted/30"
|
|
178
|
-
/>
|
|
179
|
-
<rect x="65" y="96" width="30" height="3" rx="1.5" fill="hsl(var(--primary))" />
|
|
180
|
-
<!-- Cursor decorations -->
|
|
181
|
-
<path
|
|
182
|
-
d="M28 80 L22 86 L24 87 L22 93 L30 85 L27 84 Z"
|
|
183
|
-
fill="currentColor"
|
|
184
|
-
class="text-muted/40"
|
|
185
|
-
/>
|
|
186
|
-
<path
|
|
187
|
-
d="M172 68 L166 74 L168 75 L166 81 L174 73 L171 72 Z"
|
|
188
|
-
fill="currentColor"
|
|
189
|
-
class="text-muted/40"
|
|
190
|
-
/>
|
|
191
|
-
<path
|
|
192
|
-
d="M85 24 L79 30 L81 31 L79 37 L87 29 L84 28 Z"
|
|
193
|
-
fill="currentColor"
|
|
194
|
-
class="text-muted/30"
|
|
195
|
-
/>
|
|
196
|
-
<path
|
|
197
|
-
d="M38 120 L32 126 L34 127 L32 133 L40 125 L37 124 Z"
|
|
198
|
-
fill="currentColor"
|
|
199
|
-
class="text-muted/25"
|
|
200
|
-
/>
|
|
201
|
-
</svg>
|
|
202
|
-
|
|
203
|
-
<h2 class="text-lg font-semibold">No pending invites</h2>
|
|
204
|
-
<p class="text-muted-foreground mt-2 text-sm">
|
|
205
|
-
You can see here if someone invites you to a workspace
|
|
206
|
-
</p>
|
|
207
|
-
|
|
208
|
-
{#if data.hasOrganization}
|
|
209
|
-
<Button class="mt-6" onclick={() => goto('/admin')}>Back to home</Button>
|
|
210
|
-
{:else}
|
|
211
|
-
<Button class="mt-6" onclick={handleSignOut}>Sign out</Button>
|
|
212
|
-
{/if}
|
|
213
|
-
</div>
|
|
214
|
-
{:else}
|
|
215
|
-
<!-- Invitation cards -->
|
|
216
|
-
<div class="w-full max-w-xl space-y-4">
|
|
217
|
-
<div class="mb-6 text-center">
|
|
218
|
-
<h2 class="text-lg font-semibold">Pending Invitations</h2>
|
|
219
|
-
<p class="text-muted-foreground mt-1 text-sm">
|
|
220
|
-
You've been invited to join the following organizations
|
|
221
|
-
</p>
|
|
222
|
-
</div>
|
|
223
|
-
|
|
224
|
-
{#each pendingInvitations as invitation (invitation.id)}
|
|
225
|
-
{@const isAccepting = acceptingId === invitation.id}
|
|
226
|
-
{@const isRejecting = rejectingId === invitation.id}
|
|
227
|
-
{@const isBusy = acceptingId !== null || rejectingId !== null}
|
|
228
|
-
|
|
229
|
-
<div class="bg-card flex items-center justify-between gap-4 rounded-lg border p-5">
|
|
230
|
-
<div class="min-w-0 flex-1">
|
|
231
|
-
<div class="flex items-center gap-2">
|
|
232
|
-
<p class="truncate font-medium">{invitation.organizationName}</p>
|
|
233
|
-
<Badge variant="outline" class="capitalize">{invitation.role}</Badge>
|
|
234
|
-
</div>
|
|
235
|
-
<div class="text-muted-foreground mt-1 flex gap-3 text-xs">
|
|
236
|
-
<span>/{invitation.organizationSlug}</span>
|
|
237
|
-
<span>{timeUntilExpiry(invitation.expiresAt)}</span>
|
|
238
|
-
</div>
|
|
239
|
-
</div>
|
|
240
|
-
<div class="flex shrink-0 gap-2">
|
|
241
|
-
<Button
|
|
242
|
-
variant="outline"
|
|
243
|
-
size="sm"
|
|
244
|
-
onclick={() => handleReject(invitation)}
|
|
245
|
-
disabled={isBusy}
|
|
246
|
-
>
|
|
247
|
-
{isRejecting ? 'Declining...' : 'Decline'}
|
|
248
|
-
</Button>
|
|
249
|
-
<Button size="sm" onclick={() => handleAccept(invitation)} disabled={isBusy}>
|
|
250
|
-
{isAccepting ? 'Joining...' : 'Accept'}
|
|
251
|
-
</Button>
|
|
252
|
-
</div>
|
|
253
|
-
</div>
|
|
254
|
-
{/each}
|
|
255
|
-
|
|
256
|
-
{#if data.hasOrganization}
|
|
257
|
-
<div class="pt-2 text-center">
|
|
258
|
-
<Button variant="ghost" onclick={() => goto('/admin')}>Back to home</Button>
|
|
259
|
-
</div>
|
|
260
|
-
{/if}
|
|
261
|
-
</div>
|
|
262
|
-
{/if}
|
|
263
|
-
</main>
|
|
264
|
-
</div>
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { redirect } from '@sveltejs/kit';
|
|
2
|
-
import type { PageServerLoad } from './$types';
|
|
3
|
-
|
|
4
|
-
export const load: PageServerLoad = async ({ params, locals }) => {
|
|
5
|
-
const { databaseAdapter } = locals.aphexCMS;
|
|
6
|
-
const auth = locals.auth;
|
|
7
|
-
const { token } = params;
|
|
8
|
-
|
|
9
|
-
// Find the invitation
|
|
10
|
-
const invitation = await databaseAdapter.findInvitationByToken(token);
|
|
11
|
-
|
|
12
|
-
if (!invitation) {
|
|
13
|
-
return { error: 'invalid', invitation: null, organization: null };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (invitation.acceptedAt) {
|
|
17
|
-
return { error: 'already_accepted', invitation: null, organization: null };
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (new Date(invitation.expiresAt) < new Date()) {
|
|
21
|
-
return { error: 'expired', invitation: null, organization: null };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Get organization details for display
|
|
25
|
-
const org = await databaseAdapter.findOrganizationById(invitation.organizationId);
|
|
26
|
-
|
|
27
|
-
// If user is logged in (auth.type === 'session', org may or may not exist)
|
|
28
|
-
if (auth && auth.type === 'session') {
|
|
29
|
-
// Verify the invitation email matches the logged-in user
|
|
30
|
-
if (auth.user.email.toLowerCase() !== invitation.email.toLowerCase()) {
|
|
31
|
-
return {
|
|
32
|
-
error: 'email_mismatch',
|
|
33
|
-
invitation: {
|
|
34
|
-
email: invitation.email,
|
|
35
|
-
role: invitation.role
|
|
36
|
-
},
|
|
37
|
-
organization: org ? { name: org.name, slug: org.slug } : null
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Accept the invitation
|
|
42
|
-
await databaseAdapter.acceptInvitation(token, auth.user.id);
|
|
43
|
-
|
|
44
|
-
// Switch to the new org
|
|
45
|
-
await databaseAdapter.updateUserSession(auth.user.id, invitation.organizationId);
|
|
46
|
-
|
|
47
|
-
throw redirect(302, '/admin');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Not logged in — show invitation details with sign-in link
|
|
51
|
-
return {
|
|
52
|
-
error: null,
|
|
53
|
-
invitation: {
|
|
54
|
-
email: invitation.email,
|
|
55
|
-
role: invitation.role
|
|
56
|
-
},
|
|
57
|
-
organization: org ? { name: org.name, slug: org.slug } : null
|
|
58
|
-
};
|
|
59
|
-
};
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { page } from '$app/state';
|
|
3
|
-
import { goto } from '$app/navigation';
|
|
4
|
-
import { Button } from '@aphexcms/ui/shadcn/button';
|
|
5
|
-
import { Badge } from '@aphexcms/ui/shadcn/badge';
|
|
6
|
-
import * as Card from '@aphexcms/ui/shadcn/card';
|
|
7
|
-
import type { PageData } from './$types';
|
|
8
|
-
|
|
9
|
-
let { data }: { data: PageData } = $props();
|
|
10
|
-
|
|
11
|
-
const token = $derived(page.params.token);
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<svelte:head>
|
|
15
|
-
<title>Aphex CMS - Invitation</title>
|
|
16
|
-
</svelte:head>
|
|
17
|
-
|
|
18
|
-
<div class="bg-muted/40 flex min-h-screen items-center justify-center px-4 py-12">
|
|
19
|
-
<div class="w-full max-w-md">
|
|
20
|
-
<Card.Root class="shadow-lg">
|
|
21
|
-
{#if data.error === 'invalid'}
|
|
22
|
-
<Card.Header class="space-y-1">
|
|
23
|
-
<Card.Title class="text-center text-2xl font-bold">Invalid Invitation</Card.Title>
|
|
24
|
-
</Card.Header>
|
|
25
|
-
<Card.Content>
|
|
26
|
-
<p class="text-muted-foreground text-center">
|
|
27
|
-
This invitation link is invalid or has been revoked.
|
|
28
|
-
</p>
|
|
29
|
-
<Button class="mt-6 w-full" onclick={() => goto('/login')}>Go to Sign In</Button>
|
|
30
|
-
</Card.Content>
|
|
31
|
-
{:else if data.error === 'expired'}
|
|
32
|
-
<Card.Header class="space-y-1">
|
|
33
|
-
<Card.Title class="text-center text-2xl font-bold">Invitation Expired</Card.Title>
|
|
34
|
-
</Card.Header>
|
|
35
|
-
<Card.Content>
|
|
36
|
-
<p class="text-muted-foreground text-center">
|
|
37
|
-
This invitation has expired. Please ask the organization admin to send a new one.
|
|
38
|
-
</p>
|
|
39
|
-
<Button class="mt-6 w-full" onclick={() => goto('/login')}>Go to Sign In</Button>
|
|
40
|
-
</Card.Content>
|
|
41
|
-
{:else if data.error === 'already_accepted'}
|
|
42
|
-
<Card.Header class="space-y-1">
|
|
43
|
-
<Card.Title class="text-center text-2xl font-bold">Already Accepted</Card.Title>
|
|
44
|
-
</Card.Header>
|
|
45
|
-
<Card.Content>
|
|
46
|
-
<p class="text-muted-foreground text-center">
|
|
47
|
-
This invitation has already been accepted.
|
|
48
|
-
</p>
|
|
49
|
-
<Button class="mt-6 w-full" onclick={() => goto('/admin')}>Go to Dashboard</Button>
|
|
50
|
-
</Card.Content>
|
|
51
|
-
{:else if data.error === 'email_mismatch'}
|
|
52
|
-
<Card.Header class="space-y-1">
|
|
53
|
-
<Card.Title class="text-center text-2xl font-bold">Wrong Account</Card.Title>
|
|
54
|
-
</Card.Header>
|
|
55
|
-
<Card.Content>
|
|
56
|
-
<p class="text-muted-foreground text-center">
|
|
57
|
-
This invitation was sent to <strong>{data.invitation?.email}</strong>. Please sign in
|
|
58
|
-
with that email address to accept it.
|
|
59
|
-
</p>
|
|
60
|
-
<Button class="mt-6 w-full" onclick={() => goto(`/login?callbackUrl=/invite/${token}`)}>
|
|
61
|
-
Sign In with Different Account
|
|
62
|
-
</Button>
|
|
63
|
-
</Card.Content>
|
|
64
|
-
{:else}
|
|
65
|
-
<Card.Header class="space-y-1">
|
|
66
|
-
<Card.Title class="text-center text-2xl font-bold">You're Invited</Card.Title>
|
|
67
|
-
<Card.Description class="text-center">Sign in to accept this invitation</Card.Description>
|
|
68
|
-
</Card.Header>
|
|
69
|
-
<Card.Content>
|
|
70
|
-
<div class="space-y-4">
|
|
71
|
-
<div class="bg-muted/50 rounded-lg p-4 text-center">
|
|
72
|
-
{#if data.organization}
|
|
73
|
-
<p class="text-lg font-semibold">{data.organization.name}</p>
|
|
74
|
-
<p class="text-muted-foreground text-sm">/{data.organization.slug}</p>
|
|
75
|
-
{/if}
|
|
76
|
-
{#if data.invitation}
|
|
77
|
-
<Badge variant="outline" class="mt-2 capitalize">{data.invitation.role}</Badge>
|
|
78
|
-
{/if}
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
<p class="text-muted-foreground text-center text-sm">
|
|
82
|
-
Create an account with <strong>{data.invitation?.email}</strong> to join this organization.
|
|
83
|
-
</p>
|
|
84
|
-
|
|
85
|
-
<Button
|
|
86
|
-
class="w-full"
|
|
87
|
-
onclick={() =>
|
|
88
|
-
goto(
|
|
89
|
-
`/login?mode=signup&email=${encodeURIComponent(data.invitation?.email ?? '')}&callbackUrl=/invite/${token}`
|
|
90
|
-
)}
|
|
91
|
-
>
|
|
92
|
-
Create Account to Accept
|
|
93
|
-
</Button>
|
|
94
|
-
|
|
95
|
-
<Button
|
|
96
|
-
variant="outline"
|
|
97
|
-
class="w-full"
|
|
98
|
-
onclick={() => goto(`/login?callbackUrl=/invite/${token}`)}
|
|
99
|
-
>
|
|
100
|
-
Already have an account? Sign In
|
|
101
|
-
</Button>
|
|
102
|
-
</div>
|
|
103
|
-
</Card.Content>
|
|
104
|
-
{/if}
|
|
105
|
-
</Card.Root>
|
|
106
|
-
|
|
107
|
-
<p class="text-muted-foreground mt-6 text-center text-xs">Aphex CMS - Built with SvelteKit</p>
|
|
108
|
-
</div>
|
|
109
|
-
</div>
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { redirect } from '@sveltejs/kit';
|
|
2
|
-
import type { PageServerLoad } from '../(protected)/admin/$types';
|
|
3
|
-
|
|
4
|
-
export const load: PageServerLoad = async ({ locals, request }) => {
|
|
5
|
-
const { aphexCMS } = locals;
|
|
6
|
-
|
|
7
|
-
const session = await aphexCMS.auth?.getSession(request, aphexCMS.databaseAdapter);
|
|
8
|
-
// If user is already authenticated, redirect to admin
|
|
9
|
-
if (session?.session) {
|
|
10
|
-
throw redirect(302, '/admin');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return {};
|
|
14
|
-
};
|