summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--scss/base/_address.scss5
-rw-r--r--scss/base/_details.scss4
-rw-r--r--scss/base/_index.scss5
-rw-r--r--scss/base/_paypal-product-buttons.scss4
-rw-r--r--scss/base/_table.scss23
-rw-r--r--scss/modules/_index.scss1
-rw-r--r--scss/modules/_payment-methods-list.scss8
-rw-r--r--scss/values/spacing/_constants.scss2
-rw-r--r--src/components/LallansIssue.astro4
-rw-r--r--src/components/Navbar/NavLinks.astro3
-rw-r--r--src/components/PayPalButtons.astro150
-rw-r--r--src/components/PaypalProductButtons.astro26
-rw-r--r--src/components/Scotsoun.astro4
-rw-r--r--src/data/contactDetails.ts16
-rw-r--r--src/data/subscriptionPrices.ts7
-rw-r--r--src/enums/MembershipType.ts42
-rw-r--r--src/i18n/translate.ts28
-rw-r--r--src/i18n/translations/components/breadcrumbs.ts20
-rw-r--r--src/i18n/translations/enums/membershipType.ts21
-rw-r--r--src/i18n/translations/pages/home.ts76
-rw-r--r--src/i18n/translations/pages/jyne.ts190
-rw-r--r--src/pages/[locale]/furthsettins/lallans/[issueNumber]/index.astro1
-rw-r--r--src/pages/[locale]/furthsettins/scotsoun/[scotsounId]/index.astro1
-rw-r--r--src/pages/[locale]/index.astro27
-rw-r--r--src/pages/[locale]/jyne/index.astro104
-rw-r--r--src/pages/[locale]/jyne/paypal/[membershipType]/index.astro41
-rw-r--r--src/pages/[locale]/jyne/paypal/[membershipType]/success.astro30
-rw-r--r--src/pages/[locale]/jyne/paypal/index.astro27
-rw-r--r--src/scripts/wc/paypal-product-buttons.ts146
-rw-r--r--src/types/TranslationsDictionary.d.ts15
-rw-r--r--tsconfig.json1
31 files changed, 755 insertions, 277 deletions
diff --git a/scss/base/_address.scss b/scss/base/_address.scss
new file mode 100644
index 0000000..0206b83
--- /dev/null
+++ b/scss/base/_address.scss
@@ -0,0 +1,5 @@
+address {
+ margin-block: var(--spacing-block-md);
+ font-style: normal;
+ margin-inline-start: var(--spacing-inline-lg);
+}
diff --git a/scss/base/_details.scss b/scss/base/_details.scss
new file mode 100644
index 0000000..6d0b1d4
--- /dev/null
+++ b/scss/base/_details.scss
@@ -0,0 +1,4 @@
+details summary {
+ cursor: pointer;
+}
+
diff --git a/scss/base/_index.scss b/scss/base/_index.scss
index f338772..527b4c7 100644
--- a/scss/base/_index.scss
+++ b/scss/base/_index.scss
@@ -1,4 +1,9 @@
+@use 'address';
+@use 'details';
@use 'dl';
@use 'list';
+@use 'paypal-product-buttons';
+@use 'table';
@use 'theme';
@use 'typography';
+
diff --git a/scss/base/_paypal-product-buttons.scss b/scss/base/_paypal-product-buttons.scss
new file mode 100644
index 0000000..7339ede
--- /dev/null
+++ b/scss/base/_paypal-product-buttons.scss
@@ -0,0 +1,4 @@
+paypal-product-buttons div {
+ margin-block-start: var(--spacing-block-md);
+}
+
diff --git a/scss/base/_table.scss b/scss/base/_table.scss
new file mode 100644
index 0000000..ca4f957
--- /dev/null
+++ b/scss/base/_table.scss
@@ -0,0 +1,23 @@
+table {
+ border-collapse: collapse;
+ background-color: var(--background-color); // side-scrolling may be needed on narrow screens
+
+ caption {
+ font-style: italic;
+ font-size: var(--font-size-0);
+ }
+}
+
+thead, th {
+ border-block-end: var(--border-width) solid var(--text-color);
+}
+
+:is(th, td) {
+ padding-inline: var(--spacing-inline-sm);
+ padding-block: var(--spacing-block-sm);
+
+ + :is(th, td) {
+ border-inline-start: calc(var(--border-width) / 2) solid var(--text-color);
+ }
+}
+
diff --git a/scss/modules/_index.scss b/scss/modules/_index.scss
index 6e67241..822295f 100644
--- a/scss/modules/_index.scss
+++ b/scss/modules/_index.scss
@@ -5,6 +5,7 @@
@use 'link-gallery' as *;
@use 'nav' as *;
@use 'news-index' as *;
+@use 'payment-methods-list' as *;
@use 'product' as *;
@use 'schedule' as *;
@use 'skip-to-content' as *;
diff --git a/scss/modules/_payment-methods-list.scss b/scss/modules/_payment-methods-list.scss
new file mode 100644
index 0000000..a59831a
--- /dev/null
+++ b/scss/modules/_payment-methods-list.scss
@@ -0,0 +1,8 @@
+.payment-methods-list {
+ list-style: none;
+
+ details[open] :last-child {
+ margin-block-end: var(--spacing-block-sm);
+ }
+}
+
diff --git a/scss/values/spacing/_constants.scss b/scss/values/spacing/_constants.scss
index 9a990b8..c89fdc8 100644
--- a/scss/values/spacing/_constants.scss
+++ b/scss/values/spacing/_constants.scss
@@ -42,7 +42,7 @@ $grid-padding-er: (
);
$spacing-factor: (
- 'inline': (lg: 4, md: 2, sm: 1),
+ 'inline': (lg: 12, md: 6, sm: 3),
'block': (lg: 16, md: 10, sm: 5)
);
diff --git a/src/components/LallansIssue.astro b/src/components/LallansIssue.astro
index c8bc6eb..317ff2d 100644
--- a/src/components/LallansIssue.astro
+++ b/src/components/LallansIssue.astro
@@ -7,7 +7,7 @@ import type LallansIssue from '$types/LallansIssue';
import { Image } from 'astro:assets';
import LallansIssueContributions from './LallansIssueContributions.astro';
import LallansIssueContributors from './LallansIssueContributors.astro';
-import PayPalButtons from './PayPalButtons.astro';
+import PaypalProductButtons from './PaypalProductButtons.astro';
interface Props {
issue: LallansIssue;
@@ -27,7 +27,7 @@ const t = translate(locale);
<div class="product__action-block">
<Image alt="" src={issue.img.width274} />
- <PayPalButtons
+ <PaypalProductButtons
successPageUrl={relativePath(Astro.url.pathname, `payment-success-confirmation`)}
productDescription={`Lallans ${issue.issueNumber} | ${issue.issueName}`}
shortDescription={`Lallans ${issue.issueNumber}`}
diff --git a/src/components/Navbar/NavLinks.astro b/src/components/Navbar/NavLinks.astro
index 798c24e..36c7f6f 100644
--- a/src/components/Navbar/NavLinks.astro
+++ b/src/components/Navbar/NavLinks.astro
@@ -4,6 +4,7 @@ import translate from '$i18n/translate';
import tNews from '$i18n/translations/pages/news';
import tFurthsettins from '$i18n/translations/pages/furthsettins';
import tHame from '$i18n/translations/pages/home';
+import tJyne from '$i18n/translations/pages/jyne';
const locale = getLocaleFromPathOrDefault(Astro.url.pathname);
const t = translate(locale);
@@ -20,7 +21,7 @@ const t = translate(locale);
<a class="btn" href={`/${locale}/furthsettins`}>{t(tFurthsettins, { key: 'title' })}</a>
</li>
<li>
- <a class="btn" href={`/${locale}/#jyne`}>{t(tHame, { key: 'jyne' })}</a>
+ <a class="btn" href={`/${locale}/jyne`}>{t(tJyne, { key: 'title' })}</a>
</li>
<li>
<a class="btn" href={`/${locale}/#contact`}>{t(tHame, { key: 'contact' })}</a>
diff --git a/src/components/PayPalButtons.astro b/src/components/PayPalButtons.astro
deleted file mode 100644
index cdea5cb..0000000
--- a/src/components/PayPalButtons.astro
+++ /dev/null
@@ -1,150 +0,0 @@
----
-import env from '$lib/env';
-import type Price from '$types/Price';
-
-interface Props {
- successPageUrl: string;
- productDescription: string;
- shortDescription: string;
- totalPrice: Price;
-}
-
-const { successPageUrl, productDescription, shortDescription, totalPrice } = Astro.props;
-
-const PAYPAL_CLIENT_ID =
- env.ENVIRONMENT === 'prod' ? env.PAYPAL_LIVE_CLIENT_ID : env.PAYPAL_SANDBOX_CLIENT_ID;
----
-
-<paypal-buttons
- data-paypal-client-id={PAYPAL_CLIENT_ID}
- data-success-page-url={successPageUrl}
- data-product-description={productDescription}
- data-short-description={shortDescription}
- data-total-price={totalPrice}
->
- <div id="paypal-button-container"></div>
-</paypal-buttons>
-
-<script>
- import { PayPalNamespace, loadScript } from '@paypal/paypal-js';
-
- const backendUrl = '/api';
-
- class PayPalButtons extends HTMLElement {
- constructor() {
- super();
-
- const paypalClientId = this.getStringDataAttribute('paypalClientId');
-
- const paypalOptions = {
- clientId: paypalClientId,
- locale: 'en_GB',
- commit: false,
- currency: 'GBP',
- intent: 'capture',
- };
-
- loadScript(paypalOptions)
- .catch((err) => {
- throw new Error('Failed to load the PayPal JS SDK script. ', err);
- })
- .then((paypal) => {
- const constructButtons = paypal?.Buttons;
- if (paypal && constructButtons) {
- const successPageUrl = this.dataset['successPageUrl'] ?? '.';
- this.makePaypalButtons(constructButtons, successPageUrl).render(
- '#paypal-button-container'
- );
- }
- });
- }
-
- makePaypalButtons(
- constructButtons: Required<PayPalNamespace>['Buttons'],
- successPageUrl: string
- ) {
- const productDescription = this.getStringDataAttribute('productDescription');
- const shortDescription = this.getStringDataAttribute('shortDescription');
- const totalPrice = this.getStringDataAttribute('totalPrice');
-
- return constructButtons({
- // This function is called to set up the transaction details without actually carrying it out
- async createOrder() {
- const body = JSON.stringify({
- productDescription,
- shortDescription,
- totalPrice,
- });
- const headers = {
- 'Content-Type': 'application/json',
- };
-
- let response;
- try {
- response = await fetch(`${backendUrl}/order`, { method: 'POST', body, headers });
- } catch (err) {
- throw new Error(`Failed to create order. ${err}`);
- }
-
- if (!response.ok) {
- throw new Error(`
- Failed to create order. Backend response was
- ${response.status} ${response.statusText}. ${await response.text()}
- `);
- }
-
- const { orderId, approvalLink } = await response.json();
- console.info(`
- Successfully created order with ID
- ${orderId}. Approval link: ${approvalLink}
- `);
- return orderId;
- },
-
- // This function is called after payment is confirmed
- async onApprove(data) {
- const body = JSON.stringify({
- id: data.orderID,
- isApproved: true,
- });
- const headers = {
- 'Content-Type': 'application/json',
- };
-
- let response;
- try {
- response = await fetch(`${backendUrl}/order`, { method: 'PATCH', body, headers });
- } catch (err) {
- throw new Error(`Failed to capture order. ${err}`);
- }
-
- if (!response.ok) {
- throw new Error(`
- Failed to capture order. Backend response was ${response.status}
- ${response.statusText}. ${await response.text()}
- `);
- }
-
- const { orderId, requestStatus } = await response.json();
- console.info(`
- Successfully captured order with ID ${orderId},
- request status is ${requestStatus}.
- `);
-
- console.info('Redirecting to confirmation page...');
- window.location.href = successPageUrl;
- },
- });
- }
-
- getStringDataAttribute(key: string): string {
- const value = this.dataset[key];
- if (!value || typeof value !== 'string') {
- throw new Error(`data-${key} attribute not provided to PayPalButtons component.`);
- }
- return value;
- }
- }
-
- customElements.define('paypal-buttons', PayPalButtons);
-</script>
diff --git a/src/components/PaypalProductButtons.astro b/src/components/PaypalProductButtons.astro
new file mode 100644
index 0000000..0ebbd3f
--- /dev/null
+++ b/src/components/PaypalProductButtons.astro
@@ -0,0 +1,26 @@
+---
+import env from '$lib/env';
+import type Price from '$types/Price';
+
+interface Props {
+ successPageUrl: string;
+ productDescription: string;
+ shortDescription: string;
+ totalPrice: Price;
+}
+
+const { successPageUrl, productDescription, shortDescription, totalPrice } = Astro.props;
+
+const PAYPAL_CLIENT_ID =
+ env.ENVIRONMENT === 'prod' ? env.PAYPAL_LIVE_CLIENT_ID : env.PAYPAL_SANDBOX_CLIENT_ID;
+---
+
+<paypal-product-buttons
+ paypal-client-id={PAYPAL_CLIENT_ID}
+ success-page-url={successPageUrl}
+ product-description={productDescription}
+ short-description={shortDescription}
+ total-price={totalPrice}
+>
+ Loading PayPal buttons…
+</paypal-product-buttons>
diff --git a/src/components/Scotsoun.astro b/src/components/Scotsoun.astro
index cca330d..7acae8b 100644
--- a/src/components/Scotsoun.astro
+++ b/src/components/Scotsoun.astro
@@ -4,7 +4,7 @@ import translate from '$i18n/translate';
import tSite from '$i18n/translations/site';
import tComponent from '$i18n/translations/components/scotsoun';
import type Scotsoun from '$types/Scotsoun';
-import PayPalButtons from './PayPalButtons.astro';
+import PaypalProductButtons from './PaypalProductButtons.astro';
import relativePath from '$lib/relativePath';
import { getLocaleFromPathOrThrow } from '$lib/getLocaleFromPath';
@@ -27,7 +27,7 @@ const t = translate(locale);
</header>
<div class="product__action-block">
<Image src={scotsoun.img.width192} alt="" />
- <PayPalButtons
+ <PaypalProductButtons
successPageUrl={relativePath(Astro.url.pathname, `payment-success-confirmation`)}
productDescription={`SSCD${scotsoun.scotsounId} | ${scotsoun.longName}`}
shortDescription={`SSCD${scotsoun.scotsounId}`}
diff --git a/src/data/contactDetails.ts b/src/data/contactDetails.ts
index f5de6f5..2357b6e 100644
--- a/src/data/contactDetails.ts
+++ b/src/data/contactDetails.ts
@@ -1,8 +1,24 @@
export default {
membershipSecretary: {
emailAddress: 'lallans@hotmail.co.uk',
+ address: [
+ 'Scots Language Society',
+ 'c/o 61 Cliffburn Road',
+ 'Arbroath',
+ 'Angus',
+ 'DD11 5BA',
+ 'United Kingdom',
+ ],
},
generalEnquiries: {
emailAddress: 'lallans@hotmail.co.uk',
+ address: [
+ 'Scots Language Society',
+ 'c/o 61 Cliffburn Road',
+ 'Arbroath',
+ 'Angus',
+ 'DD11 5BA',
+ 'United Kingdom',
+ ],
},
};
diff --git a/src/data/subscriptionPrices.ts b/src/data/subscriptionPrices.ts
new file mode 100644
index 0000000..c52d902
--- /dev/null
+++ b/src/data/subscriptionPrices.ts
@@ -0,0 +1,7 @@
+import MembershipType from '$enums/MembershipType';
+
+export const annualSubscriptionPrice: { [key in MembershipType]: number } = {
+ [MembershipType.IndividualUk]: 20.0,
+ [MembershipType.IndividualNonUk]: 30.0,
+ [MembershipType.InstitutionUk]: 25.0,
+};
diff --git a/src/enums/MembershipType.ts b/src/enums/MembershipType.ts
new file mode 100644
index 0000000..e153755
--- /dev/null
+++ b/src/enums/MembershipType.ts
@@ -0,0 +1,42 @@
+enum MembershipType {
+ IndividualUk,
+ IndividualNonUk,
+ InstitutionUk,
+}
+
+export type MembershipTypeSnakeCase = 'individual-uk' | 'individual-non-uk' | 'institution-uk';
+
+const allMembershipTypesSnakeCaseHash: { [key in MembershipTypeSnakeCase]: null } = {
+ 'individual-uk': null,
+ 'individual-non-uk': null,
+ 'institution-uk': null,
+};
+export const allMembershipTypesSnakeCase = Object.keys(
+ allMembershipTypesSnakeCaseHash
+) as MembershipTypeSnakeCase[];
+
+export function membershipTypeToSnakeCase(membershipType: MembershipType): MembershipTypeSnakeCase {
+ switch (membershipType) {
+ case MembershipType.IndividualUk:
+ return 'individual-uk';
+ case MembershipType.IndividualNonUk:
+ return 'individual-non-uk';
+ case MembershipType.InstitutionUk:
+ return 'institution-uk';
+ }
+}
+
+export function snakeCaseToMembershipType(membershipType: MembershipTypeSnakeCase): MembershipType {
+ switch (membershipType) {
+ case 'individual-uk':
+ return MembershipType.IndividualUk;
+ case 'individual-non-uk':
+ return MembershipType.IndividualNonUk;
+ case 'institution-uk':
+ return MembershipType.InstitutionUk;
+ }
+}
+
+export const allMembershipTypes = allMembershipTypesSnakeCase.map(snakeCaseToMembershipType);
+
+export default MembershipType;
diff --git a/src/i18n/translate.ts b/src/i18n/translate.ts
index 8f27e4b..0366798 100644
--- a/src/i18n/translate.ts
+++ b/src/i18n/translate.ts
@@ -1,23 +1,23 @@
-import type CommitteeRole from '$enums/CommitteeRole';
import type Locale from '$types/Locale';
-import type { TranslationsDictionary, Translation } from '$types/TranslationsDictionary';
+import type {
+ TranslationsDictionary,
+ Translation,
+ LocalesTranslationHash,
+} from '$types/TranslationsDictionary';
import { defaultLocale } from './locales';
type TranslationsDictionaryWith<
- Key extends string | CommitteeRole,
- Params,
+ Key extends string | number,
+ ParamsHash extends object,
> = TranslationsDictionary<{
- [key in Key]: { [locale in typeof defaultLocale]: Translation<Params> } & {
- [locale in Locale]?: Translation<Params>;
- };
+ [key in Key]: LocalesTranslationHash<ParamsHash>;
}>;
export default function (locale: Locale) {
- return function <
- Key extends string,
- Params,
- Dict extends TranslationsDictionaryWith<Key, Params>,
- >(dict: Dict, params: { key: Key } & { [Param in keyof Params]: string }) {
+ return function <Key extends string | number, ParamsHash extends object>(
+ dict: TranslationsDictionaryWith<Key, ParamsHash>,
+ params: { key: Key } & ParamsHash
+ ) {
if (!(params.key in dict)) {
throw new Error(`
Could not find translation for key '${params.key}'. Available keys were:
@@ -26,7 +26,9 @@ export default function (locale: Locale) {
.join(', ')}
`);
}
- const translation = dict[params.key][locale] ?? dict[params.key][defaultLocale];
+ const localesTranslationHash: LocalesTranslationHash<ParamsHash> = dict[params.key];
+ const translation: Translation<ParamsHash> =
+ localesTranslationHash[locale] ?? localesTranslationHash[defaultLocale];
return translation(params);
};
}
diff --git a/src/i18n/translations/components/breadcrumbs.ts b/src/i18n/translations/components/breadcrumbs.ts
index 5aeff0e..8e3d027 100644
--- a/src/i18n/translations/components/breadcrumbs.ts
+++ b/src/i18n/translations/components/breadcrumbs.ts
@@ -22,6 +22,26 @@ const tBreadcrumbs = {
sco: () => 'Scotsoun',
'en-GB': () => 'Scotsoun',
},
+ '/jyne/': {
+ sco: () => 'Jyne',
+ 'en-GB': () => 'Join',
+ },
+ '/jyne/paypal/': {
+ sco: () => 'PayPal',
+ 'en-GB': () => 'PayPal',
+ },
+ '/jyne/paypal/individual-uk/': {
+ sco: () => 'Individual (UK)',
+ 'en-GB': () => 'Individual (UK)',
+ },
+ '/jyne/paypal/individual-non-uk/': {
+ sco: () => 'Individual (non-UK)',
+ 'en-GB': () => 'Individual (non-UK)',
+ },
+ '/jyne/paypal/institution-uk/': {
+ sco: () => 'Institutional (UK)',
+ 'en-GB': () => 'Institutional (UK)',
+ },
'/news/': {
sco: () => 'News',
'en-GB': () => 'News',
diff --git a/src/i18n/translations/enums/membershipType.ts b/src/i18n/translations/enums/membershipType.ts
new file mode 100644
index 0000000..0e1db06
--- /dev/null
+++ b/src/i18n/translations/enums/membershipType.ts
@@ -0,0 +1,21 @@
+import type { TranslationsDictionary, LocalesTranslationHash } from '$types/TranslationsDictionary';
+
+import MembershipType from '$enums/MembershipType';
+
+const tEnum: { [key in MembershipType]: LocalesTranslationHash<object> } = {
+ [MembershipType.IndividualUk]: {
+ sco: () => 'Individual (UK)',
+ 'en-GB': () => 'Individual (UK)',
+ },
+ [MembershipType.IndividualNonUk]: {
+ sco: () => 'Individual (non-UK)',
+ 'en-GB': () => 'Individual (non-UK)',
+ },
+ [MembershipType.InstitutionUk]: {
+ sco: () => 'Institutional (UK)',
+ 'en-GB': () => 'Institutional (UK)',
+ },
+};
+
+type Raw = typeof tEnum;
+export default tEnum as TranslationsDictionary<Raw>;
diff --git a/src/i18n/translations/pages/home.ts b/src/i18n/translations/pages/home.ts
index b577a46..6628682 100644
--- a/src/i18n/translations/pages/home.ts
+++ b/src/i18n/translations/pages/home.ts
@@ -119,82 +119,6 @@ const tPage = {
sco: () => 'Aw furthsettins',
'en-GB': () => 'All publications',
},
- jyne: {
- sco: () => 'Jyne',
- 'en-GB': () => 'Join',
- },
- 'why-jyne': {
- sco: () => 'Why jyne the Scots Leid Associe?',
- 'en-GB': () => 'Why join the Scots Language Society?',
- },
- 'why-jyne-para-1': {
- sco: () => `
- Bi jynin us, ye ar helpin us tae heize up the Scots leid, maistly bi giein
- Scots scrievers a place tae lairn frae anither an finnd their ain Scots vyce.
- `,
- 'en-GB': () => `
- By joining us, you are helping us to champion the Scots language,
- especially by giving Scots writers a place to learn from one
- another and to find their own Scots voice.
- `,
- },
- 'why-jyne-para-2': {
- sco: () => `
- Forby, memmers get biannual issues o our aesome <i>Lallans</i> magazine gratis,
- an hae the richt tae atten our Annual Collogues, whaur we haud our AGM an
- enjoy talks frae kenspeckle spikkers on thochtie maiters effeirin til Scots
- leid an leiteratur.
- `,
- 'en-GB': () => `
- In addition, members get biannual issues of our unique <i>Lallans</i>
- magazine gratis, and have the right to attend our Annual Conferences, where
- we hold our AGM and enjoy talks from highly regarded speakers on
- thought-provoking subjects relating to Scots language and literature.
- `,
- },
- 'why-jyne-para-3': {
- sco: () => `
- Gin whit ye ar seekin is a community o prose-scrievers, makars, pleywreichts
- an owersetters, aw wi muckle care fur the Scots leid, the ar nae peer wi hus,
- the auldest Scots-leid activist organisation that’s about eenou an the anely
- fully dedicatit til the forderance o new Scots leiteratur.
- `,
- 'en-GB': () => `
- If what you’re looking for is a community of prose-writers, poets,
- playwrights and translators with a passion for the Scots language, we have
- no equal. We are the oldest extant Scots-language activist organisation and
- the only one solely dedicated to the development of new Scots literature.
- `,
- },
- 'hou-jyne': {
- sco: () => 'Hou can A jyne?',
- 'en-GB': () => 'How can I join?',
- },
- 'hou-jyne-para-1': {
- sco: () => `
- Fur individuals, memmership costs £20 per annum. Fur institutions, the stent is £25 per annum.
- `,
- 'en-GB': () => `
- For individuals, membership costs £20 per annum. For institutions, the fee is £25 per annum.
- `,
- },
- 'hou-jyne-para-2': {
- sco: ({ membershipEmail }: { membershipEmail: string }) => `
- Fur tae become a memmer, simply get in titch wi us at
- <a href="mailto:${membershipEmail}">${membershipEmail}</a>
- an we’ll provide ye wi our bank details; or ye can jyne bi sendin us a cheque
- made peyable til <b>The Scots Language Society</b>. See
- <a href="#contact">Contact us</a>
- fur our address.
- `,
- 'en-GB': ({ membershipEmail }: { membershipEmail: string }) => `
- To become a member, simply get in touch with us at
- <a href="mailto:${membershipEmail}">${membershipEmail}</a>
- and we’ll provide you with our bank details; or you can join by sending us
- a cheque made payable to <b>The Scots Language Society</b>. See
- <a href="#contact">Contact us</a> for our address.
- `,
- },
contact: {
sco: () => 'Contact',
'en-GB': () => 'Contact',
diff --git a/src/i18n/translations/pages/jyne.ts b/src/i18n/translations/pages/jyne.ts
new file mode 100644
index 0000000..bbcc67c
--- /dev/null
+++ b/src/i18n/translations/pages/jyne.ts
@@ -0,0 +1,190 @@
+import type { TranslationsDictionary } from '$types/TranslationsDictionary';
+
+import MembershipType from '$enums/MembershipType';
+import { annualSubscriptionPrice } from '$data/subscriptionPrices';
+import tMembershipType from '$i18n/translations/enums/membershipType';
+import translate from '$i18n/translate';
+
+const tPage = {
+ title: {
+ sco: () => 'Jyne',
+ 'en-GB': () => 'Join',
+ },
+ 'why-jyne': {
+ sco: () => 'Why jyne the Scots Leid Associe?',
+ 'en-GB': () => 'Why join the Scots Language Society?',
+ },
+ 'why-jyne-para-1': {
+ sco: () => `
+ Bi jynin us, ye ar helpin us tae heize up the Scots leid, maistly bi giein
+ Scots scrievers a place tae lairn frae anither an finnd their ain Scots vyce.
+ `,
+ 'en-GB': () => `
+ By joining us, you are helping us to champion the Scots language,
+ especially by giving Scots writers a place to learn from one
+ another and to find their own Scots voice.
+ `,
+ },
+ 'why-jyne-para-2': {
+ sco: () => `
+ Forby, memmers get biannual issues o our aesome <i>Lallans</i> magazine gratis,
+ an hae the richt tae atten our Annual Collogues, whaur we haud our AGM an
+ enjoy talks frae kenspeckle spikkers on thochtie maiters effeirin til Scots
+ leid an leiteratur.
+ `,
+ 'en-GB': () => `
+ In addition, members get biannual issues of our unique <i>Lallans</i>
+ magazine gratis, and have the right to attend our Annual Conferences, where
+ we hold our AGM and enjoy talks from highly regarded speakers on
+ thought-provoking subjects relating to Scots language and literature.
+ `,
+ },
+ 'why-jyne-para-3': {
+ sco: () => `
+ Gin whit ye ar seekin is a community o prose-scrievers, makars, pleywreichts
+ an owersetters, aw wi muckle care fur the Scots leid, the ar nae peer wi hus,
+ the auldest Scots-leid activist organisation that’s about eenou an the anely
+ fully dedicatit til the forderance o new Scots leiteratur.
+ `,
+ 'en-GB': () => `
+ If what you’re looking for is a community of prose-writers, poets,
+ playwrights and translators with a passion for the Scots language, we have
+ no equal. We are the oldest extant Scots-language activist organisation and
+ the only one solely dedicated to the development of new Scots literature.
+ `,
+ },
+ 'hou-jyne': {
+ sco: () => 'Hou can A jyne?',
+ 'en-GB': () => 'How can I join?',
+ },
+ 'hou-jyne-intro': {
+ sco: () => `
+ For tae jyne, ye can pey by PayPal, bank transfer or cheque. The fee is
+ as ablo.
+ `,
+ 'en-GB': () => `
+ To join, you can pay by PayPal, bank transfer or cheque. The fee is as below.
+ `,
+ },
+ 'subscription-price-table-caption': {
+ sco: () => 'Annual subscription price per membership type',
+ 'en-GB': () => 'Annual subscription price per membership type',
+ },
+ 'hou-pey-by-paypal-title': {
+ sco: () => 'PayPal',
+ 'en-GB': () => 'PayPal',
+ },
+ 'hou-pey-by-paypal-para': {
+ sco: () => 'Select a membership type',
+ 'en-GB': () => 'Select a membership type',
+ },
+ 'membership-type-individual-uk': {
+ sco: () => 'Ane year individual UK membership',
+ 'en-GB': () => 'One year individual UK membership',
+ },
+ 'membership-type-individual-uk-para': {
+ sco: () => `
+ Here, ye can pey for ane year’s annual subscription til the Scots Leid Associe
+ gin ye ar an individual and ye want yer copies o <i>Lallans</i> sent til
+ a UK address. This will cost
+ £${annualSubscriptionPrice[MembershipType.IndividualUk]}. Gin ye want
+ some ither kind o membership,
+ <a href="/sco/jyne/paypal">see the ither options</a>.
+ `,
+ 'en-GB': () => `
+ Here, you can pay for one year’s annual subscription to the Scots Language
+ Society if you are an individual and you want your copies of <i>Lallans</i>
+ sent to a UK address. This will cost
+ £${annualSubscriptionPrice[MembershipType.IndividualNonUk]}. If you want
+ some other kind of membership,
+ <a href="/en-GB/jyne/paypal">see the other options</a>.
+ `,
+ },
+ 'membership-type-individual-non-uk': {
+ sco: () => 'Ane year individual non-UK membership',
+ 'en-GB': () => 'One year individual non-UK membership',
+ },
+ 'membership-type-individual-non-uk-para': {
+ sco: () => `
+ Here, ye can pey for ane year’s annual subscription til the Scots Leid Associe
+ gin ye ar an individual an ye want yer copies o <i>Lallans</i> sent til
+ a non-UK address. This will cost
+ £${annualSubscriptionPrice[MembershipType.IndividualNonUk]}. Gin ye want
+ some ither kind o membership,
+ <a href="/sco/jyne/paypal">see the ither options</a>.
+ `,
+ 'en-GB': () => `
+ Here, you can pay for one year’s annual subscription to the Scots Language
+ Society if you are an individual and you want your copies of <i>Lallans</i>
+ sent to a non-UK address. This will cost
+ £${annualSubscriptionPrice[MembershipType.IndividualNonUk]}. If you want
+ some other kind of membership,
+ <a href="/en-GB/jyne/paypal">see the other options</a>.
+ `,
+ },
+ 'membership-type-institution-uk': {
+ sco: () => 'Ane year UK institutional membership',
+ 'en-GB': () => 'One year UK institutional membership',
+ },
+ 'membership-type-institution-uk-para': {
+ sco: () => `
+ Here, ye can pey for ane year’s annual subscription til the Scots Leid Associe
+ gin ye ar an institution. This will cost
+ £${annualSubscriptionPrice[MembershipType.InstitutionUk]}. Gin ye want
+ some ither kind o membership,
+ <a href="/sco/jyne/paypal">see the ither options</a>.
+ `,
+ 'en-GB': () => `
+ Here, you can pay for one year’s annual subscription to the Scots
+ Language Society if you are an institution based in the UK. This will
+ cost £${annualSubscriptionPrice[MembershipType.InstitutionUk]}. If you
+ want some other kind of membership,
+ <a href="/en-GB/jyne/paypal">see the other options</a>.
+ `,
+ },
+ 'hou-pey-by-bank-transfer-title': {
+ sco: () => 'Bank transfer',
+ 'en-GB': () => 'Bank transfer',
+ },
+ 'hou-pey-by-bank-transfer-para': {
+ sco: ({ emailAddress }: { emailAddress: string }) => `
+ Send us an email at <a href="mailto:${emailAddress}">${emailAddress}</a> an we’ll send ye oor bank details.
+ `,
+ 'en-GB': ({ emailAddress }: { emailAddress: string }) => `
+ Send us an email at <a href="mailto:${emailAddress}">${emailAddress}</a> and we’ll send you our bank details.
+ `,
+ },
+ 'hou-pey-by-cheque-title': {
+ sco: () => 'Cheque',
+ 'en-GB': () => 'Cheque',
+ },
+ 'hou-pey-by-cheque-para': {
+ sco: ({ address }: { address: string[] }) => `
+ Mak out a cheque til <b>The Scots Language Society</b>, syne send it til us at the follaein address:
+ <address>${address.join('<br />')}</address>
+ `,
+ 'en-GB': ({ address }: { address: string[] }) => `
+ Make out a cheque to <b>The Scots Language Society</b>, then send it to us at the following address:
+ <address>${address.join('<br />')}</address>
+ `,
+ },
+ success: {
+ sco: () => 'Successfullie bocht membership',
+ 'en-GB': () => 'Successfully bought membership',
+ },
+ 'success-para': {
+ sco: ({ membershipType }: { membershipType: MembershipType }) => `
+ Ye hae successfullie bocht ane year’s
+ ${translate('sco')(tMembershipType, { key: membershipType })}
+ membership. We shuid be in contact wi ye some time shuin.
+ `,
+ 'en-GB': ({ membershipType }: { membershipType: MembershipType }) => `
+ You have successfully bought one year’s
+ ${translate('en-GB')(tMembershipType, { key: membershipType })}
+ membership. We should be in contact with you some time soon.
+ `,
+ },
+};
+
+type Raw = typeof tPage;
+export default tPage as TranslationsDictionary<Raw>;
diff --git a/src/pages/[locale]/furthsettins/lallans/[issueNumber]/index.astro b/src/pages/[locale]/furthsettins/lallans/[issueNumber]/index.astro
index 48f1947..82fabf0 100644
--- a/src/pages/[locale]/furthsettins/lallans/[issueNumber]/index.astro
+++ b/src/pages/[locale]/furthsettins/lallans/[issueNumber]/index.astro
@@ -23,4 +23,5 @@ const issue = await getLallansIssue[issueNumber]();
<Page title={t(tFurthsettins, { key: 'title' })}>
<LallansIssue issue={issue} />
+ <script src="$scripts/wc/paypal-product-buttons.ts"></script>
</Page>
diff --git a/src/pages/[locale]/furthsettins/scotsoun/[scotsounId]/index.astro b/src/pages/[locale]/furthsettins/scotsoun/[scotsounId]/index.astro
index 90053fe..4244f70 100644
--- a/src/pages/[locale]/furthsettins/scotsoun/[scotsounId]/index.astro
+++ b/src/pages/[locale]/furthsettins/scotsoun/[scotsounId]/index.astro
@@ -24,4 +24,5 @@ const scotsoun = await getScotsoun[scotsounId]();
<Page title={t(tPage, { key: 'title', scotsounId, name: scotsoun.title })}>
<Scotsoun scotsoun={scotsoun} />
+ <script src="$scripts/wc/paypal-product-buttons.ts"></script>
</Page>
diff --git a/src/pages/[locale]/index.astro b/src/pages/[locale]/index.astro
index e8b9cf0..d930c20 100644
--- a/src/pages/[locale]/index.astro
+++ b/src/pages/[locale]/index.astro
@@ -55,36 +55,11 @@ const mostRecentNewsItem = await getMostRecentNewsItem(locale);
</Button>
</section>
- <section id="jyne">
- <h2 set:html={t(tPage, { key: 'jyne' })} />
-
- <h3 set:html={t(tPage, { key: 'why-jyne' })} />
- <p set:html={t(tPage, { key: 'why-jyne-para-1' })} />
- <p set:html={t(tPage, { key: 'why-jyne-para-2' })} />
- <p set:html={t(tPage, { key: 'why-jyne-para-3' })} />
-
- <h3 set:html={t(tPage, { key: 'hou-jyne' })} />
- <p set:html={t(tPage, { key: 'hou-jyne-para-1' })} />
- <p
- set:html={t(tPage, {
- key: 'hou-jyne-para-2',
- membershipEmail: contactDetails.membershipSecretary.emailAddress,
- })}
- />
- </section>
-
<section id="contact">
<h2 set:html={t(tPage, { key: 'contact' })} />
<a href={`${locale}/mailto:${contactDetails.generalEnquiries.emailAddress}`}>
{contactDetails.generalEnquiries.emailAddress}
</a>
- <address>
- Scots Language Society<br />
- c/o 61 Cliffburn Road<br />
- Arbroath<br />
- Angus<br />
- DD11 5BA<br />
- United Kingdom<br />
- </address>
+ <address set:html={contactDetails.generalEnquiries.address.join('<br />')} />
</section>
</Page>
diff --git a/src/pages/[locale]/jyne/index.astro b/src/pages/[locale]/jyne/index.astro
new file mode 100644
index 0000000..7a1b4f6
--- /dev/null
+++ b/src/pages/[locale]/jyne/index.astro
@@ -0,0 +1,104 @@
+---
+import type Locale from '$types/Locale';
+
+import { annualSubscriptionPrice } from '$data/subscriptionPrices';
+import contactDetails from '$data/contactDetails';
+import Page from '$layouts/Page.astro';
+import localeStaticPaths from '$lib/localeStaticPaths';
+import translate from '$i18n/translate';
+import tPage from '$i18n/translations/pages/jyne';
+import tMembershipType from '$i18n/translations/enums/membershipType';
+import { allMembershipTypes } from '$enums/MembershipType';
+
+export async function getStaticPaths() {
+ return localeStaticPaths;
+}
+
+const locale = Astro.params.locale as Locale;
+const t = translate(locale);
+---
+
+<Page title={t(tPage, { key: 'title' })}>
+ <header>
+ <h1 set:html={t(tPage, { key: 'title' })} />
+ </header>
+
+ <section>
+ <h2 set:html={t(tPage, { key: 'hou-jyne' })} />
+ <p set:html={t(tPage, { key: 'hou-jyne-intro' })} />
+
+ <table>
+ <caption>{t(tPage, { key: 'subscription-price-table-caption' })}</caption>
+ <thead>
+ <tr>
+ {
+ allMembershipTypes.map((membershipType) => (
+ <th scope="col">{t(tMembershipType, { key: membershipType })}</th>
+ ))
+ }
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr>
+ {
+ allMembershipTypes.map((membershipType) => (
+ <td>
+ {new Intl.NumberFormat('en-GB', {
+ style: 'currency',
+ currency: 'GBP',
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ }).format(annualSubscriptionPrice[membershipType])}
+ </td>
+ ))
+ }
+ </tr>
+ </tbody>
+ </table>
+
+ <ul class="payment-methods-list">
+ <li>
+ <details>
+ <summary set:html={t(tPage, { key: 'hou-pey-by-paypal-title' })} />
+ <ul>
+ <li><a href={`/${locale}/jyne/paypal/individual-uk`}>Individual (UK)</a></li>
+ <li><a href={`/${locale}/jyne/paypal/individual-non-uk`}>Individual (non-UK)</a></li>
+ <li><a href={`/${locale}/jyne/paypal/institution-uk`}>Institutional (UK)</a></li>
+ </ul>
+ </details>
+ </li>
+
+ <li>
+ <details>
+ <summary set:html={t(tPage, { key: 'hou-pey-by-bank-transfer-title' })} />
+ <p
+ set:html={t(tPage, {
+ key: 'hou-pey-by-bank-transfer-para',
+ emailAddress: contactDetails.membershipSecretary.emailAddress,
+ })}
+ />
+ </details>
+ </li>
+
+ <li>
+ <details>
+ <summary set:html={t(tPage, { key: 'hou-pey-by-cheque-title' })} />
+ <p
+ set:html={t(tPage, {
+ key: 'hou-pey-by-cheque-para',
+ address: contactDetails.membershipSecretary.address,
+ })}
+ />
+ </details>
+ </li>
+ </ul>
+ </section>
+
+ <section>
+ <h2 set:html={t(tPage, { key: 'why-jyne' })} />
+ <p set:html={t(tPage, { key: 'why-jyne-para-1' })} />
+ <p set:html={t(tPage, { key: 'why-jyne-para-2' })} />
+ <p set:html={t(tPage, { key: 'why-jyne-para-3' })} />
+ </section>
+</Page>
diff --git a/src/pages/[locale]/jyne/paypal/[membershipType]/index.astro b/src/pages/[locale]/jyne/paypal/[membershipType]/index.astro
new file mode 100644
index 0000000..0934854
--- /dev/null
+++ b/src/pages/[locale]/jyne/paypal/[membershipType]/index.astro
@@ -0,0 +1,41 @@
+---
+import type Locale from '$types/Locale';
+import {
+ type MembershipTypeSnakeCase,
+ allMembershipTypesSnakeCase,
+ snakeCaseToMembershipType,
+} from '$enums/MembershipType';
+import { annualSubscriptionPrice } from '$data/subscriptionPrices';
+import Page from '$layouts/Page.astro';
+import PaypalProductButtons from '$components/PaypalProductButtons.astro';
+import localeStaticPaths from '$lib/localeStaticPaths';
+import translate from '$i18n/translate';
+import tJyne from '$i18n/translations/pages/jyne';
+
+export async function getStaticPaths() {
+ return allMembershipTypesSnakeCase.flatMap((membershipType) =>
+ localeStaticPaths.map((p) => ({ ...p, params: { ...p.params, membershipType } }))
+ );
+}
+
+const locale = Astro.params.locale as Locale;
+const t = translate(locale);
+
+const membershipType = Astro.params.membershipType as MembershipTypeSnakeCase;
+---
+
+<Page title={t(tJyne, { key: `membership-type-${membershipType}` })}>
+ <section>
+ <h1>{t(tJyne, { key: `membership-type-${membershipType}` })}</h1>
+
+ <p set:html={t(tJyne, { key: `membership-type-${membershipType}-para` })} />
+
+ <PaypalProductButtons
+ successPageUrl={`/${locale}/jyne/paypal/${membershipType}/success`}
+ productDescription={t(tJyne, { key: `membership-type-${membershipType}` })}
+ shortDescription="SLA 1y subscription"
+ totalPrice={annualSubscriptionPrice[snakeCaseToMembershipType(membershipType)]}
+ />
+ <script src="$scripts/wc/paypal-product-buttons"></script>
+ </section>
+</Page>
diff --git a/src/pages/[locale]/jyne/paypal/[membershipType]/success.astro b/src/pages/[locale]/jyne/paypal/[membershipType]/success.astro
new file mode 100644
index 0000000..569e294
--- /dev/null
+++ b/src/pages/[locale]/jyne/paypal/[membershipType]/success.astro
@@ -0,0 +1,30 @@
+---
+import type Locale from '$types/Locale';
+import { type MembershipTypeSnakeCase } from '$enums/MembershipType';
+
+import Page from '$layouts/Page.astro';
+import { allMembershipTypesSnakeCase, snakeCaseToMembershipType } from '$enums/MembershipType';
+import localeStaticPaths from '$lib/localeStaticPaths';
+import translate from '$i18n/translate';
+import tJyne from '$i18n/translations/pages/jyne';
+
+export async function getStaticPaths() {
+ return allMembershipTypesSnakeCase.flatMap((membershipType) =>
+ localeStaticPaths.map((p) => ({ ...p, params: { ...p.params, membershipType } }))
+ );
+}
+
+const locale = Astro.params.locale as Locale;
+const t = translate(locale);
+
+const membershipTypeSnakeCase = Astro.params.membershipType as MembershipTypeSnakeCase;
+const membershipType = snakeCaseToMembershipType(membershipTypeSnakeCase);
+---
+
+<Page title={t(tJyne, { key: 'success' })}>
+ <section>
+ <h1>{t(tJyne, { key: 'success' })}</h1>
+
+ <p set:html={t(tJyne, { key: 'success-para', membershipType })} />
+ </section>
+</Page>
diff --git a/src/pages/[locale]/jyne/paypal/index.astro b/src/pages/[locale]/jyne/paypal/index.astro
new file mode 100644
index 0000000..55905d1
--- /dev/null
+++ b/src/pages/[locale]/jyne/paypal/index.astro
@@ -0,0 +1,27 @@
+---
+import type Locale from '$types/Locale';
+
+import Page from '$layouts/Page.astro';
+import localeStaticPaths from '$lib/localeStaticPaths';
+import translate from '$i18n/translate';
+import tJyne from '$i18n/translations/pages/jyne';
+
+export async function getStaticPaths() {
+ return localeStaticPaths;
+}
+
+const locale = Astro.params.locale as Locale;
+const t = translate(locale);
+---
+
+<Page title={t(tJyne, { key: 'hou-pey-by-paypal-title' })}>
+ <section>
+ <h1 set:html={t(tJyne, { key: 'hou-pey-by-paypal-title' })} />
+ <p set:html={t(tJyne, { key: 'hou-pey-by-paypal-para' })} />
+ <ul>
+ <li><a href={`/${locale}/jyne/paypal/individual-uk`}>Individual (UK)</a></li>
+ <li><a href={`/${locale}/jyne/paypal/individual-non-uk`}>Individual (non-UK)</a></li>
+ <li><a href={`/${locale}/jyne/paypal/institution-uk`}>Institutional (UK)</a></li>
+ </ul>
+ </section>
+</Page>
diff --git a/src/scripts/wc/paypal-product-buttons.ts b/src/scripts/wc/paypal-product-buttons.ts
new file mode 100644
index 0000000..ca06524
--- /dev/null
+++ b/src/scripts/wc/paypal-product-buttons.ts
@@ -0,0 +1,146 @@
+import { type PayPalNamespace, loadScript } from '@paypal/paypal-js';
+
+const backendUrl = '/api';
+
+class PaypalProductButtons extends HTMLElement {
+ constructor() {
+ super();
+
+ try {
+ const paypalClientId = this.getAttribute('paypal-client-id');
+
+ const paypalOptions = {
+ clientId: paypalClientId,
+ locale: 'en_GB',
+ commit: false,
+ currency: 'GBP',
+ intent: 'capture',
+ };
+
+ loadScript(paypalOptions)
+ .catch((err) => {
+ this.innerHTML = `
+ <details>
+ <summary>Failed to load PayPal buttons</summary>
+ <p>${err}</p>
+ </details>
+ `;
+ console.error(`Failed to load the PayPal JS SDK script. ${err}`);
+ })
+ .then((paypal) => {
+ const constructButtons = paypal?.Buttons;
+ if (paypal && constructButtons) {
+ const successPageUrl = this.getAttribute('success-page-url');
+ this.innerHTML = '';
+ const div = document.createElement('div');
+ this.appendChild(div);
+ this.makePaypalButtons(constructButtons, successPageUrl).render(div);
+ }
+ })
+ .catch((err) => {
+ this.innerHTML = `
+ <details>
+ <summary>Failed to load PayPal buttons</summary>
+ <p>${err}</p>
+ </details>
+ `;
+ console.error(`Failed to construct the PayPal buttons. ${err}`);
+ });
+ } catch (err) {
+ this.innerHTML = `
+ <details>
+ <summary>Failed to load PayPal buttons</summary>
+ <p>${err}</p>
+ </details>
+ `;
+ }
+ }
+
+ makePaypalButtons(
+ constructButtons: Required<PayPalNamespace>['Buttons'],
+ successPageUrl: string
+ ) {
+ const productDescription = this.getAttribute('product-description');
+ const shortDescription = this.getAttribute('short-description');
+ const totalPrice = this.getAttribute('total-price');
+
+ return constructButtons({
+ // This function is called to set up the transaction details without actually carrying it out
+ async createOrder() {
+ const body = JSON.stringify({
+ productDescription,
+ shortDescription,
+ totalPrice,
+ });
+ const headers = {
+ 'Content-Type': 'application/json',
+ };
+
+ let response: Response;
+ try {
+ response = await fetch(`${backendUrl}/order`, { method: 'POST', body, headers });
+ } catch (err) {
+ throw new Error(`Failed to create order. ${err}`);
+ }
+
+ if (!response.ok) {
+ throw new Error(`
+ Failed to create order. Backend response was
+ ${response.status} ${response.statusText}. ${await response.text()}
+ `);
+ }
+
+ const { orderId, approvalLink } = await response.json();
+ console.info(`
+ Successfully created order with ID
+ ${orderId}. Approval link: ${approvalLink}
+ `);
+ return orderId;
+ },
+
+ // This function is called after payment is confirmed
+ async onApprove(data) {
+ const body = JSON.stringify({
+ id: data.orderID,
+ isApproved: true,
+ });
+ const headers = {
+ 'Content-Type': 'application/json',
+ };
+
+ let response: Response;
+ try {
+ response = await fetch(`${backendUrl}/order`, { method: 'PATCH', body, headers });
+ } catch (err) {
+ throw new Error(`Failed to capture order. ${err}`);
+ }
+
+ if (!response.ok) {
+ throw new Error(`
+ Failed to capture order. Backend response was ${response.status}
+ ${response.statusText}. ${await response.text()}
+ `);
+ }
+
+ const { orderId, requestStatus } = await response.json();
+ console.info(`
+ Successfully captured order with ID ${orderId},
+ request status is ${requestStatus}.
+ `);
+
+ console.info(`Redirecting to confirmation page, ${successPageUrl}...`);
+ window.location.href = successPageUrl;
+ },
+ });
+ }
+
+ getAttribute(key: string): string {
+ const value = super.getAttribute(key);
+ if (!value || typeof value !== 'string') {
+ throw new Error(`${key} attribute not provided to PaypalProductButtons component.`);
+ }
+ return value;
+ }
+}
+
+customElements.define('paypal-product-buttons', PaypalProductButtons);
diff --git a/src/types/TranslationsDictionary.d.ts b/src/types/TranslationsDictionary.d.ts
index 5033ecd..47ee7f4 100644
--- a/src/types/TranslationsDictionary.d.ts
+++ b/src/types/TranslationsDictionary.d.ts
@@ -1,12 +1,15 @@
import type { defaultLocale } from '$i18n/locales';
import type Locale from './Locale';
-export type Translation<Params> = (params: { [key in keyof Params]: string }) => string;
+export type Translation<ParamsHash extends object> = (params: ParamsHash) => string;
+export type LocalesTranslationHash<ParamsHash extends object> = {
+ [locale in Locale]?: Translation<ParamsHash>;
+} & {
+ [locale in typeof defaultLocale]: Translation<ParamsHash>;
+};
export type TranslationsDictionary<Raw> = {
- readonly [key in keyof Raw]: {
- [locale in Locale]?: Translation<Parameters<Raw[key][typeof defaultLocale]>[0]>;
- } & {
- [locale in typeof defaultLocale]: Translation<Parameters<Raw[key][typeof defaultLocale]>[0]>;
- };
+ readonly [key in keyof Raw]: LocalesTranslationHash<
+ Parameters<Raw[key][typeof defaultLocale]>[0]
+ >;
};
diff --git a/tsconfig.json b/tsconfig.json
index 61fcd49..956d1c9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,6 +11,7 @@
"$layouts/*": ["./layouts/*"],
"$lib/*": ["./lib/*"],
"$pages/*": ["./pages/*"],
+ "$scripts/*": ["./scripts/*"],
"$types/*": ["./types/*"]
},
"strictNullChecks": true