Skip to content

Improve component logic and enhance user experience#331

Merged
Starefossen merged 16 commits intomainfrom
storybook-update
Feb 13, 2026
Merged

Improve component logic and enhance user experience#331
Starefossen merged 16 commits intomainfrom
storybook-update

Conversation

@Starefossen
Copy link
Copy Markdown
Member

@Starefossen Starefossen commented Feb 12, 2026

User description

Refactor the DraggableProposal component for better readability, add confirmation modals for delete actions across multiple pages, and enhance the status badge implementation. Implement AttachmentManager and ProposalAttachmentsPanel for improved documentation and testing. Enhance ProgramFilters with a new SearchInput component for better search functionality and update ProgramSystem documentation with detailed architecture descriptions.


PR Type

Enhancement, Tests, Documentation


Description

  • Comprehensive Storybook migration: Added 30+ new story files documenting components across admin, proposal, travel-support, and schedule modules with mock data and multiple variants

  • Modal refactoring: Replaced Headless UI Dialog, DialogPanel, Transition, and TransitionChild components with new ModalShell wrapper component across 7 modal components, reducing boilerplate and improving consistency

  • Component extraction and consolidation: Created NotificationToast component extracted from NotificationProvider, refactored StatusBadge usage across multiple pages to use shared component with semantic BadgeColor type

  • Enhanced confirmation UX: Added ConfirmationModal for delete actions in VolunteerAdminPage and BadgeManagementClient instead of browser confirm()

  • Search input standardization: Replaced manual search input implementation in SpeakerMultiSelect with new reusable SearchInput component

  • Architecture documentation: Enhanced ProgramSystem documentation with detailed component hierarchy, hooks, utilities, and schedule library sections

  • Development tooling: Added simple-git-hooks for pre-commit hooks and updated build scripts to use concurrently for parallel execution with caching support


Diagram Walkthrough

flowchart LR
  A["Headless UI<br/>Dialog/Transition"] -->|"Replace with"| B["ModalShell<br/>Wrapper"]
  C["Manual StatusBadge<br/>Implementations"] -->|"Consolidate to"| D["Shared StatusBadge<br/>Component"]
  E["Browser confirm()"] -->|"Replace with"| F["ConfirmationModal<br/>Component"]
  G["Manual SearchInput"] -->|"Replace with"| H["SearchInput<br/>Component"]
  I["NotificationProvider"] -->|"Extract"| J["NotificationToast<br/>Component"]
  K["Components"] -->|"Document with"| L["30+ Storybook<br/>Stories"]
  B --> M["Reduced Boilerplate<br/>& Consistency"]
  D --> M
  F --> M
  H --> M
  L --> N["Enhanced<br/>Documentation"]
Loading

File Walkthrough

Relevant files
Enhancement
8 files
ImageMetadataModal.tsx
Refactor modal to use ModalShell wrapper component             

src/components/admin/gallery/ImageMetadataModal.tsx

  • Replaced Headless UI Dialog, DialogPanel, Transition, and
    TransitionChild components with new ModalShell wrapper component
  • Removed useTheme hook dependency and manual dark mode class handling
  • Simplified modal structure by removing nested transition and dialog
    panel layers
  • Maintained all form functionality and styling while reducing
    boilerplate code
+416/-461
SponsorAddModal.tsx
Refactor sponsor modal to use ModalShell component             

src/components/admin/sponsor/SponsorAddModal.tsx

  • Replaced Headless UI Dialog, DialogPanel, Transition, and
    TransitionChild with ModalShell component
  • Removed useTheme hook and associated dark mode class logic
  • Simplified modal structure by eliminating nested transition layers
  • Preserved all form fields, sponsor selection, and logo editor
    functionality
+272/-316
EmailModal.tsx
Refactor email modal to use ModalShell wrapper                     

src/components/admin/EmailModal.tsx

  • Replaced Headless UI Dialog, DialogPanel, DialogTitle, Transition, and
    TransitionChild with ModalShell component
  • Removed useTheme hook and manual dark mode handling
  • Simplified modal structure while preserving email editor, preview, and
    draft storage features
  • Updated modal to use ModalShell with custom sizing and padding options
+236/-279
SpeakerProfilePreview.tsx
Refactor speaker profile modal to use ModalShell                 

src/components/SpeakerProfilePreview.tsx

  • Replaced Headless UI Dialog, DialogPanel, DialogTitle, Transition, and
    TransitionChild with ModalShell component
  • Removed useTheme hook and dark mode class management
  • Simplified modal structure while maintaining speaker profile display,
    bio, talks, and social links
  • Updated to use ModalShell with custom sizing and padding configuration
+217/-265
SpeakerMultiSelect.tsx
Replace search input with SearchInput component                   

src/components/admin/SpeakerMultiSelect.tsx

  • Replaced manual search input with new SearchInput component
  • Removed MagnifyingGlassIcon import and manual icon positioning
  • Updated search input to use SearchInput component with custom
    className prop
  • Simplified search functionality by delegating to reusable component
+4/-6     
NotificationToast.tsx
Create NotificationToast component with styling                   

src/components/admin/NotificationToast.tsx

  • New component extracted from NotificationProvider for independent use
    and testing
  • Implements notification toast UI with icon, title, message, and
    dismiss button
  • Supports four notification types: success, error, warning, info with
    distinct styling
  • Includes count badge for duplicate notifications
+142/-0 
VolunteerAdminPage.tsx
Add confirmation modal and refactor status badges               

src/components/volunteer/VolunteerAdminPage.tsx

  • Added ConfirmationModal for delete volunteer action instead of browser
    confirm()
  • Extracted volunteer status badge configuration into
    volunteerStatusConfig object
  • Refactored StatusBadge to use shared component with BadgeColor type
  • Added state management for delete confirmation with deleteVolunteerId
+41/-37 
BadgeManagementClient.tsx
Add confirmation modal for badge deletion                               

src/components/admin/BadgeManagementClient.tsx

  • Added ConfirmationModal for delete badge action instead of browser
    confirm()
  • Added state management for delete confirmation with deleteBadgeInfo
  • Changed import path for ActionMenu from admin to root components
    folder
  • Maintains same delete functionality with improved UX
+22/-9   
Tests
20 files
DashboardLayout.stories.tsx
Add DashboardLayout Storybook stories and documentation   

src/components/common/DashboardLayout.stories.tsx

  • Added comprehensive Storybook stories for DashboardLayout component
    with full documentation
  • Implemented mock dashboard component supporting both admin and speaker
    modes with different color schemes
  • Created stories for admin mode, speaker mode, and admin with realistic
    content
  • Included detailed parameter descriptions and component behavior
    documentation
+339/-0 
ExpenseSummary.stories.tsx
Add ExpenseSummary Storybook stories with mock component 

src/components/travel-support/ExpenseSummary.stories.tsx

  • New Storybook story file for ExpenseSummary component
  • Includes mock component rendering expense totals (approved, pending,
    rejected) with currency formatting
  • Demonstrates multiple story variants: Default, AllApproved,
    MultipleCurrencies, and Empty states
  • Shows currency conversion breakdown and dark mode support
+182/-0 
DraggableProposal.stories.tsx
Add DraggableProposal Storybook stories with drag context

src/components/admin/schedule/DraggableProposal.stories.tsx

  • New Storybook story file for DraggableProposal component used in
    schedule editor
  • Includes mock proposal data and topic definitions
  • Demonstrates variants: Presentation45, LightningTalk, Presentation25,
    Workshop, AcceptedNotConfirmed, WithdrawnProposal, NoTopics, and
    Dragging states
  • Wrapped in DndContext decorator for drag-and-drop functionality
+203/-0 
DroppableTrack.stories.tsx
Add DroppableTrack Storybook stories with mock data           

src/components/admin/schedule/DroppableTrack.stories.tsx

  • New Storybook story file for DroppableTrack component in schedule
    editor
  • Creates mock proposals and track configurations with various talk
    arrangements
  • Demonstrates variants: EmptyTrack, WithTalks, and WithServiceSessions
  • Includes mock event handlers and DndContext decorator for
    drag-and-drop testing
+175/-0 
GalleryModal.stories.tsx
Add GalleryModal Storybook stories with carousel mock       

src/components/GalleryModal.stories.tsx

  • New Storybook story file for full-screen gallery modal component
  • Includes mock gallery images with photographer, location, and speaker
    metadata
  • Demonstrates carousel navigation, thumbnail strip, and speaker tag
    functionality
  • Shows keyboard and touch gesture support documentation
+143/-0 
ProposalDetailsForm.stories.tsx
Add ProposalDetailsForm Storybook stories with mock data 

src/components/proposal/ProposalDetailsForm.stories.tsx

  • New Storybook story file for ProposalDetailsForm component
  • Includes mock topics, conference data, and proposal examples (empty,
    filled, workshop)
  • Demonstrates variants: Empty, PreFilled, ReadOnly, and WorkshopFormat
    states
  • Shows form with all fields: title, language, description, format,
    level, audiences, topics, outline
+175/-0 
ProposalReviewPanel.stories.tsx
Add ProposalReviewPanel Storybook stories                               

src/components/admin/ProposalReviewPanel.stories.tsx

  • New Storybook story file for ProposalReviewPanel composite component
  • Includes mock reviewer data, reviews, and current user context
  • Demonstrates variants: WithReviews, EmptyReviews,
    WithExistingUserReview, and NoCurrentUser states
  • Wrapped in NotificationProvider decorator
+154/-0 
ProposalManagementModal.stories.tsx
Add ProposalManagementModal Storybook stories                       

src/components/admin/ProposalManagementModal.stories.tsx

  • New Storybook story file for ProposalManagementModal component
  • Includes mock conference, speaker, and proposal data for create/edit
    scenarios
  • Demonstrates variants: CreateNew, EditExisting, and Closed states
  • Shows modal with speaker multi-select and proposal details form
+158/-0 
AdminPageHeader.stories.tsx
Add AdminPageHeader Storybook stories                                       

src/components/admin/AdminPageHeader.stories.tsx

  • New Storybook story file for AdminPageHeader reusable component
  • Demonstrates variants with different feature combinations: basic,
    context highlight, stats, action items, back link
  • Shows responsive stat cards grid and action button layouts
  • Includes FullFeatured and ManyStats examples
+150/-0 
Header.stories.tsx
Add Header Storybook stories with auth states                       

src/components/Header.stories.tsx

  • New Storybook story file for main site Header component
  • Includes mock header with conference logo, date/location, registration
    button, theme toggle
  • Demonstrates variants: Default, LoggedIn, PastEvent, and
    PastEventLoggedIn states
  • Shows responsive layout and user authentication states
+137/-0 
ModalShell.stories.tsx
Add ModalShell Storybook stories with size variants           

src/components/ModalShell.stories.tsx

  • New Storybook story file for ModalShell shared modal wrapper component
  • Demonstrates size variants: Small, Medium, Large, ExtraLarge, and
    Unpadded
  • Shows HeadlessUI Dialog + Transition boilerplate usage
  • Includes examples of modal content layouts and button configurations
+145/-0 
ExpensesList.stories.tsx
Add ExpensesList Storybook stories with mock expenses       

src/components/travel-support/ExpensesList.stories.tsx

  • New Storybook story file for ExpensesList component
  • Includes mock travel expense data with various categories and statuses
  • Demonstrates variants: Default, Editable, EmptyState, and AllApproved
  • Shows expense items with status badges, amounts, receipts, and
    edit/delete actions
+136/-0 
AdminHeaderActions.stories.tsx
Add AdminHeaderActions Storybook stories                                 

src/components/admin/AdminHeaderActions.stories.tsx

  • New Storybook story file for AdminHeaderActions responsive button bar
    component
  • Demonstrates variants: PrimaryButtons, MixedVariants, WithLinks,
    WithDisabled, SingleAction, ManyActions
  • Shows desktop inline layout and mobile dropdown menu behavior
  • Includes icon, link, and disabled state examples
+148/-0 
ProposalActionModal.stories.tsx
Add ProposalActionModal Storybook stories                               

src/components/admin/ProposalActionModal.stories.tsx

  • New Storybook story file for ProposalActionModal component
  • Demonstrates action variants: AcceptAction, RejectAction,
    RemindAction, DeleteAction, SpeakerWithdraw, SpeakerSubmit
  • Includes mock proposal and speaker data
  • Shows admin and speaker UI modes with action-specific messaging
+140/-0 
NotificationToast.stories.tsx
Add NotificationToast Storybook stories                                   

src/components/admin/NotificationToast.stories.tsx

  • New Storybook story file for NotificationToast component
  • Demonstrates all notification types: Success, Error, Warning, Info
  • Shows variants: TitleOnly, WithCountBadge, and AllTypes comparison
  • Includes documentation for duplicate notification count badge behavior
+162/-0 
BadgePreviewModal.stories.tsx
Add BadgePreviewModal Storybook stories                                   

src/components/admin/BadgePreviewModal.stories.tsx

  • New Storybook story file for BadgePreviewModal component
  • Includes mock OpenBadge credential data with speaker and conference
    info
  • Demonstrates variants: Default, EmailNotSent, and EmailError states
  • Shows badge metadata, email delivery status, and JSON credential
    display
+106/-0 
SpeakerEmailModal.stories.tsx
Add SpeakerEmailModal Storybook stories                                   

src/components/admin/SpeakerEmailModal.stories.tsx

  • New Storybook story file for SpeakerEmailModal component
  • Includes mock proposal data and speaker lists (single and multiple)
  • Demonstrates variants: SingleSpeaker and MultipleSpeakers with
    greeting templates
  • Shows email composition with speaker-specific recipient display
+104/-0 
ProposalPublishedContent.stories.tsx
Add ProposalPublishedContent Storybook stories                     

src/components/admin/ProposalPublishedContent.stories.tsx

  • New Storybook story file for ProposalPublishedContent component
  • Includes mock attachments (video and slides)
  • Demonstrates variants: WithVideoAndSlides, NoContentYet,
    AcceptedStatus, ConferenceNotEnded, SubmittedStatus
  • Shows post-conference content management for accepted/confirmed
    proposals
+115/-0 
PaymentDetailsModal.stories.tsx
Add PaymentDetailsModal Storybook stories                               

src/components/admin/PaymentDetailsModal.stories.tsx

  • New Storybook story file for PaymentDetailsModal component
  • Includes mock CheckinPay order data with payment details
  • Demonstrates variants: Paid, Pending, Overdue, Loading, and Error
    states
  • Shows payment status, amounts, customer info, and important dates
+109/-0 
AttachmentManager.stories.tsx
Add AttachmentManager Storybook stories                                   

src/components/proposal/AttachmentManager.stories.tsx

  • New Storybook story file for AttachmentManager component
  • Includes mock file and URL attachments with various types (slides,
    recording, resource)
  • Demonstrates variants: Empty, WithAttachments, WithFileAttachment, and
    ReadOnly states
  • Shows file upload, drag-and-drop, and delete confirmation
    functionality
+102/-0 
Refactoring
7 files
BadgePreviewModal.tsx
Refactor BadgePreviewModal to use ModalShell component     

src/components/admin/BadgePreviewModal.tsx

  • Replaced Headlessui Dialog/Transition components with ModalShell
    wrapper for consistent modal styling
  • Removed useTheme hook dependency and manual dark mode class handling
  • Simplified modal structure by removing nested TransitionChild and
    DialogPanel elements
  • Updated image max-width from max-w-[300px] to max-w-75 (Tailwind
    class)
+203/-241
ProposalManagementModal.tsx
Refactor ProposalManagementModal to use ModalShell             

src/components/admin/ProposalManagementModal.tsx

  • Replaced Headlessui Dialog/Transition with ModalShell component for
    consistent modal behavior
  • Removed useTheme hook and manual dark mode styling
  • Simplified modal structure by eliminating nested transition elements
  • Added padded={false} to ModalShell for custom padding control
+103/-142
SpeakerManagementModal.tsx
Refactor SpeakerManagementModal to use ModalShell               

src/components/admin/SpeakerManagementModal.tsx

  • Replaced Headlessui Dialog/Transition with ModalShell component
  • Removed useTheme hook and manual dark mode class handling
  • Simplified modal structure by removing nested transition elements
  • Maintained form functionality and validation error display
+82/-120
ConfirmationModal.tsx
Refactor ConfirmationModal to use ModalShell                         

src/components/admin/ConfirmationModal.tsx

  • Replaced Headlessui Dialog/Transition with ModalShell component
  • Removed useTheme hook and manual dark mode styling
  • Simplified modal structure by eliminating nested transition elements
  • Maintained variant-based styling (danger, warning, success) and button
    states
+51/-85 
NotificationProvider.tsx
Extract NotificationToast component from provider               

src/components/admin/NotificationProvider.tsx

  • Refactored notification rendering logic into separate
    NotificationToast component
  • Removed inline icon and style mapping functions (getIcon, getStyles)
  • Simplified provider to import and use NotificationToast for cleaner
    separation of concerns
  • Maintains same notification context and API
+9/-131 
CompactProposalList.tsx
Refactor CompactProposalList to use shared StatusBadge     

src/components/cfp/CompactProposalList.tsx

  • Refactored StatusBadge from inline component to use shared StatusBadge
    component
  • Extracted status configuration logic into getProposalStatusConfig
    function
  • Changed from inline Tailwind classes to semantic BadgeColor type and
    reusable badge component
  • Maintains same visual appearance and functionality
+52/-82 
SpeakerTable.tsx
Refactor SpeakerTable to use shared StatusBadge                   

src/components/admin/SpeakerTable.tsx

  • Refactored StatusBadge from inline component to use shared StatusBadge
    component
  • Extracted status-to-color mapping into getProposalBadgeColor function
  • Changed import path for ActionMenu from admin to root components
    folder
  • Updated badge rendering to use semantic BadgeColor type
+30/-17 
Documentation
9 files
AdminLayout.stories.tsx
Add AdminLayout Storybook stories and documentation           

src/components/admin/AdminLayout.stories.tsx

  • New Storybook story file documenting the admin layout shell component
  • Includes mock navigation structure with four sections (Core, People,
    Events & Content, System)
  • Provides three story variants: Default, ProposalsPage, and
    SettingsPage
  • Documents sidebar, top bar, and main content area layout patterns
+270/-0 
ScheduleEditor.stories.tsx
Add ScheduleEditor Storybook stories with mock data           

src/components/admin/schedule/ScheduleEditor.stories.tsx

  • New comprehensive Storybook stories for the ScheduleEditor component
  • Includes mock data generators for proposals, conferences, and schedule
    structures
  • Provides three story variants: EmptySchedule, SingleDayWithTracks, and
    MultiDay
  • Documents drag-and-drop scheduling workflow with unassigned proposals
    sidebar
+330/-0 
ImageMetadataModal.stories.tsx
Add ImageMetadataModal Storybook stories                                 

src/components/admin/gallery/ImageMetadataModal.stories.tsx

  • New Storybook stories for ImageMetadataModal component
  • Documents single-image and bulk-edit modes with different field
    availability
  • Includes speaker tagging, hotspot editor preview, and notification
    checkbox
  • Provides SingleImage and BulkEdit story variants
+226/-0 
SearchModal.stories.tsx
Add SearchModal Storybook stories and states                         

src/components/admin/SearchModal.stories.tsx

  • New Storybook stories for the command palette-style SearchModal
    component
  • Documents search states: empty, results, no-results, and error
  • Shows result grouping by Talks and Workshops with status badges
  • Includes keyboard shortcut documentation (⌘K, ↵, Esc)
+214/-0 
ProgramSystem.stories.tsx
Enhance ProgramSystem architecture documentation                 

src/docs/ProgramSystem.stories.tsx

  • Enhanced Architecture section with detailed component hierarchy
    diagram
  • Added individual component cards documenting ScheduleEditor,
    DroppableTrack, DraggableProposal, DraggableServiceSession, and
    UnassignedProposals
  • Added Hooks & Utilities section documenting useScheduleEditor,
    usePerformanceTimer, useDragPerformance, useBatchUpdates
  • Added Schedule Library section documenting types, client, and
    performance utilities
+147/-32
UnassignedProposals.stories.tsx
Add UnassignedProposals Storybook stories                               

src/components/admin/schedule/UnassignedProposals.stories.tsx

  • New Storybook stories for UnassignedProposals sidebar component
  • Includes mock proposal data with various formats, levels, topics, and
    audiences
  • Provides four story variants: WithProposals, Empty, SingleProposal,
    MixedStatuses
  • Documents search, filter, and virtual scrolling functionality
+283/-0 
ProposalDetail.stories.tsx
Add ProposalDetail Storybook stories                                         

src/components/admin/ProposalDetail.stories.tsx

  • New Storybook stories for ProposalDetail component
  • Includes mock data for proposals, speakers, and topics
  • Provides seven story variants covering different proposal types and
    speaker scenarios
  • Documents speaker cards, metadata sidebar, and other submissions
    section
+245/-0 
SpeakerManagementModal.stories.tsx
Add SpeakerManagementModal Storybook stories                         

src/components/admin/SpeakerManagementModal.stories.tsx

  • New Storybook stories for SpeakerManagementModal component
  • Documents create and edit modes with form fields and validation
  • Includes email input, SpeakerDetailsForm integration, image upload,
    and consent checkboxes
  • Provides CreateNew and EditExisting story variants
+175/-0 
SignupDetailsModal.stories.tsx
Add SignupDetailsModal Storybook stories                                 

src/components/admin/workshop/SignupDetailsModal.stories.tsx

  • New Storybook stories for SignupDetailsModal component
  • Includes mock workshop signup data with confirmed and waitlist
    statuses
  • Provides four story variants: Confirmed, Waitlist, Empty, and
    Confirming
  • Documents signup table display and admin actions (confirm, delete)
+128/-0 
Configuration changes
1 files
package.json
Add concurrent checks and git hooks                                           

package.json

  • Updated check script to use concurrently for parallel execution of
    typecheck, lint, knip, and format
  • Added --cache flag to lint and lint:fix commands for faster subsequent
    runs
  • Added --cache flag to format:check and format commands
  • Added simple-git-hooks dependency with pre-commit hook configuration
+9/-5     
Dependencies
1 files
pnpm-lock.yaml
Update lock file with simple-git-hooks                                     

pnpm-lock.yaml

  • Added lock entry for simple-git-hooks@2.13.1 dependency
  • Updated dependency tree to reflect new package addition
+9/-0     
Additional files
39 files
AGENTS.md +1/-0     
README.md +26/-0   
page.tsx +4/-2     
ActionMenu.stories.tsx +97/-0   
ActionMenu.tsx [link]   
CallToAction.stories.tsx +109/-0 
LoadingSpinner.tsx +21/-0   
ModalShell.tsx +84/-0   
SearchInput.stories.tsx +44/-0   
SearchInput.tsx +50/-0   
StatusBadge.stories.tsx +71/-0   
StatusBadge.tsx +44/-0   
AudienceFeedbackPanel.stories.tsx +99/-0   
BackToProposalsButton.tsx +0/-42   
DiscountCodeManager.tsx +4/-2     
FeaturedSpeakersManager.tsx +4/-7     
FeaturedTalksManager.tsx +4/-7     
GeneralBroadcastModal.stories.tsx +80/-0   
OrdersTableWithSearch.tsx +9/-13   
WidgetConfigModal.stories.tsx +67/-0   
index.ts +0/-2     
DraggableProposal.tsx +9/-8     
DraggableServiceSession.stories.tsx +89/-0   
AddParticipantModal.stories.tsx +40/-0   
EditCapacityModal.stories.tsx +53/-0   
WorkshopsClientPage.tsx +22/-7   
ProgramFilters.tsx +7/-18   
ProposalAttachmentsPanel.stories.tsx +80/-0   
BankingDetailsDisplay.stories.tsx +114/-0 
BankingDetailsForm.stories.tsx +79/-0   
ErrorComponents.stories.tsx +122/-0 
ExpenseForm.stories.tsx +86/-0   
ExpensesList.tsx +11/-21 
LoadingStates.stories.tsx +39/-0   
LoadingStates.tsx +3/-21   
ReceiptViewer.stories.tsx +66/-0   
StatusBadge.stories.tsx +59/-0   
StatusBadge.tsx +6/-18   
TravelSupportPageClient.tsx +44/-12 

…ge, TravelSupportPageClient, and VolunteerAdminPage

refactor: improve status badge implementation in CompactProposalList, ExpensesList, and TravelSupport components

feat: implement AttachmentManager and ProposalAttachmentsPanel stories for better documentation and testing

refactor: enhance ProgramFilters with SearchInput component for improved search functionality

chore: update ProgramSystem documentation with detailed component architecture and descriptions
@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cloudnativedays Ready Ready Preview, Comment Feb 13, 2026 9:51am

Request Review

feat: create DashboardLayout stories for admin and speaker modes

feat: implement BankingDetailsDisplay stories for travel support

feat: add BankingDetailsForm stories for managing banking details

feat: create ErrorComponents stories for handling errors in travel support

feat: implement ExpenseForm stories for adding and editing expenses

feat: add ExpenseSummary stories for displaying expense totals

feat: create ExpensesList stories for rendering travel expenses

feat: implement LoadingStates stories for loading indicators in travel support

feat: add ReceiptViewer stories for displaying receipts

feat: create StatusBadge stories for visualizing travel support statuses
@qodo-code-review
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 Security concerns

Unsafe URL scheme:
In src/components/SpeakerProfilePreview.tsx, href={link} renders user/data-provided URLs directly. If link can be influenced by untrusted input, it could include javascript: or other unsafe schemes. Consider validating/allowlisting schemes (e.g., only https?) before rendering and/or sanitizing the value.

⚡ Recommended focus areas for review

Security

The speaker social links are rendered directly into anchor href from data. If the input is not strictly validated, this can allow unsafe URL schemes (e.g., javascript:) or unexpected protocols. Consider normalizing/allowlisting http/https (and possibly mailto) before rendering.

{speaker.links.map((link: string) => {
  const label = titleForLink(link)

  return (
    <a
      key={link}
      href={link}
      target="_blank"
      rel="noopener noreferrer"
      className="flex h-10 w-10 items-center justify-center rounded-full bg-white shadow-md transition-all hover:scale-105 hover:shadow-lg dark:bg-gray-800 dark:hover:bg-gray-700"
      title={label}
    >
      {iconForLink(
        link,
        'h-5 w-5 text-brand-slate-gray dark:text-gray-300',
      )}
    </a>
  )
})}
Accessibility

The spinner is a purely visual div with no ARIA semantics. Consider adding role="status" and an accessible name (e.g., aria-label="Loading" or visually hidden text) so screen readers announce loading state appropriately.

interface LoadingSpinnerProps {
  size?: 'sm' | 'md' | 'lg'
  className?: string
}

export function LoadingSpinner({
  size = 'md',
  className = '',
}: LoadingSpinnerProps) {
  const sizeClasses = {
    sm: 'h-4 w-4',
    md: 'h-8 w-8',
    lg: 'h-12 w-12',
  }

  return (
    <div
      className={`animate-spin rounded-full border-b-2 border-indigo-600 dark:border-indigo-400 ${sizeClasses[size]} ${className}`}
    />
  )
Modal UX

With the move to ModalShell, verify focus management and scroll behavior: the modal content is set to max-h-screen overflow-y-auto, which can lead to nested scrolling depending on ModalShell implementation. Ensure the scroll container is consistent, focus is trapped, and onClose is not triggered unexpectedly during form interactions.

<ModalShell
  isOpen={isOpen}
  onClose={onClose}
  size="2xl"
  className="max-h-screen overflow-y-auto rounded-xl shadow-lg"
>
  <div className="flex items-center justify-between">
    <h3 className="text-lg leading-6 font-semibold text-gray-900 dark:text-white">
      {isBulkMode
        ? `Bulk Update ${images!.length} Images`
        : 'Edit Image Metadata'}
    </h3>
    <button
      type="button"
      onClick={onClose}
      className="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none dark:bg-white/10 dark:text-gray-300 dark:hover:text-gray-200"
    >
      <span className="sr-only">Close</span>
      <XMarkIcon className="h-6 w-6" aria-hidden="true" />
    </button>
  </div>

  {isBulkMode && (
    <div className="mt-4 space-y-3">
      <div className="grid gap-4 sm:grid-cols-2">
        <div>
          <label
            htmlFor="bulk-photographer"
            className="block text-sm/6 font-medium text-gray-900 dark:text-white"
          >
            Photographer (optional)
          </label>
          <div className="mt-2">
            <input
              type="text"
              id="bulk-photographer"
              value={formData.photographer}
              onChange={(e) =>
                setFormData((prev) => ({
                  ...prev,
                  photographer: e.target.value,
                }))
              }
              className="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"
              placeholder="Leave empty to keep existing"
            />
          </div>
        </div>

        <div>
          <label
            htmlFor="bulk-location"
            className="block text-sm/6 font-medium text-gray-900 dark:text-white"
          >
            Location (optional)
          </label>
          <div className="mt-2">
            <input
              type="text"
              id="bulk-location"
              value={formData.location}
              onChange={(e) =>
                setFormData((prev) => ({
                  ...prev,
                  location: e.target.value,
                }))
              }
              className="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"
              placeholder="Leave empty to keep existing"
            />
          </div>
        </div>
      </div>
    </div>
  )}

  <form onSubmit={handleSubmit} className="mt-4 space-y-4">
    <div className="space-y-4">

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Feb 13, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Fix invalid Tailwind height utility

Replace the invalid Tailwind utility h-150 with a valid arbitrary value like
h-[150px] to ensure the container renders correctly.

src/components/GalleryModal.stories.tsx [53]

-<div className="relative h-150 w-full bg-black">
+<div className="relative h-[150px] w-full bg-black">
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies an invalid Tailwind CSS class h-150 and proposes a valid fix, which is crucial for the component to render correctly in the Storybook.

Medium
Correct background gradient class

Correct the invalid Tailwind CSS class bg-linear-to-br to bg-gradient-to-br to
apply the intended background gradient.

src/components/GalleryModal.stories.tsx [72]

-<div className="flex h-full w-full items-center justify-center bg-linear-to-br from-gray-800 to-gray-900">
+<div className="flex h-full w-full items-center justify-center bg-gradient-to-br from-gray-800 to-gray-900">
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies an invalid Tailwind CSS class bg-linear-to-br and proposes the correct class bg-gradient-to-br, which is essential for the background to render as intended.

Medium
Add missing dark mode styles
Suggestion Impact:The commit adds the suggested dark mode Tailwind classes to ComboboxOptions (dark background and ring) and to ComboboxOption text/checkmark colors (dark text and dark indigo for the check icon when inactive), matching the intent of the recommendation.

code diff:

-                  <ComboboxOptions className="ring-opacity-5 absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black focus:outline-none sm:text-sm">
+                  <ComboboxOptions className="ring-opacity-5 absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black focus:outline-none sm:text-sm dark:bg-gray-800 dark:ring-gray-700">
                     {!isCreatingNew && (
                       <ComboboxOption
                         value={null}
-                        className="group relative cursor-pointer py-2 pr-9 pl-3 text-gray-900 select-none hover:bg-indigo-600 hover:text-white"
+                        className="group relative cursor-pointer py-2 pr-9 pl-3 text-gray-900 select-none hover:bg-indigo-600 hover:text-white dark:text-white"
                       >
                         <div className="flex items-center">
                           <PlusIcon className="h-5 w-5 text-indigo-600 group-hover:text-white" />
@@ -451,7 +438,7 @@
                           `relative cursor-default py-2 pr-9 pl-3 select-none ${
                             active
                               ? 'bg-indigo-600 text-white'
-                              : 'text-gray-900'
+                              : 'text-gray-900 dark:text-white'
                           }`
                         }
                       >
@@ -474,7 +461,9 @@
                             {selected && (
                               <span
                                 className={`absolute inset-y-0 right-0 flex items-center pr-4 ${
-                                  active ? 'text-white' : 'text-indigo-600'
+                                  active
+                                    ? 'text-white'
+                                    : 'text-indigo-600 dark:text-indigo-400'
                                 }`}
                               >

Add missing dark mode styles to the ComboboxOptions and ComboboxOption
components to ensure they render correctly in dark mode.

src/components/admin/sponsor/SponsorAddModal.tsx [432-490]

-<ComboboxOptions className="ring-opacity-5 absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black focus:outline-none sm:text-sm">
+<ComboboxOptions className="ring-opacity-5 absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black focus:outline-none sm:text-sm dark:bg-gray-800 dark:ring-gray-700">
   {!isCreatingNew && (
     <ComboboxOption
       value={null}
-      className="group relative cursor-pointer py-2 pr-9 pl-3 text-gray-900 select-none hover:bg-indigo-600 hover:text-white"
+      className="group relative cursor-pointer py-2 pr-9 pl-3 text-gray-900 select-none hover:bg-indigo-600 hover:text-white dark:text-white"
     >
       <div className="flex items-center">
         <PlusIcon className="h-5 w-5 text-indigo-600 group-hover:text-white" />
         <span className="ml-3 block truncate font-medium">
           Create new sponsor
         </span>
       </div>
     </ComboboxOption>
   )}
   {filteredSponsors.map((sponsor) => (
     <ComboboxOption
       key={sponsor._id}
       value={sponsor}
       className={({ active }) =>
         `relative cursor-default py-2 pr-9 pl-3 select-none ${
           active
             ? 'bg-indigo-600 text-white'
-            : 'text-gray-900'
+            : 'text-gray-900 dark:text-white'
         }`
       }
     >
       {({ active, selected }) => (
         <>
           <div className="flex items-center">
             <BuildingOffice2Icon
               className={`h-5 w-5 ${
                 active ? 'text-white' : 'text-gray-400'
               }`}
             />
             <span
               className={`ml-3 block truncate ${
                 selected ? 'font-semibold' : 'font-normal'
               }`}
             >
               {sponsor.name}
             </span>
           </div>
           {selected && (
             <span
               className={`absolute inset-y-0 right-0 flex items-center pr-4 ${
-                active ? 'text-white' : 'text-indigo-600'
+                active ? 'text-white' : 'text-indigo-600 dark:text-indigo-400'
               }`}
             >
               <CheckIcon
                 className="h-5 w-5"
                 aria-hidden="true"
               />
             </span>
           )}
         </>
       )}
     </ComboboxOption>
   ))}
 </ComboboxOptions>

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies missing dark mode styles in the ComboboxOptions component, which would lead to UI inconsistencies. Applying the suggested fix improves the visual quality in dark mode.

Low
Add loading state to modal
Suggestion Impact:The ConfirmationModal now receives an isLoading prop sourced from the deleteVolunteer mutation (using deleteVolunteer.isPending), enabling loading/disabled behavior during deletion.

code diff:

@@ -548,6 +551,7 @@
         isOpen={!!deleteVolunteerId}
         onClose={() => setDeleteVolunteerId(null)}
         onConfirm={confirmDelete}
+        isLoading={deleteVolunteer.isPending}
         title="Delete Volunteer"
         message="Are you sure you want to delete this volunteer? This action cannot be undone."
         confirmButtonText="Delete"

Pass the isLoading state from the deleteVolunteer mutation to the
ConfirmationModal to provide visual feedback and prevent multiple clicks.

src/components/volunteer/VolunteerAdminPage.tsx [547-555]

     const confirmDelete = async () => {
       if (!deleteVolunteerId) return
       await deleteVolunteer.mutateAsync({ volunteerId: deleteVolunteerId })
       setDeleteVolunteerId(null)
     }
 
     return (
       <div className="mx-auto max-w-7xl p-4 sm:p-6 lg:p-8">
         ...
         <ConfirmationModal
           isOpen={!!deleteVolunteerId}
           onClose={() => setDeleteVolunteerId(null)}
           onConfirm={confirmDelete}
+          isLoading={deleteVolunteer.isLoading}
           title="Delete Volunteer"
           message="Are you sure you want to delete this volunteer? This action cannot be undone."
           confirmButtonText="Delete"
           variant="danger"
         />
       </div>
     )

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that the isLoading state from the deleteVolunteer mutation should be passed to the ConfirmationModal to improve UX by disabling buttons during the async operation.

Low
Use valid Tailwind max-width class

Replace the invalid Tailwind class max-w-75 with a valid one like max-w-[300px]
to correctly constrain the badge image width.

src/components/admin/BadgePreviewModal.tsx [71-75]

 <img
   src={imageUrl}
   alt={`${badge.badgeType} badge`}
-  className="h-auto w-full max-w-75"
+  className="h-auto w-full max-w-[300px]"
 />
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that max-w-75 is not a standard Tailwind CSS class, and this change in the PR is a regression from the previous max-w-[300px], which would break the styling.

Low
Fix invalid Tailwind min-width utility

Replace the invalid Tailwind class min-w-35 with a valid utility like
min-w-[140px] to correctly set the button's minimum width.

src/components/admin/ProposalManagementModal.tsx [422-428]

 <Button
   type="submit"
   variant="primary"
   disabled={isPending}
-  className="min-w-35"
+  className="min-w-[140px]"
 >
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out that min-w-35 is not a standard Tailwind CSS class. The PR introduced this invalid class, which is a regression from the previous min-w-[140px], and fixing it restores the intended styling.

Low
Possible issue
Fix invalid Tailwind classes

Fix invalid Tailwind CSS class names for outline-offset and remove the invalid
dark:scheme-dark class.

src/components/admin/gallery/ImageMetadataModal.tsx [389]

-className="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:scheme-dark dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"
+className="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies and fixes invalid Tailwind CSS classes (-outline-offset-1, focus:-outline-offset-2, dark:scheme-dark) that would not be applied by the framework, thus correcting the component's styling.

Medium
Correct Tailwind utility names
Suggestion Impact:The commit removed the element that contained the invalid Tailwind classes by replacing it with a shared component, thereby eliminating the problematic class names rather than correcting them in place. code diff: - <label - htmlFor="tier" - className="block text-sm/6 font-medium text-gray-900 dark:text-white" - > - Sponsor Tier * - </label> - <div className="mt-2 grid grid-cols-1"> - <select - id="tier" - value={formData.tierId} - onChange={(e) => - setFormData((prev) => ({ - ...prev, - tierId: e.target.value, - })) - } - required - className="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pr-8 pl-3 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:*:bg-gray-800 dark:focus:outline-indigo-500" - > - <option value="">Select a tier...</option> - {sponsorTiers - .sort((a, b) => { - const getMaxPrice = (tier: SponsorTierExisting) => { - if (!tier.price || tier.price.length === 0) return 0 - return Math.max(...tier.price.map((p) => p.amount)) - } - return getMaxPrice(b) - getMaxPrice(a) - }) - .map((tier) => ( - <option key={tier._id} value={tier._id}> - {tier.title} - </option> - ))} - </select> - <ChevronDownIconSmall - aria-hidden="true" - className="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-500 sm:size-4 dark:text-gray-400" - /> - </div> + <Dropdown + name="tier" + label="Sponsor Tier *" + options={ + new Map( + sponsorTiers + .sort((a, b) => { + const getMaxPrice = (tier: SponsorTierExisting) => { + if (!tier.price || tier.price.length === 0) return 0 + return Math.max(...tier.price.map((p) => p.amount)) + } + return getMaxPrice(b) - getMaxPrice(a) + }) + .map((tier) => [tier._id, tier.title]), + ) + } + value={formData.tierId} + setValue={(val) => + setFormData((prev) => ({ + ...prev, + tierId: val, + })) + } + required + placeholder="Select a tier..." + /> Correct invalid Tailwind CSS class names for outline-offset and the dark mode variant for the background color. src/components/admin/sponsor/SponsorAddModal.tsx [382] -className="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pr-8 pl-3 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:*:bg-gray-800 dark:focus:outline-indigo-500" +className="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pr-8 pl-3 text-base text-gray-900 outline-1 outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:bg-gray-800 dark:focus:outline-indigo-500" [To ensure code accuracy, apply this suggestion manually] Suggestion importance[1-10]: 7 __ Why: The suggestion correctly identifies and fixes invalid Tailwind CSS classes (-outline-offset-1, focus:-outline-offset-2, dark:*:bg-gray-800) that would not be applied, ensuring the intended styling works correctly.
Medium
Fix invalid hex color values

Add the '#' prefix to the hexadecimal color strings in the mockTopics array to
fix rendering in Storybook.

src/components/admin/ProposalManagementModal.stories.tsx [18-47]

     const mockTopics: Topic[] = [
       {
         _id: 'topic-1',
         _type: 'topic',
     title: 'Kubernetes',
-        color: '326CE5',
+        color: '#326CE5',
         slug: { current: 'kubernetes' },
       },
       {
         _id: 'topic-2',
         _type: 'topic',
     title: 'DevOps',
-        color: 'FF6B35',
+        color: '#FF6B35',
         slug: { current: 'devops' },
       },
       {
         _id: 'topic-3',
         _type: 'topic',
     title: 'Security',
-        color: 'E91E63',
+        color: '#E91E63',
         slug: { current: 'security' },
       },
       {
         _id: 'topic-4',
         _type: 'topic',
     title: 'Observability',
-        color: '4CAF50',
+        color: '#4CAF50',
         slug: { current: 'observability' },
       },
     ]
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that the hex color codes in the mock data are missing the '#' prefix, which would cause rendering issues in the Storybook story.

Low
  • Update

- Introduced AdminButton component for consistent button styling across admin pages.
- Replaced button elements in AdminActionBar, AudienceFeedbackPanel, BadgeValidator, and other admin components with AdminButton.
- Added EmptyState component to display user-friendly messages for empty states in OrdersTableWithSearch, SpeakerTable, and VolunteerAdminPage.
- Created ContentCard component for consistent content presentation in CachedTermsContent.
- Updated ExpenseForm to use AdminButton for form actions.
- Ensured all changes maintain existing functionality while improving code readability and maintainability.
@Starefossen Starefossen merged commit a198cd6 into main Feb 13, 2026
9 checks passed
@Starefossen Starefossen deleted the storybook-update branch February 13, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant