5180 fichiers 38.5 Mo 0 téléchargements 391 vues mis à jour il y a 7 heures ui
nitro-v36-with-renderer-v36 / src / components / inventory / views / furniture / InventoryTradeView.tsx
InventoryTradeView.tsx 19.3 Ko Télécharger le fichier
import { IObjectData, TradingListAddItemComposer, TradingListAddItemsComposer } from '@octane/renderer';
import { FC, useEffect, useState } from 'react';
import { FaChevronLeft, FaChevronRight, FaLock, FaUnlock } from 'react-icons/fa';
import {
    FurniCategory,
    GetConfigurationValue,
    GroupItem,
    getGuildFurniType,
    getTradeOfferSummary,
    IFurnitureItem,
    LocalizeText,
    localizeWithFallback,
    NotificationAlertType,
    isTradeOfferingCredits,
    SendMessageComposer,
    TradeState
} from '../../../../api';
import { AutoGrid, BaseButton, Button, Column, Flex, Grid, LayoutGridItem, Text } from '../../../../common';
import { useInventoryTrade, useNotification } from '../../../../hooks';
import { InventoryFurnitureSearchView } from './InventoryFurnitureSearchView';
import { NumberInput } from '../../../../common/NumberInput';
import { clampTradeQuantity } from '../../../../api/inventory/TradeQuantity';
import { NativeTextTree } from '../../../../common/native-text/NativeTextTree';

interface InventoryTradeViewProps {
    cancelTrade: () => void;
}

const MAX_ITEMS_TO_TRADE: number = 9;

export const InventoryTradeView: FC<InventoryTradeViewProps> = (props) => {
    const { cancelTrade = null } = props;
    const [groupItem, setGroupItem] = useState<GroupItem>(null);
    const [ownGroupItem, setOwnGroupItem] = useState<GroupItem>(null);
    const [otherGroupItem, setOtherGroupItem] = useState<GroupItem>(null);
    const [filteredGroupItems, setFilteredGroupItems] = useState<GroupItem[]>(null);
    const [countdownTick, setCountdownTick] = useState(3);
    const [quantity, setQuantity] = useState<number>(1);
    const {
        ownUser = null,
        otherUser = null,
        groupItems = [],
        tradeState = TradeState.TRADING_STATE_READY,
        progressTrade = null,
        removeItem = null,
        setTradeState = null
    } = useInventoryTrade();
    const { simpleAlert = null } = useNotification();

    const canTradeItem = (isWallItem: boolean, spriteId: number, category: number, groupable: boolean, stuffData: IObjectData) => {
        if (!ownUser || ownUser.accepts || !ownUser.userItems) return false;

        if (ownUser.userItems.length < MAX_ITEMS_TO_TRADE) return true;

        if (!groupable) return false;

        let type = spriteId.toString();

        if (category === FurniCategory.POSTER) {
            type = type + 'poster' + stuffData.getLegacyString();
        } else {
            if (category === FurniCategory.GUILD_FURNI) {
                type = getGuildFurniType(spriteId, stuffData);
            } else {
                type = (isWallItem ? 'I' : 'S') + type;
            }
        }

        return !!ownUser.userItems.getValue(type);
    };

    const attemptItemOffer = (count: number) => {
        if (!groupItem) return;

        const tradeItems = groupItem.getTradeItems(count);

        if (!tradeItems || !tradeItems.length) return;

        let coreItem: IFurnitureItem = null;
        const itemIds: number[] = [];

        for (const item of tradeItems) {
            itemIds.push(item.id);

            if (!coreItem) coreItem = item;
        }

        const ownItemCount = ownUser.userItems.length;

        if (ownItemCount + itemIds.length <= 1500) {
            if (!coreItem.isGroupable && itemIds.length) {
                SendMessageComposer(new TradingListAddItemComposer(itemIds.pop()));
            } else {
                const tradeIds: number[] = [];

                for (const itemId of itemIds) {
                    if (canTradeItem(coreItem.isWallItem, coreItem.type, coreItem.category, coreItem.isGroupable, coreItem.stuffData)) {
                        tradeIds.push(itemId);
                    }
                }

                if (tradeIds.length) {
                    if (tradeIds.length === 1) {
                        SendMessageComposer(new TradingListAddItemComposer(tradeIds.pop()));
                    } else {
                        SendMessageComposer(new TradingListAddItemsComposer(...tradeIds));
                    }
                }
            }
        } else {
            simpleAlert(
                LocalizeText('trading.items.too_many_items.desc'),
                NotificationAlertType.DEFAULT,
                null,
                null,
                LocalizeText('trading.items.too_many_items.title')
            );
        }
    };

    const getLockIcon = (accepts: boolean) => {
        if (accepts) {
            return <FaLock className="text-success fa-icon" />;
        } else {
            return <FaUnlock className="text-danger fa-icon" />;
        }
    };

    const changeCount = (totalItemCount: number) => {
        // Offer the amount once checked: the raw field could be NaN, which offered the whole stack.
        const count = clampTradeQuantity(quantity, totalItemCount);

        if (count <= 0) return;

        setQuantity(count);
        attemptItemOffer(count);
    };

    useEffect(() => {
        setQuantity(1);
    }, [groupItem]);

    useEffect(() => {
        if (tradeState !== TradeState.TRADING_STATE_COUNTDOWN) return;

        setCountdownTick(3);

        const interval = setInterval(() => {
            setCountdownTick((prevValue) => {
                const newValue = prevValue - 1;

                if (newValue === 0) clearInterval(interval);

                return newValue;
            });
        }, 1000);

        return () => clearInterval(interval);
    }, [tradeState, setTradeState]);

    useEffect(() => {
        if (countdownTick !== 0) return;

        setTradeState(TradeState.TRADING_STATE_CONFIRMING);
    }, [countdownTick, setTradeState]);

    if (tradeState === TradeState.TRADING_STATE_READY || !ownUser || !otherUser) return null;

    // The server says when an account may not trade; without this the trade just never completes.
    let tradeBlockedWarning: string = null;

    if (!ownUser.canTrade && !otherUser.canTrade)
        tradeBlockedWarning = localizeWithFallback('inventory.trading.warning.both_accounts_disabled', 'Neither of you can trade right now.');
    else if (!ownUser.canTrade)
        tradeBlockedWarning = localizeWithFallback('inventory.trading.warning.own_account_disabled', 'Your account can not trade right now.');
    else if (!otherUser.canTrade)
        tradeBlockedWarning = localizeWithFallback(
            'inventory.trading.warning.others_account_disabled',
            `${otherUser.userName} can not trade right now.`,
            ['otherusername'],
            [otherUser.userName]
        );

    // AIR TradingView.showOfferInfo / updateActionState: with trading.warning.enabled each side shows its item count and
    // credit value, and an orange line warns that credit furni turns into wallet credits once either side offers some.
    const tradeWarningEnabled = GetConfigurationValue<boolean>('trading.warning.enabled', true) !== false;
    const offerSummary = (user: typeof ownUser) => {
        if (!tradeWarningEnabled) return null;

        const summary = getTradeOfferSummary(user);

        return (
            <NativeTextTree>
                <div className="octane-trade-offer-summary flex flex-col text-xs text-black">
                    <span className="is-items">{summary.items}</span>
                    <span className="is-credits">{summary.credits}</span>
                </div>
            </NativeTextTree>
        );
    };
    const creditWarning =
        tradeWarningEnabled && isTradeOfferingCredits(ownUser, otherUser)
            ? localizeWithFallback('inventory.trading.warning.credits', 'Credit furni will be converted to credits in your purse')
            : null;

    return (
        <NativeTextTree>
            <>
                {tradeBlockedWarning && <div className="mb-1 rounded bg-[#ffe6b3] px-2 py-1 text-xs text-black">{tradeBlockedWarning}</div>}
                <Grid>
                    <Column overflow="hidden" size={4}>
                        <InventoryFurnitureSearchView groupItems={groupItems} setGroupItems={setFilteredGroupItems} />
                        <Flex column fullHeight gap={2} justifyContent="between" overflow="hidden">
                            <AutoGrid columnCount={3}>
                                {filteredGroupItems &&
                                    filteredGroupItems.length > 0 &&
                                    filteredGroupItems.map((item, index) => {
                                        const count = item.getUnlockedCount();

                                        return (
                                            <LayoutGridItem
                                                key={index}
                                                className={!count ? 'opacity-50 ' : ''}
                                                component="div"
                                                itemActive={groupItem === item}
                                                itemCount={count}
                                                itemImage={item.iconUrl}
                                                itemUniqueNumber={item.stuffData.uniqueNumber}
                                                onClick={(event) => count && setGroupItem(item)}
                                                onDoubleClick={(event) => attemptItemOffer(1)}
                                            >
                                                {count > 0 && groupItem === item && (
                                                    <Button
                                                        className="bottom-1 inset-e-1 z-[5] min-h-0 text-[8px] px-[2px] py-[1px]"
                                                        position="absolute"
                                                        variant="success"
                                                        onClick={(event) => attemptItemOffer(1)}
                                                    >
                                                        <FaChevronRight className="fa-icon" />
                                                    </Button>
                                                )}
                                            </LayoutGridItem>
                                        );
                                    })}
                            </AutoGrid>
                            <Column alignItems="end" gap={1}>
                                <Grid overflow="hidden">
                                    <Column overflow="hidden" size={6}>
                                        <NumberInput
                                            className="w-[49px] min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm"
                                            disabled={!groupItem}
                                            placeholder={LocalizeText('catalog.bundlewidget.spinner.select.amount')}
                                            value={quantity}
                                            onChange={(event) => setQuantity(event.target.valueAsNumber)}
                                        />
                                    </Column>
                                    <Column overflow="hidden" size={6}>
                                        <Button className="octane-trade-button" disabled={!groupItem} variant="secondary" onClick={(event) => changeCount(groupItem.getUnlockedCount())}>
                                            {LocalizeText('inventory.trading.areoffering')}
                                        </Button>
                                    </Column>
                                </Grid>
                                <div className="badge bg-muted w-full">{groupItem ? groupItem.name : LocalizeText('catalog_selectproduct')}</div>
                            </Column>
                        </Flex>
                    </Column>
                    <Column overflow="hidden" size={8}>
                        <Grid overflow="hidden">
                            <Column overflow="hidden" size={6}>
                                <div className="flex justify-between items-center">
                                    <Text className="octane-trade-offer-title">
                                        <span className="octane-trade-offer-user">{LocalizeText('inventory.trading.you')}</span>{' '}
                                        {LocalizeText('inventory.trading.areoffering')}:
                                    </Text>
                                    {getLockIcon(ownUser.accepts)}
                                </div>
                                <AutoGrid columnCount={3}>
                                    {Array.from(Array(MAX_ITEMS_TO_TRADE), (e, i) => {
                                        const item = ownUser.userItems.getWithIndex(i) || null;

                                        if (!item) return <LayoutGridItem key={i} />;

                                        return (
                                            <LayoutGridItem
                                                key={i}
                                                component="div"
                                                itemActive={ownGroupItem === item}
                                                itemCount={item.getTotalCount()}
                                                itemImage={item.iconUrl}
                                                itemUniqueNumber={item.stuffData.uniqueNumber}
                                                onClick={(event) => setOwnGroupItem(item)}
                                                onDoubleClick={(event) => removeItem(item)}
                                            >
                                                {ownGroupItem === item && (
                                                    <Button
                                                        className="bottom-1 inset-s-1 z-[5] min-h-0 text-[8px] px-[2px] py-[1px]"
                                                        position="absolute"
                                                        variant="danger"
                                                        onClick={(event) => removeItem(item)}
                                                    >
                                                        <FaChevronLeft className="fa-icon" />
                                                    </Button>
                                                )}
                                            </LayoutGridItem>
                                        );
                                    })}
                                </AutoGrid>
                                <div className="badge bg-muted w-full">{ownGroupItem ? ownGroupItem.name : LocalizeText('catalog_selectproduct')}</div>
                                {offerSummary(ownUser)}
                            </Column>
                            <Column overflow="hidden" size={6}>
                                <div className="flex justify-between items-center">
                                    <Text className="octane-trade-offer-title">
                                        <span className="octane-trade-offer-user">{otherUser.userName}</span> {LocalizeText('inventory.trading.isoffering')}:
                                    </Text>
                                    {getLockIcon(otherUser.accepts)}
                                </div>
                                <AutoGrid columnCount={3}>
                                    {Array.from(Array(MAX_ITEMS_TO_TRADE), (e, i) => {
                                        const item = otherUser.userItems.getWithIndex(i) || null;

                                        if (!item) return <LayoutGridItem key={i} />;

                                        return (
                                            <LayoutGridItem
                                                key={i}
                                                aria-pressed={otherGroupItem === item}
                                                component={BaseButton}
                                                itemActive={otherGroupItem === item}
                                                itemCount={item.getTotalCount()}
                                                itemImage={item.iconUrl}
                                                itemUniqueNumber={item.stuffData.uniqueNumber}
                                                onClick={(event) => setOtherGroupItem(item)}
                                            />
                                        );
                                    })}
                                </AutoGrid>
                                <div className="badge bg-muted w-full">{otherGroupItem ? otherGroupItem.name : LocalizeText('catalog_selectproduct')}</div>
                                {offerSummary(otherUser)}
                            </Column>
                        </Grid>
                        {creditWarning && (
                            <div
                                className="octane-trade-credit-warning mb-1 rounded border border-[#fc9228] bg-[#fc9228] px-[5px] py-[5px] text-xs text-black"
                                role="status"
                            >
                                {creditWarning}
                            </div>
                        )}
                        <div className="flex grow! justify-between">
                            <Button className="octane-trade-button" variant="secondary" onClick={cancelTrade}>
                                {LocalizeText('generic.cancel')}
                            </Button>
                            {tradeState === TradeState.TRADING_STATE_READY && (
                                <Button className="octane-trade-button" disabled={!ownUser.itemCount && !otherUser.itemCount} variant="secondary" onClick={progressTrade}>
                                    {LocalizeText('inventory.trading.accept')}
                                </Button>
                            )}
                            {tradeState === TradeState.TRADING_STATE_RUNNING && (
                                <Button className="octane-trade-button" disabled={!ownUser.itemCount && !otherUser.itemCount} variant="secondary" onClick={progressTrade}>
                                    {LocalizeText(ownUser.accepts ? 'inventory.trading.modify' : 'inventory.trading.accept')}
                                </Button>
                            )}
                            {tradeState === TradeState.TRADING_STATE_COUNTDOWN && (
                                <Button className="octane-trade-button" disabled variant="secondary">
                                    {LocalizeText('inventory.trading.countdown', ['counter'], [countdownTick.toString()])}
                                </Button>
                            )}
                            {tradeState === TradeState.TRADING_STATE_CONFIRMING && (
                                <Button className="octane-trade-button" variant="secondary" onClick={progressTrade}>
                                    {LocalizeText('inventory.trading.button.restore')}
                                </Button>
                            )}
                            {tradeState === TradeState.TRADING_STATE_CONFIRMED && (
                                <Button className="octane-trade-button" variant="secondary">{LocalizeText('inventory.trading.info.waiting')}</Button>
                            )}
                        </div>
                    </Column>
                </Grid>
            </>
        </NativeTextTree>
    );
};
15Sujets
1Dépôts
20Membres
3En 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