diff --git a/src/api/artist-notes/types.ts b/src/api/artist-notes/types.ts index 5ada6a05..263b1389 100644 --- a/src/api/artist-notes/types.ts +++ b/src/api/artist-notes/types.ts @@ -7,8 +7,8 @@ export type SetNote = { note_content: string; created_at: string; updated_at: string; - author_username?: string; - author_email?: string; + author_username?: string | undefined; + author_email?: string | undefined; }; // Query key factory diff --git a/src/api/auth/useSignInWithOtpMutation.ts b/src/api/auth/useSignInWithOtpMutation.ts index 46346246..85372dc4 100644 --- a/src/api/auth/useSignInWithOtpMutation.ts +++ b/src/api/auth/useSignInWithOtpMutation.ts @@ -4,7 +4,7 @@ import { useToast } from "@/components/ui/use-toast"; interface SignInWithOtpParams { email: string; - inviteToken?: string; + inviteToken?: string | undefined; } export function useSignInWithOtpMutation() { diff --git a/src/api/auth/useUpdateProfile.ts b/src/api/auth/useUpdateProfile.ts index ea7b8f57..4acbe373 100644 --- a/src/api/auth/useUpdateProfile.ts +++ b/src/api/auth/useUpdateProfile.ts @@ -1,8 +1,12 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useToast } from "@/hooks/use-toast"; import { supabase } from "@/integrations/supabase/client"; +import type { Database } from "@/integrations/supabase/types"; import { profileKeys } from "./types"; +type ValidateProfileUpdateArgs = + Database["public"]["Functions"]["validate_profile_update"]["Args"]; + // Mutation function async function updateProfile(variables: { userId: string; @@ -11,12 +15,14 @@ async function updateProfile(variables: { const { userId, updates } = variables; // Validate username uniqueness before attempting update + const trimmedUsername = updates.username?.trim(); + const rpcArgs: ValidateProfileUpdateArgs = { user_id: userId }; + if (trimmedUsername !== undefined) { + rpcArgs.new_username = trimmedUsername; + } const { data: validationResult, error: validationError } = await supabase.rpc( "validate_profile_update", - { - user_id: userId, - new_username: updates.username?.trim(), - }, + rpcArgs, ); if (validationError) { diff --git a/src/api/editions/types.ts b/src/api/editions/types.ts index 29341d98..fdd18269 100644 --- a/src/api/editions/types.ts +++ b/src/api/editions/types.ts @@ -8,7 +8,7 @@ export type FestivalEdition = export const editionsKeys = { root: (festivalId: string) => [...festivalsKeys.root(), festivalId, "editions"] as const, - all: (festivalId: string, { all }: { all?: boolean } = {}) => + all: (festivalId: string, { all }: { all?: boolean | undefined } = {}) => [...editionsKeys.root(festivalId), { all }] as const, item: ({ editionId, diff --git a/src/api/editions/useFestivalEditionsForFestival.ts b/src/api/editions/useFestivalEditionsForFestival.ts index 9c9f93dc..7f7b04e3 100644 --- a/src/api/editions/useFestivalEditionsForFestival.ts +++ b/src/api/editions/useFestivalEditionsForFestival.ts @@ -4,7 +4,7 @@ import { FestivalEdition, editionsKeys } from "./types"; export async function fetchFestivalEditions( festivalId: string, - { all }: { all?: boolean } = {}, + { all }: { all?: boolean | undefined } = {}, ): Promise { let query = supabase .from("festival_editions") @@ -31,7 +31,7 @@ export async function fetchFestivalEditions( export function editionsForFestivalQuery( festivalId: string, - { all }: { all?: boolean } = {}, + { all }: { all?: boolean | undefined } = {}, ) { return queryOptions({ queryKey: editionsKeys.all(festivalId, { all }), @@ -41,7 +41,7 @@ export function editionsForFestivalQuery( export function useFestivalEditionsForFestivalQuery( festivalId: string | undefined, - { all }: { all?: boolean } = {}, + { all }: { all?: boolean | undefined } = {}, ) { return useQuery({ ...editionsForFestivalQuery(festivalId!, { all }), diff --git a/src/api/festivals/types.ts b/src/api/festivals/types.ts index 7d096669..47e129b0 100644 --- a/src/api/festivals/types.ts +++ b/src/api/festivals/types.ts @@ -5,7 +5,7 @@ export type Festival = Database["public"]["Tables"]["festivals"]["Row"]; // Query key factory for festivals export const festivalsKeys = { root: () => ["festivals"] as const, - all: ({ all }: { all?: boolean } = {}) => + all: ({ all }: { all?: boolean | undefined } = {}) => [...festivalsKeys.root(), { all }] as const, item: (festivalId: string) => [...festivalsKeys.root(), festivalId] as const, bySlug: (festivalSlug: string) => diff --git a/src/api/festivals/useFestivals.ts b/src/api/festivals/useFestivals.ts index e6d7e6af..ebdf05f9 100644 --- a/src/api/festivals/useFestivals.ts +++ b/src/api/festivals/useFestivals.ts @@ -6,7 +6,9 @@ import { isTimeoutError, withTimeout } from "@/lib/timeout"; async function fetchFestivals({ all, signal, -}: { all?: boolean; signal?: AbortSignal } = {}): Promise { +}: { all?: boolean | undefined; signal?: AbortSignal } = {}): Promise< + Festival[] +> { let query = supabase .from("festivals") .select("*") @@ -32,7 +34,7 @@ async function fetchFestivals({ export function festivalsQuery({ all, timeoutMs = 10000, -}: { all?: boolean; timeoutMs?: number } = {}) { +}: { all?: boolean | undefined; timeoutMs?: number } = {}) { return queryOptions({ queryKey: festivalsKeys.all({ all }), queryFn: ({ signal }) => diff --git a/src/api/groups/types.ts b/src/api/groups/types.ts index 1f25bf67..98268a93 100644 --- a/src/api/groups/types.ts +++ b/src/api/groups/types.ts @@ -11,8 +11,8 @@ export type Group = Database["public"]["Tables"]["groups"]["Row"] & { export type GroupMember = Database["public"]["Tables"]["group_members"]["Row"] & { profiles?: { - username?: string; - email?: string; + username?: string | undefined; + email?: string | undefined; }; }; diff --git a/src/api/groups/useCreateGroup.ts b/src/api/groups/useCreateGroup.ts index 86bade55..2a5f2e9f 100644 --- a/src/api/groups/useCreateGroup.ts +++ b/src/api/groups/useCreateGroup.ts @@ -1,25 +1,32 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useToast } from "@/hooks/use-toast"; import { supabase } from "@/integrations/supabase/client"; +import type { Database } from "@/integrations/supabase/types"; import { groupsKeys } from "./types"; import { generateSlug } from "@/lib/slug"; +type GroupInsert = Database["public"]["Tables"]["groups"]["Insert"]; + // Mutation function async function createGroup(variables: { name: string; - description?: string; + description?: string | undefined; userId: string; }) { const { name, description, userId } = variables; + const groupData: GroupInsert = { + name, + slug: generateSlug(name), + created_by: userId, + }; + if (description !== undefined) { + groupData.description = description; + } + const { data: group, error } = await supabase .from("groups") - .insert({ - name, - slug: generateSlug(name), - description, - created_by: userId, - }) + .insert(groupData) .select() .single(); diff --git a/src/api/invites/useGenerateInviteMutation.ts b/src/api/invites/useGenerateInviteMutation.ts index 77813585..ce0cba25 100644 --- a/src/api/invites/useGenerateInviteMutation.ts +++ b/src/api/invites/useGenerateInviteMutation.ts @@ -1,8 +1,12 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useToast } from "@/components/ui/use-toast"; import { supabase } from "@/integrations/supabase/client"; +import type { Database } from "@/integrations/supabase/types"; import { inviteKeys } from "./types"; +type GroupInviteInsert = + Database["public"]["Tables"]["group_invites"]["Insert"]; + async function generateInviteLink( groupId: string, options?: { @@ -21,13 +25,17 @@ async function generateInviteLink( throw new Error("Authentication required"); } - const inviteData = { + const inviteData: GroupInviteInsert = { group_id: groupId, invite_token: token, created_by: user.id, - expires_at: options?.expiresAt?.toISOString(), - max_uses: options?.maxUses, }; + if (options?.expiresAt !== undefined) { + inviteData.expires_at = options.expiresAt.toISOString(); + } + if (options?.maxUses !== undefined) { + inviteData.max_uses = options.maxUses; + } const { error } = await supabase.from("group_invites").insert(inviteData); diff --git a/src/api/ratings/useRateSet.ts b/src/api/ratings/useRateSet.ts index 953dad8b..a34170fd 100644 --- a/src/api/ratings/useRateSet.ts +++ b/src/api/ratings/useRateSet.ts @@ -12,7 +12,7 @@ async function rateSet({ setId: string; rating: number; userId: string; - existingRating?: number; + existingRating?: number | undefined; }) { if (existingRating === rating) { const { error } = await supabase diff --git a/src/api/sets/useUpdateSet.ts b/src/api/sets/useUpdateSet.ts index 952ab571..e2162a57 100644 --- a/src/api/sets/useUpdateSet.ts +++ b/src/api/sets/useUpdateSet.ts @@ -24,18 +24,29 @@ export type UpdateSetInput = Partial< async function updateSet(variables: { id: string; updates: UpdateSetInput }) { const { id, updates } = variables; - const updateData: SetUpdate = { - name: updates.name, - description: updates.description, - festival_edition_id: updates.festival_edition_id, - stage_id: updates.stage_id, - time_start: updates.time_start, - time_end: updates.time_end, - archived: updates.archived, - }; + const updateData: SetUpdate = {}; if (updates.name !== undefined) { + updateData.name = updates.name; updateData.slug = generateSlug(updates.name); } + if (updates.description !== undefined) { + updateData.description = updates.description; + } + if (updates.festival_edition_id !== undefined) { + updateData.festival_edition_id = updates.festival_edition_id; + } + if (updates.stage_id !== undefined) { + updateData.stage_id = updates.stage_id; + } + if (updates.time_start !== undefined) { + updateData.time_start = updates.time_start; + } + if (updates.time_end !== undefined) { + updateData.time_end = updates.time_end; + } + if (updates.archived !== undefined) { + updateData.archived = updates.archived; + } const { data, error } = await supabase .from("sets") diff --git a/src/api/voting/useVoteMutation.ts b/src/api/voting/useVoteMutation.ts index 8ba4614c..7780bc4a 100644 --- a/src/api/voting/useVoteMutation.ts +++ b/src/api/voting/useVoteMutation.ts @@ -8,7 +8,7 @@ export async function vote(variables: { setId: string; voteType: number; userId: string; - existingVote?: number; + existingVote?: number | undefined; }) { const { setId, voteType, userId, existingVote } = variables; diff --git a/src/components/Admin/ScheduleImport/CsvUploadStep.tsx b/src/components/Admin/ScheduleImport/CsvUploadStep.tsx index 5c300a3c..4e1150f2 100644 --- a/src/components/Admin/ScheduleImport/CsvUploadStep.tsx +++ b/src/components/Admin/ScheduleImport/CsvUploadStep.tsx @@ -10,7 +10,7 @@ import { CsvDropZone } from "./CsvDropZone"; type Props = { festivalEditionId: string; - defaultTimezone?: string; + defaultTimezone?: string | undefined; onDiffReady: (diff: DiffResult, timezone: string) => void; }; diff --git a/src/components/ArtistImageLoader.tsx b/src/components/ArtistImageLoader.tsx index 57fa5437..f692cade 100644 --- a/src/components/ArtistImageLoader.tsx +++ b/src/components/ArtistImageLoader.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { Music } from "lucide-react"; interface ArtistImageLoaderProps { - src?: string | null; + src?: string | null | undefined; alt: string; className?: string; } diff --git a/src/components/AuthDialog/AuthDialog.tsx b/src/components/AuthDialog/AuthDialog.tsx index 38681419..bc9819de 100644 --- a/src/components/AuthDialog/AuthDialog.tsx +++ b/src/components/AuthDialog/AuthDialog.tsx @@ -15,8 +15,8 @@ interface AuthDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onSuccess: () => void; - inviteToken?: string; - groupName?: string; + inviteToken?: string | undefined; + groupName?: string | undefined; } export function AuthDialog({ diff --git a/src/components/AuthDialog/EmailStep.tsx b/src/components/AuthDialog/EmailStep.tsx index 7838140e..8c7b5fa3 100644 --- a/src/components/AuthDialog/EmailStep.tsx +++ b/src/components/AuthDialog/EmailStep.tsx @@ -5,7 +5,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; interface EmailStepProps { - inviteToken?: string; + inviteToken?: string | undefined; onSuccess: (email: string) => void; } diff --git a/src/components/AuthDialog/OtpStep.tsx b/src/components/AuthDialog/OtpStep.tsx index 6130597a..d0385ea1 100644 --- a/src/components/AuthDialog/OtpStep.tsx +++ b/src/components/AuthDialog/OtpStep.tsx @@ -11,7 +11,7 @@ import { interface OtpStepProps { email: string; - inviteToken?: string; + inviteToken?: string | undefined; onSuccess: () => void; } diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 4740e0f0..04128c46 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -2,12 +2,15 @@ import React from "react"; interface ErrorBoundaryState { hasError: boolean; - error?: Error; + error?: Error | undefined; } interface ErrorBoundaryProps { children: React.ReactNode; - fallback?: React.ComponentType<{ error?: Error; retry: () => void }>; + fallback?: React.ComponentType<{ + error?: Error | undefined; + retry: () => void; + }>; } export class ErrorBoundary extends React.Component< @@ -46,7 +49,7 @@ function DefaultErrorFallback({ error, retry, }: { - error?: Error; + error?: Error | undefined; retry: () => void; }) { return ( diff --git a/src/components/StageBadge.tsx b/src/components/StageBadge.tsx index 726d9eb8..1f3993c1 100644 --- a/src/components/StageBadge.tsx +++ b/src/components/StageBadge.tsx @@ -2,9 +2,9 @@ import { MapPin } from "lucide-react"; interface StageBadgeProps { stageName: string; - stageColor?: string; - size?: "sm" | "md"; - showIcon?: boolean; + stageColor?: string | undefined; + size?: "sm" | "md" | undefined; + showIcon?: boolean | undefined; } export function StageBadge({ diff --git a/src/components/filters/FilterToggle.tsx b/src/components/filters/FilterToggle.tsx index 352c144f..0fe5c7f5 100644 --- a/src/components/filters/FilterToggle.tsx +++ b/src/components/filters/FilterToggle.tsx @@ -8,7 +8,7 @@ interface FilterToggleProps { hasActiveFilters: boolean; activeFilterCount: number; label?: string; - onClearFilters?: () => void; + onClearFilters?: (() => void) | undefined; } export function FilterToggle({ diff --git a/src/components/layout/AppHeader/FestivalIndicator.tsx b/src/components/layout/AppHeader/FestivalIndicator.tsx index 7039c799..0d64e412 100644 --- a/src/components/layout/AppHeader/FestivalIndicator.tsx +++ b/src/components/layout/AppHeader/FestivalIndicator.tsx @@ -1,7 +1,7 @@ interface FestivalIndicatorProps { isTitleVisible?: boolean; - logoUrl?: string | null; - festivalName?: string; + logoUrl?: string | null | undefined; + festivalName?: string | undefined; } export function FestivalIndicator({ diff --git a/src/components/layout/AppHeader/TitleSection.tsx b/src/components/layout/AppHeader/TitleSection.tsx index 2ac70738..83cc44c4 100644 --- a/src/components/layout/AppHeader/TitleSection.tsx +++ b/src/components/layout/AppHeader/TitleSection.tsx @@ -3,7 +3,7 @@ import { Music, Heart } from "lucide-react"; interface TitleSectionProps { title: string; - logoUrl?: string | null; + logoUrl?: string | null | undefined; onLogoRefChange?: (ref: HTMLElement | null) => void; } diff --git a/src/components/layout/AppHeader/UserAvatar.tsx b/src/components/layout/AppHeader/UserAvatar.tsx index ac1538de..0480b7e6 100644 --- a/src/components/layout/AppHeader/UserAvatar.tsx +++ b/src/components/layout/AppHeader/UserAvatar.tsx @@ -1,8 +1,8 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; interface UserAvatarProps { - username?: string | null; - email?: string | null; + username?: string | null | undefined; + email?: string | null | undefined; size?: "sm" | "md" | "lg"; } diff --git a/src/components/layout/AppHeader/UserMenu.tsx b/src/components/layout/AppHeader/UserMenu.tsx index a00db88d..f3993a66 100644 --- a/src/components/layout/AppHeader/UserMenu.tsx +++ b/src/components/layout/AppHeader/UserMenu.tsx @@ -18,7 +18,7 @@ type Profile = Database["public"]["Tables"]["profiles"]["Row"]; interface UserMenuProps { user: User; - profile?: Profile; + profile?: Profile | undefined; onSignOut: () => void; isMobile?: boolean; } diff --git a/src/components/ui/context-menu.tsx b/src/components/ui/context-menu.tsx index 35559580..61a8a2f3 100644 --- a/src/components/ui/context-menu.tsx +++ b/src/components/ui/context-menu.tsx @@ -90,14 +90,13 @@ ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName; const ContextMenuCheckboxItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, children, checked, ...props }, ref) => ( +>(({ className, children, ...props }, ref) => ( diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index 3a7794a2..e1c51542 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -93,14 +93,13 @@ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; const DropdownMenuCheckboxItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, children, checked, ...props }, ref) => ( +>(({ className, children, ...props }, ref) => ( diff --git a/src/components/ui/menubar.tsx b/src/components/ui/menubar.tsx index f97f0c41..7c2ea6e6 100644 --- a/src/components/ui/menubar.tsx +++ b/src/components/ui/menubar.tsx @@ -126,14 +126,13 @@ MenubarItem.displayName = MenubarPrimitive.Item.displayName; const MenubarCheckboxItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, children, checked, ...props }, ref) => ( +>(({ className, children, ...props }, ref) => ( diff --git a/src/components/ui/multi-select.tsx b/src/components/ui/multi-select.tsx index 12c4fecc..6f8d7fff 100644 --- a/src/components/ui/multi-select.tsx +++ b/src/components/ui/multi-select.tsx @@ -29,7 +29,7 @@ interface MultiSelectProps { searchPlaceholder?: string; emptyMessage?: string; disabled?: boolean; - className?: string; + className?: string | undefined; } export function MultiSelect({ diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 9b769ec5..98cc3cd8 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -4,7 +4,21 @@ import { Check, ChevronDown, ChevronUp } from "lucide-react"; import { cn } from "@/lib/utils"; -const Select = SelectPrimitive.Root; +type SelectProps = Omit< + React.ComponentProps, + "value" +> & { + value?: string | undefined; +}; + +function Select({ value, ...props }: SelectProps) { + return ( + + ); +} const SelectGroup = SelectPrimitive.Group; diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx index 3bdb4184..b38250ea 100644 --- a/src/components/ui/sonner.tsx +++ b/src/components/ui/sonner.tsx @@ -8,7 +8,7 @@ function Toaster({ ...props }: ToasterProps) { return ( & { + value?: string | undefined; + }) + | (Omit & { + value?: string[] | undefined; + }) +) & + VariantProps; + const ToggleGroup = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps ->(({ className, variant, size, children, ...props }, ref) => ( - - - {children} - - -)); + ToggleGroupProps +>(({ className, variant, size, children, value, ...props }, ref) => { + // TS can't carry the exactOptionalPropertyTypes-safe shape through a + // spread of a destructured union's rest; the cast just restates what + // ToggleGroupProps above already guarantees. + const rootProps = { + ...props, + ...(value !== undefined ? { value } : {}), + } as + | ToggleGroupPrimitive.ToggleGroupSingleProps + | ToggleGroupPrimitive.ToggleGroupMultipleProps; + + return ( + + + {children} + + + ); +}); ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName; diff --git a/src/contexts/FestivalEditionContext.tsx b/src/contexts/FestivalEditionContext.tsx index 6d493858..697d36cb 100644 --- a/src/contexts/FestivalEditionContext.tsx +++ b/src/contexts/FestivalEditionContext.tsx @@ -24,7 +24,7 @@ export function useFestivalEdition() { interface FestivalEditionProviderProps { festival: Festival; - editionSlug?: string; + editionSlug?: string | undefined; } export function FestivalEditionProvider({ diff --git a/src/hooks/use-toast.ts b/src/hooks/use-toast.ts index 21a5c5ec..f4e5b0ab 100644 --- a/src/hooks/use-toast.ts +++ b/src/hooks/use-toast.ts @@ -39,11 +39,11 @@ type Action = } | { type: ActionType["DISMISS_TOAST"]; - toastId?: ToasterToast["id"]; + toastId?: ToasterToast["id"] | undefined; } | { type: ActionType["REMOVE_TOAST"]; - toastId?: ToasterToast["id"]; + toastId?: ToasterToast["id"] | undefined; }; interface State { diff --git a/src/hooks/useScheduleData.ts b/src/hooks/useScheduleData.ts index 81028817..b3630315 100644 --- a/src/hooks/useScheduleData.ts +++ b/src/hooks/useScheduleData.ts @@ -26,8 +26,8 @@ export interface ScheduleArtist { name: string; slug?: string; stageId?: string; - startTime?: Date; - endTime?: Date; + startTime?: Date | undefined; + endTime?: Date | undefined; votes?: { vote_type: number; user_id: string }[]; formattedTimeRange?: string | null; conflictsWith?: string[]; diff --git a/src/lib/scheduleFilter.ts b/src/lib/scheduleFilter.ts index c92f48ac..50976b22 100644 --- a/src/lib/scheduleFilter.ts +++ b/src/lib/scheduleFilter.ts @@ -19,9 +19,9 @@ export interface ScheduleFilterCriteria { voteTypes?: VoteType[]; voteScope?: VoteScope; /** `undefined` (logged out) makes vote filtering inert, not exclusionary. */ - currentUserId?: string; + currentUserId?: string | undefined; /** `undefined` (group members still loading, or no group) makes group-scope vote filtering inert. */ - groupMemberIds?: Set; + groupMemberIds?: Set | undefined; } function matchesTimeOfDay( diff --git a/src/lib/timelineCalculator.ts b/src/lib/timelineCalculator.ts index 558230ca..9b1a2549 100644 --- a/src/lib/timelineCalculator.ts +++ b/src/lib/timelineCalculator.ts @@ -44,7 +44,7 @@ export interface TimelineData { timeSlots: Date[]; stages: Array<{ name: string; - color?: string; + color?: string | undefined; sets: HorizontalTimelineSet[]; }>; totalWidth: number; diff --git a/src/lib/timelineMountMoment.ts b/src/lib/timelineMountMoment.ts index d9f061ef..653b7173 100644 --- a/src/lib/timelineMountMoment.ts +++ b/src/lib/timelineMountMoment.ts @@ -3,7 +3,7 @@ import { fromZonedTime } from "date-fns-tz"; import type { ScheduleWindow } from "@/lib/timelineCalculator"; export interface TimelineMountMomentInput { - scrollTo?: string; + scrollTo?: string | undefined; day: string; timezone: string; festivalStart: Date; diff --git a/src/pages/EditionView/EditionHero.tsx b/src/pages/EditionView/EditionHero.tsx index 1b820f65..085bb14a 100644 --- a/src/pages/EditionView/EditionHero.tsx +++ b/src/pages/EditionView/EditionHero.tsx @@ -3,7 +3,7 @@ import { useFestivalPhase } from "@/hooks/useFestivalPhase"; interface EditionHeroProps { title: string; - logoUrl?: string | null; + logoUrl?: string | null | undefined; onRowRefChange?: (node: HTMLElement | null) => void; } diff --git a/src/pages/EditionView/tabs/InfoTab/EditionTitle.tsx b/src/pages/EditionView/tabs/InfoTab/EditionTitle.tsx index 4865156a..89b87a82 100644 --- a/src/pages/EditionView/tabs/InfoTab/EditionTitle.tsx +++ b/src/pages/EditionView/tabs/InfoTab/EditionTitle.tsx @@ -1,5 +1,5 @@ interface EditionTitleProps { - name?: string; + name?: string | undefined; } export function EditionTitle({ name }: EditionTitleProps) { diff --git a/src/pages/EditionView/tabs/ScheduleTab/horizontal/StageLabels.tsx b/src/pages/EditionView/tabs/ScheduleTab/horizontal/StageLabels.tsx index d8adfca4..51c21b92 100644 --- a/src/pages/EditionView/tabs/ScheduleTab/horizontal/StageLabels.tsx +++ b/src/pages/EditionView/tabs/ScheduleTab/horizontal/StageLabels.tsx @@ -1,7 +1,7 @@ import { DEFAULT_STAGE_COLOR } from "@/lib/constants/stages"; interface StageLabelsProps { - stages: Array<{ name: string; color?: string }>; + stages: Array<{ name: string; color?: string | undefined }>; } export function StageLabels({ stages }: StageLabelsProps) { diff --git a/src/pages/EditionView/tabs/ScheduleTab/horizontal/StageRow.tsx b/src/pages/EditionView/tabs/ScheduleTab/horizontal/StageRow.tsx index 752cfb44..35fc873a 100644 --- a/src/pages/EditionView/tabs/ScheduleTab/horizontal/StageRow.tsx +++ b/src/pages/EditionView/tabs/ScheduleTab/horizontal/StageRow.tsx @@ -4,7 +4,7 @@ import type { HorizontalTimelineSet } from "@/lib/timelineCalculator"; interface StageRowProps { stage: { name: string; - color?: string; + color?: string | undefined; sets: HorizontalTimelineSet[]; }; totalWidth: number; diff --git a/src/pages/EditionView/tabs/ScheduleTab/list/ListDayGroup.tsx b/src/pages/EditionView/tabs/ScheduleTab/list/ListDayGroup.tsx index db04dab3..f2fa952d 100644 --- a/src/pages/EditionView/tabs/ScheduleTab/list/ListDayGroup.tsx +++ b/src/pages/EditionView/tabs/ScheduleTab/list/ListDayGroup.tsx @@ -9,7 +9,10 @@ import type { ScheduleSet } from "@/hooks/useScheduleData"; interface TimeSlot { time: Date; - sets: (ScheduleSet & { stageName: string; stageColor?: string })[]; + sets: (ScheduleSet & { + stageName: string; + stageColor?: string | undefined; + })[]; } interface ListDayGroupProps { diff --git a/src/pages/EditionView/tabs/ScheduleTab/list/MobileSetCard.tsx b/src/pages/EditionView/tabs/ScheduleTab/list/MobileSetCard.tsx index fa9082ad..8858413c 100644 --- a/src/pages/EditionView/tabs/ScheduleTab/list/MobileSetCard.tsx +++ b/src/pages/EditionView/tabs/ScheduleTab/list/MobileSetCard.tsx @@ -8,8 +8,8 @@ import { StageBadge } from "@/components/StageBadge"; import type { ScheduleSet } from "@/hooks/useScheduleData"; interface MobileSetCardProps { - set: ScheduleSet & { stageName: string; stageColor?: string }; - timezone?: string; + set: ScheduleSet & { stageName: string; stageColor?: string | undefined }; + timezone?: string | undefined; } export function MobileSetCard({ set, timezone }: MobileSetCardProps) { diff --git a/src/pages/EditionView/tabs/ScheduleTab/list/TimeSlotGroup.tsx b/src/pages/EditionView/tabs/ScheduleTab/list/TimeSlotGroup.tsx index 7381c20d..5dfc59be 100644 --- a/src/pages/EditionView/tabs/ScheduleTab/list/TimeSlotGroup.tsx +++ b/src/pages/EditionView/tabs/ScheduleTab/list/TimeSlotGroup.tsx @@ -6,12 +6,15 @@ import type { ScheduleSet } from "@/hooks/useScheduleData"; interface TimeSlot { time: Date; - sets: (ScheduleSet & { stageName: string; stageColor?: string })[]; + sets: (ScheduleSet & { + stageName: string; + stageColor?: string | undefined; + })[]; } interface TimeSlotGroupProps { timeSlot: TimeSlot; - timezone?: string; + timezone?: string | undefined; } export function TimeSlotGroup({ timeSlot, timezone }: TimeSlotGroupProps) { diff --git a/src/pages/EditionView/tabs/VoteTab/filters/FilterSortControls.tsx b/src/pages/EditionView/tabs/VoteTab/filters/FilterSortControls.tsx index 5da56744..4296c39b 100644 --- a/src/pages/EditionView/tabs/VoteTab/filters/FilterSortControls.tsx +++ b/src/pages/EditionView/tabs/VoteTab/filters/FilterSortControls.tsx @@ -22,7 +22,7 @@ interface FilterSortControlsProps { onStateChange: (updates: Partial) => void; onClear: () => void; editionId: string; - votePerspective?: VotePerspectiveProps; + votePerspective?: VotePerspectiveProps | undefined; } export function FilterSortControls({ diff --git a/src/pages/ExploreSetPage/SetExploreCard/SetAudioPlayer.tsx b/src/pages/ExploreSetPage/SetExploreCard/SetAudioPlayer.tsx index fcff52f2..1ced8013 100644 --- a/src/pages/ExploreSetPage/SetExploreCard/SetAudioPlayer.tsx +++ b/src/pages/ExploreSetPage/SetExploreCard/SetAudioPlayer.tsx @@ -1,5 +1,5 @@ interface SetAudioPlayerProps { - soundcloudUrl?: string; + soundcloudUrl?: string | undefined; isActive?: boolean; } diff --git a/src/pages/ExploreSetPage/SetExploreCard/SetCardHeader.tsx b/src/pages/ExploreSetPage/SetExploreCard/SetCardHeader.tsx index 9d131e9b..e67a56b8 100644 --- a/src/pages/ExploreSetPage/SetExploreCard/SetCardHeader.tsx +++ b/src/pages/ExploreSetPage/SetExploreCard/SetCardHeader.tsx @@ -4,7 +4,7 @@ import { StageBadgeById } from "@/components/StageBadgeById"; import { useScheduleReveal } from "@/hooks/useScheduleReveal"; interface SetCardHeaderProps { - stageId?: string; + stageId?: string | undefined; timeStart: string | null; } diff --git a/src/pages/ExploreSetPage/SetExploreCard/SoundCloudBadge.tsx b/src/pages/ExploreSetPage/SetExploreCard/SoundCloudBadge.tsx index c29e5cc5..9d4288ca 100644 --- a/src/pages/ExploreSetPage/SetExploreCard/SoundCloudBadge.tsx +++ b/src/pages/ExploreSetPage/SetExploreCard/SoundCloudBadge.tsx @@ -1,6 +1,6 @@ interface SoundCloudBadgeProps { soundcloudUrl?: string | null; - onClick?: (e: React.MouseEvent) => void; + onClick?: ((e: React.MouseEvent) => void) | undefined; } export function SoundCloudBadge({ diff --git a/src/pages/ExploreSetPage/useExplorableSets.tsx b/src/pages/ExploreSetPage/useExplorableSets.tsx index e3bbb7f8..fe8d37e7 100644 --- a/src/pages/ExploreSetPage/useExplorableSets.tsx +++ b/src/pages/ExploreSetPage/useExplorableSets.tsx @@ -5,7 +5,7 @@ export function useExplorableSets({ editionId, userVotes, }: { - editionId?: string; + editionId?: string | undefined; userVotes: Record; }) { const setsQuery = useSetsByEditionQuery(editionId); diff --git a/src/pages/admin/festivals/info/FestivalFields/FestivalInfoField.tsx b/src/pages/admin/festivals/info/FestivalFields/FestivalInfoField.tsx index 8d495b28..dfb4b1c4 100644 --- a/src/pages/admin/festivals/info/FestivalFields/FestivalInfoField.tsx +++ b/src/pages/admin/festivals/info/FestivalFields/FestivalInfoField.tsx @@ -8,7 +8,7 @@ import { getTextAlignmentClasses } from "@/lib/textAlignment"; interface FestivalInfoFieldProps { festivalId: string; - infoText?: string | null; + infoText?: string | null | undefined; } interface InfoFormData { @@ -50,7 +50,7 @@ function InfoFieldForm({ onSave, }: { festivalId: string; - infoText?: string | null; + infoText?: string | null | undefined; onCancel: () => void; onSave: () => void; }) { diff --git a/src/pages/admin/festivals/info/FestivalFields/FestivalMapField.tsx b/src/pages/admin/festivals/info/FestivalFields/FestivalMapField.tsx index ff325c07..3ca0aa04 100644 --- a/src/pages/admin/festivals/info/FestivalFields/FestivalMapField.tsx +++ b/src/pages/admin/festivals/info/FestivalFields/FestivalMapField.tsx @@ -8,7 +8,7 @@ import { useMapUpload } from "./shared/useMapUpload"; interface FestivalMapFieldProps { festivalId: string; - mapImageUrl?: string | null; + mapImageUrl?: string | null | undefined; } interface MapFormData { @@ -51,7 +51,7 @@ function MapFieldForm({ onSave, }: { festivalId: string; - mapImageUrl?: string | null; + mapImageUrl?: string | null | undefined; onCancel: () => void; onSave: () => void; }) { diff --git a/src/pages/admin/festivals/info/FestivalFields/FestivalSocialField.tsx b/src/pages/admin/festivals/info/FestivalFields/FestivalSocialField.tsx index 6f86876b..367eecfb 100644 --- a/src/pages/admin/festivals/info/FestivalFields/FestivalSocialField.tsx +++ b/src/pages/admin/festivals/info/FestivalFields/FestivalSocialField.tsx @@ -8,8 +8,8 @@ import { useFestivalInfoMutation } from "@/api/festival-info/useFestivalInfoMuta interface FestivalSocialFieldProps { festivalId: string; - facebookUrl?: string | null; - instagramUrl?: string | null; + facebookUrl?: string | null | undefined; + instagramUrl?: string | null | undefined; } interface SocialFormData { @@ -72,8 +72,8 @@ function SocialFieldForm({ onSave, }: { festivalId: string; - facebookUrl?: string | null; - instagramUrl?: string | null; + facebookUrl?: string | null | undefined; + instagramUrl?: string | null | undefined; onCancel: () => void; onSave: () => void; diff --git a/src/routes/festivals/$festivalSlug/editions/$editionSlug/schedule/list.tsx b/src/routes/festivals/$festivalSlug/editions/$editionSlug/schedule/list.tsx index d49178e3..c9b40aa3 100644 --- a/src/routes/festivals/$festivalSlug/editions/$editionSlug/schedule/list.tsx +++ b/src/routes/festivals/$festivalSlug/editions/$editionSlug/schedule/list.tsx @@ -33,7 +33,10 @@ export const Route = createFileRoute( interface TimeSlot { time: Date; - sets: (ScheduleSet & { stageName: string; stageColor?: string })[]; + sets: (ScheduleSet & { + stageName: string; + stageColor?: string | undefined; + })[]; } interface DayGroup { @@ -86,7 +89,7 @@ function ListSchedule() { // startTime can't be placed into a time slot, so they're dropped here. const allSets: (ScheduleSet & { stageName: string; - stageColor?: string; + stageColor?: string | undefined; })[] = []; filteredScheduleDays.forEach((day) => { @@ -108,7 +111,7 @@ function ListSchedule() { // Group sets by start time const timeGroups = new Map< string, - (ScheduleSet & { stageName: string; stageColor?: string })[] + (ScheduleSet & { stageName: string; stageColor?: string | undefined })[] >(); allSets.forEach((set) => { diff --git a/src/services/scheduleImport/parseCsv.ts b/src/services/scheduleImport/parseCsv.ts index 8886548e..dac84f48 100644 --- a/src/services/scheduleImport/parseCsv.ts +++ b/src/services/scheduleImport/parseCsv.ts @@ -23,15 +23,22 @@ export function parseScheduleCsv(csvContent: string): CsvRow[] { .filter(Boolean), ); - return { - artists, - setName: row["set name"]?.trim() || undefined, - stage: row.stage?.trim() || undefined, - date: row.date?.trim() || undefined, - startTime: row["start time"]?.trim() || undefined, - endTime: row["end time"]?.trim() || undefined, - description: row.description?.trim() || undefined, - }; + const setName = row["set name"]?.trim() || undefined; + const stage = row.stage?.trim() || undefined; + const date = row.date?.trim() || undefined; + const startTime = row["start time"]?.trim() || undefined; + const endTime = row["end time"]?.trim() || undefined; + const description = row.description?.trim() || undefined; + + const csvRow: CsvRow = { artists }; + if (setName !== undefined) csvRow.setName = setName; + if (stage !== undefined) csvRow.stage = stage; + if (date !== undefined) csvRow.date = date; + if (startTime !== undefined) csvRow.startTime = startTime; + if (endTime !== undefined) csvRow.endTime = endTime; + if (description !== undefined) csvRow.description = description; + + return csvRow; }) .filter((row) => row.artists.length > 0); diff --git a/tsconfig.app.json b/tsconfig.app.json index 18756ac5..6650053e 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -16,6 +16,7 @@ /* Linting */ "strict": true, + "exactOptionalPropertyTypes": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true,