+ {#each phantomClaims as phantom}
+ {@const crew = phantom.crew}
+ {@const isProcessing = processingId === phantom.id}
- {#if crew}
-
-
-
-
-
{crew.name}
- {#if crew.gamertag}
-
[{crew.gamertag}]
- {/if}
+ {#if crew}
+
+
+
+
+ {phantom.name}
+
+
+ From crew {crew.name}{crew.gamertag ? ` [${crew.gamertag}]` : ''}
+
+ {#if phantom.joinedAt}
+
+ Joined {formatDate(phantom.joinedAt)}
+
+ {/if}
+
- {#if invitedBy}
-
- Invited by {invitedBy.username}
-
- {/if}
-
- {#if expired}
-
Expired
- {:else}
-
- Expires {formatDate(invitation.expiresAt)}
+
+
+
- {/if}
-
-
- {#if !expired}
-
-
-
{/if}
-
- {/if}
- {/each}
-
+ {/each}
+
+
+ {/if}
+
+
+ {#if hasInvitations}
+
+
Crew Invitations
+
+ {#each invitations as invitation}
+ {@const expired = isExpired(invitation.expiresAt)}
+ {@const crew = invitation.crew}
+ {@const invitedBy = invitation.invitedBy}
+ {@const isProcessing = processingId === invitation.id}
+
+ {#if crew}
+
+
+
+
+ {crew.name}
+ {#if crew.gamertag}
+ [{crew.gamertag}]
+ {/if}
+
+ {#if invitedBy}
+
+ Invited by {invitedBy.username}
+
+ {/if}
+
+
+ {#if expired}
+
Expired
+ {:else}
+
+ Expires {formatDate(invitation.expiresAt)}
+
+ {/if}
+
+
+ {#if !expired}
+
+
+
+
+ {/if}
+
+ {/if}
+ {/each}
+
+
+ {/if}
{/if}
@@ -190,13 +298,34 @@
}
}
- .invitations-list {
+ .section {
+ &:not(:first-child) {
+ margin-top: spacing.$unit-3x;
+ padding-top: spacing.$unit-3x;
+ border-top: 1px solid var(--border-color);
+ }
+ }
+
+ .section-title {
+ margin: 0 0 spacing.$unit-half 0;
+ font-size: typography.$font-regular;
+ font-weight: typography.$medium;
+ color: var(--text-primary);
+ }
+
+ .section-description {
+ margin: 0 0 spacing.$unit-2x 0;
+ font-size: typography.$font-small;
+ color: var(--text-secondary);
+ }
+
+ .notifications-list {
display: flex;
flex-direction: column;
gap: spacing.$unit;
}
- .invitation-card {
+ .notification-card {
background: var(--surface-secondary, #f9fafb);
border: 1px solid var(--border-color);
border-radius: 8px;
@@ -207,7 +336,7 @@
}
}
- .invitation-content {
+ .notification-content {
display: flex;
justify-content: space-between;
align-items: flex-start;
@@ -215,19 +344,19 @@
margin-bottom: spacing.$unit;
}
- .crew-info {
+ .notification-info {
display: flex;
flex-direction: column;
gap: spacing.$unit-quarter;
}
- .crew-name-row {
+ .notification-title-row {
display: flex;
align-items: baseline;
gap: spacing.$unit-half;
}
- .crew-name {
+ .notification-title {
font-weight: typography.$medium;
color: var(--text-primary);
}
@@ -237,11 +366,16 @@
font-size: typography.$font-small;
}
- .invited-by {
+ .notification-subtitle {
font-size: typography.$font-small;
color: var(--text-secondary);
}
+ .notification-meta {
+ font-size: typography.$font-tiny;
+ color: var(--text-tertiary);
+ }
+
.expires-info {
font-size: typography.$font-tiny;
color: var(--text-secondary);
@@ -257,7 +391,7 @@
font-weight: typography.$medium;
}
- .invitation-actions {
+ .notification-actions {
display: flex;
justify-content: flex-end;
gap: spacing.$unit;
diff --git a/src/routes/(app)/crew/members/+page.svelte b/src/routes/(app)/crew/members/+page.svelte
index 9bb62215..b3f48b39 100644
--- a/src/routes/(app)/crew/members/+page.svelte
+++ b/src/routes/(app)/crew/members/+page.svelte
@@ -8,7 +8,8 @@
import {
useRemoveMember,
useUpdateMembership,
- useDeletePhantom
+ useDeletePhantom,
+ useDeclinePhantomClaim
} from '$lib/api/mutations/crew.mutations'
import { crewAdapter } from '$lib/api/adapters/crew.adapter'
import { crewStore } from '$lib/stores/crew.store.svelte'
@@ -83,6 +84,7 @@
const removeMemberMutation = useRemoveMember()
const updateMembershipMutation = useUpdateMembership()
const deletePhantomMutation = useDeletePhantom()
+ const declinePhantomClaimMutation = useDeclinePhantomClaim()
// Filter options - Pending only shown to officers
const filterOptions = $derived.by(() => {
@@ -266,12 +268,26 @@
assignPhantomDialogOpen = true
}
- // Confirm claim
+ // Confirm claim (opens modal)
function openConfirmClaimDialog(phantom: PhantomPlayer) {
phantomToClaim = phantom
confirmClaimDialogOpen = true
}
+ // Decline claim (direct action, no confirmation needed)
+ async function handleDeclineClaim(phantom: PhantomPlayer) {
+ if (!crewStore.crew) return
+
+ try {
+ await declinePhantomClaimMutation.mutateAsync({
+ crewId: crewStore.crew.id,
+ phantomId: phantom.id
+ })
+ } catch (error) {
+ console.error('Failed to decline phantom claim:', error)
+ }
+ }
+
// Format date
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString(undefined, {
@@ -417,7 +433,8 @@
onEdit={() => openEditPhantomDialog(phantom)}
onDelete={() => openDeletePhantomDialog(phantom)}
onAssign={() => openAssignPhantomDialog(phantom)}
- onConfirmClaim={() => openConfirmClaimDialog(phantom)}
+ onAccept={() => openConfirmClaimDialog(phantom)}
+ onDecline={() => handleDeclineClaim(phantom)}
/>
{/each}
@@ -438,7 +455,8 @@
onEdit={() => openEditPhantomDialog(phantom)}
onDelete={() => openDeletePhantomDialog(phantom)}
onAssign={() => openAssignPhantomDialog(phantom)}
- onConfirmClaim={() => openConfirmClaimDialog(phantom)}
+ onAccept={() => openConfirmClaimDialog(phantom)}
+ onDecline={() => handleDeclineClaim(phantom)}
/>
{/each}