Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
"mcp__ide__getDiagnostics",
"Bash(git checkout:*)",
"Bash(mkdir:*)",
"Bash(npx oxlint:*)"
"Bash(npx oxlint:*)",
"WebSearch",
"WebFetch(domain:tanstack.com)",
"Bash(node:*)"
],
"deny": []
}
Expand Down
7 changes: 7 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copilot Custom Instructions

see CLAUDE.md for more details and rules.

## Function Definitions After Return in React Components

In this codebase, it is acceptable and preferred to define helper functions (such as event handlers) after the main component’s return statement. This style improves readability by keeping the primary component logic at the top and allowing additional details to be found below. JavaScript and TypeScript support function hoisting for function declarations, so this pattern is safe and intentional. Please do not flag this as a style issue in reviews.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ tests/screenshots/
supabase/.temp
supabase/.branches
.vercel
dev-dist
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ src/
- **Forms**: ALL forms must use react-hook-form with proper validation. Never use plain HTML forms or manual state management for form inputs. Use @hookform/resolvers for validation schemas when needed.
- **Long Components**: Break long components (>150 lines) into smaller focused pieces. Follow the FilterSortControls pattern of primary controls + expandable sections.

#### Function Definitions After Return in React Components

In this codebase, it is acceptable and preferred to define helper functions (such as event handlers) after the main component’s return statement. This style improves readability by keeping the primary component logic at the top and allowing additional details to be found below. JavaScript and TypeScript support function hoisting for function declarations, so this pattern is safe and intentional. Please do not flag this as a style issue in reviews.

### Important Notes

- Server runs on port 8080 (not standard 3000)
Expand Down
82 changes: 75 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@
"@radix-ui/react-tooltip": "^1.1.4",
"@supabase/supabase-js": "^2.50.0",
"@tailwindcss/line-clamp": "^0.4.4",
"@tanstack/query-async-storage-persister": "^5.86.0",
"@tanstack/react-query": "^5.56.2",
"@tanstack/react-query-devtools": "^5.81.2",
"@tanstack/react-query-persist-client": "^5.85.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import { BrowserRouter } from "react-router-dom";
import { CookieConsentBanner } from "@/components/layout/legal/CookieConsentBanner";
import { OfflineIndicator } from "@/components/ui/OfflineIndicator";
import {
getSubdomainInfo,
shouldRedirectFromWww,
Expand Down Expand Up @@ -40,6 +41,7 @@ function App() {
</FestivalEditionProvider>
</AuthProvider>
</BrowserRouter>
<OfflineIndicator />
</TooltipProvider>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/router/EditionRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { MapTab } from "@/pages/EditionView/tabs/MapTab";
import { InfoTab } from "@/pages/EditionView/tabs/InfoTab";
import { SocialTab } from "@/pages/EditionView/tabs/SocialTab";
import { ScheduleTabTimeline } from "@/pages/EditionView/tabs/ScheduleTab/TimelineTab";
import { ScheduleTabList } from "@/pages/EditionView/tabs/ScheduleTab/ListTab";
import { ScheduleTabList } from "@/pages/EditionView/tabs/ScheduleTab/list/ListTab";
import { ScheduleTab } from "@/pages/EditionView/tabs/ScheduleTab";

interface EditionRoutesProps {
Expand Down
16 changes: 16 additions & 0 deletions src/components/ui/OfflineIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useOnlineStatus } from "@/hooks/useOnlineStatus";
import { Badge } from "@/components/ui/badge";
import { WifiOff } from "lucide-react";

export function OfflineIndicator() {
const isOnline = useOnlineStatus();

if (isOnline) return null;

return (
<Badge variant="destructive" className="fixed top-4 right-4 z-50 gap-1">
<WifiOff size={12} />
Offline
</Badge>
);
}
5 changes: 2 additions & 3 deletions src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
import { User } from "@supabase/supabase-js";
import { supabase } from "@/integrations/supabase/client";
import { useProfileQuery } from "@/hooks/queries/auth/useProfile";
import { profileOfflineService } from "@/services/profileOfflineService";
import { useToast } from "@/hooks/use-toast";
import { AuthDialog } from "@/components/AuthDialog/AuthDialog";
import { Profile } from "@/hooks/queries/auth/useProfile";
Expand Down Expand Up @@ -60,7 +59,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
if (event === "SIGNED_OUT") {
// For sign out, use the current user state from closure
if (user?.id) {
await profileOfflineService.clearCachedProfile(user.id);
// await profileOfflineService.clearCachedProfile(user.id);
}
}

Expand Down Expand Up @@ -127,7 +126,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
async function signOut() {
// Clear cached profile before signing out
if (user?.id) {
await profileOfflineService.clearCachedProfile(user.id);
// await profileOfflineService.clearCachedProfile(user.id);
}
await supabase.auth.signOut();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@ import { useToast } from "@/hooks/use-toast";
import { supabase } from "@/integrations/supabase/client";
import { artistNotesKeys } from "./types";

async function saveArtistNote(variables: {
artistId: string;
async function createNote({
setId,
userId,
noteContent,
}: {
setId: string;
userId: string;
noteContent: string;
}) {
const { artistId, userId, noteContent } = variables;

const { data, error } = await supabase
.from("artist_notes")
.upsert({
artist_id: artistId,
artist_id: setId,
user_id: userId,
note_content: noteContent,
})
Expand All @@ -24,16 +26,16 @@ async function saveArtistNote(variables: {
return data;
}

export function useSaveNoteMutation() {
export function useCreateNoteMutation() {
const queryClient = useQueryClient();
const { toast } = useToast();

return useMutation({
mutationFn: saveArtistNote,
mutationFn: createNote,
onSuccess: (_, variables) => {
// Invalidate and refetch notes for this artist
queryClient.invalidateQueries({
queryKey: artistNotesKeys.notes(variables.artistId),
queryKey: artistNotesKeys.notes(variables.setId),
});
toast({
title: "Success",
Expand Down
47 changes: 1 addition & 46 deletions src/hooks/queries/auth/useProfile.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import { supabase } from "@/integrations/supabase/client";
import type { Database } from "@/integrations/supabase/types";
import { profileOfflineService } from "@/services/profileOfflineService";
import { useOfflineProfileToast } from "@/hooks/useOfflineProfileToast";

export type Profile = Database["public"]["Tables"]["profiles"]["Row"];

Expand All @@ -27,53 +25,10 @@ async function fetchProfile(userId: string) {
return data;
}

// Hook with offline support
export function useProfileQuery(userId: string | undefined) {
const { showOfflineProfileToast, isOnline } = useOfflineProfileToast();

return useQuery({
queryKey: profileKeys.detail(userId),
queryFn: async () => {
if (!userId) return null;

try {
// Try online fetch first
if (isOnline) {
const profile = await fetchProfile(userId);
// Cache successful fetch
await profileOfflineService.cacheProfile(userId, profile);
return profile;
} else {
// Use cached data when offline
const cachedProfile =
await profileOfflineService.getCachedProfile(userId);
if (cachedProfile) {
showOfflineProfileToast();
return cachedProfile;
}
throw new Error("No profile data available offline");
}
} catch (error) {
// Fallback to cache on error
if (isOnline) {
console.error("Online profile fetch failed, using cache:", error);
const cachedProfile =
await profileOfflineService.getCachedProfile(userId);
if (cachedProfile) {
showOfflineProfileToast();
return cachedProfile;
}
}
throw error;
}
},
queryFn: () => fetchProfile(userId!),
enabled: !!userId,
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 24 * 60 * 60 * 1000, // 24 hours (was cacheTime)
retry: (failureCount, _error) => {
// Don't retry if we're offline and have cached data
if (!isOnline) return false;
return failureCount < 2;
},
});
}
9 changes: 1 addition & 8 deletions src/hooks/queries/auth/useUpdateProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useToast } from "@/hooks/use-toast";
import { supabase } from "@/integrations/supabase/client";
import { profileKeys } from "./useProfile";
import { profileOfflineService } from "@/services/profileOfflineService";

// Mutation function
async function updateProfile(variables: {
Expand Down Expand Up @@ -56,13 +55,7 @@ export function useUpdateProfileMutation() {

return useMutation({
mutationFn: updateProfile,
onSuccess: async (data, variables) => {
// Update the profile cache
queryClient.setQueryData(profileKeys.detail(variables.userId), data);

// Update offline cache
await profileOfflineService.cacheProfile(variables.userId, data);

onSuccess: async (_data, variables) => {
// Invalidate to ensure consistency
queryClient.invalidateQueries({
queryKey: profileKeys.detail(variables.userId),
Expand Down
Loading
Loading