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
45 changes: 31 additions & 14 deletions apps/site/src/components/site/signing-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
"use client";

import Link from "next/link";
import { useState } from "react";
import { type ComponentProps, useState } from "react";
import { Text } from "@/components/typography/text.tsx";
import { COUNTRIES } from "@/lib/countries.ts";
import type { RestoredSigningForm } from "@/lib/signing/form-restoration.ts";
import { signingDisclosure, siteConfig, siteFlags } from "@/lib/site-config.ts";
import { SUPPORTER_SIGNING_INTERIM_NOTICE } from "@/lib/site-facts.ts";
Expand Down Expand Up @@ -34,12 +35,6 @@ import { cn } from "@/lib/utils.ts";
// /api/sign, because anything checked only in this file is checked only for
// people who use the form.
//
// The country and type lists are the prototype's. They are deliberately short
// and will grow; a free-text country field would make the public register
// unsortable and invite an address.

const COUNTRIES = ["Australia", "Germany", "Netherlands", "Switzerland", "United Kingdom", "United States"] as const;

const ORGANISATION_TYPES = ["Company", "Platform", "Research institute", "Civil society", "Public body"] as const;

type SignatoryKind = "individual" | "organisation";
Expand All @@ -50,6 +45,8 @@ const fieldClassName = cn(
"focus-visible:border-primary focus-visible:outline-none"
);

const choiceTextClassName = "font-sans text-[14px] leading-[1.6]";

function Field({ children, htmlFor, label }: { children: React.ReactNode; htmlFor: string; label: string }) {
return (
<div className="flex flex-col gap-1.5">
Expand All @@ -61,6 +58,26 @@ function Field({ children, htmlFor, label }: { children: React.ReactNode; htmlFo
);
}

function Select({ children, className, ...props }: ComponentProps<"select">) {
return (
<div className="relative flex items-center">
<select className={cn(fieldClassName, "appearance-none pr-8", className)} {...props}>
{children}
</select>
<svg aria-hidden className="pointer-events-none absolute right-3 size-3 text-foreground" viewBox="0 0 12 12">
<path
d="M3 4.5 L6 7.5 L9 4.5"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
/>
</svg>
</div>
);
}

function Consent({
checked,
id,
Expand Down Expand Up @@ -133,7 +150,8 @@ export function PdppSigningForm({ restoredForm }: { restoredForm?: RestoredSigni
<button
aria-pressed={kind === option}
className={cn(
"cursor-pointer border border-border px-4 py-2 font-sans text-[14px] capitalize",
"cursor-pointer border border-border px-4 py-2 capitalize",
choiceTextClassName,
kind === option
? "bg-primary text-on-primary-emphasis"
: "bg-background text-muted-foreground hover:text-primary"
Expand Down Expand Up @@ -188,8 +206,8 @@ export function PdppSigningForm({ restoredForm }: { restoredForm?: RestoredSigni
)}

<Field htmlFor="sign-country" label="Country">
<select
className={fieldClassName}
<Select
className={choiceTextClassName}
defaultValue={restoredForm?.country ?? ""}
id="sign-country"
name="country"
Expand All @@ -203,13 +221,12 @@ export function PdppSigningForm({ restoredForm }: { restoredForm?: RestoredSigni
{country}
</option>
))}
</select>
</Select>
</Field>

{isOrganisation && (
<Field htmlFor="sign-type" label="Type">
<select
className={fieldClassName}
<Select
defaultValue={restoredForm?.organisation_type ?? ""}
id="sign-type"
name="organisation_type"
Expand All @@ -223,7 +240,7 @@ export function PdppSigningForm({ restoredForm }: { restoredForm?: RestoredSigni
{type}
</option>
))}
</select>
</Select>
</Field>
)}
</div>
Expand Down
256 changes: 256 additions & 0 deletions apps/site/src/lib/countries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
// Copyright The PDP-Connect Contributors
// SPDX-License-Identifier: Apache-2.0

// ISO 3166-1 countries and territories, named in English with Unicode CLDR.
// Keep this shared list aligned between the public form and server validation.
export const COUNTRIES = [
"Afghanistan",
"Åland Islands",
"Albania",
"Algeria",
"American Samoa",
"Andorra",
"Angola",
"Anguilla",
"Antarctica",
"Antigua & Barbuda",
"Argentina",
"Armenia",
"Aruba",
"Australia",
"Austria",
"Azerbaijan",
"Bahamas",
"Bahrain",
"Bangladesh",
"Barbados",
"Belarus",
"Belgium",
"Belize",
"Benin",
"Bermuda",
"Bhutan",
"Bolivia",
"Bosnia & Herzegovina",
"Botswana",
"Bouvet Island",
"Brazil",
"British Indian Ocean Territory",
"British Virgin Islands",
"Brunei",
"Bulgaria",
"Burkina Faso",
"Burundi",
"Cambodia",
"Cameroon",
"Canada",
"Cape Verde",
"Caribbean Netherlands",
"Cayman Islands",
"Central African Republic",
"Chad",
"Chile",
"China",
"Christmas Island",
"Cocos (Keeling) Islands",
"Colombia",
"Comoros",
"Congo - Brazzaville",
"Congo - Kinshasa",
"Cook Islands",
"Costa Rica",
"Côte d’Ivoire",
"Croatia",
"Cuba",
"Curaçao",
"Cyprus",
"Czechia",
"Denmark",
"Djibouti",
"Dominica",
"Dominican Republic",
"Ecuador",
"Egypt",
"El Salvador",
"Equatorial Guinea",
"Eritrea",
"Estonia",
"Eswatini",
"Ethiopia",
"Falkland Islands",
"Faroe Islands",
"Fiji",
"Finland",
"France",
"French Guiana",
"French Polynesia",
"French Southern Territories",
"Gabon",
"Gambia",
"Georgia",
"Germany",
"Ghana",
"Gibraltar",
"Greece",
"Greenland",
"Grenada",
"Guadeloupe",
"Guam",
"Guatemala",
"Guernsey",
"Guinea",
"Guinea-Bissau",
"Guyana",
"Haiti",
"Heard & McDonald Islands",
"Honduras",
"Hong Kong SAR China",
"Hungary",
"Iceland",
"India",
"Indonesia",
"Iran",
"Iraq",
"Ireland",
"Isle of Man",
"Israel",
"Italy",
"Jamaica",
"Japan",
"Jersey",
"Jordan",
"Kazakhstan",
"Kenya",
"Kiribati",
"Kuwait",
"Kyrgyzstan",
"Laos",
"Latvia",
"Lebanon",
"Lesotho",
"Liberia",
"Libya",
"Liechtenstein",
"Lithuania",
"Luxembourg",
"Macao SAR China",
"Madagascar",
"Malawi",
"Malaysia",
"Maldives",
"Mali",
"Malta",
"Marshall Islands",
"Martinique",
"Mauritania",
"Mauritius",
"Mayotte",
"Mexico",
"Micronesia",
"Moldova",
"Monaco",
"Mongolia",
"Montenegro",
"Montserrat",
"Morocco",
"Mozambique",
"Myanmar (Burma)",
"Namibia",
"Nauru",
"Nepal",
"Netherlands",
"New Caledonia",
"New Zealand",
"Nicaragua",
"Niger",
"Nigeria",
"Niue",
"Norfolk Island",
"North Korea",
"North Macedonia",
"Northern Mariana Islands",
"Norway",
"Oman",
"Pakistan",
"Palau",
"Palestinian Territories",
"Panama",
"Papua New Guinea",
"Paraguay",
"Peru",
"Philippines",
"Pitcairn Islands",
"Poland",
"Portugal",
"Puerto Rico",
"Qatar",
"Réunion",
"Romania",
"Russia",
"Rwanda",
"Samoa",
"San Marino",
"São Tomé & Príncipe",
"Saudi Arabia",
"Senegal",
"Serbia",
"Seychelles",
"Sierra Leone",
"Singapore",
"Sint Maarten",
"Slovakia",
"Slovenia",
"Solomon Islands",
"Somalia",
"South Africa",
"South Georgia & South Sandwich Islands",
"South Korea",
"South Sudan",
"Spain",
"Sri Lanka",
"St. Barthélemy",
"St. Helena",
"St. Kitts & Nevis",
"St. Lucia",
"St. Martin",
"St. Pierre & Miquelon",
"St. Vincent & Grenadines",
"Sudan",
"Suriname",
"Svalbard & Jan Mayen",
"Sweden",
"Switzerland",
"Syria",
"Taiwan",
"Tajikistan",
"Tanzania",
"Thailand",
"Timor-Leste",
"Togo",
"Tokelau",
"Tonga",
"Trinidad & Tobago",
"Tunisia",
"Türkiye",
"Turkmenistan",
"Turks & Caicos Islands",
"Tuvalu",
"U.S. Outlying Islands",
"U.S. Virgin Islands",
"Uganda",
"Ukraine",
"United Arab Emirates",
"United Kingdom",
"United States",
"Uruguay",
"Uzbekistan",
"Vanuatu",
"Vatican City",
"Venezuela",
"Vietnam",
"Wallis & Futuna",
"Western Sahara",
"Yemen",
"Zambia",
"Zimbabwe",
] as const;
3 changes: 1 addition & 2 deletions apps/site/src/lib/signing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
import "server-only";
import { z } from "zod";
import { COUNTRIES } from "@/lib/countries.ts";

// The interim Supporter signing system, server side.
//
Expand Down Expand Up @@ -53,8 +54,6 @@ const personName = z.string().trim().min(1).max(120);
// out shapes that cannot be an address at all.
const email = z.string().trim().toLowerCase().min(3).max(254).email();

const COUNTRIES = ["Australia", "Germany", "Netherlands", "Switzerland", "United Kingdom", "United States"] as const;

const ORGANISATION_TYPES = ["Company", "Platform", "Research institute", "Civil society", "Public body"] as const;

// A checkbox that was ticked arrives as "on"; an unticked one is absent.
Expand Down