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: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
STAGING_SECRET=
DONATION_DATA_FILE=./data/donation-progress.json
CONTENTFUL_SPACE_ID=
CONTENTFUL_ENVIRONMENT_ID=
CONTENTFUL_ACCESS_TOKEN=
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,7 @@ yarn-error.log*
*.tsbuildinfo

tmp

crowdin-faq-answers.csv
crowdin-new-strings.csv*
crowdin-new-strings.state.json
9 changes: 8 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@
},
"files": {
"ignoreUnknown": true,
"includes": ["**", "!**/lib/app_localization/**", "!**/locales/**", "!contentful/export.json"]
"includes": [
"./**/*.ts",
"./**/*.tsx",
"!**/lib/app_localization/**",
"!**/locales/**",
"!contentful/export.json",
"!**/.next/**"
]
},
"formatter": {
"enabled": true,
Expand Down
11 changes: 6 additions & 5 deletions components/RichBody.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** biome-ignore-all lint/suspicious/noExplicitAny: ignored for now */
import { documentToPlainTextString } from '@contentful/rich-text-plain-text-renderer';
import { documentToReactComponents, type Options } from '@contentful/rich-text-react-renderer';
import { BLOCKS, type Document, INLINES, type Inline, MARKS } from '@contentful/rich-text-types';
Expand All @@ -8,7 +9,7 @@ import Link from 'next/link';
import { Children, cloneElement, type ReactElement } from 'react';
import SHORTCODES from '@/constants/shortcodes';
import { renderEmbeddedEntry } from '@/services/render';
import { hasLocalID, isLocal, parseUrl } from '@/utils/links';
import { hasLocalID, isLocal, parseUrl, slugifyHeading } from '@/utils/links';
import { renderShortcode } from '@/utils/shortcodes';

interface Props {
Expand Down Expand Up @@ -138,7 +139,7 @@ export default function RichBody(props: Props): ReactElement {
[BLOCKS.HEADING_1]: (node, children: any) => (
<h1
dir={getDirection(children)}
id={hasLocalID(node as Inline)}
id={hasLocalID(node as Inline) || slugifyHeading(node)}
className={classNames('mb-5 text-3xl leading-snug', 'lg:text-5xl', headingClasses)}
>
{children}
Expand All @@ -147,7 +148,7 @@ export default function RichBody(props: Props): ReactElement {
[BLOCKS.HEADING_2]: (node, children: any) => (
<h2
dir={getDirection(children)}
id={hasLocalID(node as Inline)}
id={hasLocalID(node as Inline) || slugifyHeading(node)}
className={classNames('mb-5 text-2xl leading-snug', 'lg:text-3xl', headingClasses)}
>
{children}
Expand All @@ -156,7 +157,7 @@ export default function RichBody(props: Props): ReactElement {
[BLOCKS.HEADING_3]: (node, children: any) => (
<h3
dir={getDirection(children)}
id={hasLocalID(node as Inline)}
id={hasLocalID(node as Inline) || slugifyHeading(node)}
className={classNames('mb-2 text-xl leading-snug', 'lg:text-2xl', headingClasses)}
>
{children}
Expand All @@ -165,7 +166,7 @@ export default function RichBody(props: Props): ReactElement {
[BLOCKS.HEADING_4]: (node, children: any) => (
<h4
dir={getDirection(children)}
id={hasLocalID(node as Inline)}
id={hasLocalID(node as Inline) || slugifyHeading(node)}
className={classNames('mb-2 text-md leading-snug', 'lg:text-xl', headingClasses)}
>
{children}
Expand Down
9 changes: 8 additions & 1 deletion components/copied/ArbitrumIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import { forwardRef } from 'react';
import type { SVGAttributes } from './types';

export const ArbitrumIcon = forwardRef<SVGSVGElement, SVGAttributes>((props, ref) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2500 2500" {...props} ref={ref}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 2500 2500"
{...props}
ref={ref}
aria-hidden="true"
focusable="false"
>
<path fill="none" d="M0 0h2500v2500H0z" />
<path
fill="#213147"
Expand Down
2 changes: 2 additions & 0 deletions components/copied/BNBIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export const BNBIcon = forwardRef<SVGSVGElement, SVGAttributes>((props, ref) =>
viewBox="0 0 2496 2496"
{...props}
ref={ref}
aria-hidden="true"
focusable="false"
>
<path
d="M1248 0c689.3 0 1248 558.7 1248 1248s-558.7 1248-1248 1248S0 1937.3 0 1248 558.7 0 1248 0z"
Expand Down
2 changes: 2 additions & 0 deletions components/copied/BankIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const BankIcon = forwardRef<SVGSVGElement, SVGAttributes>((props, ref) =>
xmlns="http://www.w3.org/2000/svg"
{...props}
ref={ref}
aria-hidden="true"
focusable="false"
>
<path fill="#66757F" d="M3 16h30v18H3z"></path>
<path fill="#CCD6DD" d="M2 34h32a2 2 0 0 1 2 2H0a2 2 0 0 1 2-2z"></path>
Expand Down
2 changes: 2 additions & 0 deletions components/copied/BitcoinCashIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const BitcoinCashIcon = forwardRef<SVGSVGElement, SVGAttributes>((props,
viewBox="0 0 788 788"
{...props}
ref={ref}
aria-hidden="true"
focusable="false"
>
<circle cx="394" cy="394" r="394" style={{ fill: '#0ac18e' }} />
<path
Expand Down
2 changes: 2 additions & 0 deletions components/copied/BitcoinIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const BitcoinIcon = forwardRef<SVGSVGElement, SVGAttributes>((props, ref)
shapeRendering="geometricPrecision"
textRendering="geometricPrecision"
viewBox="0 0 4091.27 4091.73"
aria-hidden="true"
focusable="false"
>
<g fillRule="nonzero">
<path
Expand Down
2 changes: 2 additions & 0 deletions components/copied/EthIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const EthIcon = forwardRef<SVGSVGElement, SVGAttributes>((props, ref) =>
xmlns="http://www.w3.org/2000/svg"
{...props}
ref={ref}
aria-hidden="true"
focusable="false"
>
<g fill="none" fillRule="evenodd">
<circle cx="16" cy="16" r="16" fill="#627EEA" />
Expand Down
2 changes: 2 additions & 0 deletions components/copied/MoneroIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const MoneroIcon = forwardRef<SVGSVGElement, SVGAttributes>((props, ref)
xmlns="http://www.w3.org/2000/svg"
{...props}
ref={ref}
aria-hidden="true"
focusable="false"
>
<path
d="M1000 0C447.8 0 0 447.8 0 1000a993.26 993.26 0 0 0 51 315.8h299V474.4l650 650 650-650v841.4h299a999.82 999.82 0 0 0 51-315.8C2000 447.8 1552.2 0 1000 0"
Expand Down
10 changes: 9 additions & 1 deletion components/copied/USDCIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import { forwardRef } from 'react';
import type { SVGAttributes } from './types';

export const USDCIcon = forwardRef<SVGSVGElement, SVGAttributes>((props, ref) => (
<svg viewBox="0 0 2000 2000" fill="none" xmlns="http://www.w3.org/2000/svg" {...props} ref={ref}>
<svg
viewBox="0 0 2000 2000"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
ref={ref}
aria-hidden="true"
focusable="false"
>
<path
d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z"
fill="#2775ca"
Expand Down
12 changes: 4 additions & 8 deletions constants/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,10 @@ export type NavItemKey = keyof Omit<Messages['navigation'], 'aria'>;
type INavList = Record<NavItemKey | string, INavItem>;

const NAV_ITEMS: INavList = {
...(process.env.ENABLE_PRO_PAGE === '1'
? {
pro: {
href: '/pro',
target: '_self',
},
}
: {}),
pro: {
href: '/pro',
target: '_self',
},
resources: {
href: '/resources',
target: '_self',
Expand Down
5 changes: 0 additions & 5 deletions data/donation-progress.json

This file was deleted.

2 changes: 1 addition & 1 deletion lib/app_localization
59 changes: 30 additions & 29 deletions lib/proBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,37 @@ export type PricingApiResponse = {
* Expected API response shape:
* {
* plans: [
* { duration: "P1M", label: "Monthly", unitText: "MONTH", price: "2.49", currency: "USD" },
* { duration: "P3M", label: "3 Months", unitText: "MONTH", price: "5.99", currency: "USD" },
* { duration: "P1Y", label: "Annual", unitText: "YEAR", price: "14.99", currency: "USD" }
* { duration: "P1M", label: "Monthly", unitText: "MONTH", price: "4.49", currency: "USD" },
* { duration: "P3M", label: "3 Months", unitText: "MONTH", price: "11.99", currency: "USD" },
* { duration: "P1Y", label: "Annual", unitText: "YEAR", price: "35.99", currency: "USD" }
* ]
* }
*/
export async function fetchProPricing(locale: string): Promise<PricingApiResponse> {
return {
plans: [
{ duration: 'P1M', label: 'Monthly', unitText: 'MONTH', price: '2.49', currency: 'USD' },
{ duration: 'P3M', label: '3 Months', unitText: 'MONTH', price: '5.99', currency: 'USD' },
{ duration: 'P1Y', label: 'Annual', unitText: 'YEAR', price: '14.99', currency: 'USD' },
],
};

const res = await fetch(`${process.env.PRICING_API_BASE_URL}/pro/pricing?locale=${locale}`, {
headers: {
'Content-Type': 'application/json',
// Add any auth headers your API requires, e.g.:
// Authorization: `Bearer ${process.env.PRICING_API_KEY}`,
export const proPricing: PricingApiResponse = {
plans: [
{
duration: 'P1M',
label: 'Monthly',
unitText: 'MONTH',
price: '4.49',
currency: 'USD',
priceNumber: 4.49,
},
// Revalidate pricing every hour during ISR if you use it
next: { revalidate: 3600 },
});

if (!res.ok) {
throw new Error(
`Failed to fetch Pro pricing for locale "${locale}": ${res.status} ${res.statusText}`
);
}

return res.json() as Promise<PricingApiResponse>;
}
{
duration: 'P3M',
label: '3 Months',
unitText: 'MONTH',
price: '11.99',
currency: 'USD',
priceNumber: 11.99,
},
{
duration: 'P1Y',
label: 'Annual',
unitText: 'YEAR',
price: '35.99',
currency: 'USD',
priceNumber: 35.99,
},
],
};
17 changes: 13 additions & 4 deletions lib/proPageSchema.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** biome-ignore-all lint/suspicious/noExplicitAny: ignored for now */
import { NON_LOCALIZED_STRING } from '@/constants/localization';
import {
isCrowdinLocale,
Expand All @@ -13,6 +14,7 @@ export type PricingPlan = {
label: string; // e.g. "Monthly", "3 Months", "Annual"
unitText: 'MONTH' | 'YEAR';
price: string; // e.g. "2.49"
priceNumber: number;
currency: string; // e.g. "USD"
};

Expand Down Expand Up @@ -51,6 +53,7 @@ export type SchemaRoadmapItem = {
export type ProPageSchemaProps = {
locale: string;
pricing: PricingApiResponse;

messages: Record<string, any>;
};

Expand Down Expand Up @@ -114,7 +117,14 @@ function resolve(obj: Record<string, any>, path: string): string {
const t = new LocalizedStringBuilder(rawMessage as any);
t.withArgs({ ...NON_LOCALIZED_STRING });
// @ts-expect-error TODO: make method public
return t.formatStringWithArgs(rawMessage);
const formatted: string = t.formatStringWithArgs(rawMessage);

// schema.org values are plain text, so the rich-text tags the page renders (<br>, <bold>,
// <li>, the link tags) would otherwise ship as literal markup inside the JSON-LD
return formatted
.replace(/<[^<>]*>/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}

function buildOffersSchema(pricing: ProPageSchemaProps['pricing']): SchemaOffer[] {
Expand Down Expand Up @@ -143,7 +153,7 @@ function buildSoftwareApplicationSchema(offers: SchemaOffer[], messages: Record<
};
}

function buildProFeaturesSchema(messages: Record<string, any>) {
function buildProFeaturesSchema(_messages: Record<string, any>) {
// TODO: set locale
const features = getProFeatures(false);

Expand Down Expand Up @@ -205,8 +215,7 @@ function buildFAQSchema(messages: Record<string, any>) {
*
* @example
* // In getStaticProps:
* const pricing = await fetchProPricing(context.locale);
* const schemas = generateProPageSchemas({ locale, pricing, messages });
* const schemas = generateProPageSchemas({ locale, pricing: proPricing, messages });
* return { props: { messages, schemas } };
*
* // In ProPage component:
Expand Down
Loading
Loading