From d5c3953888c4067b80486d3e0da61b78d3eada74 Mon Sep 17 00:00:00 2001 From: dzinesco Date: Fri, 22 Aug 2025 13:31:19 -0600 Subject: [PATCH] fix(typescript): resolve build errors and improve type safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix billing components ConnectError type compatibility with exactOptionalPropertyTypes - Update Select component usage to match proper API (options vs children) - Remove unused imports and fix optional property assignments in system components - Resolve duplicate Order/Ticket type definitions and add null safety checks - Handle optional branding properties correctly in organization features - Add window property type declarations for test environment - Fix Playwright API usage (page.setOffline → page.context().setOffline) - Clean up unused imports, variables, and parameters across codebase - Add comprehensive global type declarations for test window extensions Resolves major TypeScript compilation issues and improves type safety throughout the application. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../billing/StripeConnectButton.tsx | 85 ++ .../billing/StripeConnectStatus.tsx | 181 +++ .../components/events/EventDetailsStep.tsx | 300 +++++ .../src/components/system/DataError.tsx | 170 +++ .../src/components/system/ErrorBoundary.tsx | 207 ++++ .../src/features/orders/OrdersTable.tsx | 432 +++++++ .../src/features/orders/RefundModal.tsx | 426 +++++++ .../src/features/org/BrandingSettings.tsx | 495 ++++++++ reactrebuild0825/src/types/business.ts | 138 ++- reactrebuild0825/src/types/global.d.ts | 44 + .../tests/offline-scenarios.spec.ts | 607 +++++++++ .../tests/real-world-gate.spec.ts | 933 ++++++++++++++ .../tests/refunds-disputes.spec.ts | 1102 +++++++++++++++++ .../tests/scanner-abuse-prevention.spec.ts | 225 ++++ .../tests/territory-access.spec.ts | 265 ++++ reactrebuild0825/tests/whitelabel.spec.ts | 481 +++++++ 16 files changed, 6085 insertions(+), 6 deletions(-) create mode 100644 reactrebuild0825/src/components/billing/StripeConnectButton.tsx create mode 100644 reactrebuild0825/src/components/billing/StripeConnectStatus.tsx create mode 100644 reactrebuild0825/src/components/events/EventDetailsStep.tsx create mode 100644 reactrebuild0825/src/components/system/DataError.tsx create mode 100644 reactrebuild0825/src/components/system/ErrorBoundary.tsx create mode 100644 reactrebuild0825/src/features/orders/OrdersTable.tsx create mode 100644 reactrebuild0825/src/features/orders/RefundModal.tsx create mode 100644 reactrebuild0825/src/features/org/BrandingSettings.tsx create mode 100644 reactrebuild0825/src/types/global.d.ts create mode 100644 reactrebuild0825/tests/offline-scenarios.spec.ts create mode 100644 reactrebuild0825/tests/real-world-gate.spec.ts create mode 100644 reactrebuild0825/tests/refunds-disputes.spec.ts create mode 100644 reactrebuild0825/tests/scanner-abuse-prevention.spec.ts create mode 100644 reactrebuild0825/tests/territory-access.spec.ts create mode 100644 reactrebuild0825/tests/whitelabel.spec.ts diff --git a/reactrebuild0825/src/components/billing/StripeConnectButton.tsx b/reactrebuild0825/src/components/billing/StripeConnectButton.tsx new file mode 100644 index 0000000..12c23b8 --- /dev/null +++ b/reactrebuild0825/src/components/billing/StripeConnectButton.tsx @@ -0,0 +1,85 @@ +import React from 'react'; + +import { ExternalLink, CreditCard, Loader2 } from 'lucide-react'; + +import { useStripeConnect } from '../../hooks/useStripeConnect'; +import { Button } from '../ui/Button'; + +import type { StripeConnectButtonProps, ConnectError } from '../../types/stripe'; + +export const StripeConnectButton: React.FC = ({ + orgId, + onSuccess, + onError, + className = '', + children, +}) => { + const { startOnboarding, isLoading, error } = useStripeConnect(orgId); + + const handleConnect = async () => { + try { + await startOnboarding(); + // Note: This will redirect to Stripe, so success callback + // will be called when user returns via the return URL + if (onSuccess) { + // We can't actually call this here since we redirect, + // but it's useful for the component API + } + } catch (err) { + if (onError) { + const errorObj: ConnectError = { + error: err instanceof Error ? err.message : 'Failed to start onboarding', + }; + if (err instanceof Error && err.stack) { + errorObj.details = err.stack; + } + onError(errorObj); + } + } + }; + + React.useEffect(() => { + if (error && onError) { + const errorObj: ConnectError = error instanceof Error + ? error.stack + ? { error: error.message, details: error.stack } + : { error: error.message } + : error; + onError(errorObj); + } + }, [error, onError]); + + return ( + + ); +}; \ No newline at end of file diff --git a/reactrebuild0825/src/components/billing/StripeConnectStatus.tsx b/reactrebuild0825/src/components/billing/StripeConnectStatus.tsx new file mode 100644 index 0000000..d842eef --- /dev/null +++ b/reactrebuild0825/src/components/billing/StripeConnectStatus.tsx @@ -0,0 +1,181 @@ +import React, { useEffect, useState } from 'react'; + +import { CheckCircle, AlertCircle, Clock, CreditCard, Building } from 'lucide-react'; + +import { useStripeConnect } from '../../hooks/useStripeConnect'; +import { Alert } from '../ui/Alert'; +import { Badge } from '../ui/Badge'; +import { Button } from '../ui/Button'; +import { Card } from '../ui/Card'; + +import { StripeConnectButton } from './StripeConnectButton'; + +import type { StripeConnectStatusProps, OrgPaymentData } from '../../types/stripe'; + +export const StripeConnectStatus: React.FC = ({ + orgId, + onStatusUpdate, + className = '', +}) => { + const { checkStatus, isLoading, error, paymentData } = useStripeConnect(orgId); + const [lastChecked, setLastChecked] = useState(null); + + // Check status on mount and when URL indicates return from Stripe + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + const status = urlParams.get('status'); + + // Always check status on mount + handleRefreshStatus(); + + // If we're returning from Stripe onboarding, check again after a delay + if (status === 'connected' || status === 'refresh') { + setTimeout(() => { + handleRefreshStatus(); + }, 2000); + } + }, []); + + // Call onStatusUpdate when paymentData changes + useEffect(() => { + if (paymentData && onStatusUpdate) { + onStatusUpdate(paymentData); + } + }, [paymentData, onStatusUpdate]); + + const handleRefreshStatus = async () => { + const data = await checkStatus(); + if (data) { + setLastChecked(new Date()); + } + }; + + const getStatusInfo = (data: OrgPaymentData | null) => { + if (!data) { + return { + icon: , + badge: Not Connected, + title: 'Stripe Account Required', + description: 'Connect your Stripe account to accept payments.', + }; + } + + const { connected, stripe } = data; + + if (connected) { + return { + icon: , + badge: Connected, + title: 'Stripe Account Connected', + description: `Ready to accept payments${stripe.businessName ? ` as ${stripe.businessName}` : ''}.`, + }; + } + + if (stripe.detailsSubmitted && !stripe.chargesEnabled) { + return { + icon: , + badge: Under Review, + title: 'Account Under Review', + description: 'Stripe is reviewing your account. This usually takes 1-2 business days.', + }; + } + + return { + icon: , + badge: Incomplete, + title: 'Setup Incomplete', + description: 'Please complete your Stripe account setup to accept payments.', + }; + }; + + const statusInfo = getStatusInfo(paymentData); + + return ( +
+ +
+
+
+ {statusInfo.icon} +
+
+
+

+ {statusInfo.title} +

+ {statusInfo.badge} +
+

+ {statusInfo.description} +

+ + {paymentData?.stripe && ( +
+
+ + Account ID: {paymentData.stripe.accountId} +
+ {paymentData.stripe.businessName && ( +
+ + Business: {paymentData.stripe.businessName} +
+ )} +
+ )} +
+
+ +
+ {!paymentData?.connected && ( + console.error('Stripe Connect error:', err)} + > + {paymentData ? 'Continue Setup' : 'Connect Stripe'} + + )} + + +
+
+
+ + {error && ( + + +
+

Failed to check Stripe status

+

{'error' in error ? error.error : error.message}

+
+
+ )} + + {lastChecked && ( +

+ Last checked: {lastChecked.toLocaleTimeString()} +

+ )} + + {/* Development info */} + {import.meta.env.DEV && paymentData && ( + +

+ Development Info +

+
+            {JSON.stringify(paymentData, null, 2)}
+          
+
+ )} +
+ ); +}; \ No newline at end of file diff --git a/reactrebuild0825/src/components/events/EventDetailsStep.tsx b/reactrebuild0825/src/components/events/EventDetailsStep.tsx new file mode 100644 index 0000000..4758430 --- /dev/null +++ b/reactrebuild0825/src/components/events/EventDetailsStep.tsx @@ -0,0 +1,300 @@ +import React, { useCallback, useState, useEffect } from 'react'; + +import { useClaims, useAccessibleTerritories } from '@/hooks/useClaims'; +import { MOCK_TERRITORIES, type Territory } from '@/types/territory'; + +import { useWizardEventDetails, useWizardValidation } from '../../stores'; +import { Input, Select } from '../ui'; +import { Card, CardBody, CardHeader } from '../ui/Card'; + +import type { Event } from '../../types/business'; + +// Legacy props interface - kept for backward compatibility +export interface EventDetailsStepProps { + eventDetails?: Partial; + onUpdate?: (updates: Partial) => void; +} + +export const EventDetailsStep: React.FC = () => { + // Use store hooks + const eventDetails = useWizardEventDetails(); + const validation = useWizardValidation(); + + // Territory management + const { claims } = useClaims(); + const { accessibleTerritoryIds, hasFullAccess } = useAccessibleTerritories(); + const [availableTerritories, setAvailableTerritories] = useState([]); + + // Load available territories based on user role and access + useEffect(() => { + if (!claims?.orgId) { + setAvailableTerritories([]); + return; + } + + // Filter territories by organization + const orgTerritories = MOCK_TERRITORIES.filter( + territory => territory.orgId === claims.orgId + ); + + // Further filter by user access if they don't have full access + const filtered = hasFullAccess + ? orgTerritories + : orgTerritories.filter(territory => + accessibleTerritoryIds.includes(territory.id) + ); + + setAvailableTerritories(filtered); + + // Auto-select territory for territory managers if not already set + if (claims.role === 'territoryManager' && + !eventDetails.eventDetails.territoryId && + filtered.length === 1) { + eventDetails.updateEventDetails({ territoryId: filtered[0].id }); + } + }, [claims, accessibleTerritoryIds, hasFullAccess, eventDetails]); + const handleInputChange = useCallback( + (field: keyof Event) => (value: string) => { + if (field === 'title') {eventDetails.setEventTitle(value);} + else if (field === 'description') {eventDetails.setEventDescription(value);} + else if (field === 'venue') {eventDetails.setEventVenue(value);} + else if (field === 'image') {eventDetails.setEventImage(value);} + else {eventDetails.updateEventDetails({ [field]: value });} + }, + [eventDetails] + ); + + const handleCheckboxChange = useCallback( + (field: keyof Event) => (checked: boolean) => { + if (field === 'isPublic') {eventDetails.setEventVisibility(checked);} + else {eventDetails.updateEventDetails({ [field]: checked });} + }, + [eventDetails] + ); + + const handleTagsChange = useCallback( + (tagsString: string) => { + const tags = tagsString + .split(',') + .map(tag => tag.trim()) + .filter(tag => tag.length > 0); + eventDetails.updateEventDetails({ tags }); + }, + [eventDetails] + ); + + // Convert date for datetime-local input + const formatDateForInput = (dateString: string) => { + if (!dateString) {return '';} + try { + const date = new Date(dateString); + // Format as YYYY-MM-DDTHH:mm for datetime-local input + return date.toISOString().slice(0, 16); + } catch { + return ''; + } + }; + + const handleDateChange = (dateString: string) => { + if (!dateString) { + eventDetails.setEventDate(''); + return; + } + + try { + // Convert from datetime-local format to ISO string + const date = new Date(dateString); + eventDetails.setEventDate(date.toISOString()); + } catch { + // Invalid date, don't update + } + }; + + const currentTags = eventDetails.eventDetails.tags ? eventDetails.eventDetails.tags.join(', ') : ''; + + // Get validation errors + const errors = validation.validationErrors.eventDetails || []; + + return ( +
+ {errors.length > 0 && ( +
+

Please fix the following errors:

+
    + {errors.map((error, index) => ( +
  • • {error}
  • + ))} +
+
+ )} + + +

Basic Information

+

+ Provide the essential details for your event +

+
+ + handleInputChange('title')(e.target.value)} + placeholder="Enter event title" + required + helperText="A clear, descriptive title for your event" + error={errors.some(err => err.includes('title')) ? 'Please provide a valid title' : undefined} + /> + +
+ +