-
Notifications
You must be signed in to change notification settings - Fork 0
Add /donate Support Saba page with typed recipient registry (#171) #172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
spizeck
wants to merge
11
commits into
master
Choose a base branch
from
feat/donate-support-saba-171
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
db071ba
Add /donate Support Saba page with typed recipient registry (#171)
spizeck 9dbc331
Extend /donate with Sea Saba community-giving program (#171)
spizeck fbf23eb
Merge branch 'master' into feat/donate-support-saba-171
spizeck be25ca8
Tighten support-form spacing and drop em dashes from giving copy
spizeck 6ab588b
Polish /donate cards and footer after owner review
spizeck 771dc5e
Merge branch 'master' into feat/donate-support-saba-171
spizeck a41a56c
Move Support Saba footer link into Plan Your Trip column
spizeck 8e3977e
Merge branch 'master' into feat/donate-support-saba-171
spizeck fc41b5a
Populate /donate recipients: Sea & Learn and SCF (#171)
spizeck c0310b6
Add organization-supplied logos to /donate recipient cards (#171)
spizeck 942c5fb
Update SCF /donate card to describe land-and-sea mission (#171)
spizeck File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| import Link from "next/link"; | ||
| import { createMetadata } from "@/lib/metadata"; | ||
| import { PageHero } from "@/components/page-hero"; | ||
| import { DonationsSection } from "@/components/donations/donations-section"; | ||
| import { CommunitySupportSection } from "@/components/donations/community-support-section"; | ||
| import { DONATION_RECIPIENTS } from "@/data/donations"; | ||
| import { OPERATIONS } from "@/data/operations"; | ||
| import { divingAnchors } from "@/lib/anchors"; | ||
|
|
||
| export const metadata = createMetadata({ | ||
| title: "Support Saba", | ||
| description: | ||
| "Ways to support Saba beyond your visit, and how island organizations, projects, and events can request a donation or sponsorship from Sea Saba.", | ||
| path: "/donate", | ||
| }); | ||
|
|
||
| export default function DonatePage() { | ||
| return ( | ||
| <> | ||
| <PageHero | ||
| src="/images/optimized/windwardside-village-saba.webp" | ||
| alt="Colorful red-roofed cottages of Windwardside village on the green hillside of Saba" | ||
| title="Support Saba" | ||
| subtitle="Giving back to the island and asking us to help" | ||
| /> | ||
|
|
||
| <p className="text-base leading-relaxed text-muted-foreground"> | ||
| Visitors fall for Saba quickly: the reefs, the trails, the quiet | ||
| villages, and the people who keep it all running. Many guests ask how | ||
| they can give something back once they're home. And on the island | ||
| itself, community groups, teams, and projects regularly need a hand. | ||
| This page is for both. | ||
| </p> | ||
| <p className="mt-3 text-base leading-relaxed text-muted-foreground"> | ||
| If you're a visitor, the most meaningful support goes straight to | ||
| the island organizations doing the work. If you're on Saba and | ||
| looking for a donation or sponsorship from Sea Saba, scroll down to{" "} | ||
| <Link | ||
| href="/donate#request-support" | ||
| className="font-medium text-primary underline-offset-4 hover:underline" | ||
| > | ||
| Request Support from Sea Saba | ||
| </Link> | ||
| . | ||
| </p> | ||
|
|
||
| {/* Your Visit Already Helps */} | ||
| <section className="mt-14"> | ||
| <h2 className="text-xl font-semibold text-foreground"> | ||
| Your Visit Already Helps | ||
| </h2> | ||
| <p className="mt-3 text-sm leading-relaxed text-muted-foreground"> | ||
| Everything we dive sits inside the Saba National Marine Park, and | ||
| every dive carries a conservation contribution of{" "} | ||
| ${OPERATIONS.conservationFees.marineParkPerDiveUsd + OPERATIONS.conservationFees.chamberContributionPerDiveUsd}{" "} | ||
| per diver: ${OPERATIONS.conservationFees.marineParkPerDiveUsd} to the | ||
| Marine Park and ${OPERATIONS.conservationFees.chamberContributionPerDiveUsd}{" "} | ||
| to the island's hyperbaric chamber fund. Snorkel trips contribute{" "} | ||
| ${OPERATIONS.conservationFees.snorkelParkPerPersonUsd} per person. | ||
| These fees are set by the park and the chamber, not Sea Saba, and they | ||
| fund the moorings, patrols, and emergency coverage that keep the | ||
| reefs healthy. | ||
| </p> | ||
| <p className="mt-3 text-sm text-muted-foreground"> | ||
| <Link | ||
| href={`/diving#${divingAnchors.marinePark}`} | ||
| className="font-medium text-primary underline-offset-4 hover:underline" | ||
| > | ||
| How the Saba Marine Park works | ||
| </Link> | ||
| </p> | ||
| </section> | ||
|
|
||
| {/* Ways to Give Back */} | ||
| <section className="mt-14"> | ||
| <h2 className="text-xl font-semibold text-foreground"> | ||
| Ways to Give Back | ||
| </h2> | ||
| <p className="mt-2 text-sm leading-relaxed text-muted-foreground"> | ||
| When you're ready to do more, a number of island organizations | ||
| welcome direct support. Every card below links out to the | ||
| organization's own website. Your donation goes to them, not | ||
| through us. | ||
| </p> | ||
|
|
||
| <DonationsSection recipients={DONATION_RECIPIENTS} /> | ||
|
|
||
| <p className="mt-6 text-xs leading-relaxed text-muted-foreground"> | ||
| Any donation link on this page opens the organization's own | ||
| website in a new tab. Sea Saba never collects, processes, or retains | ||
| donated funds. Each organization manages its own gifts and receipts. | ||
| </p> | ||
| </section> | ||
|
|
||
| {/* Request Support from Sea Saba — the other half of the page: local | ||
| organizations and project leads asking Sea Saba for help. All policy | ||
| content renders from data/community-support.ts (DRAFT pending owner | ||
| review). */} | ||
| <CommunitySupportSection /> | ||
| </> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| import { | ||
| SUPPORT_REQUEST_CATEGORIES, | ||
| SUPPORT_TYPES, | ||
| WHO_CAN_REQUEST, | ||
| SUPPORT_STANDARDS, | ||
| SUPPORT_REQUEST_STEPS, | ||
| SUPPORT_REQUEST_NO_GUARANTEE, | ||
| } from "@/data/community-support"; | ||
| import { SupportRequestForm } from "./support-request-form"; | ||
|
|
||
| /** | ||
| * The request side of /donate (#171): who may ask Sea Saba for support, what | ||
| * kinds of help are possible, the ground rules, the process, and the request | ||
| * form. All policy content renders from data/community-support.ts — edit the | ||
| * wording there, not here. DRAFT copy pending owner review. | ||
| */ | ||
| export function CommunitySupportSection() { | ||
| return ( | ||
| <section className="mt-14" aria-labelledby="request-support"> | ||
| <h2 id="request-support" className="text-xl font-semibold text-foreground"> | ||
| Request Support from Sea Saba | ||
| </h2> | ||
| <p className="mt-3 text-sm leading-relaxed text-muted-foreground"> | ||
| Sea Saba has been part of this island since 1985, and giving back is part | ||
| of how we operate: a raffle prize for a school fundraiser, air fills for | ||
| a beach cleanup, sponsorship for a youth team. If you're working on | ||
| something worthwhile on Saba, we'd like to hear about it. | ||
| </p> | ||
| <p className="mt-3 text-sm leading-relaxed text-muted-foreground"> | ||
| We're a small local business, so we keep a few clear ground rules, | ||
| not to make asking harder, but so we can keep saying yes to good | ||
| projects and know our support lands where it's meant to. | ||
| </p> | ||
|
|
||
| {/* Who can ask */} | ||
| <h3 className="mt-8 text-base font-semibold text-foreground">Who can ask</h3> | ||
| <ul className="mt-3 list-disc space-y-1.5 pl-5 text-sm leading-relaxed text-muted-foreground"> | ||
| {WHO_CAN_REQUEST.map((item) => ( | ||
| <li key={item}>{item}</li> | ||
| ))} | ||
| </ul> | ||
| <p className="mt-3 text-sm text-muted-foreground"> | ||
| Requests we see most often fall into a few areas ( | ||
| {SUPPORT_REQUEST_CATEGORIES.map((c) => c.label).join(", ").replace(/, ([^,]*)$/, " or $1")} | ||
| ), but anything that benefits the island is worth asking about. | ||
| </p> | ||
|
|
||
| {/* What support can look like */} | ||
| <h3 className="mt-8 text-base font-semibold text-foreground"> | ||
| What support can look like | ||
| </h3> | ||
| <p className="mt-2 text-sm leading-relaxed text-muted-foreground"> | ||
| It doesn't have to be money. Depending on the request, Sea Saba can help with: | ||
| </p> | ||
| <ul role="list" className="mt-3 grid list-none gap-2.5 pl-0 sm:grid-cols-2"> | ||
| {SUPPORT_TYPES.map((type) => ( | ||
| <li | ||
| key={type.value} | ||
| className="rounded-lg border border-border/50 bg-muted/20 px-4 py-2.5" | ||
| > | ||
| <p className="text-sm font-medium text-foreground">{type.label}</p> | ||
| <p className="mt-0.5 text-xs leading-relaxed text-muted-foreground"> | ||
| {type.description} | ||
| </p> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
|
|
||
| {/* Ground rules */} | ||
| <h3 className="mt-8 text-base font-semibold text-foreground"> | ||
| A few ground rules | ||
| </h3> | ||
| <ul role="list" className="mt-3 grid list-none gap-2.5 pl-0 sm:grid-cols-2"> | ||
| {SUPPORT_STANDARDS.map((standard) => ( | ||
| <li | ||
| key={standard.title} | ||
| className="rounded-lg border border-border/50 px-4 py-2.5" | ||
| > | ||
| <p className="text-sm font-medium text-foreground">{standard.title}</p> | ||
| <p className="mt-0.5 text-xs leading-relaxed text-muted-foreground"> | ||
| {standard.description} | ||
| </p> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
|
|
||
| {/* How it works */} | ||
| <h3 className="mt-8 text-base font-semibold text-foreground">How it works</h3> | ||
| <ol className="mt-3 list-decimal space-y-1.5 pl-5 text-sm leading-relaxed text-muted-foreground"> | ||
| {SUPPORT_REQUEST_STEPS.map((step) => ( | ||
| <li key={step}>{step}</li> | ||
| ))} | ||
| </ol> | ||
| <p className="mt-3 text-sm leading-relaxed text-muted-foreground"> | ||
| {SUPPORT_REQUEST_NO_GUARANTEE} | ||
| </p> | ||
|
|
||
| {/* The form */} | ||
| <h3 className="mt-8 text-base font-semibold text-foreground">Send a request</h3> | ||
| <p className="mt-2 text-sm leading-relaxed text-muted-foreground"> | ||
| This gives us what we need to evaluate your request fairly. It's | ||
| not a grant application. Short, honest answers are perfect. | ||
| </p> | ||
| <div className="mt-4 rounded-lg border border-border/60 bg-card p-6"> | ||
| <SupportRequestForm /> | ||
| </div> | ||
| </section> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| "use client"; | ||
|
|
||
| import Image from "next/image"; | ||
| import Link from "next/link"; | ||
| import { ExternalLink } from "lucide-react"; | ||
| import { TrackedOutboundLink } from "@/components/tracked-outbound-link"; | ||
| import { cn } from "@/lib/utils"; | ||
| import type { DonationRecipient } from "@/data/donations"; | ||
|
|
||
| const CATEGORY_LABELS: Record<NonNullable<DonationRecipient["category"]>, string> = { | ||
| conservation: "Conservation", | ||
| community: "Community", | ||
| animals: "Animals", | ||
| youth: "Youth", | ||
| culture: "Culture", | ||
| "marine-conservation": "Marine conservation & coral restoration", | ||
| "science-education": "Science, education, youth & culture", | ||
| }; | ||
|
|
||
| interface DonationsSectionProps { | ||
| recipients: DonationRecipient[]; | ||
| } | ||
|
|
||
| /** | ||
| * Recipient grid for /donate (#171). While DONATION_RECIPIENTS is empty the | ||
| * section renders a graceful in-progress note — no fabricated organizations | ||
| * may appear publicly. Cards always send visitors to the recipient's own | ||
| * site; Sea Saba never processes donations. | ||
| */ | ||
| export function DonationsSection({ recipients }: DonationsSectionProps) { | ||
| if (recipients.length === 0) { | ||
| return ( | ||
| <div className="mt-6 rounded-xl border border-dashed border-border/60 bg-muted/20 p-6"> | ||
| <p className="text-sm leading-relaxed text-muted-foreground"> | ||
| We're assembling a short list of Saba organizations we can | ||
| wholeheartedly recommend. Until it's ready, the most reliable | ||
| route is also the simplest:{" "} | ||
| <Link | ||
| href="/contact" | ||
| className="font-medium text-primary underline-offset-4 hover:underline" | ||
| > | ||
| ask us | ||
| </Link> | ||
| . We live here, and we're happy to point you at a cause that fits. | ||
| </p> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| // Three columns only once the registry outgrows two entries — with one or | ||
| // two approved recipients a wider card fills the row and keeps the longer | ||
| // focus labels on one line instead of leaving an empty third column. | ||
| return ( | ||
| <div | ||
| className={cn( | ||
| "mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2", | ||
| recipients.length > 2 && "lg:grid-cols-3" | ||
| )} | ||
| > | ||
| {recipients.map((recipient) => ( | ||
| <DonationCard key={recipient.name} recipient={recipient} /> | ||
| ))} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| function DonationCard({ recipient }: { recipient: DonationRecipient }) { | ||
| return ( | ||
| <article className="group flex flex-col rounded-xl border border-border/50 bg-background p-5 transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/30 hover:shadow-sm"> | ||
| {recipient.image && ( | ||
| <Image | ||
| src={recipient.image} | ||
| alt={recipient.imageAlt ?? ""} | ||
| width={160} | ||
| height={48} | ||
| className="mb-3 h-14 w-auto self-start object-contain object-left" | ||
| /> | ||
| )} | ||
| <div className="flex flex-wrap items-center justify-between gap-x-3 gap-y-1.5"> | ||
| <h3 className="text-base font-semibold text-foreground">{recipient.name}</h3> | ||
| {recipient.category && ( | ||
| <span className="shrink-0 rounded-full bg-primary/10 px-2.5 py-0.5 text-xs font-medium text-primary"> | ||
| {CATEGORY_LABELS[recipient.category]} | ||
| </span> | ||
| )} | ||
| </div> | ||
| <p className="mt-2 text-sm leading-relaxed text-muted-foreground"> | ||
| {recipient.description} | ||
| </p> | ||
| {recipient.funds && ( | ||
| <p className="mt-2 text-sm leading-relaxed text-muted-foreground"> | ||
| <span className="font-medium text-foreground">Helps fund:</span>{" "} | ||
| {recipient.funds} | ||
| </p> | ||
| )} | ||
|
|
||
| <div className="mt-auto flex flex-wrap items-center gap-x-4 gap-y-2 pt-5"> | ||
| {recipient.donationUrl && ( | ||
| <TrackedOutboundLink | ||
| href={recipient.donationUrl} | ||
| eventName="donation_click" | ||
| buttonText={`Donate to ${recipient.name}`} | ||
| ariaLabel={`Donate directly to ${recipient.name} on their own website, opens in a new tab`} | ||
| className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3.5 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90" | ||
| > | ||
| Donate directly | ||
| <ExternalLink className="h-3.5 w-3.5" aria-hidden="true" /> | ||
| </TrackedOutboundLink> | ||
| )} | ||
| <TrackedOutboundLink | ||
| href={recipient.website} | ||
| eventName="social_click" | ||
| buttonText={`Visit ${recipient.name}`} | ||
| ariaLabel={`Visit ${recipient.name}'s website, opens in a new tab`} | ||
| className="inline-flex items-center gap-1.5 text-sm font-medium text-primary transition-colors hover:text-primary/80" | ||
| > | ||
| {recipient.donationUrl ? "Visit website" : "Visit their website"} | ||
| <ExternalLink className="h-3.5 w-3.5" aria-hidden="true" /> | ||
| </TrackedOutboundLink> | ||
| </div> | ||
| </article> | ||
| ); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): When a recipient has an
imagebut omitsimageAlt,DonationCardrendersalt="", causing the organization image to be treated as decorative and hiding potentially meaningful content from screen-reader users. The interface comment says alt text is required, but the optional property does not enforce that contract.Triggers: When a future registry entry supplies
imagewithoutimageAlt.Suggested fix: Make
imageAltrequired wheneverimageis present using a discriminated union, or provide a meaningful fallback alt value before rendering the image.