Age Verification: Follow Up (#6096)

This commit is contained in:
Alexander Zinchuk 2025-08-01 12:28:30 +02:00
parent 7f089bb858
commit 7e503ecc32
7 changed files with 44 additions and 19 deletions

View File

@ -2142,9 +2142,9 @@
"TonModalHint" = "You can top-up your TON using Fragment."; "TonModalHint" = "You can top-up your TON using Fragment.";
"TonGiftReceived" = "TON Top-Up"; "TonGiftReceived" = "TON Top-Up";
"MediaSpoilerSensitive" = "18+"; "MediaSpoilerSensitive" = "18+";
"TitleSensitiveModal" = "18+ content"; "TitleSensitiveModal" = "{years}+ content";
"TextSensitiveModal" = "This media may contain sensitive content suitable only for adults. Do you still want to view it?"; "TextSensitiveModal" = "This media may contain sensitive content suitable only for adults. Do you still want to view it?";
"ButtonSensitiveAlways" = "Always show 18+ media"; "ButtonSensitiveAlways" = "Always show {years}+ media";
"ButtonSensitiveView" = "View Anyway"; "ButtonSensitiveView" = "View Anyway";
"TitleAgeVerificationModal" = "Age Verification"; "TitleAgeVerificationModal" = "Age Verification";
"TextAgeVerificationModal_one" = "To access such content, you must confirm that you are at least **{count}** year old as required by UK law."; "TextAgeVerificationModal_one" = "To access such content, you must confirm that you are at least **{count}** year old as required by UK law.";

View File

@ -1,5 +1,7 @@
import type { FC } from '../../lib/teact/teact';
import { memo } from '../../lib/teact/teact'; import { memo } from '../../lib/teact/teact';
import { withGlobal } from '../../global';
import { VERIFY_AGE_MIN_DEFAULT } from '../../config';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
@ -16,18 +18,23 @@ type OwnProps = {
confirmHandler: NoneToVoidFunction; confirmHandler: NoneToVoidFunction;
}; };
const SensitiveContentConfirmModal: FC<OwnProps> = ({ type StateProps = {
verifyAgeMin: number;
};
const SensitiveContentConfirmModal = ({
isOpen, isOpen,
onClose, onClose,
shouldAlwaysShow, shouldAlwaysShow,
onAlwaysShowChanged, onAlwaysShowChanged,
confirmHandler, confirmHandler,
}) => { verifyAgeMin,
}: OwnProps & StateProps) => {
const lang = useLang(); const lang = useLang();
return ( return (
<ConfirmDialog <ConfirmDialog
title={lang('TitleSensitiveModal')} title={lang('TitleSensitiveModal', { years: verifyAgeMin })}
confirmLabel={lang('ButtonSensitiveView')} confirmLabel={lang('ButtonSensitiveView')}
isOpen={isOpen} isOpen={isOpen}
onClose={onClose} onClose={onClose}
@ -36,7 +43,7 @@ const SensitiveContentConfirmModal: FC<OwnProps> = ({
{lang('TextSensitiveModal')} {lang('TextSensitiveModal')}
<Checkbox <Checkbox
className={styles.checkBox} className={styles.checkBox}
label={lang('ButtonSensitiveAlways')} label={lang('ButtonSensitiveAlways', { years: verifyAgeMin })}
checked={shouldAlwaysShow} checked={shouldAlwaysShow}
onCheck={onAlwaysShowChanged} onCheck={onAlwaysShowChanged}
/> />
@ -44,4 +51,11 @@ const SensitiveContentConfirmModal: FC<OwnProps> = ({
); );
}; };
export default memo(SensitiveContentConfirmModal); export default memo(withGlobal<OwnProps>((global): StateProps => {
const appConfig = global.appConfig;
const verifyAgeMin = appConfig?.verifyAgeMin;
return {
verifyAgeMin: verifyAgeMin || VERIFY_AGE_MIN_DEFAULT,
};
})(SensitiveContentConfirmModal));

View File

@ -4,6 +4,8 @@ import { getActions, withGlobal } from '../../../global';
import type { TabState } from '../../../global/types'; import type { TabState } from '../../../global/types';
import { VERIFY_AGE_MIN_DEFAULT } from '../../../config';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
@ -19,18 +21,17 @@ export type OwnProps = {
type StateProps = { type StateProps = {
verifyAgeBotUsername?: string; verifyAgeBotUsername?: string;
verifyAgeMin: number;
}; };
const AGE_REQUIRED = 18;
const AgeVerificationModal: FC<OwnProps & StateProps> = ({ const AgeVerificationModal: FC<OwnProps & StateProps> = ({
modal, modal,
verifyAgeBotUsername, verifyAgeBotUsername,
verifyAgeMin,
}) => { }) => {
const { closeAgeVerificationModal, openChatByUsername } = getActions(); const { closeAgeVerificationModal, openChatByUsername } = getActions();
const lang = useLang(); const lang = useLang();
const isOpen = Boolean(modal); const isOpen = Boolean(modal);
const ageRequired = AGE_REQUIRED;
const handleVerifyAge = useLastCallback(() => { const handleVerifyAge = useLastCallback(() => {
if (verifyAgeBotUsername) { if (verifyAgeBotUsername) {
@ -62,10 +63,10 @@ const AgeVerificationModal: FC<OwnProps & StateProps> = ({
{lang('TitleAgeVerificationModal')} {lang('TitleAgeVerificationModal')}
</h2> </h2>
<p className={styles.mainText}> <p className={styles.mainText}>
{lang('TextAgeVerificationModal', { count: ageRequired }, { {lang('TextAgeVerificationModal', { count: verifyAgeMin }, {
withMarkdown: true, withMarkdown: true,
withNodes: true, withNodes: true,
pluralValue: ageRequired, pluralValue: verifyAgeMin,
})} })}
</p> </p>
<p className={styles.description}> <p className={styles.description}>
@ -86,8 +87,10 @@ const AgeVerificationModal: FC<OwnProps & StateProps> = ({
export default memo(withGlobal((global): StateProps => { export default memo(withGlobal((global): StateProps => {
const appConfig = global.appConfig; const appConfig = global.appConfig;
const verifyAgeBotUsername = appConfig?.verifyAgeBotUsername; const verifyAgeBotUsername = appConfig?.verifyAgeBotUsername;
const verifyAgeMin = appConfig?.verifyAgeMin || VERIFY_AGE_MIN_DEFAULT;
return { return {
verifyAgeBotUsername, verifyAgeBotUsername,
verifyAgeMin,
}; };
})(AgeVerificationModal)); })(AgeVerificationModal));

View File

@ -1,9 +1,10 @@
import type { ElementRef } from '../../../../lib/teact/teact'; import type { ElementRef } from '../../../../lib/teact/teact';
import { useCallback, useEffect, useRef } from '../../../../lib/teact/teact'; import { useCallback, useEffect, useRef } from '../../../../lib/teact/teact';
import { getActions } from '../../../../global'; import { getActions, getGlobal } from '../../../../global';
import type { WebApp, WebAppInboundEvent, WebAppOutboundEvent } from '../../../../types/webapp'; import type { WebApp, WebAppInboundEvent, WebAppOutboundEvent } from '../../../../types/webapp';
import { VERIFY_AGE_MIN_DEFAULT } from '../../../../config';
import { getWebAppKey } from '../../../../global/helpers'; import { getWebAppKey } from '../../../../global/helpers';
import { extractCurrentThemeParams } from '../../../../util/themeStyle'; import { extractCurrentThemeParams } from '../../../../util/themeStyle';
import { REM } from '../../../common/helpers/mediaDimensions'; import { REM } from '../../../common/helpers/mediaDimensions';
@ -385,8 +386,10 @@ const useWebAppFrame = (
if (eventType === 'web_app_verify_age') { if (eventType === 'web_app_verify_age') {
const { passed } = eventData; const { passed } = eventData;
const minAge = getGlobal().appConfig?.verifyAgeMin || VERIFY_AGE_MIN_DEFAULT;
const ageFromParam = eventData.age || 0;
if (passed) { if (passed && ageFromParam >= minAge) {
showNotification({ showNotification({
message: { message: {
key: 'TitleAgeCheckSuccess', key: 'TitleAgeCheckSuccess',

View File

@ -120,6 +120,7 @@ export const STARS_SUGGESTED_POST_FUTURE_MIN = 300; // 5 minutes in seconds
export const TON_CURRENCY_CODE = 'TON'; export const TON_CURRENCY_CODE = 'TON';
export const TON_SUGGESTED_POST_COMMISSION_PERMILLE = 850; export const TON_SUGGESTED_POST_COMMISSION_PERMILLE = 850;
export const TON_USD_RATE_DEFAULT = 3; export const TON_USD_RATE_DEFAULT = 3;
export const VERIFY_AGE_MIN_DEFAULT = 18;
export const TON_TOPUP_URL_DEFAULT = 'https://fragment.com/ads/topup'; export const TON_TOPUP_URL_DEFAULT = 'https://fragment.com/ads/topup';
export const TON_SUGGESTED_POST_AMOUNT_MIN = 10000000; // 0.01 TON in nanos export const TON_SUGGESTED_POST_AMOUNT_MIN = 10000000; // 0.01 TON in nanos
export const TON_SUGGESTED_POST_AMOUNT_MAX = 10000000000000; // 10 000 TON in nanos export const TON_SUGGESTED_POST_AMOUNT_MAX = 10000000000000; // 10 000 TON in nanos

View File

@ -1667,10 +1667,10 @@ addActionHandler('openChatByUsername', async (global, actions, payload): Promise
const chatByUsername = await fetchChatByUsername(global, username); const chatByUsername = await fetchChatByUsername(global, username);
global = getGlobal(); global = getGlobal();
const user = chatByUsername && selectUser(global, chatByUsername.id); const user = chatByUsername && selectUser(global, chatByUsername.id);
if (!chatByUsername || !chat || !user?.hasMainMiniApp) return; if (!chatByUsername || !user?.hasMainMiniApp) return;
actions.requestMainWebView({ actions.requestMainWebView({
botId: chatByUsername.id, botId: chatByUsername.id,
peerId: chat.id, peerId: chat?.id || chatByUsername.id,
theme, theme,
startParam: startApp, startParam: startApp,
mode, mode,

View File

@ -1601,9 +1601,7 @@ export interface LangPair {
'TonModalHint': undefined; 'TonModalHint': undefined;
'TonGiftReceived': undefined; 'TonGiftReceived': undefined;
'MediaSpoilerSensitive': undefined; 'MediaSpoilerSensitive': undefined;
'TitleSensitiveModal': undefined;
'TextSensitiveModal': undefined; 'TextSensitiveModal': undefined;
'ButtonSensitiveAlways': undefined;
'ButtonSensitiveView': undefined; 'ButtonSensitiveView': undefined;
'TitleAgeVerificationModal': undefined; 'TitleAgeVerificationModal': undefined;
'DescriptionAgeVerificationModal': undefined; 'DescriptionAgeVerificationModal': undefined;
@ -2781,6 +2779,12 @@ export interface LangPairWithVariables<V = LangVariable> {
'number': V; 'number': V;
'owner': V; 'owner': V;
}; };
'TitleSensitiveModal': {
'years': V;
};
'ButtonSensitiveAlways': {
'years': V;
};
} }
export interface LangPairPlural { export interface LangPairPlural {