+ {!isDesktop && !sidebarMobileVisible ? (
+
+ ) : null}
+
{selectedId == null && (
No user selected
)}
+
+ {selectedId != null && userQuery.isPending && (
+
+ Loading user details...
+
+ )}
+
+ {selectedId != null && userQuery.error && (
+
+ Error:{' '}
+ {userQuery.error instanceof Error
+ ? userQuery.error.message
+ : 'Unknown error'}
+
+ )}
+
{selectedId && userQuery.data && (
<>
@@ -660,6 +721,6 @@ export default function Page() {
>
)}
- >
+
)
}
diff --git a/src/app/admin/panels/members/panel_views/DonorView.module.css b/src/app/admin/panels/members/panel_views/DonorView.module.css
index cc99eeea..362c4709 100644
--- a/src/app/admin/panels/members/panel_views/DonorView.module.css
+++ b/src/app/admin/panels/members/panel_views/DonorView.module.css
@@ -173,3 +173,199 @@
color: #475569;
padding-right: 1rem;
}
+
+.modalBackdrop {
+ position: fixed;
+ inset: 0;
+ z-index: 60;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.25rem;
+ background: rgba(2, 6, 23, 0.38);
+ backdrop-filter: blur(10px) saturate(140%);
+ -webkit-backdrop-filter: blur(10px) saturate(140%);
+ will-change: opacity, backdrop-filter;
+}
+
+.modal {
+ position: relative;
+ width: min(56rem, 100%);
+ height: min(80dvh, 48rem);
+ min-height: 18rem;
+ display: flex;
+ flex-direction: column;
+ border-radius: 1rem;
+ background: rgba(255, 255, 255, 0.86);
+ backdrop-filter: blur(18px) saturate(160%);
+ -webkit-backdrop-filter: blur(18px) saturate(160%);
+ border: 1px solid rgba(17, 24, 39, 0.12);
+ box-shadow:
+ 0 1px 0 rgba(255, 255, 255, 0.75) inset,
+ 0 30px 80px rgba(15, 23, 42, 0.35);
+ overflow: hidden;
+ transform-origin: 50% 40%;
+ will-change: transform, opacity;
+}
+
+.modal::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ background: linear-gradient(
+ to bottom,
+ rgba(255, 255, 255, 0.55),
+ rgba(255, 255, 255, 0) 22%
+ );
+ opacity: 0.35;
+}
+
+.modalHeader {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ padding: 0.95rem 1rem;
+ border-bottom: 1px solid rgba(17, 24, 39, 0.1);
+ background: rgba(255, 255, 255, 0.6);
+}
+
+.modalHeaderLeft {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ min-width: 0;
+}
+
+.modalTitle {
+ font-size: 1.05rem;
+ font-weight: 650;
+ letter-spacing: -0.01em;
+ color: #0f172a;
+}
+
+.modalSubtitle {
+ font-size: 0.92rem;
+ color: #475569;
+ line-height: 1.35;
+}
+
+.modalHeaderRight {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ flex-shrink: 0;
+}
+
+.modalBody {
+ flex: 1;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ overflow: auto;
+ padding: 0;
+ background: rgba(255, 255, 255, 0.62);
+ backdrop-filter: blur(18px) saturate(160%);
+ -webkit-backdrop-filter: blur(18px) saturate(160%);
+ font-weight: 500;
+ padding-inline: 1rem;
+}
+
+.modalBody :global(.pinned),
+.modalBody :global(.elementList),
+.modalBody :global(.listStatus) {
+ padding: 0.9rem 1rem 1rem;
+}
+
+.modalBody :global(.listStatus) {
+ flex: 1;
+ min-height: 0;
+}
+
+.modalFooter {
+ padding: 0.5rem 1rem;
+ border-top: 1px solid rgba(17, 24, 39, 0.1);
+ background: rgba(255, 255, 255, 0.6);
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.6rem;
+}
+
+.modalFooterButton {
+ min-width: 7rem;
+}
+
+.modalSearch {
+ width: min(22rem, 52vw);
+}
+
+.searchInputBare {
+ display: flex;
+ width: 100%;
+ align-items: center;
+}
+
+.searchInputBare > input {
+ width: 100%;
+ height: 2.35rem;
+ line-height: 2.35rem;
+ border-radius: 0.6rem;
+ border: 1px solid #d1d5db;
+ padding: 0 0.75rem;
+ background: rgba(255, 255, 255, 0.75);
+}
+
+.detailsNavigationButton {
+ width: auto;
+ display: inline-block;
+ margin-top: 0.35rem;
+}
+
+.detailsNavigationLink {
+ display: inline-flex;
+ width: auto;
+ align-items: center;
+ justify-content: center;
+ padding: 0.28rem 0.75rem;
+ border-radius: 9999px;
+ background: rgba(14, 165, 233, 0.12);
+ color: rgba(2, 132, 199, 1);
+ font-size: 0.7rem;
+ font-weight: 700;
+ letter-spacing: 0.02em;
+ text-transform: uppercase;
+}
+
+.detailsNavigationLink:hover {
+ background: rgba(14, 165, 233, 0.18);
+}
+
+.detailsNavigationLink:active {
+ transform: translateY(0) scale(0.99);
+}
+
+.detailsNavigationLabel {
+ flex: 0 0 auto;
+ width: auto;
+ max-width: none;
+ color: inherit;
+ font-size: inherit;
+ font-weight: inherit;
+ letter-spacing: inherit;
+ white-space: nowrap;
+}
+
+.detailsNavigationTagSection {
+ display: none;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .modalBackdrop {
+ will-change: auto;
+ }
+
+ .modal {
+ will-change: auto;
+ }
+}
diff --git a/src/app/admin/panels/members/panel_views/DonorView.tsx b/src/app/admin/panels/members/panel_views/DonorView.tsx
index d30b484f..f52d9bae 100644
--- a/src/app/admin/panels/members/panel_views/DonorView.tsx
+++ b/src/app/admin/panels/members/panel_views/DonorView.tsx
@@ -9,6 +9,7 @@ import {
FormGroup,
TextField,
} from '@/components/common/forms'
+import { NavigationButton } from '@/components/common/navigation_stack/navigation_button/NavigationButton'
import {
ActBlueContribution,
ActBlueContributionCustomField,
@@ -99,6 +100,12 @@ const calcContributionData = (donor: ActBlueDonor): ContributionData => {
}
}
+const formatLineitemDate = (value: Date) =>
+ Intl.DateTimeFormat('en-US', {
+ dateStyle: 'medium',
+ timeStyle: 'short',
+ }).format(value)
+
export function DonorView({
selectedId,
user,
@@ -366,17 +373,112 @@ export function DonorView({
/>
-
- Open in Donors Panel
-
+ {(
+ contributionData.lineitems ??
+ []
+ ).map((lineitem) => (
+
+
+ label="Paid At"
+ getter={() =>
+ lineitem.paidAt
+ }
+ />
+
+ label="Sequence"
+ getter={() =>
+ `${lineitem.sequence}`
+ }
+ />
+
+ label="Amount"
+ getter={() =>
+ `$${lineitem.amount}`
+ }
+ />
+
+ label="Recurring Amount"
+ getter={() =>
+ `$${lineitem.recurringAmount}`
+ }
+ />
+
+ label="Amount Less AB Fees"
+ getter={() =>
+ `$${lineitem.amountLessAbFees}`
+ }
+ />
+
+ donor
+ .contributions?.[0]
+ ?.contributionForm
+ }
+ />
+ {contributionData.customFields?.map(
+ (field) => (
+
+ field.answer
+ }
+ />
+ )
+ )}
+
+
+
+ ))}
+
diff --git a/src/app/admin/panels/permissions/page.module.css b/src/app/admin/panels/permissions/page.module.css
index 3a109c84..53a348c1 100644
--- a/src/app/admin/panels/permissions/page.module.css
+++ b/src/app/admin/panels/permissions/page.module.css
@@ -1,3 +1,143 @@
+.sidebarBg {
+ --navigation-stack-sidebar-bg: #f9fafb;
+ --navigation-stack-sidebar-padding: 0rem;
+ --navigation-stack-sidebar-gap: 0rem;
+}
+
+.sidebarSearchInput {
+ width: 100%;
+ border-radius: 0.6rem;
+ border: 1px solid #d1d5db;
+ padding: 0.35rem 0.75rem;
+ color: rgba(15, 23, 42, 0.85);
+ background: rgba(255, 255, 255, 0.9);
+}
+
+.sidebarSearchInput:focus {
+ border-color: rgba(14, 165, 233, 0.45);
+ box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.12);
+ outline: none;
+}
+
+.searchInput {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ gap: 0.5rem;
+ padding-inline: 0.5rem;
+}
+
+.searchInput > input {
+ width: 100%;
+ border-radius: 0.6rem;
+ border-width: 1px;
+ border-style: solid;
+ border-color: #d1d5db;
+ padding: 0.25rem 0.75rem;
+}
+
+.searchInput > button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+
+ border: 0;
+ background: transparent;
+ border-radius: 0.85rem;
+
+ padding: 0.5rem;
+
+ cursor: pointer;
+ color: rgba(15, 23, 42, 0.62);
+
+ gap: 0.5rem;
+}
+
+.searchInput > button:hover {
+ background: rgba(15, 23, 42, 0.06);
+}
+
+.iconToggleButton {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+
+ border: 0;
+ background: transparent;
+ border-radius: 0.85rem;
+
+ padding: 0.5rem;
+
+ cursor: pointer;
+ color: rgba(15, 23, 42, 0.62);
+}
+
+.iconToggleButton:hover {
+ background: rgba(15, 23, 42, 0.06);
+}
+
+.permissionNavigationButton {
+ --navigation-button-item-margin: 0.1rem 0.25rem;
+ --navigation-button-item-width: auto;
+ --navigation-button-item-radius: 1rem;
+ --navigation-button-link-gap: 1.25rem;
+ --navigation-button-link-padding: 1rem;
+ --navigation-button-link-radius: 1rem;
+ --navigation-button-link-color: #000;
+ --navigation-button-label-font-size: 1rem;
+ --navigation-button-label-font-weight: 500;
+ --navigation-button-label-letter-spacing: 0;
+ --navigation-button-label-color: #000;
+ --navigation-button-link-hover-bg: #e5e7eb;
+ --navigation-button-link-active-bg: #e5e7eb;
+ --navigation-button-link-active-color: #000;
+ --navigation-button-link-active-hover-bg: #d1d5db;
+}
+
+.sidebarList {
+ display: flex;
+ flex: 1 1 auto;
+ min-height: 0;
+ flex-direction: column;
+ gap: 0.25rem;
+ padding: 0.35rem;
+}
+
+.sidebarItem {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ gap: 0.75rem;
+ border: 0;
+ border-radius: 1rem;
+ background: transparent;
+ margin: 0;
+ padding: 0.85rem 0.9rem;
+ text-align: left;
+ cursor: pointer;
+}
+
+.sidebarItem:hover {
+ background-color: #e5e7eb;
+}
+
+.sidebarItem[data-selected='true'] {
+ background-color: #e5e7eb;
+}
+
+.sidebarItem[data-selected='true']:hover {
+ background-color: #d1d5db;
+}
+
+.sidebarState {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 7rem;
+ color: rgba(15, 23, 42, 0.64);
+ text-align: center;
+}
+
.detailPane {
height: calc(100dvh - 100px);
flex: 1 1 auto;
@@ -6,9 +146,63 @@
padding-block: 1rem;
}
-.listItemText {
+.mobileBackButton {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ margin: 0 1rem 1rem;
+ border: 0;
+ background: transparent;
+ border-radius: 0.5rem;
+ padding: 0.6rem 0.65rem;
+ color: #2986cc;
+ font: inherit;
+ font-weight: 500;
+ line-height: 1;
+ cursor: pointer;
+}
+
+.mobileBackButton::before {
+ content: '';
+ display: inline-block;
+ width: 0.58rem;
+ height: 0.58rem;
+ border-left: 0.14rem solid currentColor;
+ border-bottom: 0.14rem solid currentColor;
+ transform: rotate(45deg);
+ position: relative;
+ top: -0.5px;
+ border-bottom-left-radius: 0.04rem;
+}
+
+.mobileBackButton:hover {
+ color: #549ed6;
+ background: rgba(15, 23, 42, 0.05);
+}
+
+.element {
+ display: flex;
+ cursor: pointer;
+ align-items: center;
+ gap: 1.25rem;
+ padding: 1rem;
+ border-radius: 1rem;
+ margin: 0.25rem;
font-weight: 500;
color: #000;
+ background-color: #e5e7eb;
+}
+
+.element:hover {
+ background-color: #e5e7eb;
+}
+
+.element.selected {
+ background-color: #e5e7eb;
+}
+
+.element.selected:hover {
+ background-color: #d1d5db;
}
.emptyState {
@@ -17,3 +211,112 @@
align-items: center;
justify-content: center;
}
+
+.filterOverlay {
+ min-width: 16rem;
+}
+
+.filterOverlayBody {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ padding: 0.75rem;
+}
+
+.filterSelect {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.filterSelect > strong {
+ font-weight: 500;
+ color: rgba(15, 23, 42, 0.75);
+}
+
+.select {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.5rem;
+}
+
+.select > span {
+ font-weight: 500;
+ letter-spacing: -0.01em;
+ color: rgba(15, 23, 42, 0.7);
+}
+
+.select > select {
+ border-radius: 0.85rem;
+ border: 1px solid rgba(17, 24, 39, 0.12);
+ background: rgba(255, 255, 255, 0.75);
+ padding: 0.35rem 0.55rem;
+ color: rgba(15, 23, 42, 0.8);
+ outline: none;
+}
+
+.pageSelectButtons {
+ margin-left: auto;
+ margin-right: auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.25rem;
+ padding: 0.9rem;
+}
+
+.pageSelectForm {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+ color: rgba(15, 23, 42, 0.72);
+}
+
+.pageSelectInput {
+ width: 6ch;
+ max-width: min-content;
+ border-radius: 0.65rem;
+ border-width: 1px;
+ border-color: #d1d5db;
+ padding: 0.125rem 0.5rem;
+ text-align: center;
+ color: rgba(15, 23, 42, 0.62);
+}
+
+.pageSelectInput:focus {
+ border-color: rgba(14, 165, 233, 0.35);
+ box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.12);
+}
+
+.arrow {
+ display: flex;
+ user-select: none;
+ align-items: center;
+ justify-content: center;
+ border-radius: 0.85rem;
+ padding: 0.45rem;
+
+ font-weight: 600;
+}
+
+.arrow.disabled {
+ cursor: not-allowed;
+ color: rgba(15, 23, 42, 0.28);
+}
+
+.arrow.enabled {
+ cursor: pointer;
+ color: rgba(15, 23, 42, 0.62);
+}
+
+.arrow.enabled:hover {
+ background: rgba(15, 23, 42, 0.06);
+}
+
+.arrow.enabled:active {
+ background-color: #f3f4f6;
+}
diff --git a/src/app/admin/panels/permissions/page.tsx b/src/app/admin/panels/permissions/page.tsx
index 89e9d1cd..9e72f25f 100644
--- a/src/app/admin/panels/permissions/page.tsx
+++ b/src/app/admin/panels/permissions/page.tsx
@@ -1,13 +1,14 @@
'use client'
import styles from './page.module.css'
-import { ListElement, List } from '@/app/admin/layout/List'
import {
Form,
FormGroup,
FormState,
TextField,
} from '@/components/common/forms'
+import Panel from '@/components/common/panel/Panel'
+import { SidebarBody } from '@/components/common/panel/sidebar_list/SidebarBody'
import { Permission, zPermission } from '@/contracts/data'
import { SortDirection, UpdatePermissionRequest } from '@/contracts/requests'
import { PaginatedResponse } from '@/contracts/responses'
@@ -15,6 +16,7 @@ import { FetchError } from '@/models'
import { useFetch, usePaginatedSearch } from '@/util/hooks'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useState } from 'react'
+import { useMediaQuery } from 'usehooks-ts'
export default function Page() {
const queryClient = useQueryClient()
@@ -22,6 +24,8 @@ export default function Page() {
const [selectedPermission, setSelectedPermission] =
useState