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
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
dist/
.rumdl_cache/
.wrangler/
.astro/
# css files are linted by biome
Expand Down
6 changes: 5 additions & 1 deletion astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,11 @@ export default defineConfig({
}),
]
: []),
starlightLlmsTxt(),
starlightLlmsTxt({
// We use MDX with components extensively which starlightLlmsTxt doesn't
// handle well otherwise
rawContent: true,
}),
starlightImageZoom(),
],
title: "SumUp Developer",
Expand Down
10 changes: 10 additions & 0 deletions src/components/content/AddressRequirementsTable.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
import merchantCountryData from "../../data/merchant-country-data.json";

import AddressRequirementsTableClient from "./AddressRequirementsTable";
---

<AddressRequirementsTableClient
client:only="react"
data={merchantCountryData}
/>
8 changes: 8 additions & 0 deletions src/components/content/AddressRequirementsTable.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.fieldList {
margin: 0;
padding-left: var(--cui-spacings-kilo);
}

.fieldList li + li {
margin-top: var(--cui-spacings-bit);
}
111 changes: 111 additions & 0 deletions src/components/content/AddressRequirementsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import SearchableTable, { type SearchableTableColumn } from "./SearchableTable";
import styles from "./AddressRequirementsTable.module.css";
import type {
MerchantCountry,
MerchantCountryData,
} from "./merchantCountryData";
import { createCountryColumn } from "./countryColumn";

type Props = {
data: MerchantCountryData;
};

type AddressRequirementRow = {
country: string;
countryCode: string;
fields: { key: string; name: string; optional: boolean }[];
};

const defaultFieldNames: Record<string, string> = {
street_address: "street_address",
post_code: "post_code",
administrative_unit_level1: "province",
administrative_unit_level2: "administrative_unit_level2",
administrative_unit_level3: "administrative_unit_level3",
locality_level1: "city",
locality_level2: "district",
locality_level3: "neighborhood",
};

const getCountryFieldNames = (
country: MerchantCountry,
): Partial<Record<string, string>> => country.addressRequirements.fieldNames;

const getFieldDisplayName = (
field: string,
countryFieldNames: Partial<Record<string, string>>,
): string => countryFieldNames[field] ?? defaultFieldNames[field] ?? field;

const mapFields = (
fields: string[],
requiredFields: Set<string>,
countryFieldNames: Partial<Record<string, string>>,
): { key: string; name: string; optional: boolean }[] =>
fields.map((field) => ({
key: field,
name: getFieldDisplayName(field, countryFieldNames),
optional: !requiredFields.has(field),
}));

const buildAddressRequirementRows = (
countries: MerchantCountry[],
): AddressRequirementRow[] =>
countries
.slice()
.sort((a, b) => a.displayName.localeCompare(b.displayName))
.map((country) => {
const countryFieldNames = getCountryFieldNames(country);
const requiredFieldSet = new Set(
country.addressRequirements.requiredFields,
);

return {
country: country.displayName,
countryCode: country.isoCode,
fields: mapFields(
country.addressRequirements.allowedFields,
requiredFieldSet,
countryFieldNames,
),
};
});

const AddressRequirementsTable = ({ data }: Props) => {
const rows = buildAddressRequirementRows(data.countries);

return (
<SearchableTable
searchPlaceholder="Search countries or address fields"
tableLayout="auto"
rows={rows}
getRowKey={(row) => row.countryCode}
columns={
[
{
...createCountryColumn<AddressRequirementRow>(),
width: "1%",
wrap: "nowrap",
},
{
key: "fields",
label: "Fields",
getValue: (row) =>
row.fields.map((field) => `${field.key} ${field.name}`).join(" "),
render: (row) => (
<ul className={styles.fieldList}>
{row.fields.map((field) => (
<li key={`${row.countryCode}-${field.key}`}>
<code>{field.name}</code>
{field.optional ? " (Optional)" : null}
</li>
))}
</ul>
),
},
] satisfies SearchableTableColumn<AddressRequirementRow>[]
}
/>
);
};

export default AddressRequirementsTable;
10 changes: 10 additions & 0 deletions src/components/content/CountryCell.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.countryCell {
display: inline-flex;
align-items: center;
gap: var(--cui-spacings-bit);
}

.countryFlag {
width: 15px;
height: 10px;
}
21 changes: 21 additions & 0 deletions src/components/content/CountryCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getIconURL, type IconName } from "@sumup-oss/icons";

import styles from "./CountryCell.module.css";

type Props = {
country: string;
countryCode: string;
};

const CountryCell = ({ country, countryCode }: Props) => {
const url = getIconURL(`flag_${countryCode.toLowerCase()}` as IconName);

return (
<span className={styles.countryCell}>
<span>{country}</span>
<img src={url} alt="" className={styles.countryFlag} />
</span>
);
};

export default CountryCell;
22 changes: 22 additions & 0 deletions src/components/content/MerchantCountrySection.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
import merchantCountryData from "../../data/merchant-country-data.json";

import MerchantCountrySectionClient from "./MerchantCountrySectionClient";

type MerchantCountrySectionType =
| "companyIdentifiers"
| "legalTypes"
| "personIdentifiers";

interface Props {
section: MerchantCountrySectionType;
}

const { section } = Astro.props;
---

<MerchantCountrySectionClient
client:load
section={section}
data={merchantCountryData}
/>
113 changes: 113 additions & 0 deletions src/components/content/MerchantCountrySectionClient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import SearchableTable, { type SearchableTableColumn } from "./SearchableTable";
import type {
MerchantCountry,
MerchantCountryData,
} from "./merchantCountryData";
import { createCountryColumn } from "./countryColumn";

export type MerchantCountrySectionType =
| "companyIdentifiers"
| "legalTypes"
| "personIdentifiers";

type IdentifierRow = {
country: string;
countryCode: string;
name: string;
ref: string;
};

type LegalTypeRow = {
country: string;
countryCode: string;
description: string;
uniqueRef: string;
};

type Props = {
section: MerchantCountrySectionType;
data: MerchantCountryData;
};

const buildIdentifierRows = (
countries: MerchantCountry[],
section: "companyIdentifiers" | "personIdentifiers",
): IdentifierRow[] =>
countries.flatMap((country) =>
country[section].map((item) => ({
country: country.displayName,
countryCode: country.isoCode,
name: item.name,
ref: item.ref,
})),
);

const buildLegalTypeRows = (countries: MerchantCountry[]): LegalTypeRow[] =>
countries.flatMap((country) =>
country.legalTypes.map((item) => ({
country: country.displayName,
countryCode: country.isoCode,
description: item.description,
uniqueRef: item.uniqueRef,
})),
);

const MerchantCountrySectionClient = ({ section, data }: Props) => {
if (section === "legalTypes") {
const rows = buildLegalTypeRows(data.countries);

return (
<SearchableTable
searchPlaceholder="Search legal types"
rows={rows}
getRowKey={(row) => `${row.countryCode}:${row.uniqueRef}`}
columns={
[
createCountryColumn<LegalTypeRow>(),
{
key: "description",
label: "Description",
getValue: (row) => row.description,
},
{
key: "uniqueRef",
label: "Reference",
wrap: "nowrap",
getValue: (row) => row.uniqueRef,
render: (row) => <code>{row.uniqueRef}</code>,
},
] satisfies SearchableTableColumn<LegalTypeRow>[]
}
/>
);
}

const rows = buildIdentifierRows(data.countries, section);

return (
<SearchableTable
searchPlaceholder={
section === "companyIdentifiers"
? "Search company identifiers"
: "Search person identifiers"
}
rows={rows}
getRowKey={(row) => `${row.countryCode}:${row.ref}`}
columns={
[
createCountryColumn<IdentifierRow>(),
{ key: "name", label: "Name", getValue: (row) => row.name },
{
key: "ref",
label: "Reference",
wrap: "nowrap",
getValue: (row) => row.ref,
render: (row) => <code>{row.ref}</code>,
},
] satisfies SearchableTableColumn<IdentifierRow>[]
}
/>
);
};

export default MerchantCountrySectionClient;
59 changes: 59 additions & 0 deletions src/components/content/SearchableTable.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.section {
margin: var(--cui-spacings-kilo) 0;
width: 100%;
}

.tableFrame {
width: 100%;
box-sizing: border-box;
border: var(--cui-border-width-kilo) solid var(--cui-border-normal);
border-radius: var(--cui-border-radius-byte);
overflow: hidden;
margin-top: var(--cui-spacings-byte);
}

.tableContainer {
width: 100%;
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}

.table {
width: 100%;
min-width: 100%;
border-collapse: separate;
border-spacing: 0;
table-layout: fixed;
}

.table th,
.table td {
padding: var(--cui-spacings-byte) var(--cui-spacings-kilo);
border-bottom: var(--cui-border-width-kilo) solid var(--cui-border-subtle);
vertical-align: top;
text-align: left;
font-size: var(--cui-body-s-font-size);
line-height: var(--cui-body-s-line-height);
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
}

.table th {
position: sticky;
top: 0;
z-index: 1;
background: var(--sl-color-bg);
}

.table tbody tr:last-child td {
border-bottom: 0;
}

.button {
display: block;
margin: 0 auto;
margin-top: var(--cui-spacings-byte);
}
Loading