Mega push vol 5, working on messaging now

This commit is contained in:
AlacrisDevs
2026-02-07 01:31:55 +02:00
parent d8bbfd9dc3
commit e55881b38b
77 changed files with 8478 additions and 1554 deletions

View File

@@ -0,0 +1,350 @@
<script lang="ts">
import { Button, Modal, Card, Input } from "$lib/components/ui";
import { toasts } from "$lib/stores/toast.svelte";
import type { SupabaseClient } from "@supabase/supabase-js";
import type { Database } from "$lib/supabase/types";
import * as m from "$lib/paraglide/messages";
interface OrgRole {
id: string;
org_id: string;
name: string;
color: string;
permissions: string[];
is_default: boolean;
is_system: boolean;
position: number;
}
interface Props {
supabase: SupabaseClient<Database>;
orgId: string;
roles: OrgRole[];
}
let { supabase, orgId, roles = $bindable() }: Props = $props();
let showRoleModal = $state(false);
let editingRole = $state<OrgRole | null>(null);
let newRoleName = $state("");
let newRoleColor = $state("#6366f1");
let newRolePermissions = $state<string[]>([]);
let isSavingRole = $state(false);
const permissionGroups = [
{
name: "Documents",
permissions: [
"documents.view",
"documents.create",
"documents.edit",
"documents.delete",
],
},
{
name: "Kanban",
permissions: [
"kanban.view",
"kanban.create",
"kanban.edit",
"kanban.delete",
],
},
{
name: "Calendar",
permissions: [
"calendar.view",
"calendar.create",
"calendar.edit",
"calendar.delete",
],
},
{
name: "Members",
permissions: [
"members.view",
"members.invite",
"members.manage",
"members.remove",
],
},
{
name: "Roles",
permissions: [
"roles.view",
"roles.create",
"roles.edit",
"roles.delete",
],
},
{ name: "Settings", permissions: ["settings.view", "settings.edit"] },
];
const roleColors = [
{ value: "#ef4444", label: "Red" },
{ value: "#f59e0b", label: "Amber" },
{ value: "#10b981", label: "Emerald" },
{ value: "#3b82f6", label: "Blue" },
{ value: "#6366f1", label: "Indigo" },
{ value: "#8b5cf6", label: "Violet" },
{ value: "#ec4899", label: "Pink" },
{ value: "#6b7280", label: "Gray" },
];
function openRoleModal(role?: OrgRole) {
if (role) {
editingRole = role;
newRoleName = role.name;
newRoleColor = role.color;
newRolePermissions = [...role.permissions];
} else {
editingRole = null;
newRoleName = "";
newRoleColor = "#6366f1";
newRolePermissions = [
"documents.view",
"kanban.view",
"calendar.view",
"members.view",
];
}
showRoleModal = true;
}
async function saveRole() {
if (!newRoleName.trim()) return;
isSavingRole = true;
if (editingRole) {
const { error } = await supabase
.from("org_roles")
.update({
name: newRoleName,
color: newRoleColor,
permissions: newRolePermissions,
})
.eq("id", editingRole.id);
if (!error) {
roles = roles.map((r) =>
r.id === editingRole!.id
? {
...r,
name: newRoleName,
color: newRoleColor,
permissions: newRolePermissions,
}
: r,
);
}
} else {
const { data: role, error } = await supabase
.from("org_roles")
.insert({
org_id: orgId,
name: newRoleName,
color: newRoleColor,
permissions: newRolePermissions,
position: roles.length,
})
.select()
.single();
if (!error && role) {
roles = [...roles, role as OrgRole];
}
}
showRoleModal = false;
isSavingRole = false;
}
async function deleteRole(role: OrgRole) {
if (role.is_system) return;
if (
!confirm(
`Delete role "${role.name}"? Members with this role will need to be reassigned.`,
)
)
return;
const { error } = await supabase
.from("org_roles")
.delete()
.eq("id", role.id);
if (error) {
toasts.error(m.toast_error_delete_role());
return;
}
roles = roles.filter((r) => r.id !== role.id);
}
function togglePermission(perm: string) {
if (newRolePermissions.includes(perm)) {
newRolePermissions = newRolePermissions.filter((p) => p !== perm);
} else {
newRolePermissions = [...newRolePermissions, perm];
}
}
</script>
<div class="space-y-6">
<div class="flex items-center justify-between">
<div>
<h2 class="text-lg font-semibold text-light">Roles</h2>
<p class="text-sm text-light/50">
Create custom roles with specific permissions.
</p>
</div>
<Button onclick={() => openRoleModal()} icon="add">
Create Role
</Button>
</div>
<div class="grid gap-4">
{#each roles as role}
<Card>
<div class="p-4">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-3">
<div
class="w-3 h-3 rounded-full"
style="background-color: {role.color}"
></div>
<span class="font-medium text-light"
>{role.name}</span
>
{#if role.is_system}
<span
class="text-xs text-light/40 bg-light/10 px-2 py-0.5 rounded"
>System</span
>
{/if}
{#if role.is_default}
<span
class="text-xs text-primary bg-primary/10 px-2 py-0.5 rounded"
>Default</span
>
{/if}
</div>
<div class="flex items-center gap-2">
{#if !role.is_system || role.name !== "Owner"}
<Button
variant="tertiary"
size="sm"
onclick={() => openRoleModal(role)}
>Edit</Button
>
{/if}
{#if !role.is_system}
<Button
variant="danger"
size="sm"
onclick={() => deleteRole(role)}
>Delete</Button
>
{/if}
</div>
</div>
<div class="flex flex-wrap gap-1">
{#if role.permissions.includes("*")}
<span
class="text-xs bg-light/10 text-light/70 px-2 py-1 rounded"
>All Permissions</span
>
{:else}
{#each role.permissions.slice(0, 6) as perm}
<span
class="text-xs bg-light/10 text-light/50 px-2 py-1 rounded"
>{perm}</span
>
{/each}
{#if role.permissions.length > 6}
<span class="text-xs text-light/40"
>+{role.permissions.length - 6} more</span
>
{/if}
{/if}
</div>
</div>
</Card>
{/each}
</div>
</div>
<!-- Edit/Create Role Modal -->
<Modal
isOpen={showRoleModal}
onClose={() => (showRoleModal = false)}
title={editingRole ? "Edit Role" : "Create Role"}
>
<div class="space-y-4">
<Input
label="Name"
bind:value={newRoleName}
placeholder="e.g., Moderator"
disabled={editingRole?.is_system}
/>
<div>
<label class="block text-sm font-medium text-light mb-2"
>Color</label
>
<div class="flex gap-2">
{#each roleColors as color}
<button
type="button"
class="w-8 h-8 rounded-full transition-transform {newRoleColor ===
color.value
? 'ring-2 ring-white scale-110'
: ''}"
style="background-color: {color.value}"
onclick={() => (newRoleColor = color.value)}
title={color.label}
></button>
{/each}
</div>
</div>
<div>
<label class="block text-sm font-medium text-light mb-2"
>Permissions</label
>
<div class="space-y-3 max-h-64 overflow-y-auto">
{#each permissionGroups as group}
<div class="p-3 bg-light/5 rounded-lg">
<p class="text-sm font-medium text-light mb-2">
{group.name}
</p>
<div class="grid grid-cols-2 gap-2">
{#each group.permissions as perm}
<label
class="flex items-center gap-2 text-sm text-light/70 cursor-pointer"
>
<input
type="checkbox"
checked={newRolePermissions.includes(
perm,
)}
onchange={() => togglePermission(perm)}
class="rounded"
/>
{perm.split(".")[1]}
</label>
{/each}
</div>
</div>
{/each}
</div>
</div>
<div class="flex justify-end gap-2 pt-2">
<Button variant="tertiary" onclick={() => (showRoleModal = false)}
>Cancel</Button
>
<Button
onclick={saveRole}
loading={isSavingRole}
disabled={!newRoleName.trim()}
>{editingRole ? "Save" : "Create"}</Button
>
</div>
</div>
</Modal>