start roles
This commit is contained in:
parent
ac7c4f77fb
commit
0a8a9236b1
13 changed files with 1809 additions and 191 deletions
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { OverviewServer } from '$lib';
|
||||
import { PERMISSION_LABELS, type OverviewServer, type Permission } from '$lib';
|
||||
import { enhance } from '$app/forms';
|
||||
import * as Tabs from '$lib/components/ui/tabs/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
|
|
@ -15,81 +15,10 @@
|
|||
|
||||
let inviteCode = $state();
|
||||
|
||||
type FakePermission =
|
||||
| 'changeChannelName'
|
||||
| 'changeServerName'
|
||||
| 'createInvite'
|
||||
| 'createChannels'
|
||||
| 'deleteChannels'
|
||||
| 'deleteInvites'
|
||||
| 'addRoles'
|
||||
| 'deleteRoles'
|
||||
| 'kickPeople'
|
||||
| 'banPeople';
|
||||
|
||||
type FakeRole = {
|
||||
id: string;
|
||||
name: string;
|
||||
permissions: Record<FakePermission, boolean>;
|
||||
};
|
||||
|
||||
let newRoleName = $state('');
|
||||
|
||||
let fakeRoles = $state<FakeRole[]>([
|
||||
{
|
||||
id: 'admin',
|
||||
name: 'Admin',
|
||||
permissions: {
|
||||
changeChannelName: true,
|
||||
changeServerName: true,
|
||||
createInvite: true,
|
||||
createChannels: true,
|
||||
deleteChannels: true,
|
||||
deleteInvites: true,
|
||||
addRoles: true,
|
||||
deleteRoles: true,
|
||||
kickPeople: true,
|
||||
banPeople: true
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'moderator',
|
||||
name: 'Moderator',
|
||||
permissions: {
|
||||
changeChannelName: true,
|
||||
changeServerName: false,
|
||||
createInvite: true,
|
||||
createChannels: false,
|
||||
deleteChannels: true,
|
||||
deleteInvites: true,
|
||||
addRoles: true,
|
||||
deleteRoles: true,
|
||||
kickPeople: true,
|
||||
banPeople: false
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'member',
|
||||
name: 'Member',
|
||||
permissions: {
|
||||
changeChannelName: false,
|
||||
changeServerName: false,
|
||||
createInvite: true,
|
||||
createChannels: false,
|
||||
deleteChannels: false,
|
||||
deleteInvites: false,
|
||||
addRoles: false,
|
||||
deleteRoles: false,
|
||||
kickPeople: false,
|
||||
banPeople: false
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
let selectedRoleId = $state<string | null>(null);
|
||||
|
||||
let selectedRole = $derived(fakeRoles.find((r) => r.id === selectedRoleId));
|
||||
|
||||
const {
|
||||
server,
|
||||
psd,
|
||||
|
|
@ -99,6 +28,8 @@
|
|||
psd: PageServerData;
|
||||
currentPage: string;
|
||||
} = $props();
|
||||
|
||||
let selectedRole = $derived(server.roles.find((r) => r.id === selectedRoleId));
|
||||
</script>
|
||||
|
||||
<Tabs.Root value="roles" class="max-w-200 p-2">
|
||||
|
|
@ -140,104 +71,148 @@
|
|||
</Tabs.Content>
|
||||
<Tabs.Content value="roles" class="space-y-6 p-4">
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-medium">Roles (fake)</h2>
|
||||
<div class="flex gap-2">
|
||||
<Input placeholder="New role name" class="h-8 w-40" bind:value={newRoleName} />
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onclick={() => {
|
||||
if (!newRoleName) return;
|
||||
const newRole: FakeRole = {
|
||||
id: crypto.randomUUID(),
|
||||
name: newRoleName,
|
||||
permissions: {
|
||||
changeChannelName: false,
|
||||
changeServerName: false,
|
||||
createInvite: false,
|
||||
createChannels: false,
|
||||
deleteChannels: false,
|
||||
deleteInvites: false,
|
||||
addRoles: false,
|
||||
deleteRoles: false,
|
||||
kickPeople: false,
|
||||
banPeople: false
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<h2 class="text-lg font-medium">Roles</h2>
|
||||
<form
|
||||
method="POST"
|
||||
action="?/createRole"
|
||||
use:enhance={() => {
|
||||
return async ({ result }) => {
|
||||
if (result.type === 'success') {
|
||||
toast.success('Role created successfully');
|
||||
newRoleName = '';
|
||||
await invalidateAll();
|
||||
await fill_overview_data(psd);
|
||||
} else if (result.type == 'error' || result.type == 'failure') {
|
||||
toast.error(
|
||||
'Failed to create role: ' +
|
||||
(result.type === 'error' ? result.error.message : result.data?.error)
|
||||
);
|
||||
}
|
||||
};
|
||||
fakeRoles = [...fakeRoles, newRole];
|
||||
selectedRoleId = newRole.id;
|
||||
newRoleName = '';
|
||||
}}
|
||||
class="flex gap-2"
|
||||
>
|
||||
Create Role
|
||||
</Button>
|
||||
<input type="hidden" name="serverId" value={currentPage} />
|
||||
<Input
|
||||
placeholder="New role name"
|
||||
name="name"
|
||||
class="h-8 w-40"
|
||||
bind:value={newRoleName}
|
||||
/>
|
||||
<Button size="sm" variant="outline" type="submit" disabled={!newRoleName}>
|
||||
Create Role
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each fakeRoles as role (role.id)}
|
||||
<Button
|
||||
type="button"
|
||||
variant={selectedRoleId === role.id ? 'secondary' : 'ghost'}
|
||||
size="sm"
|
||||
class="gap-1"
|
||||
onclick={() => (selectedRoleId = role.id)}
|
||||
>
|
||||
{role.name}
|
||||
{#if role.id !== 'member'}
|
||||
<button
|
||||
type="button"
|
||||
class="text-destructive hover:text-destructive/80"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
fakeRoles = fakeRoles.filter((r) => r.id !== role.id);
|
||||
if (selectedRoleId === role.id) selectedRoleId = null;
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each server.roles as role (role.id)}
|
||||
<Button
|
||||
type="button"
|
||||
variant={selectedRoleId === role.id ? 'secondary' : 'ghost'}
|
||||
size="sm"
|
||||
class="gap-1"
|
||||
onclick={() => (selectedRoleId = role.id)}
|
||||
>
|
||||
{role.name}
|
||||
{#if role.canBeDeleted}
|
||||
<form
|
||||
method="POST"
|
||||
action="?/deleteRole"
|
||||
use:enhance={() => {
|
||||
return async ({ result }) => {
|
||||
if (result.type === 'success') {
|
||||
toast.success('Role deleted successfully');
|
||||
if (selectedRoleId === role.id) selectedRoleId = null;
|
||||
await invalidateAll();
|
||||
await fill_overview_data(psd);
|
||||
} else if (result.type == 'error' || result.type == 'failure') {
|
||||
toast.error(
|
||||
'Failed to delete role: ' +
|
||||
(result.type === 'error' ? result.error.message : result.data?.error)
|
||||
);
|
||||
}
|
||||
};
|
||||
}}
|
||||
class="m-0"
|
||||
>
|
||||
<input type="hidden" name="serverId" value={currentPage} />
|
||||
<input type="hidden" name="roleId" value={role.id} />
|
||||
<button type="submit" class="text-destructive hover:text-destructive/80">
|
||||
×
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if selectedRoleId}
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>{selectedRole?.name} permissions</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class="space-y-4">
|
||||
<form
|
||||
method="POST"
|
||||
action="?/changeRole"
|
||||
use:enhance={() => {
|
||||
return async ({ result }) => {
|
||||
if (result.type === 'success') {
|
||||
toast.success('Role updated successfully');
|
||||
await invalidateAll();
|
||||
await fill_overview_data(psd);
|
||||
} else if (result.type == 'error' || result.type == 'failure') {
|
||||
toast.error(
|
||||
'Failed to change role: ' +
|
||||
(result.type === 'error' ? result.error.message : result.data?.error)
|
||||
);
|
||||
}
|
||||
};
|
||||
}}
|
||||
class="space-y-4"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
{/if}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if selectedRole}
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>{selectedRole.name} permissions</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content class="space-y-4">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
{#each Object.entries(selectedRole.permissions) as [perm, enabled] (perm)}
|
||||
<div class="flex items-center justify-between">
|
||||
<Label>
|
||||
{#if perm === 'changeChannelName'}Change channel name{/if}
|
||||
{#if perm === 'changeServerName'}Change server name{/if}
|
||||
{#if perm === 'createInvite'}Create invite{/if}
|
||||
{#if perm === 'createChannels'}Create channels{/if}
|
||||
{#if perm === 'deleteChannels'}Delete channels{/if}
|
||||
{#if perm === 'deleteInvites'}Delete invites{/if}
|
||||
{#if perm === 'addRoles'}Add roles{/if}
|
||||
{#if perm === 'deleteRoles'}Delete roles{/if}
|
||||
{#if perm === 'kickPeople'}Kick members{/if}
|
||||
{#if perm === 'banPeople'}Ban members{/if}
|
||||
</Label>
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onchange={(e) => {
|
||||
selectedRole.permissions[perm as FakePermission] = e.detail;
|
||||
}}
|
||||
/>
|
||||
<input type="hidden" name="serverId" value={currentPage} />
|
||||
<input type="hidden" name="roleId" value={selectedRoleId} />
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
{#each Object.entries(selectedRole?.permissions || {}) as [perm, enabled] (perm)}
|
||||
<div class="flex items-center justify-between">
|
||||
<Label>
|
||||
{PERMISSION_LABELS[perm as Permission]}
|
||||
</Label>
|
||||
<Switch name={perm} checked={enabled} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">Changes are local only (no backend yet)</p>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
{/if}
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-sm text-muted-foreground">Changes will be saved to the server</p>
|
||||
<Button type="submit" variant="outline">Save Changes</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Card.Content>
|
||||
<Card.Content class="space-y-4 pt-0">
|
||||
<div>
|
||||
<h3 class="mb-2 font-medium">Role Information</h3>
|
||||
<div class="space-y-1 text-sm text-muted-foreground">
|
||||
<p>
|
||||
Created by: {selectedRole?.createdBy.username}
|
||||
</p>
|
||||
<p>
|
||||
Created at: {formatTimestamp(selectedRole?.createdAt!)}
|
||||
</p>
|
||||
<p>
|
||||
Members: {selectedRole?.users.length || 0}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
{/if}
|
||||
</div>
|
||||
</div></Tabs.Content
|
||||
>
|
||||
<Tabs.Content value="general" class="space-y-6 p-4">
|
||||
<div class="space-y-2">
|
||||
<h2 class="text-lg font-medium">Server Settings</h2>
|
||||
|
|
@ -250,8 +225,11 @@
|
|||
toast.success('Server name updated successfully');
|
||||
await invalidateAll();
|
||||
await fill_overview_data(psd);
|
||||
} else if (result.type === 'failure') {
|
||||
toast.error('Failed to update server name: ' + result.data?.error);
|
||||
} else if (result.type == 'error' || result.type == 'failure') {
|
||||
toast.error(
|
||||
'Failed to update server name: ' +
|
||||
(result.type === 'error' ? result.error.message : result.data?.error)
|
||||
);
|
||||
}
|
||||
};
|
||||
}}
|
||||
|
|
@ -275,8 +253,11 @@
|
|||
inviteCode = location.origin + '/invite/' + result.data!.code;
|
||||
await invalidateAll();
|
||||
await fill_overview_data(psd);
|
||||
} else if (result.type === 'failure') {
|
||||
toast.error('Failed to create invite: ' + result.data?.error);
|
||||
} else if (result.type == 'error' || result.type == 'failure') {
|
||||
toast.error(
|
||||
'Failed to create invite: ' +
|
||||
(result.type === 'error' ? result.error.message : result.data?.error)
|
||||
);
|
||||
}
|
||||
};
|
||||
}}
|
||||
|
|
@ -314,8 +295,11 @@
|
|||
toast.success('Invite deleted successfully');
|
||||
await invalidateAll();
|
||||
await fill_overview_data(psd);
|
||||
} else if (result.type === 'failure') {
|
||||
toast.error('Failed to delete invite: ' + result.data?.error);
|
||||
} else if (result.type == 'error' || result.type == 'failure') {
|
||||
toast.error(
|
||||
'Failed to delete invite: ' +
|
||||
(result.type === 'error' ? result.error.message : result.data?.error)
|
||||
);
|
||||
}
|
||||
};
|
||||
}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue