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:
@@ -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 -->
|
||||
|
||||
Reference in New Issue
Block a user