refactor members page, add pending filter tab
This commit is contained in:
parent
5fb2331958
commit
c875f3cefb
2 changed files with 156 additions and 389 deletions
|
|
@ -10,7 +10,7 @@ export type CrewRole = 'member' | 'vice_captain' | 'captain'
|
|||
export type InvitationStatus = 'pending' | 'accepted' | 'rejected' | 'expired'
|
||||
|
||||
// Member filter options for GET /crew/members
|
||||
export type MemberFilter = 'active' | 'retired' | 'phantom' | 'all'
|
||||
export type MemberFilter = 'active' | 'retired' | 'phantom' | 'pending' | 'all'
|
||||
|
||||
// Crew from CrewBlueprint
|
||||
export interface Crew {
|
||||
|
|
|
|||
|
|
@ -21,10 +21,19 @@
|
|||
import SettingsRow from '$lib/components/ui/SettingsRow.svelte'
|
||||
import Switch from '$lib/components/ui/switch/Switch.svelte'
|
||||
import CrewHeader from '$lib/components/crew/CrewHeader.svelte'
|
||||
import MemberRow from '$lib/components/crew/MemberRow.svelte'
|
||||
import PhantomRow from '$lib/components/crew/PhantomRow.svelte'
|
||||
import ScoutUserModal from '$lib/components/crew/ScoutUserModal.svelte'
|
||||
import BulkPhantomModal from '$lib/components/crew/BulkPhantomModal.svelte'
|
||||
import AssignPhantomModal from '$lib/components/crew/AssignPhantomModal.svelte'
|
||||
import ConfirmClaimModal from '$lib/components/crew/ConfirmClaimModal.svelte'
|
||||
import { DropdownMenu as DropdownMenuBase } from 'bits-ui'
|
||||
import type { MemberFilter, CrewMembership, PhantomPlayer, CrewInvitation } from '$lib/types/api/crew'
|
||||
import type {
|
||||
MemberFilter,
|
||||
CrewMembership,
|
||||
PhantomPlayer,
|
||||
CrewInvitation
|
||||
} from '$lib/types/api/crew'
|
||||
import type { PageData } from './$types'
|
||||
|
||||
interface Props {
|
||||
|
|
@ -59,13 +68,9 @@
|
|||
const activeRosterSize = $derived.by(() => {
|
||||
// Use active filter data if viewing active, otherwise use dedicated query
|
||||
const activeMembers =
|
||||
filter === 'active'
|
||||
? (membersQuery.data?.members ?? [])
|
||||
: (activeQuery.data?.members ?? [])
|
||||
filter === 'active' ? (membersQuery.data?.members ?? []) : (activeQuery.data?.members ?? [])
|
||||
const activePhantoms =
|
||||
filter === 'active'
|
||||
? (membersQuery.data?.phantoms ?? [])
|
||||
: (activeQuery.data?.phantoms ?? [])
|
||||
filter === 'active' ? (membersQuery.data?.phantoms ?? []) : (activeQuery.data?.phantoms ?? [])
|
||||
|
||||
const activeMemberCount = activeMembers.filter((m) => !m.retired).length
|
||||
const activePhantomCount = activePhantoms.filter((p) => !p.retired).length
|
||||
|
|
@ -79,13 +84,19 @@
|
|||
const updateMembershipMutation = useUpdateMembership()
|
||||
const deletePhantomMutation = useDeletePhantom()
|
||||
|
||||
// Filter options
|
||||
const filterOptions: { value: MemberFilter; label: string }[] = [
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'active', label: 'Active' },
|
||||
{ value: 'phantom', label: 'Phantoms' },
|
||||
{ value: 'retired', label: 'Retired' }
|
||||
]
|
||||
// Filter options - Pending only shown to officers
|
||||
const filterOptions = $derived.by(() => {
|
||||
const options: { value: MemberFilter; label: string }[] = [
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'active', label: 'Active' },
|
||||
{ value: 'phantom', label: 'Phantoms' }
|
||||
]
|
||||
if (crewStore.isOfficer) {
|
||||
options.push({ value: 'pending', label: 'Pending' })
|
||||
}
|
||||
options.push({ value: 'retired', label: 'Retired' })
|
||||
return options
|
||||
})
|
||||
|
||||
// Change filter
|
||||
function handleFilterChange(newFilter: MemberFilter) {
|
||||
|
|
@ -113,9 +124,6 @@
|
|||
// Dialog state for scout modal
|
||||
let scoutModalOpen = $state(false)
|
||||
|
||||
// Pending invitations section visibility
|
||||
let invitationsSectionOpen = $state(true)
|
||||
|
||||
// Dialog state for phantom creation
|
||||
let bulkPhantomDialogOpen = $state(false)
|
||||
|
||||
|
|
@ -123,28 +131,13 @@
|
|||
let deletePhantomDialogOpen = $state(false)
|
||||
let phantomToDelete = $state<PhantomPlayer | null>(null)
|
||||
|
||||
// Role display helpers
|
||||
function getRoleLabel(role: string): string {
|
||||
switch (role) {
|
||||
case 'captain':
|
||||
return 'Captain'
|
||||
case 'vice_captain':
|
||||
return 'Vice Captain'
|
||||
default:
|
||||
return 'Member'
|
||||
}
|
||||
}
|
||||
// Dialog state for phantom assignment
|
||||
let assignPhantomDialogOpen = $state(false)
|
||||
let phantomToAssign = $state<PhantomPlayer | null>(null)
|
||||
|
||||
function getRoleClass(role: string): string {
|
||||
switch (role) {
|
||||
case 'captain':
|
||||
return 'captain'
|
||||
case 'vice_captain':
|
||||
return 'officer'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
// Dialog state for confirm claim
|
||||
let confirmClaimDialogOpen = $state(false)
|
||||
let phantomToClaim = $state<PhantomPlayer | null>(null)
|
||||
|
||||
// Member actions
|
||||
function openRemoveDialog(member: CrewMembership) {
|
||||
|
|
@ -201,7 +194,7 @@
|
|||
editingMember = member
|
||||
editingPhantom = null
|
||||
// Format date for input
|
||||
editJoinDate = member.joinedAt ? member.joinedAt.split('T')[0] ?? '' : ''
|
||||
editJoinDate = member.joinedAt ? (member.joinedAt.split('T')[0] ?? '') : ''
|
||||
editRetired = member.retired
|
||||
editDialogOpen = true
|
||||
}
|
||||
|
|
@ -209,7 +202,7 @@
|
|||
function openEditPhantomDialog(phantom: PhantomPlayer) {
|
||||
editingPhantom = phantom
|
||||
editingMember = null
|
||||
editJoinDate = phantom.joinedAt ? phantom.joinedAt.split('T')[0] ?? '' : ''
|
||||
editJoinDate = phantom.joinedAt ? (phantom.joinedAt.split('T')[0] ?? '') : ''
|
||||
editRetired = phantom.retired
|
||||
editDialogOpen = true
|
||||
}
|
||||
|
|
@ -267,6 +260,18 @@
|
|||
phantomToDelete = null
|
||||
}
|
||||
|
||||
// Phantom assignment
|
||||
function openAssignPhantomDialog(phantom: PhantomPlayer) {
|
||||
phantomToAssign = phantom
|
||||
assignPhantomDialogOpen = true
|
||||
}
|
||||
|
||||
// Confirm claim
|
||||
function openConfirmClaimDialog(phantom: PhantomPlayer) {
|
||||
phantomToClaim = phantom
|
||||
confirmClaimDialogOpen = true
|
||||
}
|
||||
|
||||
// Format date
|
||||
function formatDate(dateString: string): string {
|
||||
return new Date(dateString).toLocaleDateString(undefined, {
|
||||
|
|
@ -285,6 +290,11 @@
|
|||
const pendingInvitationsCount = $derived(
|
||||
invitationsQuery.data?.filter((inv) => !isInvitationExpired(inv.expiresAt)).length ?? 0
|
||||
)
|
||||
|
||||
// Get phantoms with pending claims (assigned but not confirmed)
|
||||
const pendingClaimPhantoms = $derived(
|
||||
membersQuery.data?.phantoms?.filter((p) => p.claimedBy && !p.claimConfirmed) ?? []
|
||||
)
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
@ -293,7 +303,7 @@
|
|||
|
||||
<div class="page">
|
||||
<div class="card">
|
||||
<CrewHeader title="Members">
|
||||
<CrewHeader title="Members" backHref="/crew">
|
||||
{#snippet belowTitle()}
|
||||
<div class="filter-tabs">
|
||||
{#each filterOptions as option}
|
||||
|
|
@ -334,50 +344,41 @@
|
|||
{/snippet}
|
||||
</CrewHeader>
|
||||
|
||||
<!-- Pending Invitations Section (officers only) -->
|
||||
{#if crewStore.isOfficer && invitationsQuery.data && invitationsQuery.data.length > 0}
|
||||
<div class="invitations-section">
|
||||
<button
|
||||
class="invitations-header"
|
||||
onclick={() => (invitationsSectionOpen = !invitationsSectionOpen)}
|
||||
>
|
||||
<span class="invitations-title">
|
||||
Pending Invitations
|
||||
{#if pendingInvitationsCount > 0}
|
||||
<span class="invitations-count">{pendingInvitationsCount}</span>
|
||||
{/if}
|
||||
</span>
|
||||
<span class="toggle-icon" class:open={invitationsSectionOpen}>▼</span>
|
||||
</button>
|
||||
|
||||
{#if invitationsSectionOpen}
|
||||
<ul class="invitations-list">
|
||||
{#each invitationsQuery.data as invitation}
|
||||
{@const expired = isInvitationExpired(invitation.expiresAt)}
|
||||
<li class="invitation-item" class:expired>
|
||||
<div class="invitation-info">
|
||||
<span class="invited-user">{invitation.user?.username ?? 'Unknown'}</span>
|
||||
{#if invitation.invitedBy}
|
||||
<span class="invited-by">
|
||||
Invited by {invitation.invitedBy.username}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="invitation-status">
|
||||
{#if expired}
|
||||
<span class="status-badge expired">Expired</span>
|
||||
{:else}
|
||||
<span class="expires-text">Expires {formatDate(invitation.expiresAt)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if membersQuery.isLoading}
|
||||
<!-- Pending Invitations (shown when filter is 'pending') -->
|
||||
{#if filter === 'pending'}
|
||||
{#if invitationsQuery.isLoading}
|
||||
<div class="loading-state">
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
{:else if invitationsQuery.data && invitationsQuery.data.length > 0}
|
||||
<ul class="member-list">
|
||||
{#each invitationsQuery.data as invitation}
|
||||
{@const expired = isInvitationExpired(invitation.expiresAt)}
|
||||
<li class="invitation-row" class:expired>
|
||||
<div class="invitation-info">
|
||||
<span class="invited-user">{invitation.user?.username ?? 'Unknown'}</span>
|
||||
{#if invitation.invitedBy}
|
||||
<span class="invited-by">
|
||||
Invited by {invitation.invitedBy.username}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="invitation-status">
|
||||
{#if expired}
|
||||
<span class="status-badge expired">Expired</span>
|
||||
{:else}
|
||||
<span class="expires-text">Expires {formatDate(invitation.expiresAt)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<div class="empty-state">
|
||||
<p>No pending invitations.</p>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if membersQuery.isLoading}
|
||||
<div class="loading-state">
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
|
|
@ -390,62 +391,38 @@
|
|||
{#if membersQuery.data?.members && membersQuery.data.members.length > 0}
|
||||
<ul class="member-list">
|
||||
{#each membersQuery.data.members as member}
|
||||
<li class="member-item" class:retired={member.retired}>
|
||||
<div class="member-info">
|
||||
<span class="username">{member.user?.username ?? 'Unknown'}</span>
|
||||
<span class="role-badge {getRoleClass(member.role)}">
|
||||
{getRoleLabel(member.role)}
|
||||
</span>
|
||||
{#if member.joinedAt}
|
||||
<span class="joined-date">Joined {formatDate(member.joinedAt)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if crewStore.isOfficer}
|
||||
<DropdownMenu>
|
||||
{#snippet trigger({ props })}
|
||||
<Button variant="secondary" size="small" iconOnly icon="ellipsis" {...props} />
|
||||
{/snippet}
|
||||
{#snippet menu()}
|
||||
<DropdownMenuBase.Item
|
||||
class="dropdown-menu-item"
|
||||
onclick={() => openEditMemberDialog(member)}
|
||||
>
|
||||
Edit
|
||||
</DropdownMenuBase.Item>
|
||||
{#if crewStore.canActOnMember(member.role) && !member.retired && member.id !== crewStore.membership?.id}
|
||||
{#if member.role === 'member' && crewStore.canPromoteTo('vice_captain')}
|
||||
<DropdownMenuBase.Item
|
||||
class="dropdown-menu-item"
|
||||
onclick={() => openPromoteDialog(member)}
|
||||
>
|
||||
Promote
|
||||
</DropdownMenuBase.Item>
|
||||
{/if}
|
||||
{#if member.role === 'vice_captain' && crewStore.canDemote('vice_captain')}
|
||||
<DropdownMenuBase.Item
|
||||
class="dropdown-menu-item"
|
||||
onclick={() => openDemoteDialog(member)}
|
||||
>
|
||||
Demote
|
||||
</DropdownMenuBase.Item>
|
||||
{/if}
|
||||
<DropdownMenuBase.Item
|
||||
class="dropdown-menu-item danger"
|
||||
onclick={() => openRemoveDialog(member)}
|
||||
>
|
||||
Remove
|
||||
</DropdownMenuBase.Item>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</DropdownMenu>
|
||||
{/if}
|
||||
</li>
|
||||
<MemberRow
|
||||
{member}
|
||||
onEdit={() => openEditMemberDialog(member)}
|
||||
onPromote={() => openPromoteDialog(member)}
|
||||
onDemote={() => openDemoteDialog(member)}
|
||||
onRemove={() => openRemoveDialog(member)}
|
||||
/>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else if filter !== 'phantom'}
|
||||
<p class="empty-state">No members found</p>
|
||||
{/if}
|
||||
|
||||
<!-- Pending Claims Section (officers only) -->
|
||||
{#if crewStore.isOfficer && pendingClaimPhantoms.length > 0 && (filter === 'all' || filter === 'phantom')}
|
||||
<div class="section-divider pending-claims">
|
||||
<span>Pending Claims ({pendingClaimPhantoms.length})</span>
|
||||
</div>
|
||||
<ul class="member-list">
|
||||
{#each pendingClaimPhantoms as phantom}
|
||||
<PhantomRow
|
||||
{phantom}
|
||||
currentUserId={crewStore.membership?.user?.id}
|
||||
onEdit={() => openEditPhantomDialog(phantom)}
|
||||
onDelete={() => openDeletePhantomDialog(phantom)}
|
||||
onAssign={() => openAssignPhantomDialog(phantom)}
|
||||
onConfirmClaim={() => openConfirmClaimDialog(phantom)}
|
||||
/>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
|
||||
<!-- Phantom players -->
|
||||
{#if membersQuery.data?.phantoms && membersQuery.data.phantoms.length > 0}
|
||||
{#if filter === 'all' && membersQuery.data.members.length > 0}
|
||||
|
|
@ -455,51 +432,14 @@
|
|||
{/if}
|
||||
<ul class="member-list">
|
||||
{#each membersQuery.data.phantoms as phantom}
|
||||
<li class="member-item" class:retired={phantom.retired}>
|
||||
<div class="member-info">
|
||||
<div class="phantom-details">
|
||||
<span class="username">{phantom.name}</span>
|
||||
{#if phantom.granblueId}
|
||||
<span class="granblue-id">ID: {phantom.granblueId}</span>
|
||||
{/if}
|
||||
{#if phantom.joinedAt}
|
||||
<span class="joined-date">Joined {formatDate(phantom.joinedAt)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if phantom.claimConfirmed && phantom.claimedBy}
|
||||
<span class="status-badge claimed">
|
||||
Claimed by {phantom.claimedBy.username}
|
||||
</span>
|
||||
{:else if phantom.claimedBy}
|
||||
<span class="status-badge pending">
|
||||
Pending: {phantom.claimedBy.username}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="status-badge unclaimed">Unclaimed</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if crewStore.isOfficer}
|
||||
<DropdownMenu>
|
||||
{#snippet trigger({ props })}
|
||||
<Button variant="secondary" size="small" iconOnly icon="ellipsis" {...props} />
|
||||
{/snippet}
|
||||
{#snippet menu()}
|
||||
<DropdownMenuBase.Item
|
||||
class="dropdown-menu-item"
|
||||
onclick={() => openEditPhantomDialog(phantom)}
|
||||
>
|
||||
Edit
|
||||
</DropdownMenuBase.Item>
|
||||
<DropdownMenuBase.Item
|
||||
class="dropdown-menu-item danger"
|
||||
onclick={() => openDeletePhantomDialog(phantom)}
|
||||
>
|
||||
Delete
|
||||
</DropdownMenuBase.Item>
|
||||
{/snippet}
|
||||
</DropdownMenu>
|
||||
{/if}
|
||||
</li>
|
||||
<PhantomRow
|
||||
{phantom}
|
||||
currentUserId={crewStore.membership?.user?.id}
|
||||
onEdit={() => openEditPhantomDialog(phantom)}
|
||||
onDelete={() => openDeletePhantomDialog(phantom)}
|
||||
onAssign={() => openAssignPhantomDialog(phantom)}
|
||||
onConfirmClaim={() => openConfirmClaimDialog(phantom)}
|
||||
/>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else if filter === 'phantom'}
|
||||
|
|
@ -543,7 +483,12 @@
|
|||
<ModalFooter
|
||||
onCancel={() => (confirmDialogOpen = false)}
|
||||
primaryAction={{
|
||||
label: confirmAction === 'remove' ? 'Remove' : confirmAction === 'promote' ? 'Promote' : 'Demote',
|
||||
label:
|
||||
confirmAction === 'remove'
|
||||
? 'Remove'
|
||||
: confirmAction === 'promote'
|
||||
? 'Promote'
|
||||
: 'Demote',
|
||||
onclick: handleConfirmAction,
|
||||
destructive: confirmAction === 'remove'
|
||||
}}
|
||||
|
|
@ -567,10 +512,7 @@
|
|||
This date is used to determine which events a member was active for when adding
|
||||
historical GW scores.
|
||||
</p>
|
||||
<SettingsRow
|
||||
title="Retired"
|
||||
subtitle="This player is no longer a part of the crew"
|
||||
>
|
||||
<SettingsRow title="Retired" subtitle="This player is no longer a part of the crew">
|
||||
{#snippet control()}
|
||||
<Switch bind:checked={editRetired} name="retired" />
|
||||
{/snippet}
|
||||
|
|
@ -617,6 +559,16 @@
|
|||
{#if crewStore.crew?.id}
|
||||
<ScoutUserModal bind:open={scoutModalOpen} crewId={crewStore.crew.id} />
|
||||
<BulkPhantomModal bind:open={bulkPhantomDialogOpen} crewId={crewStore.crew.id} />
|
||||
<AssignPhantomModal
|
||||
bind:open={assignPhantomDialogOpen}
|
||||
crewId={crewStore.crew.id}
|
||||
phantom={phantomToAssign}
|
||||
/>
|
||||
<ConfirmClaimModal
|
||||
bind:open={confirmClaimDialogOpen}
|
||||
crewId={crewStore.crew.id}
|
||||
phantom={phantomToClaim}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
|
|
@ -627,7 +579,6 @@
|
|||
@use '$src/themes/layout' as layout;
|
||||
|
||||
.page {
|
||||
padding: spacing.$unit-2x 0;
|
||||
margin: 0 auto;
|
||||
max-width: var(--main-max-width);
|
||||
}
|
||||
|
|
@ -638,6 +589,10 @@
|
|||
border-radius: layout.$page-corner;
|
||||
box-shadow: effects.$page-elevation;
|
||||
overflow: hidden;
|
||||
|
||||
:global(.header-info) {
|
||||
gap: spacing.$unit-2x;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-tabs {
|
||||
|
|
@ -700,86 +655,6 @@
|
|||
padding: spacing.$unit;
|
||||
}
|
||||
|
||||
.member-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: spacing.$unit spacing.$unit-2x;
|
||||
border-radius: layout.$item-corner;
|
||||
transition: background-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
&.retired {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.member-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: spacing.$unit;
|
||||
}
|
||||
|
||||
.phantom-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-size: typography.$font-small;
|
||||
font-weight: typography.$medium;
|
||||
}
|
||||
|
||||
.granblue-id {
|
||||
font-size: typography.$font-small;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.role-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: layout.$item-corner-small;
|
||||
font-size: typography.$font-small;
|
||||
font-weight: typography.$medium;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
|
||||
&.captain {
|
||||
background: var(--color-gold-light, #fef3c7);
|
||||
color: var(--color-gold-dark, #92400e);
|
||||
}
|
||||
|
||||
&.officer {
|
||||
background: var(--color-blue-light, #dbeafe);
|
||||
color: var(--color-blue-dark, #1e40af);
|
||||
}
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: layout.$item-corner-small;
|
||||
font-size: typography.$font-small;
|
||||
|
||||
&.unclaimed {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
&.pending {
|
||||
background: var(--color-yellow-light, #fef9c3);
|
||||
color: var(--color-yellow-dark, #854d0e);
|
||||
}
|
||||
|
||||
&.claimed {
|
||||
background: var(--color-green-light, #dcfce7);
|
||||
color: var(--color-green-dark, #166534);
|
||||
}
|
||||
}
|
||||
|
||||
.section-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -787,6 +662,15 @@
|
|||
background: rgba(0, 0, 0, 0.02);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
|
||||
&.pending-claims {
|
||||
background: var(--color-yellow-light, #fef9c3);
|
||||
border-bottom-color: var(--color-yellow-dark, #854d0e);
|
||||
|
||||
span {
|
||||
color: var(--color-yellow-dark, #854d0e);
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: typography.$font-small;
|
||||
font-weight: typography.$medium;
|
||||
|
|
@ -794,35 +678,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: spacing.$unit-half;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 4px 8px;
|
||||
border: none;
|
||||
border-radius: layout.$item-corner-small;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
color: var(--text-secondary);
|
||||
font-size: typography.$font-small;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
&.danger {
|
||||
color: colors.$error;
|
||||
|
||||
&:hover {
|
||||
background: rgba(colors.$error, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm dialog styles
|
||||
.confirm-message {
|
||||
color: var(--text-primary);
|
||||
|
|
@ -852,31 +707,6 @@
|
|||
font-size: typography.$font-small;
|
||||
font-weight: typography.$medium;
|
||||
color: var(--text-primary);
|
||||
|
||||
.optional {
|
||||
font-weight: typography.$normal;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
padding: spacing.$unit-2x;
|
||||
border: none;
|
||||
border-radius: layout.$input-corner;
|
||||
font-size: typography.$font-regular;
|
||||
font-family: inherit;
|
||||
background: var(--input-bound-bg);
|
||||
color: var(--text-primary);
|
||||
resize: vertical;
|
||||
min-height: 60px;
|
||||
|
||||
&:hover {
|
||||
background: var(--input-bound-bg-hover);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -886,11 +716,6 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.joined-date {
|
||||
font-size: typography.$font-small;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.date-input {
|
||||
padding: spacing.$unit spacing.$unit-2x;
|
||||
border: none;
|
||||
|
|
@ -918,75 +743,17 @@
|
|||
line-height: 1.4;
|
||||
}
|
||||
|
||||
// Pending invitations section
|
||||
.invitations-section {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.invitations-header {
|
||||
// Invitation row styles
|
||||
.invitation-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: spacing.$unit-2x spacing.$unit-3x;
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: spacing.$unit spacing.$unit spacing.$unit spacing.$unit-2x;
|
||||
border-radius: layout.$item-corner;
|
||||
transition: background-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
.invitations-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: spacing.$unit;
|
||||
font-size: typography.$font-small;
|
||||
font-weight: typography.$medium;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.invitations-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 6px;
|
||||
background: colors.$error;
|
||||
color: white;
|
||||
border-radius: 9px;
|
||||
font-size: 11px;
|
||||
font-weight: typography.$medium;
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
font-size: 10px;
|
||||
color: var(--text-tertiary);
|
||||
transition: transform 0.2s;
|
||||
|
||||
&.open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.invitations-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: spacing.$unit;
|
||||
}
|
||||
|
||||
.invitation-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: spacing.$unit spacing.$unit-2x;
|
||||
border-radius: layout.$item-corner;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
&.expired {
|
||||
|
|
|
|||
Loading…
Reference in a new issue