mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
Merge branch 'codex/feature/pass-page-i18n'
This commit is contained in:
@@ -1,17 +1,48 @@
|
||||
import * as React from 'react';
|
||||
import { createElement } from 'react';
|
||||
import { Fragment, cloneElement, createElement, isValidElement } from 'react';
|
||||
import { createRoot, type Root } from 'react-dom/client';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import en from '../../../../public/translations/en/default.json';
|
||||
import Pass from '../pass';
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
|
||||
|
||||
const renderTransTemplate = (template: string, components?: Record<string, React.ReactElement>, keyPrefix = 'trans'): React.ReactNode => {
|
||||
const match = template.match(/<(\w+)>(.*?)<\/\1>/s);
|
||||
if (!match || match.index === undefined) {
|
||||
return template;
|
||||
}
|
||||
|
||||
const before = template.slice(0, match.index);
|
||||
const [, componentName, inner] = match;
|
||||
const after = template.slice(match.index + match[0].length);
|
||||
const component = components?.[componentName];
|
||||
|
||||
return createElement(
|
||||
Fragment,
|
||||
{},
|
||||
before,
|
||||
component && isValidElement(component)
|
||||
? cloneElement(component, { key: `${keyPrefix}-${componentName}` }, renderTransTemplate(inner, components, `${keyPrefix}-${componentName}`))
|
||||
: inner,
|
||||
renderTransTemplate(after, components, `${keyPrefix}-after`),
|
||||
);
|
||||
};
|
||||
|
||||
vi.mock('react-router-hash-link', () => ({
|
||||
HashLink: ({ children, to }: { children: React.ReactNode; to: string }) => createElement('a', { href: to }, children),
|
||||
}));
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
Trans: ({ components, i18nKey }: { components?: Record<string, React.ReactElement>; i18nKey: string }) =>
|
||||
createElement(Fragment, {}, renderTransTemplate((en as Record<string, string>)[i18nKey] ?? i18nKey, components)),
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => (en as Record<string, string>)[key] ?? key,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../../home', () => ({
|
||||
Footer: () => createElement('div', { 'data-testid': 'footer' }, 'footer'),
|
||||
HomeLogo: () => createElement('div', { 'data-testid': 'home-logo' }, 'home-logo'),
|
||||
@@ -47,7 +78,7 @@ describe('Pass', () => {
|
||||
|
||||
expect(container.querySelector('[data-testid="home-logo"]')).toBeTruthy();
|
||||
expect(container.querySelector('[data-testid="footer"]')).toBeTruthy();
|
||||
expect(container.textContent).toContain('Support 5chan with 5chan Pass');
|
||||
expect(container.textContent).toContain((en as Record<string, string>).pass_heading);
|
||||
expect(container.textContent).toContain('not available yet');
|
||||
expect(container.textContent).toContain('crypto only');
|
||||
expect(container.textContent).toContain('directory voting');
|
||||
@@ -65,6 +96,6 @@ describe('Pass', () => {
|
||||
expect(container.querySelector('a[href="https://github.com/bitsocialnet/mintpass"]')?.textContent).toBe('MintPass');
|
||||
expect(container.querySelector('a[href="https://bitsocial.net"]')?.textContent).toBe('Bitsocial');
|
||||
expect(window.scrollTo).toHaveBeenCalledWith(0, 0);
|
||||
expect(document.title).toBe('Pass - 5chan');
|
||||
expect(document.title).toBe((en as Record<string, string>).pass_document_title);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -131,6 +131,11 @@
|
||||
border-bottom: solid #006 1px;
|
||||
}
|
||||
|
||||
.rightBox dd:last-of-type {
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.content {
|
||||
min-width: 0;
|
||||
|
||||
+115
-112
@@ -1,93 +1,89 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { HashLink } from 'react-router-hash-link';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Footer, HomeLogo } from '../home';
|
||||
import styles from './pass.module.css';
|
||||
|
||||
const overviewQuestionLinks = [
|
||||
{ id: 'whatpass', key: 'pass_question_what_is' },
|
||||
{ id: 'available', key: 'pass_question_available' },
|
||||
{ id: 'whatdoes', key: 'pass_question_what_does' },
|
||||
{ id: 'whatnot', key: 'pass_question_what_not' },
|
||||
{ id: 'vip', key: 'pass_question_vip' },
|
||||
{ id: 'challenge', key: 'pass_question_challenge' },
|
||||
{ id: 'payment', key: 'pass_question_payment' },
|
||||
{ id: 'pricing', key: 'pass_question_pricing' },
|
||||
{ id: 'nft', key: 'pass_question_nft' },
|
||||
{ id: 'proceeds', key: 'pass_question_proceeds' },
|
||||
] as const;
|
||||
|
||||
const faqQuestionLinks = [
|
||||
{ id: 'needpass', key: 'pass_question_need' },
|
||||
{ id: 'fiat', key: 'pass_question_fiat' },
|
||||
{ id: 'vpn', key: 'pass_question_vpn' },
|
||||
{ id: 'bypass', key: 'pass_question_bypass' },
|
||||
{ id: 'privileges', key: 'pass_question_privileges' },
|
||||
] as const;
|
||||
|
||||
const Pass = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
document.title = 'Pass - 5chan';
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t('pass_document_title');
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.content}>
|
||||
<HomeLogo />
|
||||
<div className={`${styles.box} ${styles.infoBox}`}>
|
||||
<div className={styles.boxBar}>
|
||||
<h2>Support 5chan with 5chan Pass</h2>
|
||||
<h2>{t('pass_heading')}</h2>
|
||||
</div>
|
||||
<div className={styles.boxContent}>
|
||||
5chan Pass is a planned paid supporter pass for 5chan. It is <strong>not available yet</strong>. When it launches, 5chan Pass holders will be able to vote on
|
||||
directory pages, post on <Link to='/vip'>/vip/</Link>, and bypass typing a CAPTCHA verification or similar posting challenge on 5chan boards that support the
|
||||
5chan Pass challenge. Checkout will be <strong>crypto only</strong>. There is no live purchase, activation, or renewal flow today.
|
||||
<Trans
|
||||
i18nKey='pass_intro_body'
|
||||
components={{
|
||||
strong: <strong />,
|
||||
vip: <Link to='/vip' />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.columns}>
|
||||
<div className={`${styles.box} ${styles.leftBox}`}>
|
||||
<div className={styles.boxBar}>
|
||||
<h2>Questions</h2>
|
||||
<h2>{t('pass_questions')}</h2>
|
||||
</div>
|
||||
<div className={styles.boxContent}>
|
||||
<ul className={styles.list}>
|
||||
<li>
|
||||
<strong>
|
||||
<HashLink to='#overview'>Overview</HashLink>
|
||||
<HashLink to='#overview'>{t('pass_overview')}</HashLink>
|
||||
</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<HashLink to='#whatpass'>What is 5chan Pass?</HashLink>
|
||||
</li>
|
||||
<li>
|
||||
<HashLink to='#available'>Is it available now?</HashLink>
|
||||
</li>
|
||||
<li>
|
||||
<HashLink to='#whatdoes'>What will it do?</HashLink>
|
||||
</li>
|
||||
<li>
|
||||
<HashLink to='#whatnot'>What will it not do?</HashLink>
|
||||
</li>
|
||||
<li>
|
||||
<HashLink to='#vip'>What is the /vip/ benefit?</HashLink>
|
||||
</li>
|
||||
<li>
|
||||
<HashLink to='#challenge'>How does zero-friction posting work?</HashLink>
|
||||
</li>
|
||||
<li>
|
||||
<HashLink to='#payment'>How will payment work?</HashLink>
|
||||
</li>
|
||||
<li>
|
||||
<HashLink to='#pricing'>Has pricing been announced?</HashLink>
|
||||
</li>
|
||||
<li>
|
||||
<HashLink to='#nft'>How does the NFT work?</HashLink>
|
||||
</li>
|
||||
<li>
|
||||
<HashLink to='#proceeds'>What happens to the proceeds?</HashLink>
|
||||
</li>
|
||||
{overviewQuestionLinks.map(({ id, key }) => (
|
||||
<li key={id}>
|
||||
<HashLink to={`#${id}`}>{t(key)}</HashLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<strong>
|
||||
<HashLink to='#faq'>FAQ</HashLink>
|
||||
<HashLink to='#faq'>{t('pass_faq')}</HashLink>
|
||||
</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<HashLink to='#needpass'>Will I need it to browse or post?</HashLink>
|
||||
</li>
|
||||
<li>
|
||||
<HashLink to='#fiat'>Will fiat payments be accepted?</HashLink>
|
||||
</li>
|
||||
<li>
|
||||
<HashLink to='#vpn'>Will VPNs, proxies, or IP changes matter?</HashLink>
|
||||
</li>
|
||||
<li>
|
||||
<HashLink to='#bypass'>Will it bypass bans or board rules?</HashLink>
|
||||
</li>
|
||||
<li>
|
||||
<HashLink to='#privileges'>Will it grant moderation powers or board ownership?</HashLink>
|
||||
</li>
|
||||
{faqQuestionLinks.map(({ id, key }) => (
|
||||
<li key={id}>
|
||||
<HashLink to={`#${id}`}>{t(key)}</HashLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -95,84 +91,91 @@ const Pass = () => {
|
||||
</div>
|
||||
<div className={`${styles.box} ${styles.rightBox}`}>
|
||||
<div className={styles.boxBar}>
|
||||
<h2 id='overview'>Overview</h2>
|
||||
<h2 id='overview'>{t('pass_overview')}</h2>
|
||||
</div>
|
||||
<div className={styles.boxContent}>
|
||||
<dl>
|
||||
<dt className={styles.first} id='whatpass'>
|
||||
What is 5chan Pass?
|
||||
{t('pass_question_what_is')}
|
||||
</dt>
|
||||
<dd>
|
||||
5chan Pass is an upcoming supporter purchase for people who want to fund 5chan directly. When it launches, it will be used for directory voting,
|
||||
zero-friction posting on boards that support the 5chan Pass challenge, and posting on <Link to='/vip'>/vip/</Link>.
|
||||
<Trans
|
||||
i18nKey='pass_answer_what_is'
|
||||
components={{
|
||||
vip: <Link to='/vip' />,
|
||||
}}
|
||||
/>
|
||||
</dd>
|
||||
<dt id='available'>Is it available now?</dt>
|
||||
<dd>No. This page is a placeholder so the future 5chan Pass route already exists, but there is nothing to buy, activate, or renew yet.</dd>
|
||||
<dt id='whatdoes'>What will it do?</dt>
|
||||
<dt id='available'>{t('pass_question_available')}</dt>
|
||||
<dd>{t('pass_answer_available')}</dd>
|
||||
<dt id='whatdoes'>{t('pass_question_what_does')}</dt>
|
||||
<dd>
|
||||
When it launches, 5chan Pass will let eligible users vote on directory assignments through each directory's voting page, post on{' '}
|
||||
<Link to='/vip'>/vip/</Link>, and bypass typing a CAPTCHA verification or similar posting challenge on all 5chan boards that support the pass challenge.
|
||||
Reporting is not available yet, so the pass does not currently apply to reports.
|
||||
<Trans
|
||||
i18nKey='pass_answer_what_does'
|
||||
components={{
|
||||
vip: <Link to='/vip' />,
|
||||
}}
|
||||
/>
|
||||
</dd>
|
||||
<dt id='whatnot'>What will it not do?</dt>
|
||||
<dt id='whatnot'>{t('pass_question_what_not')}</dt>
|
||||
<dd>
|
||||
5chan Pass will not give moderation powers, board ownership, special posting privileges, or any way to ignore board-specific{' '}
|
||||
<Link to='/rules'>rules</Link>. It also will not unlock anything today, because the feature has not launched yet.
|
||||
<Trans
|
||||
i18nKey='pass_answer_what_not'
|
||||
components={{
|
||||
rules: <Link to='/rules' />,
|
||||
}}
|
||||
/>
|
||||
</dd>
|
||||
<dt id='vip'>What is the /vip/ benefit?</dt>
|
||||
<dt id='vip'>{t('pass_question_vip')}</dt>
|
||||
<dd>
|
||||
Only 5chan Pass users will be able to post on <Link to='/vip'>/vip/</Link>. Browsing rules and long-term direction for that board can still evolve, but
|
||||
the current plan is that posting there is restricted to pass holders.
|
||||
<Trans
|
||||
i18nKey='pass_answer_vip'
|
||||
components={{
|
||||
vip: <Link to='/vip' />,
|
||||
}}
|
||||
/>
|
||||
</dd>
|
||||
<dt id='challenge'>How does zero-friction posting work?</dt>
|
||||
<dt id='challenge'>{t('pass_question_challenge')}</dt>
|
||||
<dd>{t('pass_answer_challenge')}</dd>
|
||||
<dt id='payment'>{t('pass_question_payment')}</dt>
|
||||
<dd>{t('pass_answer_payment')}</dd>
|
||||
<dt id='pricing'>{t('pass_question_pricing')}</dt>
|
||||
<dd>{t('pass_answer_pricing')}</dd>
|
||||
<dt id='nft'>{t('pass_question_nft')}</dt>
|
||||
<dd>
|
||||
On boards that support the 5chan Pass challenge, pass holders will be able to bypass typing a CAPTCHA verification or similar challenge before posting.
|
||||
This is similar in spirit to how 4chan Pass reduces posting friction, except 5chan reports are not available yet.
|
||||
<Trans
|
||||
i18nKey='pass_answer_nft'
|
||||
components={{
|
||||
mintPass: <a href='https://github.com/bitsocialnet/mintpass' target='_blank' rel='noopener noreferrer' />,
|
||||
}}
|
||||
/>
|
||||
</dd>
|
||||
<dt id='payment'>How will payment work?</dt>
|
||||
<dt id='proceeds'>{t('pass_question_proceeds')}</dt>
|
||||
<dd>
|
||||
5chan Pass will be sold through a crypto-only checkout when it becomes available. No credit cards, PayPal, or other fiat payment methods will be
|
||||
accepted.
|
||||
<Trans
|
||||
i18nKey='pass_answer_proceeds'
|
||||
components={{
|
||||
bitsocial: <a href='https://bitsocial.net' target='_blank' rel='noopener noreferrer' />,
|
||||
}}
|
||||
/>
|
||||
</dd>
|
||||
<dt id='pricing'>Has pricing been announced?</dt>
|
||||
<dd>
|
||||
Yes, the planned pricing model is the same simple structure: Passes cost $30 for 1 year or $60 for 3 years, which is about $1.67 per month or less than
|
||||
a single 20oz bottle of soda.
|
||||
</dd>
|
||||
<dt id='nft'>How does the NFT work?</dt>
|
||||
<dd>
|
||||
5chan Pass will be an NFT under the hood, powered by the open-source{' '}
|
||||
<a href='https://github.com/bitsocialnet/mintpass' target='_blank' rel='noopener noreferrer'>
|
||||
MintPass
|
||||
</a>{' '}
|
||||
service. The 5chan Pass challenge will only accept NFTs minted within the last year, so an NFT older than one year will count as expired until it is
|
||||
renewed or replaced.
|
||||
</dd>
|
||||
<dt id='proceeds'>What happens to the proceeds?</dt>
|
||||
<dd>
|
||||
All proceeds from 5chan Pass are intended to burn BSO, the official cryptocurrency token of{' '}
|
||||
<a href='https://bitsocial.net' target='_blank' rel='noopener noreferrer'>
|
||||
Bitsocial
|
||||
</a>
|
||||
. This is planned to happen automatically in the smart contract. The resulting on-chain transactions will be transparent, so buyers should assume their
|
||||
purchase wallet activity can be publicly visible.
|
||||
</dd>
|
||||
<dt id='faq'>Will I need it to browse or post?</dt>
|
||||
<dt id='faq'>{t('pass_question_need')}</dt>
|
||||
<dd id='needpass'>
|
||||
No. 5chan Pass is not required to read boards, subscribe to boards, or post normally. It is for specific benefits like directory voting, posting on{' '}
|
||||
<Link to='/vip'>/vip/</Link>, and zero-friction posting on boards that support the challenge.
|
||||
<Trans
|
||||
i18nKey='pass_answer_need'
|
||||
components={{
|
||||
vip: <Link to='/vip' />,
|
||||
}}
|
||||
/>
|
||||
</dd>
|
||||
<dt id='fiat'>Will fiat payments be accepted?</dt>
|
||||
<dd>No. When 5chan Pass launches, payment will be crypto only.</dd>
|
||||
<dt id='vpn'>Will VPNs, proxies, or IP changes matter?</dt>
|
||||
<dd>
|
||||
No. Because 5chan Pass is planned as an NFT-based credential, it is not meant to depend on a fixed IP address. There is no intended restriction based on
|
||||
VPN use, proxies, or changing networks.
|
||||
</dd>
|
||||
<dt id='bypass'>Will it bypass bans or board rules?</dt>
|
||||
<dd>No. A pass is not a way around bans, posting restrictions, or board moderation. Every board still sets and enforces its own rules.</dd>
|
||||
<dt id='privileges'>Will it grant moderation powers or board ownership?</dt>
|
||||
<dd>No. Buying a pass will not make someone a moderator, an admin, or the owner of a board. It is a supporter purchase, not a governance override.</dd>
|
||||
<dt id='fiat'>{t('pass_question_fiat')}</dt>
|
||||
<dd>{t('pass_answer_fiat')}</dd>
|
||||
<dt id='vpn'>{t('pass_question_vpn')}</dt>
|
||||
<dd>{t('pass_answer_vpn')}</dd>
|
||||
<dt id='bypass'>{t('pass_question_bypass')}</dt>
|
||||
<dd>{t('pass_answer_bypass')}</dd>
|
||||
<dt id='privileges'>{t('pass_question_privileges')}</dt>
|
||||
<dd>{t('pass_answer_privileges')}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user