Files

612 lines
18 KiB
TypeScript

import styled from 'styled-components';
import { type ReactNode } from 'react';
import { getAppDispatch } from '../../../../../state/dispatch';
import { tr } from '../../../../../localization/localeTools';
import { Localizer } from '../../../../basic/Localizer';
import { ModalBasicHeader } from '../../../../SessionWrapperModal';
import { ModalBackButton } from '../../../shared/ModalBackButton';
import { ModalFlexContainer } from '../../../shared/ModalFlexContainer';
import { UserSettingsModalContainer } from '../../components/UserSettingsModalContainer';
import { useUserSettingsBackAction, useUserSettingsCloseAction } from '../userSettingsHooks';
import { ProHeroImage } from './ProSettingsPage';
import { assertUnreachable } from '../../../../../types/sqlSharedTypes';
import { PanelButtonGroup } from '../../../../buttons';
import { StyledContent } from '../../../../buttons/panel/PanelButton';
import { LucideIcon } from '../../../../icon/LucideIcon';
import { LUCIDE_ICONS_UNICODE, WithLucideUnicode } from '../../../../icon/lucide';
import { SessionButton, SessionButtonColor } from '../../../../basic/SessionButton';
import { showLinkVisitWarningDialog } from '../../../OpenUrlModal';
import { proButtonProps } from '../../../SessionCTA';
import { Flex } from '../../../../basic/Flex';
import type { ProNonOriginatingPageVariant } from '../../../../../types/ReduxTypes';
import { useCurrentNeverHadPro } from '../../../../../hooks/useHasPro';
import LIBSESSION_CONSTANTS from '../../../../../session/utils/libsession/libsession_constants';
import { ProPaymentProvider } from '../../../../../session/apis/pro_backend_api/types';
import { useProBackendProDetails } from '../../../../../state/selectors/proBackendData';
type VariantPageProps = {
variant: ProNonOriginatingPageVariant;
};
function ProStatusTextUpdate() {
const { data } = useProBackendProDetails();
return data.autoRenew ? (
<Localizer
token="proAccessActivatedAutoShort"
current_plan_length={data.variantString}
date={data.expiryTimeDateString}
/>
) : (
<Localizer token="proAccessExpireDate" date={data.expiryTimeDateString} />
);
}
function ProPageHero({ variant }: VariantPageProps) {
switch (variant) {
case 'upgrade':
return <ProHeroImage heroText={tr('proUpgradeAccess')} />;
case 'update':
return <ProHeroImage heroText={<ProStatusTextUpdate />} />;
case 'renew':
return <ProHeroImage heroText={tr('proAccessRenewStart')} />;
case 'cancel':
return <ProHeroImage heroText={tr('proCancelSorry')} noColors={true} />;
case 'refund':
return <ProHeroImage heroText={tr('proRefundDescription')} noColors={true} />;
default:
return assertUnreachable(variant, `Unknown pro non originating page variant: ${variant}`);
}
}
const ProInfoBlockTitle = styled.div`
font-size: var(--font-size-xl);
line-height: var(--font-size-xl);
font-weight: 700;
`;
const ProInfoBlockDescription = styled.div`
font-size: var(--font-size-md);
line-height: var(--font-size-md);
`;
const ProInfoBlockSectionSubtitle = styled.div`
font-size: var(--font-size-md);
line-height: var(--font-size-md);
color: var(--text-secondary-color);
`;
function ProInfoBlockItem({
textElement,
iconElement,
}: {
iconElement: ReactNode;
textElement: ReactNode;
}) {
return (
<PanelButtonGroup
containerStyle={{
padding: 'var(--margins-md)',
background: 'var(--background-tertiary-color)',
}}
>
<StyledContent style={{ gap: 'var(--margins-md)', alignItems: 'flex-start' }}>
{iconElement}
{textElement}
</StyledContent>
</PanelButtonGroup>
);
}
const StyledBlockItemIcon = styled.div`
display: flex;
justify-content: center;
flex-shrink: 0;
width: 38px;
height: 38px;
padding: 0;
border-radius: var(--margins-xs);
color: var(--primary-color);
`;
function ProInfoBlockIconElement({ unicode }: WithLucideUnicode) {
return (
<Flex
$container={true}
$alignItems={'center'}
$justifyContent={'center'}
$flexGap="var(--margins-sm)"
>
<StyledBlockItemIcon
style={{ background: 'color-mix(in srgb, var(--primary-color) 10%, transparent)' }}
>
<LucideIcon unicode={unicode} iconSize={'large'} />
</StyledBlockItemIcon>
</Flex>
);
}
const ProInfoBlockText = styled.div`
display: flex;
flex-direction: column;
align-items: start;
text-align: start;
gap: var(--margins-xs);
`;
function ProInfoBlockDevice({ textElement }: { textElement: ReactNode }) {
const { data } = useProBackendProDetails();
return (
<ProInfoBlockItem
iconElement={<ProInfoBlockIconElement unicode={LUCIDE_ICONS_UNICODE.SMARTPHONE} />}
textElement={
<ProInfoBlockText>
<strong>{tr('onDevice', { device_type: data.providerConstants.device })}</strong>
{textElement}
</ProInfoBlockText>
}
/>
);
}
function ProInfoBlockDeviceLinked() {
const { data } = useProBackendProDetails();
const hasNeverHadPro = useCurrentNeverHadPro();
return (
<ProInfoBlockItem
iconElement={<ProInfoBlockIconElement unicode={LUCIDE_ICONS_UNICODE.LINK} />}
textElement={
<ProInfoBlockText>
<strong>{tr('onLinkedDevice')}</strong>
<Localizer
token={hasNeverHadPro ? 'proUpgradeDesktopLinked' : 'proRenewDesktopLinked'}
platform_store={data.providerConstants.store}
platform_store_other={data.providerConstants.store_other}
/>
</ProInfoBlockText>
}
/>
);
}
function ProInfoBlockWebsite({ textElement }: { textElement: ReactNode }) {
const { data } = useProBackendProDetails();
return (
<ProInfoBlockItem
iconElement={<ProInfoBlockIconElement unicode={LUCIDE_ICONS_UNICODE.GLOBE} />}
textElement={
<ProInfoBlockText>
<strong>{tr('viaStoreWebsite', { platform: data.providerConstants.platform })}</strong>
{textElement}
</ProInfoBlockText>
}
/>
);
}
function ProInfoBlockLayout({
titleElement,
descriptionElement,
descriptionOnClick,
subtitleElement,
blockItems,
}: {
titleElement: ReactNode;
descriptionElement: ReactNode;
descriptionOnClick?: () => void;
subtitleElement: ReactNode;
blockItems: ReactNode;
}) {
return (
<PanelButtonGroup
containerStyle={{
paddingBlock: 'var(--margins-lg)',
paddingInline: 'var(--margins-lg)',
gap: 'var(--margins-sm)',
}}
>
<ProInfoBlockTitle>{titleElement}</ProInfoBlockTitle>
<ProInfoBlockDescription
onClick={descriptionOnClick}
style={{ cursor: descriptionOnClick ? 'pointer' : 'default' }}
>
{descriptionElement}
</ProInfoBlockDescription>
<ProInfoBlockSectionSubtitle>{subtitleElement}</ProInfoBlockSectionSubtitle>
<PanelButtonGroup
containerStyle={{ marginBlock: 'var(--margins-xs)', gap: 'var(--margins-sm)' }}
>
{blockItems}
</PanelButtonGroup>
</PanelButtonGroup>
);
}
function ProInfoBlockUpgrade() {
const dispatch = getAppDispatch();
const { data } = useProBackendProDetails();
return (
<ProInfoBlockLayout
titleElement={tr('proUpgradingTo')}
descriptionElement={
<Localizer
token="proAccessUpgradeDesktop"
platform_store={data.providerConstants.store}
platform_store_other={data.providerConstants.store_other}
icon={LUCIDE_ICONS_UNICODE.EXTERNAL_LINK_ICON}
/>
}
descriptionOnClick={() =>
showLinkVisitWarningDialog(LIBSESSION_CONSTANTS.LIBSESSION_PRO_URLS.roadmap, dispatch)
}
subtitleElement={
<ProInfoBlockSectionSubtitle>{tr('proUpgradeOption')}</ProInfoBlockSectionSubtitle>
}
blockItems={<ProInfoBlockDeviceLinked />}
/>
);
}
function ProInfoBlockUpdate() {
const { data } = useProBackendProDetails();
return (
<ProInfoBlockLayout
titleElement={tr('updateAccess')}
descriptionElement={
<Localizer
token="proAccessSignUp"
platform_account={data.providerConstants.platform_account}
platform_store={data.providerConstants.store}
/>
}
subtitleElement={
<ProInfoBlockSectionSubtitle>{tr('updateAccessTwo')}</ProInfoBlockSectionSubtitle>
}
blockItems={
<>
<ProInfoBlockDevice
textElement={
<Localizer
token="onDeviceDescription"
device_type={data.providerConstants.device}
platform_account={data.providerConstants.platform_account}
/>
}
/>
<ProInfoBlockWebsite
textElement={
<Localizer
token="viaStoreWebsiteDescription"
platform_store={
data.provider === ProPaymentProvider.iOSAppStore
? data.providerConstants.platform // we want `Apple website` for apple
: data.providerConstants.store // but `Google Play Store website` for google...
}
platform_account={data.providerConstants.platform_account}
/>
}
/>
</>
}
/>
);
}
function ProInfoBlockRenew() {
const dispatch = getAppDispatch();
const { data } = useProBackendProDetails();
return (
<ProInfoBlockLayout
titleElement={tr('renewingPro')}
descriptionElement={
<Localizer
token="proAccessRenewDesktop"
platform_store={data.providerConstants.store}
platform_store_other={data.providerConstants.store_other}
icon={LUCIDE_ICONS_UNICODE.EXTERNAL_LINK_ICON}
/>
}
descriptionOnClick={() =>
showLinkVisitWarningDialog('https://getsession.org/pro-roadmap', dispatch)
}
subtitleElement={
<ProInfoBlockSectionSubtitle>
{tr('proOptionsTwoRenewalSubtitle')}
</ProInfoBlockSectionSubtitle>
}
blockItems={
<>
<ProInfoBlockDeviceLinked />
<ProInfoBlockWebsite
textElement={
<Localizer
token="proAccessRenewPlatformStoreWebsite"
platform_store={data.providerConstants.platform}
platform_account={data.providerConstants.platform_account}
/>
}
/>
</>
}
/>
);
}
function ProInfoBlockCancel() {
const { data } = useProBackendProDetails();
return (
<ProInfoBlockLayout
titleElement={tr('proCancellation')}
descriptionElement={
<Localizer
token="proCancellationDescription"
platform_account={data.providerConstants.platform_account}
/>
}
subtitleElement={
<ProInfoBlockSectionSubtitle>{tr('proCancellationOptions')}</ProInfoBlockSectionSubtitle>
}
blockItems={
<>
<ProInfoBlockDevice
textElement={
<Localizer
token="onDeviceCancelDescription"
device_type={data.providerConstants.device}
platform_account={data.providerConstants.platform_account}
/>
}
/>
<ProInfoBlockWebsite
textElement={
<Localizer
token="cancelProPlatformStore"
platform_store={data.providerConstants.platform}
platform_account={data.providerConstants.platform_account}
/>
}
/>
</>
}
/>
);
}
const ProInfoBlockRefundTitle = styled.div`
font-size: var(--font-size-lg);
line-height: var(--font-size-lg);
font-weight: 700;
padding-top: var(--margins-xs);
`;
function ProInfoBlockRefundSessionSupport() {
return (
<PanelButtonGroup
containerStyle={{
paddingBlock: 'var(--margins-md)',
paddingInline: 'var(--margins-lg)',
gap: 'var(--margins-sm)',
}}
>
<ProInfoBlockRefundTitle>
<Localizer token="proRefunding" />
</ProInfoBlockRefundTitle>
<Localizer token="proRefundRequestSessionSupport" />
<ProInfoBlockRefundTitle>
<Localizer token="important" />
</ProInfoBlockRefundTitle>
<Localizer token="proImportantDescription" />
</PanelButtonGroup>
);
}
function ProInfoBlockRefundGooglePlay() {
const { data } = useProBackendProDetails();
return (
<PanelButtonGroup
containerStyle={{
paddingBlock: 'var(--margins-md)',
paddingInline: 'var(--margins-lg)',
gap: 'var(--margins-sm)',
}}
>
<ProInfoBlockRefundTitle>
<Localizer token="proRefunding" />
</ProInfoBlockRefundTitle>
<Localizer token="proRefundRequestStorePolicies" platform={data.providerConstants.platform} />
<ProInfoBlockRefundTitle>
<Localizer token="important" />
</ProInfoBlockRefundTitle>
<Localizer token="proImportantDescription" />
</PanelButtonGroup>
);
}
function ProInfoBlockRefundIOS() {
const { data } = useProBackendProDetails();
return (
<ProInfoBlockLayout
titleElement={tr('proRefunding')}
descriptionElement={
<Localizer
token="proPlanPlatformRefund"
platform_store={data.providerConstants.store}
platform_account={data.providerConstants.platform_account}
/>
}
subtitleElement={
<ProInfoBlockSectionSubtitle>{tr('refundRequestOptions')}</ProInfoBlockSectionSubtitle>
}
blockItems={
<>
<ProInfoBlockDevice
textElement={
<Localizer
token="proRefundAccountDevice"
device_type={data.providerConstants.device}
platform_account={data.providerConstants.platform_account}
/>
}
/>
<ProInfoBlockWebsite
textElement={
<Localizer
token="requestRefundPlatformWebsite"
platform={data.providerConstants.platform}
platform_account={data.providerConstants.platform_account}
/>
}
/>
</>
}
/>
);
}
function ProInfoBlockRefund() {
const { data } = useProBackendProDetails();
if (!data.isPlatformRefundAvailable) {
return <ProInfoBlockRefundSessionSupport />;
}
switch (data.provider) {
case ProPaymentProvider.iOSAppStore:
return <ProInfoBlockRefundIOS />;
case ProPaymentProvider.GooglePlayStore:
return <ProInfoBlockRefundGooglePlay />;
case ProPaymentProvider.Nil:
return <ProInfoBlockRefundSessionSupport />;
default:
return assertUnreachable(data.provider, `Unknown pro payment provider: ${data.provider}`);
}
}
function ProInfoBlock({ variant }: VariantPageProps) {
switch (variant) {
case 'upgrade':
return <ProInfoBlockUpgrade />;
case 'update':
return <ProInfoBlockUpdate />;
case 'cancel':
return <ProInfoBlockCancel />;
case 'refund':
return <ProInfoBlockRefund />;
case 'renew':
return <ProInfoBlockRenew />;
default:
return assertUnreachable(variant, `Unknown pro non originating page variant: ${variant}`);
}
}
function ProPageButtonUpdate() {
const dispatch = getAppDispatch();
const { data } = useProBackendProDetails();
return (
<SessionButton
{...proButtonProps}
buttonColor={SessionButtonColor.Primary}
onClick={() => {
showLinkVisitWarningDialog(data.providerConstants.update_subscription_url, dispatch);
}}
dataTestId="pro-open-platform-website-button"
>
<Localizer token="openPlatformWebsite" platform={data.providerConstants.platform} />
</SessionButton>
);
}
function ProPageButtonCancel() {
const dispatch = getAppDispatch();
const { data } = useProBackendProDetails();
return (
<SessionButton
{...proButtonProps}
buttonColor={SessionButtonColor.Danger}
onClick={() => {
showLinkVisitWarningDialog(data.providerConstants.cancel_subscription_url, dispatch);
}}
dataTestId="pro-open-platform-website-button"
>
<Localizer token="openPlatformWebsite" platform={data.providerConstants.platform} />
</SessionButton>
);
}
function ProPageButtonRefund() {
const dispatch = getAppDispatch();
const { data } = useProBackendProDetails();
return (
<SessionButton
{...proButtonProps}
buttonColor={SessionButtonColor.Danger}
onClick={() => {
showLinkVisitWarningDialog(
data.isPlatformRefundAvailable
? data.providerConstants.refund_platform_url
: data.providerConstants.refund_support_url,
dispatch
);
}}
dataTestId="pro-open-platform-website-button"
>
{data.isPlatformRefundAvailable ? (
<Localizer token="openPlatformWebsite" platform={data.providerConstants.platform} />
) : (
<Localizer token="requestRefund" />
)}
</SessionButton>
);
}
function ProPageButton({ variant }: VariantPageProps) {
switch (variant) {
case 'upgrade':
return null;
case 'update':
case 'renew':
return <ProPageButtonUpdate />;
case 'cancel':
return <ProPageButtonCancel />;
case 'refund':
return <ProPageButtonRefund />;
default:
return assertUnreachable(variant, `Unknown pro non originating page variant: ${variant}`);
}
}
export function ProNonOriginatingPage(modalState: {
userSettingsPage: 'proNonOriginating';
nonOriginatingVariant: ProNonOriginatingPageVariant;
hideBackButton?: boolean;
centerAlign?: boolean;
}) {
const backAction = useUserSettingsBackAction(modalState);
const closeAction = useUserSettingsCloseAction(modalState);
return (
<UserSettingsModalContainer
headerChildren={
<ModalBasicHeader
title={null}
bigHeader={true}
showExitIcon={true}
floatingHeader={true}
extraLeftButton={
backAction && !modalState.hideBackButton ? (
<ModalBackButton onClick={backAction} />
) : undefined
}
/>
}
onClose={closeAction || undefined}
centerAlign={modalState.centerAlign}
>
<ModalFlexContainer>
<ProPageHero variant={modalState.nonOriginatingVariant} />
<ProInfoBlock variant={modalState.nonOriginatingVariant} />
<ProPageButton variant={modalState.nonOriginatingVariant} />
</ModalFlexContainer>
</UserSettingsModalContainer>
);
}