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

@@ -2,7 +2,6 @@
import {
Button,
Modal,
Card,
Input,
Select,
Avatar,
@@ -169,113 +168,97 @@
}
</script>
<div class="space-y-6">
<div class="space-y-4 max-w-2xl">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold text-light">
{m.settings_members_title({
count: String(members.length),
})}
</h2>
<Button onclick={() => (showInviteModal = true)}>
<svg
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>
<div>
<h2 class="font-heading text-body text-white">
{m.settings_members_title({
count: String(members.length),
})}
</h2>
</div>
<Button size="sm" icon="person_add" onclick={() => (showInviteModal = true)}>
{m.settings_members_invite()}
</Button>
</div>
<!-- Pending Invites -->
{#if invites.length > 0}
<Card>
<div class="p-4">
<h3 class="text-sm font-medium text-light/70 mb-3">
{m.settings_members_pending()}
</h3>
<div class="space-y-2">
{#each invites as invite}
<div
class="flex items-center justify-between py-2 px-3 bg-light/5 rounded-lg"
>
<div>
<p class="text-light">{invite.email}</p>
<p class="text-xs text-light/40">
Invited as {invite.role} • Expires {new Date(
invite.expires_at,
).toLocaleDateString()}
</p>
</div>
<div class="flex items-center gap-2">
<Button
variant="tertiary"
size="sm"
onclick={() =>
navigator.clipboard.writeText(
`${window.location.origin}/invite/${invite.token}`,
)}
>{m.settings_members_copy_link()}</Button
>
<Button
variant="danger"
size="sm"
onclick={() => cancelInvite(invite.id)}
>Cancel</Button
>
</div>
<div class="bg-dark/30 border border-light/5 rounded-xl p-4">
<h3 class="text-body-sm font-heading text-light/60 mb-3">
{m.settings_members_pending()}
</h3>
<div class="space-y-2">
{#each invites as invite}
<div
class="flex items-center justify-between py-2 px-3 bg-light/5 rounded-lg"
>
<div>
<p class="text-body-sm text-white">{invite.email}</p>
<p class="text-[11px] text-light/40">
Invited as {invite.role} • Expires {new Date(
invite.expires_at,
).toLocaleDateString()}
</p>
</div>
{/each}
</div>
<div class="flex items-center gap-1.5">
<button
type="button"
class="p-1.5 text-light/40 hover:text-white hover:bg-dark/50 rounded-lg transition-colors"
onclick={() =>
navigator.clipboard.writeText(
`${window.location.origin}/invite/${invite.token}`,
)}
title={m.settings_members_copy_link()}
>
<span class="material-symbols-rounded" style="font-size: 18px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 18;">content_copy</span>
</button>
<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)}
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>
{/each}
</div>
</Card>
</div>
{/if}
<!-- Members List -->
<Card>
<div class="divide-y divide-light/10">
<div class="bg-dark/30 border border-light/5 rounded-xl overflow-hidden">
<div class="divide-y divide-light/5">
{#each members as member}
{@const rawProfile = member.profiles}
{@const profile = Array.isArray(rawProfile)
? rawProfile[0]
: rawProfile}
<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="w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center text-primary font-medium"
>
{(profile?.full_name ||
profile?.email ||
"?")[0].toUpperCase()}
</div>
<Avatar
name={profile?.full_name || profile?.email || "?"}
src={profile?.avatar_url}
size="sm"
/>
<div>
<p class="text-light font-medium">
<p class="text-body-sm text-white">
{profile?.full_name ||
profile?.email ||
"Unknown User"}
</p>
<p class="text-sm text-light/50">
<p class="text-[11px] text-light/40">
{profile?.email || "No email"}
</p>
</div>
</div>
<div class="flex items-center gap-3">
<div class="flex items-center gap-2">
<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(
(r) => r.name.toLowerCase() === member.role,
)?.color ?? '#6366f1'}20; color: {roles.find(
@@ -283,18 +266,20 @@
)?.color ?? '#6366f1'}">{member.role}</span
>
{#if member.user_id !== userId && member.role !== "owner"}
<Button
variant="tertiary"
size="sm"
<button
type="button"
class="p-1.5 text-light/40 hover:text-white hover:bg-dark/50 rounded-lg transition-colors"
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}
</div>
</div>
{/each}
</div>
</Card>
</div>
</div>
<!-- Invite Member Modal -->