5180 fichiers 38.5 Mo 0 téléchargements 249 vues mis à jour il y a 5 heures ui
nitro-v36-with-renderer-v36 / src / components / quests / RewardTrackView.tsx
RewardTrackView.tsx 45.8 Ko Télécharger le fichier
import {
    AddLinkEventTracker,
    CreateLinkEvent,
    GetSessionDataManager,
    ILinkEventTracker,
    RemoveLinkEventTracker,
    RewardTrackData,
    RewardTrackPrizeData,
    RewardTrackTaskData,
    SecurityLevel
} from '@octane/renderer';
import { CSSProperties, FC, useCallback, useEffect, useMemo, useState } from 'react';
import { FaCog } from 'react-icons/fa';
import {
    buildRewardTrackLayout,
    buildRewardTrackPages,
    countRewardTrackUnclaimed,
    filterRewardTrackTasks,
    GetConfigurationValue,
    getPremiumBoostPercent,
    getRewardTrackMainBarFillWidth,
    getRewardTrackMainBarRatio,
    getRewardTrackPrizeState,
    getRewardTrackPrizeTooltip,
    getRewardTrackSlotLeft,
    getRewardTrackTaskHintLink,
    getRewardTrackTaskText,
    getRewardTrackText,
    localizeWithFallback,
    NotificationAlertType,
    REWARD_TRACK_BAR_WIDTH,
    RewardTrackTaskFilter,
    resolveRewardTrackTheme,
    rewardTrackColorToCss
} from '../../api';
import availableIcon from '../../assets/images/reward-track/available-icon.png';
import checkmarkIcon from '../../assets/images/reward-track/checkmark.png';
import frankAndPiccoloImage from '../../assets/images/reward-track/frank-and-piccolo.png';
import frankTipsImage from '../../assets/images/reward-track/frank-tips.png';
import freeTrackIcon from '../../assets/images/reward-track/free-track.png';
import lockedRewardIcon from '../../assets/images/reward-track/locked-reward.png';
import notAvailableIcon from '../../assets/images/reward-track/not-available-icon.png';
import pageNextIcon from '../../assets/images/reward-track/page-next.png';
import pagePreviousIcon from '../../assets/images/reward-track/page-previous.png';
import pointLargeIcon from '../../assets/images/reward-track/point-large.png';
import pointSmallIcon from '../../assets/images/reward-track/point-small.png';
import premiumTrackIcon from '../../assets/images/reward-track/premium-track.png';
import prizesBackgroundImage from '../../assets/images/reward-track/prizes-background.png';
import prizesStarsImage from '../../assets/images/reward-track/prizes-background-stars.png';
import rewardGiftIcon from '../../assets/images/reward-track/reward-gift.png';
import rewardGiftPremiumIcon from '../../assets/images/reward-track/reward-gift-premium.png';
import taskListIcon from '../../assets/images/reward-track/task-list.png';
import { GetRewardTrackTaskIconUrl } from '../../assets/images/reward-track/tasks';
import { Button, DraggableWindowPosition, LayoutAvatarImageView, LayoutBadgeImageView, LayoutCurrencyIcon, LayoutFurniIconImageView, Text } from '../../common';
import { BaseButton } from '../../common/BaseButton';
import { NativeTextTree } from '../../common/native-text/NativeTextTree';
import { Hint } from '../../common/Tooltip';
import { useHasPermission, useNotification, useRewardTrackProgressBar, useRewardTracks, useUserDataSnapshot } from '../../hooks';
import { OctaneCard } from '../../layout';
import { RewardTrackAdminView } from './RewardTrackAdminView';

const CURRENCY_TYPES: Record<string, number> = { credits: -1, duckets: 0, diamonds: 5 };
const FILTERS: RewardTrackTaskFilter[] = ['all', 'in_progress', 'completed'];
const FILTER_KEYS: Record<RewardTrackTaskFilter, [string, string]> = {
    all: ['reward_track.tasks.tab.all_tasks', 'All Tasks'],
    in_progress: ['reward_track.tasks.tab.in_progress', 'In Progress'],
    completed: ['reward_track.tasks.tab.completed', 'Completed']
};
/** loading_bar widths of task_template (200) and level_template (260). */
const TASK_BAR_WIDTH = 200;
const LEVEL_BAR_WIDTH = 260;

/** The product_icon widget (40x40) of a prize. */
const RewardIcon: FC<{ rewardTypeId: string; extraParams: string; productItemTypeId: number }> = ({ rewardTypeId, extraParams, productItemTypeId }) => {
    const type = (rewardTypeId || '').toLowerCase();

    if (type in CURRENCY_TYPES) return <LayoutCurrencyIcon type={CURRENCY_TYPES[type]} className="octane-reward-track-prize-currency" />;

    if (type === 'badge') return <LayoutBadgeImageView badgeCode={extraParams} />;

    // A furni prize: the server sends its sprite id and "<s|i>:<name>" (floor or wall, then the items_base name).
    if (type === 'furni') return <LayoutFurniIconImageView productType={extraParams.startsWith('i:') ? 'i' : 's'} productClassId={productItemTypeId} className="octane-reward-track-prize-furni" />;

    return <div className="octane-reward-track-prize-generic">{rewardTypeId}</div>;
};

/** The task picture (reward_track_tasks_<action>) centred in its slot, as static_bitmap pivot_point="center". */
const RewardTrackTaskGlyph: FC<{ actionType: string; large?: boolean }> = ({ actionType, large = false }) => {
    const url = GetRewardTrackTaskIconUrl(actionType);

    return (
        <NativeTextTree>
            <div className={large ? 'octane-reward-track-detail-glyph' : 'octane-reward-track-task-glyph'}>
                {url && <img src={url} alt="" draggable={false} />}
            </div>
        </NativeTextTree>
    );
};

/**
 * A loading_bar of the task rows and levels (RewardTrackTaskProgressBarView): grey track, orange fill that
 * turns green over 300 ms once full, with its gloss. The fill moves with AIR's accelerated mover.
 */
const RewardTrackMiniBar: FC<{ ratio: number; width: number; snapKey: string; levelIndex?: number }> = ({ ratio, width, snapKey, levelIndex = -1 }) => {
    const bar = useRewardTrackProgressBar({ ratio, maxWidth: width, animate: true, snapKey, task: true, levelIndex });

    return (
        <NativeTextTree>
            <div className="octane-reward-track-mini-bar" style={{ width }} data-complete={ratio >= 1}>
                <div className="octane-reward-track-mini-bar-progress" style={{ width: bar.width }}>
                    <div className="octane-reward-track-mini-bar-fill" style={{ backgroundColor: rewardTrackColorToCss(bar.color) }} />
                    <div className="octane-reward-track-mini-bar-gloss" />
                </div>
            </div>
        </NativeTextTree>
    );
};

/**
 * The track's main loading_bar (RewardTrackMainProgressBarView): the `progress` container moves to the x of
 * the points with AIR's accelerated mover; the green shape is 4px longer (the whole bar near the end).
 */
const RewardTrackMainBar: FC<{ x: number; snapKey: string }> = ({ x, snapKey }) => {
    const bar = useRewardTrackProgressBar({
        ratio: getRewardTrackMainBarRatio(x, REWARD_TRACK_BAR_WIDTH),
        maxWidth: REWARD_TRACK_BAR_WIDTH,
        animate: true,
        snapKey
    });

    return (
        <NativeTextTree>
            <div className="octane-reward-track-bar" data-testid="reward-track-bar">
                <div className="octane-reward-track-bar-progress" style={{ width: bar.width }}>
                    <div className="octane-reward-track-bar-fill" style={{ width: getRewardTrackMainBarFillWidth(bar.width, REWARD_TRACK_BAR_WIDTH) }} />
                    <div className="octane-reward-track-bar-gloss" />
                </div>
            </div>
        </NativeTextTree>
    );
};

/** The points value with the small points icon (track_reward_txt / level_reward_txt). */
const RewardTrackPointsReward: FC<{ value: number }> = ({ value }) => (
    <NativeTextTree>
        <span className="octane-reward-track-points-reward">
            <span>{value}</span>
            <img src={pointSmallIcon} alt="" draggable={false} />
        </span>
    </NativeTextTree>
);

/**
 * One prize (prize_template 80x105 / prize_template_premium 80x104): the click region with its shadow and
 * plate, the product icon (3px higher when the quantity chip shows), the stem towards the bar, the lock and
 * the claimed check. A prize without enough points is dimmed to 0.75 and loses its shadow.
 */
const RewardTrackPrizeView: FC<{
    left: number;
    track: RewardTrackData;
    prize: RewardTrackPrizeData;
    onClaim: (prize: RewardTrackPrizeData) => void;
    onPremium: () => void;
}> = ({ left, track, prize, onClaim, onPremium }) => {
    const state = getRewardTrackPrizeState(prize, track);
    const enoughPoints = prize.hasEnoughPoints(track);

    const onClick = () => {
        if (state === 'claimed') return;

        if (state === 'premium_locked') {
            onPremium();

            return;
        }

        if (state === 'claimable') onClaim(prize);
    };

    return (
        <NativeTextTree>
            <div
                className="octane-reward-track-prize"
                data-state={state}
                data-premium={prize.premium}
                data-disabled={!enoughPoints}
                style={{ left }}
            >
                <Hint content={getRewardTrackPrizeTooltip(state)} delay={0}>
                    <BaseButton className="octane-reward-track-prize-hit" onClick={onClick} aria-label={getRewardTrackPrizeTooltip(state)}>
                        <span className="octane-reward-track-prize-shadow" />
                        <span className="octane-reward-track-prize-plate">
                            <span className="octane-reward-track-prize-product" data-shifted={prize.rewardAmount > 1}>
                                <RewardIcon rewardTypeId={prize.rewardTypeId} extraParams={prize.extraParams} productItemTypeId={prize.productItemTypeId} />
                            </span>
                            {prize.rewardAmount > 1 && <span className="octane-reward-track-prize-quantity">{prize.rewardAmount}</span>}
                        </span>
                    </BaseButton>
                </Hint>
                <span className="octane-reward-track-prize-stem" />
                {state === 'premium_locked' && <img src={lockedRewardIcon} alt="" draggable={false} className="octane-reward-track-prize-lock" />}
                {state === 'claimed' && <img src={checkmarkIcon} alt="" draggable={false} className="octane-reward-track-prize-check" />}
            </div>
        </NativeTextTree>
    );
};

/** One point_indicator_template (80x157): reached icon on the bar, connector, dot, stem and the points label. */
const RewardTrackPointIndicatorView: FC<{ left: number; points: number; reached: boolean }> = ({ left, points, reached }) => (
    <NativeTextTree>
        <div className="octane-reward-track-indicator" data-reached={reached} style={{ left }}>
            <img src={reached ? availableIcon : notAvailableIcon} alt="" draggable={false} className="octane-reward-track-indicator-icon" />
            <span className="octane-reward-track-indicator-connector" />
            <span className="octane-reward-track-indicator-dot" />
            <span className="octane-reward-track-indicator-stem" />
            <span className="octane-reward-track-indicator-points">{points}</span>
        </div>
    </NativeTextTree>
);

/** A free / premium band (free_tier_cont / premium_tier_cont 700x80) with its information cut-out. */
const RewardTrackBandView: FC<{ premium: boolean }> = ({ premium }) => (
    <NativeTextTree>
        <div className={`octane-reward-track-band octane-reward-track-band-${premium ? 'premium' : 'free'}`}>
            <div className="octane-reward-track-band-info">
                <span className="octane-reward-track-band-cut" />
                <span className="octane-reward-track-band-split" />
                <img
                    src={premium ? premiumTrackIcon : freeTrackIcon}
                    alt=""
                    draggable={false}
                    className="octane-reward-track-band-icon"
                />
                <div className="octane-reward-track-band-texts">
                    <span className="octane-reward-track-band-title">
                        {premium ? localizeWithFallback('reward_track.rewards.premium', 'Premium') : localizeWithFallback('reward_track.rewards.free', 'Free Track')}
                    </span>
                    {premium && <span className="octane-reward-track-band-desc">{localizeWithFallback('reward_track.rewards.premium.info', 'Extra rewards')}</span>}
                </div>
            </div>
        </div>
    </NativeTextTree>
);

/** The premium purchase confirmation (390x352): benefits, cost, confirm / cancel. */
const RewardTrackPremiumConfirmView: FC<{ track: RewardTrackData; pending: boolean; onConfirm: () => void; onCancel: () => void }> = ({
    track,
    pending,
    onConfirm,
    onCancel
}) => (
    <OctaneCard className="octane-reward-track-premium" uniqueKey="reward-track-premium" windowPosition={DraggableWindowPosition.CENTER}>
        <OctaneCard.Header
            headerText={localizeWithFallback('reward_track.premium.confirm.title', 'Get the premium pass')}
            onCloseClick={() => !pending && onCancel()}
        />
        <OctaneCard.Content className="octane-reward-track-premium-content">
            <div className="octane-reward-track-premium-panel">
                <div className="octane-reward-track-premium-icon" />
                <Text bold className="octane-reward-track-premium-title">
                    {localizeWithFallback('reward_track.rewards.premium', 'Premium')}
                </Text>
                <Text className="octane-reward-track-premium-info">
                    {localizeWithFallback('reward_track.rewards.premium.info', 'Extra rewards for pass holders')}
                </Text>
            </div>
            <div className="octane-reward-track-premium-benefits">
                <Text className="octane-reward-track-premium-desc">{localizeWithFallback('reward_track.premium.confirm.desc', 'The premium pass unlocks the premium row of rewards for this track.')}</Text>
                {track.taskPointsBoost > 1 && (
                    <div className="octane-reward-track-premium-benefit">
                        {localizeWithFallback(
                            'reward_track.premium.confirm.benefit.boost',
                            '%percent%% more points from every task',
                            ['percent'],
                            [String(getPremiumBoostPercent(track.taskPointsBoost))]
                        )}
                    </div>
                )}
                {track.hasPremiumPrizes && (
                    <div className="octane-reward-track-premium-benefit">
                        {localizeWithFallback('reward_track.premium.confirm.benefit.rewards', 'Exclusive premium rewards')}
                    </div>
                )}
                {track.instantPoints > 0 && (
                    <div className="octane-reward-track-premium-benefit">
                        {localizeWithFallback(
                            'reward_track.premium.confirm.benefit.instant_points',
                            '%points% points right away',
                            ['points'],
                            [String(track.instantPoints)]
                        )}
                    </div>
                )}
                {track.hasPremiumTasks && (
                    <div className="octane-reward-track-premium-benefit">
                        {localizeWithFallback('reward_track.premium.confirm.benefit.tasks', 'Extra premium tasks')}
                    </div>
                )}
                {track.hasPremiumLevels && (
                    <div className="octane-reward-track-premium-benefit">
                        {localizeWithFallback('reward_track.premium.confirm.benefit.levels', 'Extra premium task levels')}
                    </div>
                )}
            </div>
            <div className="octane-reward-track-premium-cost">
                <span className="octane-reward-track-premium-cost-label">{localizeWithFallback('catalog.purchase.confirmation.dialog.cost', 'Cost')}</span>
                {track.costCredits > 0 && (
                    <span className="octane-reward-track-premium-price">
                        {track.costCredits} <LayoutCurrencyIcon type={-1} />
                    </span>
                )}
                {track.costCredits > 0 && track.costDiamonds > 0 && <span>+</span>}
                {track.costDiamonds > 0 && (
                    <span className="octane-reward-track-premium-price">
                        {track.costDiamonds} <LayoutCurrencyIcon type={5} />
                    </span>
                )}
            </div>
            <div className="octane-reward-track-premium-buttons">
                <Button variant="secondary" className="octane-reward-track-premium-cancel" disabled={pending} onClick={onCancel}>
                    {localizeWithFallback('reward_track.premium.confirm.cancel', 'Cancel')}
                </Button>
                <Button variant="success" className="octane-reward-track-premium-buy" disabled={pending} onClick={onConfirm}>
                    {localizeWithFallback('reward_track.premium.confirm.buy', 'Buy')}
                </Button>
            </div>
        </OctaneCard.Content>
    </OctaneCard>
);

/**
 * The official reward track window (reward_track_main_xml 1103x722, `reward_track/open/<id>`), laid out
 * at the layout's pixel positions: the header stage (the rewards panel with its background, the main bar,
 * the free and premium bands, the point indicators and the prizes placed along the track by their points,
 * the page buttons, and the profile cut-out over its left edge) and the body (the task list with its
 * filters and tip, the task details with the levels and the hint).
 */
export const RewardTrackView: FC<{}> = () => {
    const [trackId, setTrackId] = useState<string>(null);
    const [filter, setFilter] = useState<RewardTrackTaskFilter>('all');
    const [selectedTaskId, setSelectedTaskId] = useState<string>(null);
    // null: the page of the track's points (RewardTrackPrizeTrackView opens on it); a number once paged.
    const [page, setPage] = useState<number>(null);
    const [premiumConfirm, setPremiumConfirm] = useState(false);
    // The gear opens the staff editor inside the same window (acc_rewardtrack).
    const isEditor = useHasPermission('acc_rewardtrack');
    const [editMode, setEditMode] = useState(false);
    const {
        tracks = [],
        reloadCount = 0,
        tracksVersion = 0,
        pendingPurchase = null,
        requestTracks = null,
        claimPrize = null,
        purchasePremium = null,
        copyTrackId = null,
        copyTaskId = null
    } = useRewardTracks();
    // RewardTrackController._ra5fbf8bddad7bd: EMPLOYEE or MODERATOR security (level 4+) may copy the ids.
    const canCopyIds = useUserDataSnapshot().securityLevel >= SecurityLevel.EMPLOYEE;
    const { simpleAlert = null } = useNotification();

    const track = useMemo(() => tracks.find((existing) => existing.id === trackId) ?? null, [tracks, trackId]);

    const open = useCallback(
        (id: string) => {
            setTrackId(id);
            setEditMode(false);
            setFilter('all');
            setSelectedTaskId(null);
            setPage(null);
            requestTracks && requestTracks();
        },
        [requestTracks]
    );

    useEffect(() => {
        const linkTracker: ILinkEventTracker = {
            linkReceived: (url: string) => {
                const parts = url.split('/');

                if (parts.length < 2) return;

                switch (parts[1]) {
                    case 'open':
                        open(parts.length >= 3 ? parts[2] : (tracks[0]?.id ?? 'introduction'));
                        return;
                    case 'hide':
                        setTrackId(null);
                        return;
                    case 'toggle':
                        if (trackId) setTrackId(null);
                        else open(tracks[0]?.id ?? 'introduction');
                        return;
                }
            },
            eventUrlPrefix: 'reward_track/'
        };

        AddLinkEventTracker(linkTracker);

        return () => RemoveLinkEventTracker(linkTracker);
    }, [open, tracks, trackId]);

    useEffect(() => {
        if (trackId && !track && tracks.length && !tracks.some((existing) => existing.id === trackId)) setTrackId(tracks[0].id);
    }, [trackId, track, tracks]);

    useEffect(() => {
        if (reloadCount > 0 && trackId && simpleAlert) {
            simpleAlert(
                localizeWithFallback('reward_track.reload.desc', 'The reward track was updated and has been reloaded.'),
                NotificationAlertType.DEFAULT,
                null,
                null,
                localizeWithFallback('reward_track.reload.title', 'Reward track updated')
            );
        }
        // the alert belongs to the reload, not to the open track
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [reloadCount]);

    useEffect(() => {
        if (pendingPurchase === null) setPremiumConfirm(false);
    }, [pendingPurchase]);

    const theme = resolveRewardTrackTheme(track?.theme ?? 'blue');
    const prizes = track?.prizes ?? [];
    const layout = useMemo(() => buildRewardTrackLayout(prizes), [prizes]);
    const pages = useMemo(() => buildRewardTrackPages(prizes, layout), [prizes, layout]);
    const points = track?.points ?? 0;
    const currentPage = Math.max(0, Math.min(layout.pageCount - 1, page ?? layout.pageOf(points)));
    // RewardTrackPrizeTrackView picks the page of the points once, when the view is built; progress
    // afterwards moves the bar on that page instead of turning the page.
    const pinnedPage = track ? currentPage : null;

    useEffect(() => {
        if (page === null && pinnedPage !== null) setPage(pinnedPage);
    }, [page, pinnedPage]);
    const pageData = pages[currentPage] ?? { free: [], premium: [], points: [] };
    const unclaimed = track ? countRewardTrackUnclaimed(prizes, layout, currentPage, (prize) => prize.isClaimable(track)) : { next: 0, previous: 0 };

    const themeStyle = {
        '--rt-dark': theme.dark,
        '--rt-medium': theme.medium,
        '--rt-light': theme.light,
        '--rt-active': theme.active,
        '--rt-prizes-background': `url("${prizesBackgroundImage}")`
    } as CSSProperties;

    const filteredTasks = useMemo(() => filterRewardTrackTasks(track?.tasks ?? [], filter), [track, filter]);
    // RewardTrackTaskListView.applyFilter(): the selection falls back to the first task the filter keeps.
    const selectedTask: RewardTrackTaskData = useMemo(
        () => filteredTasks.find((task) => task.id === selectedTaskId) ?? filteredTasks[0] ?? null,
        [selectedTaskId, filteredTasks]
    );

    if (!trackId) return null;

    const onClaim = (prize: RewardTrackPrizeData) => track && claimPrize && claimPrize(track.id, prize.id);
    const onPremium = () => track && track.hasPremiumConfig && !track.premium && setPremiumConfirm(true);

    const hintLink = track && selectedTask
        ? getRewardTrackTaskHintLink(
              selectedTask.actionType,
              GetConfigurationValue<string>(`reward_track.${track.id}.task.${selectedTask.id}.hint.internal_link`, '')
          )
        : null;

    return (
        <NativeTextTree>
            <>
                <OctaneCard className="octane-reward-track" uniqueKey="reward-track" windowPosition={DraggableWindowPosition.TOP_CENTER} style={themeStyle}>
                    <OctaneCard.Header headerText={localizeWithFallback('reward_track.title', 'Reward Track')} onCloseClick={() => setTrackId(null)} />
                    <OctaneCard.Content className="octane-reward-track-content">
                        {isEditor && !editMode && track && (
                            <Hint content={localizeWithFallback('reward_track.admin.open', 'Edit the reward tracks')}>
                                <BaseButton
                                    aria-label={localizeWithFallback('reward_track.admin.open', 'Edit the reward tracks')}
                                    type="button"
                                    className="octane-reward-track-admin-toggle"
                                    data-testid="reward-track-admin-toggle"
                                    onClick={() => setEditMode(true)}
                                >
                                    <FaCog />
                                </BaseButton>
                            </Hint>
                        )}
                        {editMode && isEditor && (
                            <RewardTrackAdminView
                                onClose={() => setEditMode(false)}
                                onPreview={(id) => {
                                    setEditMode(false);
                                    open(id);
                                }}
                            />
                        )}
                        {!editMode && !track && <Text center>{localizeWithFallback('reward_track.loading', 'Loading the reward track...')}</Text>}
                        {!editMode && track && (
                            <div className="octane-reward-track-stage">
                                <div className="octane-reward-track-header">
                                    <div className="octane-reward-track-rewards">
                                        <span className="octane-reward-track-rewards-background" />
                                        <span className="octane-reward-track-rewards-sky" />
                                        <img src={prizesStarsImage} alt="" draggable={false} className="octane-reward-track-rewards-stars" />
                                        <RewardTrackBandView premium={false} />
                                        <RewardTrackBandView premium />
                                        <RewardTrackMainBar x={layout.xFor(points, currentPage)} snapKey={`${tracksVersion}:${track.id}:${currentPage}`} />
                                        <div className="octane-reward-track-points-band" />
                                        <div className="octane-reward-track-indicators">
                                            {pageData.points.map((value) => (
                                                <RewardTrackPointIndicatorView
                                                    key={value}
                                                    left={getRewardTrackSlotLeft(layout.xFor(value, currentPage))}
                                                    points={value}
                                                    reached={points >= value}
                                                />
                                            ))}
                                        </div>
                                        <div className="octane-reward-track-prizes">
                                            {[...pageData.free, ...pageData.premium].map((prize) => (
                                                <RewardTrackPrizeView
                                                    key={prize.id}
                                                    left={getRewardTrackSlotLeft(layout.xFor(prize.requiredPoints, currentPage))}
                                                    track={track}
                                                    prize={prize}
                                                    onClaim={onClaim}
                                                    onPremium={onPremium}
                                                />
                                            ))}
                                        </div>
                                        <BaseButton
                                            type="button"
                                            className="octane-reward-track-page"
                                            data-side="previous"
                                            disabled={currentPage <= 0}
                                            onClick={() => setPage(Math.max(0, currentPage - 1))}
                                            aria-label={localizeWithFallback('reward_track.rewards.previous', 'Previous rewards')}
                                        >
                                            <img src={pagePreviousIcon} alt="" draggable={false} />
                                        </BaseButton>
                                        <BaseButton
                                            type="button"
                                            className="octane-reward-track-page"
                                            data-side="next"
                                            disabled={currentPage >= layout.pageCount - 1}
                                            onClick={() => setPage(Math.min(layout.pageCount - 1, currentPage + 1))}
                                            aria-label={localizeWithFallback('reward_track.rewards.next', 'Next rewards')}
                                        >
                                            <img src={pageNextIcon} alt="" draggable={false} />
                                        </BaseButton>
                                        {unclaimed.previous > 0 && (
                                            <span className="octane-reward-track-unclaimed" data-side="previous">
                                                {unclaimed.previous}
                                            </span>
                                        )}
                                        {unclaimed.next > 0 && (
                                            <span className="octane-reward-track-unclaimed" data-side="next">
                                                {unclaimed.next}
                                            </span>
                                        )}
                                    </div>
                                    <div className="octane-reward-track-cutout">
                                        <span className="octane-reward-track-bulge" />
                                        <div className="octane-reward-track-profile">
                                            <LayoutAvatarImageView figure={GetSessionDataManager().figure} direction={2} className="octane-reward-track-avatar" />
                                            <div className="octane-reward-track-info">
                                                {canCopyIds ? (
                                                    <BaseButton
                                                        type="button"
                                                        className="octane-reward-track-title octane-reward-track-copy-region"
                                                        onClick={() => copyTrackId && copyTrackId(track.id)}
                                                    >
                                                        {getRewardTrackText(track.id, 'name', track.id)}
                                                    </BaseButton>
                                                ) : (
                                                    <div className="octane-reward-track-title">{getRewardTrackText(track.id, 'name', track.id)}</div>
                                                )}
                                                <div className="octane-reward-track-desc">{getRewardTrackText(track.id, 'desc', '')}</div>
                                                <div className="octane-reward-track-instructions">{getRewardTrackText(track.id, 'info', '')}</div>
                                            </div>
                                            <div className="octane-reward-track-points">
                                                <span className="octane-reward-track-points-value">
                                                    <img src={pointLargeIcon} alt="" draggable={false} />
                                                    <span>{track.points}</span>
                                                </span>
                                                <span className="octane-reward-track-points-label">
                                                    {localizeWithFallback('reward_track.profile.points_collected', 'Points collected')}
                                                </span>
                                            </div>
                                            <span className="octane-reward-track-splitter" />
                                            <div className="octane-reward-track-collected">
                                                <img src={checkmarkIcon} alt="" draggable={false} />
                                                <span>
                                                    {localizeWithFallback(
                                                        'reward_track.profile.rewards_collected',
                                                        '%progress% / %total% rewards collected',
                                                        ['progress', 'total'],
                                                        [String(track.claimedPrizeCount), String(track.totalPrizeCount)]
                                                    )}
                                                </span>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                                <div className="octane-reward-track-body">
                                    <div className="octane-reward-track-tasks">
                                        <img src={taskListIcon} alt="" draggable={false} className="octane-reward-track-tasks-icon" />
                                        <span className="octane-reward-track-tasks-title">{localizeWithFallback('reward_track.tasks', 'Track Tasks')}</span>
                                        <span className="octane-reward-track-tasks-progress">
                                            {localizeWithFallback(
                                                'reward_track.tasks.progress',
                                                '%progress% / %total% completed',
                                                ['progress', 'total'],
                                                [String(track.completedTaskCount), String(track.totalTaskCount)]
                                            )}
                                        </span>
                                        <div className="octane-reward-track-filters">
                                            {FILTERS.map((value) => (
                                                <BaseButton
                                                    key={value}
                                                    type="button"
                                                    className="octane-reward-track-filter"
                                                    data-active={filter === value}
                                                    onClick={() => setFilter(value)}
                                                >
                                                    {localizeWithFallback(FILTER_KEYS[value][0], FILTER_KEYS[value][1])}
                                                </BaseButton>
                                            ))}
                                        </div>
                                        <div className="octane-reward-track-task-list">
                                            {filteredTasks.map((task) => {
                                                const level = task.activeLevel;

                                                return (
                                                    <BaseButton
                                                        key={task.id}
                                                        className="octane-reward-track-task"
                                                        data-selected={selectedTask?.id === task.id}
                                                        data-complete={task.isComplete}
                                                        onClick={() => setSelectedTaskId(task.id)}
                                                    >
                                                        <RewardTrackTaskGlyph actionType={task.actionType} />
                                                        <span className="octane-reward-track-task-name">{getRewardTrackTaskText(track.id, task.id, 'name', task.id)}</span>
                                                        <span className="octane-reward-track-task-desc">{getRewardTrackTaskText(track.id, task.id, 'desc', '')}</span>
                                                        <span className="octane-reward-track-task-bar">
                                                            <RewardTrackMiniBar
                                                                ratio={task.progressRatioFor(level)}
                                                                width={TASK_BAR_WIDTH}
                                                                snapKey={`${tracksVersion}:${track.id}`}
                                                                levelIndex={task.activeLevelIndex}
                                                            />
                                                        </span>
                                                        <span className="octane-reward-track-task-count">
                                                            {task.progressCount} / {level ? level.requiredCount : 0}
                                                        </span>
                                                        <span className="octane-reward-track-task-reward">
                                                            <RewardTrackPointsReward value={level ? level.pointsReward : 0} />
                                                        </span>
                                                    </BaseButton>
                                                );
                                            })}
                                        </div>
                                        {(!track.hasPremiumConfig || track.premium) && (
                                            <div className="octane-reward-track-tip">
                                                <img src={rewardGiftIcon} alt="" draggable={false} className="octane-reward-track-tip-gift" />
                                                <span className="octane-reward-track-tip-text">
                                                    {localizeWithFallback('reward_track.tasks.tip', 'Complete tasks to earn points and unlock rewards')}
                                                </span>
                                                <img src={frankAndPiccoloImage} alt="" draggable={false} className="octane-reward-track-tip-frank" />
                                            </div>
                                        )}
                                        {track.hasPremiumConfig && !track.premium && (
                                            <div className="octane-reward-track-tip octane-reward-track-tip-premium">
                                                <img src={rewardGiftPremiumIcon} alt="" draggable={false} className="octane-reward-track-tip-gift" />
                                                <span className="octane-reward-track-tip-text">
                                                    {localizeWithFallback('reward_track.tasks.tip_upgrade', 'Upgrade now for premium rewards and faster progress!')}
                                                </span>
                                                <BaseButton type="button" className="octane-reward-track-button octane-reward-track-get-premium" onClick={onPremium}>
                                                    {localizeWithFallback('reward_track.tasks.tip_upgrade.button', 'Get Premium')}
                                                </BaseButton>
                                            </div>
                                        )}
                                    </div>
                                    <div className="octane-reward-track-task-info">
                                        {selectedTask && (
                                            <>
                                                <RewardTrackTaskGlyph actionType={selectedTask.actionType} large />
                                                {canCopyIds ? (
                                                    <BaseButton
                                                        type="button"
                                                        className="octane-reward-track-task-info-name octane-reward-track-copy-region"
                                                        onClick={() => copyTaskId && copyTaskId(selectedTask.id)}
                                                    >
                                                        {getRewardTrackTaskText(track.id, selectedTask.id, 'name', selectedTask.id)}
                                                    </BaseButton>
                                                ) : (
                                                    <div className="octane-reward-track-task-info-name">
                                                        {getRewardTrackTaskText(track.id, selectedTask.id, 'name', selectedTask.id)}
                                                    </div>
                                                )}
                                                <div className="octane-reward-track-task-info-desc">{getRewardTrackTaskText(track.id, selectedTask.id, 'desc', '')}</div>
                                                <span className="octane-reward-track-levels-rule" />
                                                <span className="octane-reward-track-levels-title">{localizeWithFallback('reward_track.levels.title', 'Levels')}</span>
                                                <div className="octane-reward-track-levels">
                                                    {selectedTask.levels.map((level, index) => {
                                                        const ratio = selectedTask.progressRatioFor(level);

                                                        return (
                                                            <div key={index} className="octane-reward-track-level" data-active={index === selectedTask.activeLevelIndex}>
                                                                <span className="octane-reward-track-level-name">
                                                                    {localizeWithFallback('reward_track.levels.level', 'Level %level%', ['level'], [String(index + 1)])}
                                                                </span>
                                                                <span className="octane-reward-track-level-bar">
                                                                    <RewardTrackMiniBar
                                                                        ratio={ratio}
                                                                        width={LEVEL_BAR_WIDTH}
                                                                        snapKey={`${tracksVersion}:${track.id}:${selectedTask.id}`}
                                                                    />
                                                                </span>
                                                                <span className="octane-reward-track-level-count">
                                                                    {selectedTask.progressCount} / {level.requiredCount}
                                                                </span>
                                                                <span className="octane-reward-track-level-end">
                                                                    {ratio >= 1 && <img src={checkmarkIcon} alt="" draggable={false} className="octane-reward-track-level-check" />}
                                                                    <RewardTrackPointsReward value={level.pointsReward} />
                                                                </span>
                                                            </div>
                                                        );
                                                    })}
                                                </div>
                                                <div className="octane-reward-track-hint">
                                                    <span className="octane-reward-track-hint-frank">
                                                        <img src={frankTipsImage} alt="" draggable={false} />
                                                    </span>
                                                    <span className="octane-reward-track-hint-title">{localizeWithFallback('reward_track.levels.tip', 'Tip!')}</span>
                                                    <span className="octane-reward-track-hint-text">{getRewardTrackTaskText(track.id, selectedTask.id, 'hint.desc', '')}</span>
                                                    {hintLink && (
                                                        <BaseButton
                                                            type="button"
                                                            className="octane-reward-track-button octane-reward-track-hint-button"
                                                            onClick={() => CreateLinkEvent(hintLink.link)}
                                                        >
                                                            {getRewardTrackTaskText(track.id, selectedTask.id, 'hint.button_text', hintLink.fallbackText)}
                                                        </BaseButton>
                                                    )}
                                                </div>
                                            </>
                                        )}
                                    </div>
                                </div>
                            </div>
                        )}
                    </OctaneCard.Content>
                </OctaneCard>
                {premiumConfirm && track && (
                    <RewardTrackPremiumConfirmView
                        track={track}
                        pending={pendingPurchase === track.id}
                        onConfirm={() => purchasePremium && purchasePremium(track.id)}
                        onCancel={() => setPremiumConfirm(false)}
                    />
                )}
            </>
        </NativeTextTree>
    );
};
15Sujets
1Dépôts
20Membres
2En ligne
Profile
Inscrit
Dernière visite
Messages
0
Votes positifs
0
Votes négatifs
0

Nous utilisons des cookies pour assurer le bon fonctionnement du site et améliorer votre expérience. en savoir plus

Autoriser