Setting the file. One moment.
Services View · Wix Headless Fast · wix/skills · Skills Docs
ContentsBack to the top of the page Reference Service Booking View
references/bookings/app/components/bookings/ ServicesView.tsx
TypeScript · 119 lines · 4 KB
9
className
?:
string
;
10 children ?: ReactNode ;
11 }
12
13 const PlainLink = ({ href , className , children } : LinkLikeProps ) => (
14 < a href ={ href } className ={ className } >
15 { children }
16 </ a >
17 );
18
19 export interface ServiceCardProps {
20 service : ServiceSummary ;
21 serviceHref ?: ( slug : string ) => string ;
22 LinkComponent ?: ComponentType < LinkLikeProps >;
23 }
24
25 export function ServiceCard ({
26 service ,
27 serviceHref = ( slug ) => `/services/${ slug }` ,
28 LinkComponent = PlainLink,
29 } : ServiceCardProps ) {
30 return (
31 < LinkComponent href ={ serviceHref (service.slug) } className = "group block no-underline" >
32 < div className = "relative aspect-[4/3] overflow-hidden rounded-lg bg-secondary" >
33 { service.imageUrl && (
34 < img
35 src ={ service.imageUrl }
36 alt ={ service.name }
37 loading = "lazy"
38 className = "h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
39 />
40 ) }
41 < span className = "absolute left-3 top-3 rounded-full bg-background/90 px-3 py-1 text-xs font-semibold text-foreground backdrop-blur" >
42 { service.type === "CLASS" ? "Class" : "Appointment" }
43 </ span >
44 </ div >
45 < p className = "mt-3 text-sm font-medium text-foreground" > { service.name } </ p >
46 { service.tagLine && < p className = "mt-0.5 text-xs text-muted-foreground" > { service.tagLine } </ p > }
47 < p className = "mt-1 text-sm text-muted-foreground" >
48 { service.durationMinutes ? `${ service . durationMinutes } min · ` : "" }
49 < span className = "text-foreground" > { service.price } </ span >
50 </ p >
51 </ LinkComponent >
52 );
53 }
54
55 export interface ServicesViewProps {
56 initialServices ?: ServiceSummary [];
57 initialCategories ?: BookingCategory [];
58 emptyMessage ?: string ;
59 serviceHref ?: ServiceCardProps [ "serviceHref" ];
60 LinkComponent ?: ComponentType < LinkLikeProps >;
61 CardComponent ?: ComponentType < ServiceCardProps >;
62 }
63
64 const pill = ( active : boolean ) =>
65 `rounded-full border px-4 py-1.5 text-sm font-medium transition-colors ${
66 active
67 ? "border-primary bg-primary text-primary-foreground"
68 : "border-border text-foreground hover:bg-secondary"
69 }` ;
70
71 export default function ServicesView ({
72 initialServices,
73 initialCategories,
74 emptyMessage = "No services yet — check back soon." ,
75 serviceHref,
76 LinkComponent,
77 CardComponent = ServiceCard ,
78 } : ServicesViewProps) {
79 const { services , categories , activeCategoryId , setActiveCategoryId , error } = useServices ({
80 initialServices,
81 initialCategories,
82 });
83
84 return (
85 < div >
86 { categories. length > 1 && (
87 < div className = "mb-8 flex flex-wrap gap-2" role = "group" aria-label = "Categories" >
88 < button type = "button" className ={ pill (activeCategoryId === null ) } onClick ={ () => setActiveCategoryId ( null ) } >
89 All
90 </ button >
91 { categories. map (( c ) => (
92 < button key ={ c.id } type = "button" className ={ pill (activeCategoryId === c.id) } onClick ={ () => setActiveCategoryId (c.id) } >
93 { c.name }
94 </ button >
95 )) }
96 </ div >
97 ) }
98 { error && < p className = "mb-4 text-sm text-red-600" > { error } </ p > }
99 { services === null ? (
100 < div className = "grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3" aria-busy = "true" >
101 { Array. from ({ length: 6 }, ( _ , i ) => (
102 < div key ={ i } >
103 < div className = "aspect-[4/3] animate-pulse rounded-lg bg-secondary" />
104 < div className = "mt-3 h-3.5 w-2/3 animate-pulse rounded bg-secondary" />
105 </ div >
106 )) }
107 </ div >
108 ) : services. length === 0 ? (
109 < p className = "py-16 text-center text-muted-foreground" > { emptyMessage } </ p >
110 ) : (
111 < div className = "grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3" >
112 { services. map (( s ) => (
113 < CardComponent key ={ s.id } service ={ s } serviceHref ={ serviceHref } LinkComponent ={ LinkComponent } />
114 )) }
115 </ div >
116 ) }
117 </ div >
118 );
119 }