tuxedo: align storefront subpages with Field Almanac editorial voice
Deploy to route.crispygoat.com / deploy (push) Successful in 4m25s
Deploy to route.crispygoat.com / deploy (push) Successful in 4m25s
Reskin /tuxedo/contact, /tuxedo/faq, /tuxedo/stops, and /tuxedo/stops/[id] so they match the homepage's editorial magazine language (Fraunces display serif, stone-50/stone-950 alternation, amber rims, hairline rules, tabular numerals). - About page rewritten as inlined editorial sections sharing one WP_IMAGES library with the homepage; the four lazy-loaded Tuxedo about components are removed (Indian River has its own variants). - Contact page now uses a dark hero with heroField half-bleed, a hairline-ruled info strip (address/phone/email), a two-column form section with bottom-border editorial inputs, and a closing Fraunces italic quote. /tuxedo/contact was 404ing in prod because page.tsx was missing — created and removed the duplicated render from layout.tsx. - FAQ page uses a cream hero with Fraunces headline, an editorial bottom-border search, hairline-ruled accordion rows (no card wrappers), and a dark closing CTA with Contact pill. - Stops list uses a dark hero with heroField bleed and a cream hairline-ruled sidebar list: tabular date numerals on the left, city/location middle, mono time + cutoff right, chevron for upcoming rows. - Stop detail uses a dark hero pairing the city/state headline with a giant tabular day stamp, a cream hairline-ruled 4-col info strip (Date/Time/Location/Order by), and the editorial products header. Removed the dual-brand isBlue branching now that Indian River has its own page. - Extracted TUXEDO_IMAGES to src/components/storefront/tuxedo-images.ts so the homepage, about, contact, and stops list share one brand-specific asset library. - /tuxedo/faq/layout.tsx no longer double-renders the page component (was rendering <FAQClientPage /> alongside children). Verified: tsc --noEmit clean, eslint clean (only pre-existing homepage warnings), npm run build produces all Tuxedo routes including the now-existing /tuxedo/contact.
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { gsap } from "gsap";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
import StorefrontHeader from "@/components/storefront/StorefrontHeader";
|
||||
import StorefrontFooter from "@/components/storefront/StorefrontFooter";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
import { TUXEDO_IMAGES } from "@/components/storefront/tuxedo-images";
|
||||
import type { PublicStop } from "@/actions/stops";
|
||||
|
||||
type Props = {
|
||||
@@ -16,22 +18,30 @@ type Props = {
|
||||
|
||||
export default function TuxedoStopsList({ stops, brandName, brandSlug }: Props) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [now] = useState(() => new Date());
|
||||
|
||||
// Subtle scroll-in stagger for the stops list. The Motion policy in
|
||||
// ScrollAnimations.tsx caps translation to 16px / 320ms, which matches
|
||||
// what we want here — the list reveals as a quiet beat, not a show.
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
const ctx = gsap.context(() => {
|
||||
gsap.from(".stop-card", {
|
||||
y: 30,
|
||||
gsap.from(".stop-row", {
|
||||
y: 16,
|
||||
opacity: 0,
|
||||
duration: 0.5,
|
||||
stagger: 0.08,
|
||||
ease: "power3.out",
|
||||
duration: 0.32,
|
||||
stagger: 0.04,
|
||||
ease: "power1.out",
|
||||
scrollTrigger: {
|
||||
trigger: containerRef.current,
|
||||
start: "top 90%",
|
||||
toggleActions: "play none none none",
|
||||
},
|
||||
});
|
||||
}, containerRef);
|
||||
return () => ctx.revert();
|
||||
}, []);
|
||||
|
||||
const now = new Date();
|
||||
const upcomingStops = stops.filter((s) => new Date(s.date) >= now);
|
||||
const pastStops = stops.filter((s) => new Date(s.date) < now);
|
||||
|
||||
@@ -39,123 +49,315 @@ export default function TuxedoStopsList({ stops, brandName, brandSlug }: Props)
|
||||
<div ref={containerRef} className="min-h-screen bg-stone-50">
|
||||
<StorefrontHeader brandName={brandName} brandSlug={brandSlug} brandAccent="green" />
|
||||
|
||||
<main className="py-16 md:py-20">
|
||||
<LayoutContainer>
|
||||
<div className="max-w-4xl mx-auto mb-12 text-center">
|
||||
<p className="text-[11px] font-bold uppercase tracking-[0.2em] text-emerald-600 mb-4">
|
||||
{brandName}
|
||||
</p>
|
||||
<h1 className="text-4xl md:text-5xl font-black tracking-tight text-stone-950 mb-4">
|
||||
Pickup Stops
|
||||
</h1>
|
||||
<p className="text-stone-500 text-lg">
|
||||
Fresh Olathe Sweet™ sweet corn delivered to a stop near you.
|
||||
</p>
|
||||
<div className="mt-6 h-1 w-12 bg-emerald-600 mx-auto rounded-full" />
|
||||
<main>
|
||||
{/* Hero — dark editorial opener with the field photograph as a
|
||||
soft half-bleed at the bottom, same register as the
|
||||
Contact page hero. */}
|
||||
<section className="relative bg-stone-950 overflow-hidden isolate">
|
||||
{/* Soft field image, bottom-anchored, partially visible
|
||||
behind the headline column. */}
|
||||
<div className="absolute inset-x-0 bottom-0 h-2/3 -z-10">
|
||||
<Image
|
||||
src={TUXEDO_IMAGES.heroField}
|
||||
alt=""
|
||||
fill
|
||||
sizes="100vw"
|
||||
quality={85}
|
||||
priority
|
||||
className="object-cover object-center filter-editorial opacity-40"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-stone-950 via-stone-950/70 to-stone-950" />
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="absolute inset-x-0 bottom-0 h-px bg-amber-300/30 z-10"
|
||||
/>
|
||||
|
||||
{upcomingStops.length === 0 ? (
|
||||
<div className="max-w-2xl mx-auto text-center py-16">
|
||||
<div className="w-20 h-20 rounded-2xl bg-stone-100 flex items-center justify-center mx-auto mb-6">
|
||||
<svg className="w-10 h-10 text-stone-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-stone-800 mb-2">No Upcoming Stops Yet</h2>
|
||||
<p className="text-stone-500 mb-8 max-w-md mx-auto">
|
||||
The corn is still ripening in the field. New pickup stops are added every week — check back soon, or browse the full season schedule.
|
||||
<LayoutContainer>
|
||||
<div className="relative py-20 sm:py-28 lg:py-32 max-w-3xl">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-[0.32em] text-amber-300/85 mb-6">
|
||||
{brandName} · Summer 2026
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
|
||||
<Link
|
||||
href="/tuxedo"
|
||||
className="inline-flex items-center gap-2 rounded-2xl bg-stone-950 px-6 py-3 text-sm font-semibold text-white hover:bg-stone-800 active:bg-stone-900 transition-colors"
|
||||
>
|
||||
Back to Tuxedo Corn
|
||||
</Link>
|
||||
<h1
|
||||
className="font-display text-stone-50 text-[clamp(2.5rem,5.4vw,4.5rem)] leading-[1.02] tracking-[-0.02em]"
|
||||
style={{ textWrap: "balance" }}
|
||||
>
|
||||
Stops
|
||||
<br className="hidden sm:block" />{" "}
|
||||
<span className="italic text-amber-200/90">near you.</span>
|
||||
</h1>
|
||||
<div className="mt-8 h-px w-16 bg-gradient-to-r from-emerald-500 via-amber-400 to-amber-300/0" />
|
||||
<p className="mt-8 text-stone-300 text-lg sm:text-xl leading-[1.65]">
|
||||
Fresh Olathe Sweet™ sweet corn delivered to a stop
|
||||
near you. Preorder for pickup, or just show up — we’ll
|
||||
have coolers on the truck.
|
||||
</p>
|
||||
<div className="mt-10 flex flex-wrap items-center gap-x-8 gap-y-3 text-[11px] uppercase tracking-[0.28em]">
|
||||
<span className="text-amber-200/80">
|
||||
{upcomingStops.length} upcoming
|
||||
</span>
|
||||
<span className="text-stone-700">·</span>
|
||||
<span className="text-stone-400">Updated weekly</span>
|
||||
<span className="text-stone-700">·</span>
|
||||
<Link
|
||||
href="/api/tuxedo/schedule-pdf"
|
||||
download
|
||||
className="inline-flex items-center gap-2 rounded-2xl bg-white px-6 py-3 text-sm font-semibold text-stone-700 ring-1 ring-stone-200 hover:bg-stone-50 transition-colors"
|
||||
className="text-stone-400 hover:text-amber-200/80 underline underline-offset-4 transition-colors"
|
||||
>
|
||||
Download Full Schedule
|
||||
Download full schedule
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-w-4xl mx-auto space-y-3">
|
||||
{upcomingStops.map((stop) => (
|
||||
<Link
|
||||
key={stop.id}
|
||||
href={`/${brandSlug}/stops/${stop.slug}`}
|
||||
className="stop-card group block bg-white rounded-2xl p-5 shadow-sm ring-1 ring-stone-200/60 transition-all duration-300 hover:shadow-lg hover:ring-emerald-200"
|
||||
>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-4">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-14 h-14 rounded-xl bg-gradient-to-br from-emerald-500 to-emerald-600 flex flex-col items-center justify-center text-white shadow-md shadow-emerald-500/20">
|
||||
<span className="text-[10px] font-bold uppercase opacity-80">
|
||||
{new Date(stop.date).toLocaleDateString("en-US", { month: "short" })}
|
||||
</span>
|
||||
<span className="text-xl font-black leading-none">
|
||||
{new Date(stop.date).getDate()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</LayoutContainer>
|
||||
</section>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-lg font-bold text-stone-950 group-hover:text-emerald-700 transition-colors">
|
||||
{stop.city}, {stop.state}
|
||||
</h3>
|
||||
<p className="text-sm text-stone-500 truncate">{stop.location}</p>
|
||||
{stop.address && (
|
||||
<p className="text-xs text-stone-400 truncate">{stop.address}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-right">
|
||||
<p className="text-sm font-semibold text-stone-700">{stop.time}</p>
|
||||
{stop.cutoff_time && (
|
||||
<p className="text-xs text-stone-400">Order by {new Date(stop.cutoff_time).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" })}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-shrink-0 w-10 h-10 rounded-full bg-emerald-50 flex items-center justify-center group-hover:bg-emerald-500 transition-colors">
|
||||
<svg className="w-5 h-5 text-emerald-600 group-hover:text-white transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
{pastStops.length > 0 && (
|
||||
<div className="mt-8 pt-8 border-t border-stone-200">
|
||||
<h2 className="text-sm font-bold uppercase tracking-widest text-stone-400 mb-4">Past Stops</h2>
|
||||
<div className="space-y-2 opacity-60">
|
||||
{pastStops.slice(0, 5).map((stop) => (
|
||||
<div
|
||||
key={stop.id}
|
||||
className="flex items-center gap-4 p-4 rounded-xl bg-white/50 text-stone-500"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-lg bg-stone-200 flex items-center justify-center text-xs font-bold text-stone-500">
|
||||
{new Date(stop.date).toLocaleDateString("en-US", { month: "short", day: "numeric" })}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="font-medium">{stop.city}, {stop.state}</p>
|
||||
<p className="text-xs">{stop.location}</p>
|
||||
</div>
|
||||
<span className="text-xs">Completed</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Stops list — hairline-ruled magazine sidebar style. Each
|
||||
row is a single horizontal hairline + date numerals + city
|
||||
+ location + time + chevron. No cards, no gradients. */}
|
||||
<section className="relative bg-stone-50 py-20 sm:py-24">
|
||||
<LayoutContainer>
|
||||
{upcomingStops.length === 0 ? (
|
||||
<EmptyStops />
|
||||
) : (
|
||||
<div className="mx-auto max-w-5xl">
|
||||
{/* Eyebrow + hairline-ruled list header */}
|
||||
<div className="flex items-baseline justify-between gap-4 mb-8">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-[0.32em] text-emerald-700">
|
||||
Upcoming
|
||||
</p>
|
||||
<p className="text-[10px] uppercase tracking-[0.3em] text-stone-400 tabular-nums">
|
||||
{upcomingStops.length} stops
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</LayoutContainer>
|
||||
|
||||
<ol className="border-t border-stone-900/15">
|
||||
{upcomingStops.map((stop) => (
|
||||
<StopRow key={stop.id} stop={stop} brandSlug={brandSlug} />
|
||||
))}
|
||||
</ol>
|
||||
|
||||
{pastStops.length > 0 && (
|
||||
<div className="mt-20">
|
||||
<div className="flex items-baseline justify-between gap-4 mb-8">
|
||||
<p className="text-[10px] font-bold uppercase tracking-[0.32em] text-stone-400">
|
||||
Past Stops
|
||||
</p>
|
||||
<p className="text-[10px] uppercase tracking-[0.3em] text-stone-400 tabular-nums">
|
||||
Last {Math.min(5, pastStops.length)}
|
||||
</p>
|
||||
</div>
|
||||
<ol className="border-t border-stone-900/10 opacity-70">
|
||||
{pastStops.slice(0, 5).map((stop) => (
|
||||
<StopRow
|
||||
key={stop.id}
|
||||
stop={stop}
|
||||
brandSlug={brandSlug}
|
||||
past
|
||||
/>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</LayoutContainer>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<StorefrontFooter brandName={brandName} brandSlug={brandSlug} brandAccent="green" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── STOP ROW — hairline-ruled magazine-sidebar cell ────────────────
|
||||
// Date numerals on the left (large tabular day, small month above),
|
||||
// city/state + location + time in the middle, chevron on the right.
|
||||
// All hairline-divided rows; the list reads as a sidebar, not a feed.
|
||||
function StopRow({
|
||||
stop,
|
||||
brandSlug,
|
||||
past = false,
|
||||
}: {
|
||||
stop: PublicStop;
|
||||
brandSlug: string;
|
||||
past?: boolean;
|
||||
}) {
|
||||
const date = new Date(stop.date);
|
||||
const day = date.getDate();
|
||||
const month = date.toLocaleDateString("en-US", { month: "short" });
|
||||
const weekday = date.toLocaleDateString("en-US", { weekday: "short" });
|
||||
|
||||
const content = (
|
||||
<div className="grid grid-cols-12 gap-4 sm:gap-6 items-center py-5 sm:py-6 group">
|
||||
{/* Date column — big tabular day, small month/weekday above */}
|
||||
<div className="col-span-3 sm:col-span-2">
|
||||
<p
|
||||
className={`text-[10px] uppercase tracking-[0.28em] tabular-nums ${
|
||||
past ? "text-stone-400" : "text-emerald-700"
|
||||
}`}
|
||||
>
|
||||
{weekday} · {month}
|
||||
</p>
|
||||
<p
|
||||
className={`mt-1 font-display tabular-nums leading-none tracking-[-0.02em] ${
|
||||
past
|
||||
? "text-stone-400 text-3xl sm:text-4xl"
|
||||
: "text-stone-950 text-4xl sm:text-5xl"
|
||||
}`}
|
||||
>
|
||||
{day}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* City + location */}
|
||||
<div className="col-span-9 sm:col-span-6 min-w-0">
|
||||
<h3
|
||||
className={`font-display leading-[1.15] tracking-[-0.012em] ${
|
||||
past
|
||||
? "text-stone-500 text-base sm:text-lg"
|
||||
: "text-stone-950 text-lg sm:text-xl group-hover:text-emerald-800 transition-colors"
|
||||
}`}
|
||||
>
|
||||
{stop.city}, {stop.state}
|
||||
</h3>
|
||||
<p
|
||||
className={`mt-1 truncate ${past ? "text-stone-400 text-sm" : "text-stone-500 text-sm"}`}
|
||||
>
|
||||
{stop.location}
|
||||
</p>
|
||||
{stop.address && (
|
||||
<p
|
||||
className={`mt-0.5 truncate ${past ? "text-stone-400 text-xs" : "text-stone-400 text-xs"}`}
|
||||
>
|
||||
{stop.address}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Time + cutoff */}
|
||||
<div className="col-span-8 sm:col-span-3 text-right">
|
||||
<p
|
||||
className={`font-mono tabular-nums text-sm ${
|
||||
past ? "text-stone-400" : "text-stone-950"
|
||||
}`}
|
||||
>
|
||||
{stop.time}
|
||||
</p>
|
||||
{!past && stop.cutoff_time && (
|
||||
<p className="mt-1 text-[10px] uppercase tracking-[0.2em] text-stone-400">
|
||||
Order by{" "}
|
||||
{new Date(stop.cutoff_time).toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{past && (
|
||||
<p className="mt-1 text-[10px] uppercase tracking-[0.2em] text-stone-400">
|
||||
Completed
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Chevron — only on upcoming rows */}
|
||||
<div className="hidden sm:flex col-span-1 justify-end">
|
||||
{!past && (
|
||||
<svg
|
||||
className="w-4 h-4 text-stone-300 group-hover:text-emerald-700 group-hover:translate-x-1 transition-all"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const inner = (
|
||||
<li className="stop-row border-b border-stone-900/15 last:border-b-0">
|
||||
{content}
|
||||
</li>
|
||||
);
|
||||
|
||||
if (past) {
|
||||
return inner;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/${brandSlug}/stops/${stop.slug}`}
|
||||
className="stop-row block border-b border-stone-900/15 last:border-b-0 hover:bg-stone-100/60 transition-colors"
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// ── EMPTY STATE — editorial Fraunces italic copy ──────────────────
|
||||
// Same "no stops yet" message as before, but rendered as a quiet
|
||||
// editorial panel instead of an icon-in-rounded-box illustration.
|
||||
function EmptyStops() {
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl text-center py-12">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-[0.32em] text-emerald-700 mb-5">
|
||||
Coming Soon
|
||||
</p>
|
||||
<h2
|
||||
className="font-display text-stone-950 text-[clamp(2rem,4.4vw,3.25rem)] leading-[1.05] tracking-[-0.02em]"
|
||||
style={{ textWrap: "balance" }}
|
||||
>
|
||||
The corn is still ripening
|
||||
<br className="hidden sm:block" />{" "}
|
||||
<span className="italic text-amber-700/90">in the field.</span>
|
||||
</h2>
|
||||
<div className="mx-auto mt-7 h-px w-12 bg-emerald-700/60" />
|
||||
<p className="mt-7 text-stone-600 text-lg leading-[1.65]">
|
||||
New pickup stops are added every week. Check back soon, or browse
|
||||
the full season schedule below.
|
||||
</p>
|
||||
<div className="mt-10 flex flex-col sm:flex-row items-center justify-center gap-3">
|
||||
<Link
|
||||
href="/tuxedo"
|
||||
className="group inline-flex items-center gap-3 rounded-full bg-gradient-to-br from-emerald-700 to-emerald-600 px-8 py-3.5 text-sm font-bold text-white shadow-[0_8px_32px_rgba(16,185,129,0.3),inset_0_1px_0_rgba(255,255,255,0.2)] hover:-translate-y-0.5 transition-all duration-200"
|
||||
>
|
||||
<span>Back to Tuxedo Corn</span>
|
||||
<svg
|
||||
className="w-4 h-4 transition-transform group-hover:translate-x-1"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</Link>
|
||||
<Link
|
||||
href="/api/tuxedo/schedule-pdf"
|
||||
download
|
||||
className="inline-flex items-center gap-2 rounded-full bg-white px-8 py-3.5 text-sm font-bold text-stone-900 ring-1 ring-stone-200 hover:ring-emerald-600 hover:text-emerald-700 transition-all"
|
||||
>
|
||||
<svg
|
||||
className="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
Download Schedule
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user