ui: overhaul org settings components (General, Members, Roles, Integrations) - SettingsGeneral: border-based cards, compact danger zone with error border - SettingsMembers: Avatar component, icon buttons, border-based list - SettingsRoles: icon buttons for edit/delete, smaller permission badges - SettingsIntegrations: compact integration cards, Material Symbols for coming-soon - Removed unused Card imports from all settings components - svelte-check: 0 errors, vitest: 112/112 passed

This commit is contained in:
AlacrisDevs
2026-02-07 11:23:49 +02:00
parent 9d5e58f858
commit 4999836a57
4 changed files with 261 additions and 391 deletions

View File

@@ -124,15 +124,14 @@
} }
</script> </script>
<div class="flex flex-col gap-8"> <div class="flex flex-col gap-6 max-w-2xl">
<!-- Organization Details --> <!-- Organization Details -->
<h2 class="font-heading text-h2 text-white">Organization details</h2> <div class="bg-dark/30 border border-light/5 rounded-xl p-5 flex flex-col gap-5">
<h2 class="font-heading text-body text-white">Organization details</h2>
<div class="flex flex-col gap-8">
<div class="flex flex-col gap-4">
<!-- Avatar Upload --> <!-- Avatar Upload -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<span class="font-body text-body-sm text-light">Avatar</span> <span class="font-body text-body-sm text-light/60">Avatar</span>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<Avatar name={orgName || "?"} src={avatarUrl} size="lg" /> <Avatar name={orgName || "?"} src={avatarUrl} size="lg" />
<div class="flex gap-2"> <div class="flex gap-2">
@@ -174,7 +173,7 @@
placeholder="my-org" placeholder="my-org"
/> />
<div> <div>
<Button onclick={saveGeneralSettings} loading={isSaving} <Button size="sm" onclick={saveGeneralSettings} loading={isSaving}
>Save Changes</Button >Save Changes</Button
> >
</div> </div>
@@ -182,13 +181,13 @@
<!-- Danger Zone --> <!-- Danger Zone -->
{#if isOwner} {#if isOwner}
<div class="flex flex-col gap-4"> <div class="bg-dark/30 border border-error/10 rounded-xl p-5 flex flex-col gap-3">
<h4 class="font-heading text-h4 text-white">Danger Zone</h4> <h4 class="font-heading text-body-sm text-error">Danger Zone</h4>
<p class="font-body text-body text-white"> <p class="font-body text-[11px] text-light/40">
Permanently delete this organization and all its data. Permanently delete this organization and all its data.
</p> </p>
<div> <div>
<Button variant="danger" onclick={onDelete} <Button variant="danger" size="sm" onclick={onDelete}
>Delete Organization</Button >Delete Organization</Button
> >
</div> </div>
@@ -197,20 +196,16 @@
<!-- Leave Organization (non-owners) --> <!-- Leave Organization (non-owners) -->
{#if !isOwner} {#if !isOwner}
<div class="flex flex-col gap-4"> <div class="bg-dark/30 border border-light/5 rounded-xl p-5 flex flex-col gap-3">
<h4 class="font-heading text-h4 text-white"> <h4 class="font-heading text-body-sm text-white">Leave Organization</h4>
Leave Organization <p class="font-body text-[11px] text-light/40">
</h4> Leave this organization. You will need to be re-invited to rejoin.
<p class="font-body text-body text-white">
Leave this organization. You will need to be re-invited to
rejoin.
</p> </p>
<div> <div>
<Button variant="secondary" onclick={onLeave} <Button variant="secondary" size="sm" onclick={onLeave}
>Leave {org.name}</Button >Leave {org.name}</Button
> >
</div> </div>
</div> </div>
{/if} {/if}
</div> </div>
</div>

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Button, Modal, Card, Input } from "$lib/components/ui"; import { Button, Modal, Input } from "$lib/components/ui";
import { toasts } from "$lib/stores/toast.svelte"; import { toasts } from "$lib/stores/toast.svelte";
import { import {
extractCalendarId, extractCalendarId,
@@ -108,184 +108,88 @@
} }
</script> </script>
<div class="space-y-6 max-w-2xl"> <div class="space-y-3 max-w-2xl">
<Card> <!-- Google Calendar -->
<div class="p-6"> <div class="bg-dark/30 border border-light/5 rounded-xl p-5">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<div <div class="w-10 h-10 bg-white rounded-xl flex items-center justify-center shrink-0">
class="w-12 h-12 bg-white rounded-lg flex items-center justify-center" <svg class="w-6 h-6" viewBox="0 0 24 24">
> <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<svg class="w-8 h-8" viewBox="0 0 24 24"> <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
fill="#4285F4" <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
/>
<path
fill="#34A853"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="#FBBC05"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="#EA4335"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg> </svg>
</div> </div>
<div class="flex-1"> <div class="flex-1 min-w-0">
<h3 class="text-lg font-semibold text-light"> <h3 class="font-heading text-body-sm text-white">Google Calendar</h3>
Google Calendar <p class="text-[11px] text-light/40 mt-0.5">
</h3> Sync events between your organization and Google Calendar.
<p class="text-sm text-light/50 mt-1">
Sync events between your organization and Google
Calendar.
</p> </p>
{#if orgCalendar} {#if orgCalendar}
<div <div class="mt-3 p-3 bg-green-500/10 border border-green-500/20 rounded-lg">
class="mt-4 p-3 bg-green-500/10 border border-green-500/20 rounded-lg" <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
>
<div
class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 p-3 bg-green-500/10 rounded-lg"
>
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<p <p class="text-[11px] font-medium text-green-400">Connected</p>
class="text-sm font-medium text-green-400" <p class="text-body-sm text-white">{orgCalendar.calendar_name || "Google Calendar"}</p>
> <p class="text-[10px] text-light/40 truncate" title={orgCalendar.calendar_id}>{orgCalendar.calendar_id}</p>
Connected <p class="text-[10px] text-light/30 mt-1">Events sync both ways.</p>
</p>
<p class="text-light font-medium">
{orgCalendar.calendar_name ||
"Google Calendar"}
</p>
<p
class="text-xs text-light/50 truncate"
title={orgCalendar.calendar_id}
>
{orgCalendar.calendar_id}
</p>
<p class="text-xs text-light/40 mt-1">
Events sync both ways — create here or
in Google Calendar.
</p>
<a <a
href="https://calendar.google.com/calendar/u/0/r?cid={encodeURIComponent( href="https://calendar.google.com/calendar/u/0/r?cid={encodeURIComponent(orgCalendar.calendar_id)}"
orgCalendar.calendar_id,
)}"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="inline-flex items-center gap-1.5 text-xs text-blue-400 hover:text-blue-300 mt-2" class="inline-flex items-center gap-1 text-[11px] text-blue-400 hover:text-blue-300 mt-1.5"
> >
<svg <span class="material-symbols-rounded" style="font-size: 14px;">open_in_new</span>
class="w-3.5 h-3.5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"
/>
<polyline points="15 3 21 3 21 9" />
<line
x1="10"
y1="14"
x2="21"
y2="3"
/>
</svg>
Open in Google Calendar Open in Google Calendar
</a> </a>
</div> </div>
<Button <Button variant="danger" size="sm" onclick={disconnectOrgCalendar}>Disconnect</Button>
variant="danger"
size="sm"
onclick={disconnectOrgCalendar}
>Disconnect</Button
>
</div> </div>
</div> </div>
{:else if !serviceAccountEmail} {:else if !serviceAccountEmail}
<div <div class="mt-3 p-3 bg-yellow-500/10 border border-yellow-500/20 rounded-lg">
class="mt-4 p-3 bg-yellow-500/10 border border-yellow-500/20 rounded-lg" <p class="text-[11px] text-yellow-400 font-medium">Setup required</p>
> <p class="text-[10px] text-light/40 mt-1">
<p class="text-sm text-yellow-400 font-medium"> A server administrator needs to configure the <code class="bg-light/10 px-1 rounded">GOOGLE_SERVICE_ACCOUNT_KEY</code> environment variable.
Setup required
</p>
<p class="text-xs text-light/50 mt-1">
A server administrator needs to configure the <code
class="bg-light/10 px-1 rounded"
>GOOGLE_SERVICE_ACCOUNT_KEY</code
> environment variable before calendars can be connected.
</p> </p>
</div> </div>
{:else} {:else}
<div class="mt-4"> <div class="mt-3">
<Button onclick={() => (showConnectModal = true)} <Button size="sm" onclick={() => (showConnectModal = true)}>Connect Google Calendar</Button>
>Connect Google Calendar</Button
>
</div> </div>
{/if} {/if}
</div> </div>
</div> </div>
</div> </div>
</Card>
<Card> <!-- Discord (coming soon) -->
<div class="p-6 opacity-50"> <div class="bg-dark/30 border border-light/5 rounded-xl p-5 opacity-40">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<div <div class="w-10 h-10 bg-[#5865F2] rounded-xl flex items-center justify-center shrink-0">
class="w-12 h-12 bg-[#7289da] rounded-lg flex items-center justify-center" <span class="material-symbols-rounded text-white" style="font-size: 22px;">forum</span>
>
<svg
class="w-7 h-7 text-white"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"
/>
</svg>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<h3 class="text-lg font-semibold text-light">Discord</h3> <h3 class="font-heading text-body-sm text-white">Discord</h3>
<p class="text-sm text-light/50 mt-1"> <p class="text-[11px] text-light/40 mt-0.5">Get notifications in your Discord server.</p>
Get notifications in your Discord server. <p class="text-[10px] text-light/30 mt-1">Coming soon</p>
</p>
<p class="text-xs text-light/40 mt-2">Coming soon</p>
</div> </div>
</div> </div>
</div> </div>
</Card>
<Card> <!-- Slack (coming soon) -->
<div class="p-6 opacity-50"> <div class="bg-dark/30 border border-light/5 rounded-xl p-5 opacity-40">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<div <div class="w-10 h-10 bg-[#4A154B] rounded-xl flex items-center justify-center shrink-0">
class="w-12 h-12 bg-[#4A154B] rounded-lg flex items-center justify-center" <span class="material-symbols-rounded text-white" style="font-size: 22px;">tag</span>
>
<svg
class="w-7 h-7 text-white"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52a2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521a2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521a2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523a2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"
/>
</svg>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<h3 class="text-lg font-semibold text-light">Slack</h3> <h3 class="font-heading text-body-sm text-white">Slack</h3>
<p class="text-sm text-light/50 mt-1"> <p class="text-[11px] text-light/40 mt-0.5">Get notifications in your Slack workspace.</p>
Get notifications in your Slack workspace. <p class="text-[10px] text-light/30 mt-1">Coming soon</p>
</p>
<p class="text-xs text-light/40 mt-2">Coming soon</p>
</div> </div>
</div> </div>
</div> </div>
</Card>
</div> </div>
<!-- Connect Calendar Modal --> <!-- Connect Calendar Modal -->

View File

@@ -2,7 +2,6 @@
import { import {
Button, Button,
Modal, Modal,
Card,
Input, Input,
Select, Select,
Avatar, Avatar,
@@ -169,41 +168,24 @@
} }
</script> </script>
<div class="space-y-6"> <div class="space-y-4 max-w-2xl">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h2 class="text-lg font-semibold text-light"> <div>
<h2 class="font-heading text-body text-white">
{m.settings_members_title({ {m.settings_members_title({
count: String(members.length), count: String(members.length),
})} })}
</h2> </h2>
<Button onclick={() => (showInviteModal = true)}> </div>
<svg <Button size="sm" icon="person_add" onclick={() => (showInviteModal = true)}>
class="w-4 h-4 mr-2"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><circle
cx="9"
cy="7"
r="4"
/><line x1="19" y1="8" x2="19" y2="14" /><line
x1="22"
y1="11"
x2="16"
y2="11"
/>
</svg>
{m.settings_members_invite()} {m.settings_members_invite()}
</Button> </Button>
</div> </div>
<!-- Pending Invites --> <!-- Pending Invites -->
{#if invites.length > 0} {#if invites.length > 0}
<Card> <div class="bg-dark/30 border border-light/5 rounded-xl p-4">
<div class="p-4"> <h3 class="text-body-sm font-heading text-light/60 mb-3">
<h3 class="text-sm font-medium text-light/70 mb-3">
{m.settings_members_pending()} {m.settings_members_pending()}
</h3> </h3>
<div class="space-y-2"> <div class="space-y-2">
@@ -212,70 +194,71 @@
class="flex items-center justify-between py-2 px-3 bg-light/5 rounded-lg" class="flex items-center justify-between py-2 px-3 bg-light/5 rounded-lg"
> >
<div> <div>
<p class="text-light">{invite.email}</p> <p class="text-body-sm text-white">{invite.email}</p>
<p class="text-xs text-light/40"> <p class="text-[11px] text-light/40">
Invited as {invite.role} • Expires {new Date( Invited as {invite.role} • Expires {new Date(
invite.expires_at, invite.expires_at,
).toLocaleDateString()} ).toLocaleDateString()}
</p> </p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-1.5">
<Button <button
variant="tertiary" type="button"
size="sm" class="p-1.5 text-light/40 hover:text-white hover:bg-dark/50 rounded-lg transition-colors"
onclick={() => onclick={() =>
navigator.clipboard.writeText( navigator.clipboard.writeText(
`${window.location.origin}/invite/${invite.token}`, `${window.location.origin}/invite/${invite.token}`,
)} )}
>{m.settings_members_copy_link()}</Button title={m.settings_members_copy_link()}
> >
<Button <span class="material-symbols-rounded" style="font-size: 18px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 18;">content_copy</span>
variant="danger" </button>
size="sm" <button
type="button"
class="p-1.5 text-light/40 hover:text-error hover:bg-error/10 rounded-lg transition-colors"
onclick={() => cancelInvite(invite.id)} onclick={() => cancelInvite(invite.id)}
>Cancel</Button title="Cancel invite"
> >
<span class="material-symbols-rounded" style="font-size: 18px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 18;">close</span>
</button>
</div> </div>
</div> </div>
{/each} {/each}
</div> </div>
</div> </div>
</Card>
{/if} {/if}
<!-- Members List --> <!-- Members List -->
<Card> <div class="bg-dark/30 border border-light/5 rounded-xl overflow-hidden">
<div class="divide-y divide-light/10"> <div class="divide-y divide-light/5">
{#each members as member} {#each members as member}
{@const rawProfile = member.profiles} {@const rawProfile = member.profiles}
{@const profile = Array.isArray(rawProfile) {@const profile = Array.isArray(rawProfile)
? rawProfile[0] ? rawProfile[0]
: rawProfile} : rawProfile}
<div <div
class="flex items-center justify-between p-4 hover:bg-light/5 transition-colors" class="flex items-center justify-between px-4 py-3 hover:bg-light/5 transition-colors"
> >
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div <Avatar
class="w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center text-primary font-medium" name={profile?.full_name || profile?.email || "?"}
> src={profile?.avatar_url}
{(profile?.full_name || size="sm"
profile?.email || />
"?")[0].toUpperCase()}
</div>
<div> <div>
<p class="text-light font-medium"> <p class="text-body-sm text-white">
{profile?.full_name || {profile?.full_name ||
profile?.email || profile?.email ||
"Unknown User"} "Unknown User"}
</p> </p>
<p class="text-sm text-light/50"> <p class="text-[11px] text-light/40">
{profile?.email || "No email"} {profile?.email || "No email"}
</p> </p>
</div> </div>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-2">
<span <span
class="px-2 py-1 text-xs rounded-full capitalize" class="px-2 py-0.5 text-[10px] rounded-md capitalize font-body"
style="background-color: {roles.find( style="background-color: {roles.find(
(r) => r.name.toLowerCase() === member.role, (r) => r.name.toLowerCase() === member.role,
)?.color ?? '#6366f1'}20; color: {roles.find( )?.color ?? '#6366f1'}20; color: {roles.find(
@@ -283,18 +266,20 @@
)?.color ?? '#6366f1'}">{member.role}</span )?.color ?? '#6366f1'}">{member.role}</span
> >
{#if member.user_id !== userId && member.role !== "owner"} {#if member.user_id !== userId && member.role !== "owner"}
<Button <button
variant="tertiary" type="button"
size="sm" class="p-1.5 text-light/40 hover:text-white hover:bg-dark/50 rounded-lg transition-colors"
onclick={() => openMemberModal(member)} onclick={() => openMemberModal(member)}
>Edit</Button title="Edit"
> >
<span class="material-symbols-rounded" style="font-size: 18px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 18;">edit</span>
</button>
{/if} {/if}
</div> </div>
</div> </div>
{/each} {/each}
</div> </div>
</Card> </div>
</div> </div>
<!-- Invite Member Modal --> <!-- Invite Member Modal -->

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Button, Modal, Card, Input } from "$lib/components/ui"; import { Button, Modal, Input } from "$lib/components/ui";
import { toasts } from "$lib/stores/toast.svelte"; import { toasts } from "$lib/stores/toast.svelte";
import type { SupabaseClient } from "@supabase/supabase-js"; import type { SupabaseClient } from "@supabase/supabase-js";
import type { Database } from "$lib/supabase/types"; import type { Database } from "$lib/supabase/types";
@@ -188,86 +188,72 @@
} }
</script> </script>
<div class="space-y-6"> <div class="space-y-4 max-w-2xl">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div> <div>
<h2 class="text-lg font-semibold text-light">Roles</h2> <h2 class="font-heading text-body text-white">Roles</h2>
<p class="text-sm text-light/50"> <p class="text-body-sm text-light/40 mt-0.5">
Create custom roles with specific permissions. Create custom roles with specific permissions.
</p> </p>
</div> </div>
<Button onclick={() => openRoleModal()} icon="add"> <Button size="sm" onclick={() => openRoleModal()} icon="add">
Create Role Create Role
</Button> </Button>
</div> </div>
<div class="grid gap-4"> <div class="flex flex-col gap-2">
{#each roles as role} {#each roles as role}
<Card> <div class="bg-dark/30 border border-light/5 rounded-xl px-4 py-3 hover:border-light/10 transition-colors">
<div class="p-4"> <div class="flex items-center justify-between mb-2">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center gap-2">
<div class="flex items-center gap-3">
<div <div
class="w-3 h-3 rounded-full" class="w-2.5 h-2.5 rounded-full shrink-0"
style="background-color: {role.color}" style="background-color: {role.color}"
></div> ></div>
<span class="font-medium text-light" <span class="text-body-sm font-medium text-white">{role.name}</span>
>{role.name}</span
>
{#if role.is_system} {#if role.is_system}
<span <span class="text-[10px] text-light/30 bg-light/5 px-1.5 py-0.5 rounded-md">System</span>
class="text-xs text-light/40 bg-light/10 px-2 py-0.5 rounded"
>System</span
>
{/if} {/if}
{#if role.is_default} {#if role.is_default}
<span <span class="text-[10px] text-primary bg-primary/10 px-1.5 py-0.5 rounded-md">Default</span>
class="text-xs text-primary bg-primary/10 px-2 py-0.5 rounded"
>Default</span
>
{/if} {/if}
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-1.5">
{#if !role.is_system || role.name !== "Owner"} {#if !role.is_system || role.name !== "Owner"}
<Button <button
variant="tertiary" type="button"
size="sm" class="p-1.5 text-light/40 hover:text-white hover:bg-dark/50 rounded-lg transition-colors"
onclick={() => openRoleModal(role)} onclick={() => openRoleModal(role)}
>Edit</Button title="Edit"
> >
<span class="material-symbols-rounded" style="font-size: 18px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 18;">edit</span>
</button>
{/if} {/if}
{#if !role.is_system} {#if !role.is_system}
<Button <button
variant="danger" type="button"
size="sm" class="p-1.5 text-light/40 hover:text-error hover:bg-error/10 rounded-lg transition-colors"
onclick={() => deleteRole(role)} onclick={() => deleteRole(role)}
>Delete</Button title="Delete"
> >
<span class="material-symbols-rounded" style="font-size: 18px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 18;">delete</span>
</button>
{/if} {/if}
</div> </div>
</div> </div>
<div class="flex flex-wrap gap-1"> <div class="flex flex-wrap gap-1">
{#if role.permissions.includes("*")} {#if role.permissions.includes("*")}
<span <span class="text-[10px] bg-light/5 text-light/50 px-1.5 py-0.5 rounded-md">All Permissions</span>
class="text-xs bg-light/10 text-light/70 px-2 py-1 rounded"
>All Permissions</span
>
{:else} {:else}
{#each role.permissions.slice(0, 6) as perm} {#each role.permissions.slice(0, 6) as perm}
<span <span class="text-[10px] bg-light/5 text-light/40 px-1.5 py-0.5 rounded-md">{perm}</span>
class="text-xs bg-light/10 text-light/50 px-2 py-1 rounded"
>{perm}</span
>
{/each} {/each}
{#if role.permissions.length > 6} {#if role.permissions.length > 6}
<span class="text-xs text-light/40" <span class="text-[10px] text-light/30">+{role.permissions.length - 6} more</span>
>+{role.permissions.length - 6} more</span
>
{/if} {/if}
{/if} {/if}
</div> </div>
</div> </div>
</Card>
{/each} {/each}
</div> </div>
</div> </div>