Skip to content

Commit 7e61fe1

Browse files
committed
feat: add festival links to header
1 parent a60450b commit 7e61fe1

File tree

6 files changed

+125
-31
lines changed

6 files changed

+125
-31
lines changed

src/components/layout/AppHeader.tsx

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,29 @@ interface AppHeaderProps {
1616

1717
// Navigation options
1818
showGroupsButton?: boolean;
19+
20+
// External links
21+
websiteUrl?: string;
22+
ticketsUrl?: string;
1923
}
2024

2125
export function AppHeader({
2226
showBackButton = false,
2327
backLabel = "Back",
2428
title,
25-
2629
logoUrl,
27-
2830
showGroupsButton = false,
31+
websiteUrl,
32+
ticketsUrl,
2933
}: AppHeaderProps) {
30-
// Track visibility of logo specifically for festival context in top bar
3134
const titleRef = useRef<HTMLDivElement | null>(null);
3235
const logoRef = useRef<HTMLElement | null>(null);
3336

34-
// Use logo visibility with top bar offset - trigger when logo hits the top bar
35-
const isLogoVisible = useScrollVisibility(logoRef, {
36-
rootMargin: "-80px 0px 0px 0px", // Negative top margin = trigger when logo is 80px from top (behind top bar)
37+
const shouldShowFestivalIcon = useShouldShowIconInTitle({
38+
logoRef,
39+
titleRef,
40+
logoUrl,
3741
});
38-
const isTitleVisible = useScrollVisibility(titleRef, {
39-
rootMargin: "-80px 0px 0px 0px", // Same offset for consistency
40-
});
41-
const shouldShowFestivalIcon = logoUrl ? !isLogoVisible : !isTitleVisible;
4242

4343
const handleLogoRefChange = useCallback((node: HTMLElement | null) => {
4444
logoRef.current = node;
@@ -60,13 +60,36 @@ export function AppHeader({
6060
</TopBar>
6161

6262
<div ref={titleRef}>
63-
<TitleSection
64-
title={title}
65-
logoUrl={logoUrl}
66-
onLogoRefChange={handleLogoRefChange}
67-
/>
63+
{title && (
64+
<TitleSection
65+
title={title}
66+
logoUrl={logoUrl}
67+
onLogoRefChange={handleLogoRefChange}
68+
websiteUrl={websiteUrl}
69+
ticketsUrl={ticketsUrl}
70+
/>
71+
)}
6872
</div>
6973
</div>
7074
</TooltipProvider>
7175
);
7276
}
77+
function useShouldShowIconInTitle({
78+
logoRef,
79+
logoUrl,
80+
titleRef,
81+
}: {
82+
logoRef: React.RefObject<Element>;
83+
titleRef: React.RefObject<Element>;
84+
logoUrl: string | null | undefined;
85+
}) {
86+
const isLogoVisible = useScrollVisibility(logoRef, {
87+
rootMargin: "-80px 0px 0px 0px", // Negative top margin = trigger when logo is 80px from top (behind top bar)
88+
});
89+
90+
const isTitleVisible = useScrollVisibility(titleRef, {
91+
rootMargin: "-80px 0px 0px 0px", // Same offset for consistency
92+
});
93+
94+
return logoUrl ? !isLogoVisible : !isTitleVisible;
95+
}

src/components/layout/AppHeader/TitleSection.tsx

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
import { forwardRef, useCallback } from "react";
2-
import { Music, Heart } from "lucide-react";
2+
import { Music, Heart, TicketIcon, ExternalLinkIcon } from "lucide-react";
3+
import { Button } from "@/components/ui/button";
34

45
interface TitleSectionProps {
5-
title?: string;
6+
title: string;
67
logoUrl?: string | null;
78
onLogoRefChange?: (ref: HTMLElement | null) => void;
9+
websiteUrl?: string;
10+
ticketsUrl?: string;
811
}
912

1013
export const TitleSection = forwardRef<HTMLDivElement, TitleSectionProps>(
11-
({ title, logoUrl, onLogoRefChange }, ref) => {
14+
({ title, logoUrl, onLogoRefChange, ticketsUrl, websiteUrl }, ref) => {
15+
const hasLinks = !!(ticketsUrl || websiteUrl);
16+
1217
const logoRefCallback = useCallback(
1318
(node: HTMLImageElement | null) => {
1419
onLogoRefChange?.(node);
1520
},
1621
[onLogoRefChange],
1722
);
1823

19-
if (!title) return null;
20-
2124
return (
2225
<div
2326
ref={ref}
@@ -33,14 +36,43 @@ export const TitleSection = forwardRef<HTMLDivElement, TitleSectionProps>(
3336
/>
3437
) : (
3538
<>
36-
<Music className="h-6 md:h-8 w-6 md:w-8 text-purple-400 animate-pulse md:block hidden" />
37-
<h2 className="text-2xl md:text-3xl lg:text-4xl font-bold text-white tracking-tight hidden md:block">
39+
<Music className="h-6 md:h-8 w-6 md:w-8 text-purple-400 animate-pulse" />
40+
<h2 className="text-2xl md:text-3xl lg:text-4xl font-bold text-white tracking-tight">
3841
{title}
3942
</h2>
40-
<Heart className="h-6 md:h-8 w-6 md:w-8 text-pink-400 animate-pulse md:block hidden" />
43+
<Heart className="h-6 md:h-8 w-6 md:w-8 text-pink-400 animate-pulse" />
4144
</>
4245
)}
4346
</div>
47+
48+
{hasLinks && (
49+
<div className="flex gap-3 justify-center">
50+
{ticketsUrl && (
51+
<Button
52+
size="sm"
53+
asChild
54+
className="gap-2 bg-white/10 backdrop-blur-sm border border-white/20 hover:bg-white/20 hover:border-white/30 text-white font-semibold transition-all"
55+
>
56+
<a href={ticketsUrl} target="_blank" rel="noopener noreferrer">
57+
<TicketIcon className="h-5 w-5" />
58+
Tickets
59+
</a>
60+
</Button>
61+
)}
62+
{websiteUrl && (
63+
<Button
64+
size="sm"
65+
asChild
66+
className="gap-2 bg-white/10 backdrop-blur-sm border border-white/20 hover:bg-white/20 hover:border-white/30 text-white font-semibold transition-all"
67+
>
68+
<a href={websiteUrl} target="_blank" rel="noopener noreferrer">
69+
<ExternalLinkIcon className="h-4 w-4" />
70+
Website
71+
</a>
72+
</Button>
73+
)}
74+
</div>
75+
)}
4476
</div>
4577
);
4678
},

src/integrations/supabase/types.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ export type Database = {
210210
display_order: number | null;
211211
festival_id: string;
212212
id: string;
213+
link_type: Database["public"]["Enums"]["link_type"];
213214
title: string;
214215
updated_at: string | null;
215216
url: string;
@@ -219,6 +220,7 @@ export type Database = {
219220
display_order?: number | null;
220221
festival_id: string;
221222
id?: string;
223+
link_type?: Database["public"]["Enums"]["link_type"];
222224
title: string;
223225
updated_at?: string | null;
224226
url: string;
@@ -228,6 +230,7 @@ export type Database = {
228230
display_order?: number | null;
229231
festival_id?: string;
230232
id?: string;
233+
link_type?: Database["public"]["Enums"]["link_type"];
231234
title?: string;
232235
updated_at?: string | null;
233236
url?: string;
@@ -642,7 +645,6 @@ export type Database = {
642645
playlist_title: string | null;
643646
playlist_url: string | null;
644647
soundcloud_id: number | null;
645-
soundcloud_url: string;
646648
updated_at: string | null;
647649
username: string | null;
648650
};
@@ -656,7 +658,6 @@ export type Database = {
656658
playlist_title?: string | null;
657659
playlist_url?: string | null;
658660
soundcloud_id?: number | null;
659-
soundcloud_url: string;
660661
updated_at?: string | null;
661662
username?: string | null;
662663
};
@@ -670,7 +671,6 @@ export type Database = {
670671
playlist_title?: string | null;
671672
playlist_url?: string | null;
672673
soundcloud_id?: number | null;
673-
soundcloud_url?: string;
674674
updated_at?: string | null;
675675
username?: string | null;
676676
};
@@ -798,9 +798,9 @@ export type Database = {
798798
use_invite_token: {
799799
Args: { token: string; user_id: string };
800800
Returns: {
801-
success: boolean;
802-
message: string;
803801
group_id: string;
802+
message: string;
803+
success: boolean;
804804
}[];
805805
};
806806
users_share_group: {
@@ -810,9 +810,9 @@ export type Database = {
810810
validate_invite_token: {
811811
Args: { token: string };
812812
Returns: {
813-
invite_id: string;
814813
group_id: string;
815814
group_name: string;
815+
invite_id: string;
816816
is_valid: boolean;
817817
reason: string;
818818
}[];
@@ -824,6 +824,7 @@ export type Database = {
824824
};
825825
Enums: {
826826
admin_role: "super_admin" | "admin" | "moderator";
827+
link_type: "website" | "tickets" | "custom";
827828
};
828829
CompositeTypes: {
829830
[_ in never]: never;
@@ -958,6 +959,7 @@ export const Constants = {
958959
public: {
959960
Enums: {
960961
admin_role: ["super_admin", "admin", "moderator"],
962+
link_type: ["website", "tickets", "custom"],
961963
},
962964
},
963965
} as const;

src/pages/EditionView/EditionLayout.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { MainTabNavigation } from "./TabNavigation/TabNavigation";
33
import ErrorBoundary from "@/components/ErrorBoundary";
44
import { useFestivalEdition } from "@/contexts/FestivalEditionContext";
55
import { Outlet } from "react-router-dom";
6+
import { useCustomLinksQuery } from "@/hooks/queries/custom-links/useCustomLinks";
67

78
export default function EditionView() {
89
const { festival, edition, isContextReady } = useFestivalEdition();
10+
const customLinksQuery = useCustomLinksQuery(festival?.id);
911

1012
if (!isContextReady) {
1113
return (
@@ -23,13 +25,23 @@ export default function EditionView() {
2325
);
2426
}
2527

28+
const customLinks = customLinksQuery.data || [];
29+
const websiteUrl = customLinks.find(
30+
(link) => link.link_type === "website",
31+
)?.url;
32+
const ticketsUrl = customLinks.find(
33+
(link) => link.link_type === "tickets",
34+
)?.url;
35+
console.log(customLinks);
2636
return (
2737
<div className="min-h-screen bg-app-gradient">
2838
<div className="container mx-auto px-4 py-4 md:py-8 pb-20 md:pb-8">
2939
<AppHeader
30-
title={festival.name}
40+
title={`${festival.name} - ${edition.name}`}
3141
logoUrl={festival.logo_url}
3242
showGroupsButton
43+
websiteUrl={websiteUrl}
44+
ticketsUrl={ticketsUrl}
3345
/>
3446

3547
<MainTabNavigation />

src/pages/FestivalSelection.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ export default function FestivalSelection() {
9797

9898
function FestivalCard({ festival }: { festival: Festival }) {
9999
const customLinksQuery = useCustomLinksQuery(festival.id);
100-
const websiteUrl = customLinksQuery.data?.[0]?.url;
100+
const websiteUrl = customLinksQuery.data?.find(
101+
(link) => link.link_type === "website",
102+
)?.url;
101103

102104
return (
103105
<Link
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-- Add link_type enum to custom_links table for type-safe link classification
2+
3+
-- Create enum type for link types
4+
CREATE TYPE link_type AS ENUM ('website', 'tickets', 'custom');
5+
6+
-- Add link_type column with default 'custom' for backward compatibility
7+
ALTER TABLE public.custom_links
8+
ADD COLUMN link_type link_type NOT NULL DEFAULT 'custom';
9+
10+
-- Update existing links based on title
11+
UPDATE public.custom_links
12+
SET link_type = 'website'
13+
WHERE LOWER(title) = 'website';
14+
15+
UPDATE public.custom_links
16+
SET link_type = 'tickets'
17+
WHERE LOWER(title) IN ('tickets', 'ticket', 'buy tickets');
18+
19+
-- Create index for performance on link_type queries
20+
CREATE INDEX idx_custom_links_link_type ON public.custom_links(festival_id, link_type);
21+
22+
-- Add comment for documentation
23+
COMMENT ON COLUMN public.custom_links.link_type IS 'Type of link: website (main festival site), tickets (ticket sales), or custom (user-defined)';

0 commit comments

Comments
 (0)