Eslint and fix some imports

This commit is contained in:
Bill 2024-06-06 19:48:18 -04:00
parent 10a7dcad24
commit df81cc7f92
224 changed files with 3940 additions and 10279 deletions

View File

@ -44,7 +44,7 @@
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" class="h-full w-full"></div>
<div id="root" class="w-full h-full"></div>
<script>
window.NitroConfig = {
"config.urls": ["/renderer-config.json", "/ui-config.json"],
@ -62,31 +62,6 @@
"friend.id":
new URLSearchParams(window.location.search).get("friend") ||
0,
"external.texts.url": [
"https://images.habbo.ws/assets/gamedata/" +
(new URLSearchParams(window.location.search).get(
"lang",
) || null) +
"/ExternalTexts.json",
"https://images.habbo.ws/assets/gamedata/" +
(new URLSearchParams(window.location.search).get(
"lang",
) || null) +
"/UITexts.json",
],
"furnidata.url":
"https://images.habbo.ws/assets/gamedata/" +
(new URLSearchParams(window.location.search).get("lang") ||
null) +
"/FurnitureData.json",
"productdata.url":
"https://images.habbo.ws/assets/gamedata/" +
(new URLSearchParams(window.location.search).get("lang") ||
null) +
"/ProductData.json",
lang:
new URLSearchParams(window.location.search).get("lang") ||
null,
};
</script>
<script type="module" src="./src/index.tsx"></script>

6326
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ NitroVersion.UI_VERSION = GetUIVersion();
export const App: FC<{}> = props =>
{
const [isReady, setIsReady] = useState(false);
const [ isReady, setIsReady ] = useState(false);
useMessageEvent<LoadGameUrlEvent>(LoadGameUrlEvent, event =>
{
@ -94,10 +94,10 @@ export const App: FC<{}> = props =>
}, []);
return (
<div className={classNames('w-full h-full overflow-hidden text-base', !(window.devicePixelRatio % 1) && '[image-rendering:pixelated]')}>
{!isReady &&
<LoadingView />}
{isReady && <MainView />}
<div className={ classNames('w-full h-full overflow-hidden text-base', !(window.devicePixelRatio % 1) && '[image-rendering:pixelated]') }>
{ !isReady &&
<LoadingView /> }
{ isReady && <MainView /> }
<div id="draggable-windows-container" />
</div>
);

View File

@ -56,7 +56,7 @@ export const Base: FC<BaseProps<HTMLDivElement>> = props =>
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [display, fit, fitV, grow, shrink, fullWidth, fullHeight, overflow, position, float, pointer, visible, textColor, classNames]);
}, [ display, fit, fitV, grow, shrink, fullWidth, fullHeight, overflow, position, float, pointer, visible, textColor, classNames ]);
const getClassName = useMemo(() =>
{
@ -65,7 +65,7 @@ export const Base: FC<BaseProps<HTMLDivElement>> = props =>
if (className.length) newClassName += (' ' + className);
return newClassName.trim();
}, [getClassNames, className]);
}, [ getClassNames, className ]);
const getStyle = useMemo(() =>
{
@ -74,11 +74,11 @@ export const Base: FC<BaseProps<HTMLDivElement>> = props =>
if (Object.keys(style).length) newStyle = { ...newStyle, ...style };
return newStyle;
}, [style]);
}, [ style ]);
return (
<div ref={innerRef} className={getClassName} style={getStyle} {...rest}>
{children}
<div ref={ innerRef } className={ getClassName } style={ getStyle } { ...rest }>
{ children }
</div>
);
}

View File

@ -19,7 +19,7 @@ export const Button: FC<ButtonProps> = props =>
// fucked up method i know (i dont have a clue what im doing because im a ninja)
const newClassNames: string[] = ['pointer-events-auto inline-block font-normal leading-normal text-[#fff] text-center no-underline align-middle cursor-pointer select-none border-[1px] border-[solid] border-[transparent] px-[.75rem] py-[.375rem] text-[.9rem] rounded-[.25rem] [transition:color_.15s_ease-in-out,_background-color_.15s_ease-in-out,_border-color_.15s_ease-in-out,_box-shadow_.15s_ease-in-out]'];
const newClassNames: string[] = [ 'pointer-events-auto inline-block font-normal leading-normal text-[#fff] text-center no-underline align-middle cursor-pointer select-none border-[1px] border-[solid] border-[transparent] px-[.75rem] py-[.375rem] text-[.9rem] rounded-[.25rem] [transition:color_.15s_ease-in-out,_background-color_.15s_ease-in-out,_border-color_.15s_ease-in-out,_box-shadow_.15s_ease-in-out]' ];
if (variant)
{
@ -62,7 +62,7 @@ export const Button: FC<ButtonProps> = props =>
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [variant, size, active, disabled, classNames]);
}, [ variant, size, active, disabled, classNames ]);
return <Flex center classNames={getClassNames} {...rest} />;
return <Flex center classNames={ getClassNames } { ...rest } />;
}

View File

@ -21,18 +21,18 @@ export const Column: FC<ColumnProps> = props =>
if (size)
{
let colClassName = `col-span-${size}`;
let colClassName = `col-span-${ size }`;
if (isCssGrid) colClassName = `${colClassName}`;
if (isCssGrid) colClassName = `${ colClassName }`;
newClassNames.push(colClassName);
}
if (offset)
{
let colClassName = `offset-${offset}`;
let colClassName = `offset-${ offset }`;
if (isCssGrid) colClassName = `g-start-${offset}`;
if (isCssGrid) colClassName = `g-start-${ offset }`;
newClassNames.push(colClassName);
}
@ -40,7 +40,7 @@ export const Column: FC<ColumnProps> = props =>
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [size, offset, isCssGrid, classNames]);
}, [ size, offset, isCssGrid, classNames ]);
return <Flex classNames={getClassNames} column={column} gap={gap} {...rest} />;
return <Flex classNames={ getClassNames } column={ column } gap={ gap } { ...rest } />;
}

View File

@ -44,7 +44,7 @@ export const Flex: FC<FlexProps> = props =>
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [column, reverse, gap, center, alignSelf, alignItems, justifyContent, classNames]);
}, [ column, reverse, gap, center, alignSelf, alignItems, justifyContent, classNames ]);
return <Base classNames={getClassNames} display={display} {...rest} />;
return <Base classNames={ getClassNames } display={ display } { ...rest } />;
}

View File

@ -43,7 +43,7 @@ export const Grid: FC<GridProps> = props =>
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [inline, gap, maxContent, alignSelf, alignItems, justifyContent, center, classNames]);
}, [ inline, gap, maxContent, alignSelf, alignItems, justifyContent, center, classNames ]);
const getStyle = useMemo(() =>
{
@ -54,11 +54,11 @@ export const Grid: FC<GridProps> = props =>
if (Object.keys(style).length) newStyle = { ...newStyle, ...style };
return newStyle;
}, [columnCount, style]);
}, [ columnCount, style ]);
return (
<GridContextProvider value={{ isCssGrid: true }}>
<Base classNames={getClassNames} fullHeight={fullHeight} style={getStyle} {...rest} />
<GridContextProvider value={ { isCssGrid: true } }>
<Base classNames={ getClassNames } fullHeight={ fullHeight } style={ getStyle } { ...rest } />
</GridContextProvider>
);
}

View File

@ -1,19 +1,19 @@
// @flow strict
"use client"
import { useEffect, useRef, useState } from "react";
'use client'
import { useEffect, useRef, useState } from 'react';
function ReactPopover({
children,
content,
trigger = "click"
trigger = 'click'
})
{
const [show, setShow] = useState(false);
const [ show, setShow ] = useState(false);
const wrapperRef = useRef(null);
const handleMouseOver = () =>
{
if (trigger === "hover")
if (trigger === 'hover')
{
setShow(true);
};
@ -21,7 +21,7 @@ function ReactPopover({
const handleMouseLeft = () =>
{
if (trigger === "hover")
if (trigger === 'hover')
{
setShow(false);
};
@ -40,31 +40,31 @@ function ReactPopover({
if (show)
{
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
document.addEventListener('mousedown', handleClickOutside);
return () =>
{
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener('mousedown', handleClickOutside);
};
}
}, [show, wrapperRef]);
}, [ show, wrapperRef ]);
return (
<div
ref={wrapperRef}
onMouseEnter={handleMouseOver}
onMouseLeave={handleMouseLeft}
className="w-fit h-fit relative flex justify-center">
ref={ wrapperRef }
className="w-fit h-fit relative flex justify-center"
onMouseEnter={ handleMouseOver }
onMouseLeave={ handleMouseLeft }>
<div
onClick={() => setShow(!show)}
onClick={ () => setShow(!show) }
>
{children}
{ children }
</div>
<div
hidden={!show}
className="min-w-fit w-[200px] h-fit absolute bottom-[100%] z-50 transition-all">
className="min-w-fit w-[200px] h-fit absolute bottom-[100%] z-50 transition-all"
hidden={ !show }>
<div className="rounded bg-white p-3 shadow-[10px_30px_150px_rgba(46,38,92,0.25)] mb-[10px]">
{content}
{ content }
</div>
</div>
</div>

View File

@ -26,7 +26,7 @@ export const Text: FC<TextProps> = props =>
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['inline'];
const newClassNames: string[] = [ 'inline' ];
if (variant) newClassNames.push('text-' + variant);
@ -57,7 +57,7 @@ export const Text: FC<TextProps> = props =>
if (textBreak) newClassNames.push('text-break');
return newClassNames;
}, [variant, fontWeight, fontSize, align, bold, underline, italics, truncate, center, textEnd, small, wrap, noWrap, textBreak]);
}, [ variant, fontWeight, fontSize, align, bold, underline, italics, truncate, center, textEnd, small, wrap, noWrap, textBreak ]);
return <Base classNames={getClassNames} {...rest} />;
return <Base classNames={ getClassNames } { ...rest } />;
}

View File

@ -8,12 +8,12 @@ export const NitroCardContentView: FC<ColumnProps> = props =>
const getClassNames = useMemo(() =>
{
// Theme Changer
const newClassNames: string[] = ['container-fluid', 'h-full p-[8px] overflow-auto', 'bg-light'];
const newClassNames: string[] = [ 'container-fluid', 'h-full p-[8px] overflow-auto', 'bg-light' ];
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [classNames]);
}, [ classNames ]);
return <Column classNames={getClassNames} overflow={overflow} {...rest} />;
return <Column classNames={ getClassNames } overflow={ overflow } { ...rest } />;
}

View File

@ -24,18 +24,15 @@ export const NitroCardHeaderView: FC<NitroCardHeaderViewProps> = props =>
}
return (
<Column center className={"relative flex items-center justify-center flex-col drag-handler min-h-card-header max-h-card-header bg-card-header"} {...rest}>
<Column center className={ 'relative flex items-center justify-center flex-col drag-handler min-h-card-header max-h-card-header bg-card-header' } { ...rest }>
<Flex center fullWidth>
<span className="text-xl text-white drop-shadow-lg">{headerText}</span>
{isGalleryPhoto &&
<Base className="end-4 nitro-card-header-report-camera" position="absolute" onClick={onReportPhoto}>
<span className="text-xl text-white drop-shadow-lg">{ headerText }</span>
{ isGalleryPhoto &&
<Base className="end-4 nitro-card-header-report-camera" position="absolute" onClick={ onReportPhoto }>
<FaFlag className="fa-icon" />
</Base>
}
<div className="absolute flex items-center justify-center cursor-pointer right-2 p-[2px] ubuntu-close-button" onClick={onCloseClick} onMouseDownCapture={onMouseDown}>
<div className="absolute flex items-center justify-center cursor-pointer right-2 p-[2px] ubuntu-close-button" onClick={ onCloseClick } onMouseDownCapture={ onMouseDown }>
</div>
</Flex>

View File

@ -15,7 +15,7 @@ export const NitroCardView: FC<NitroCardViewProps> = props =>
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['resize', 'rounded', 'shadow',];
const newClassNames: string[] = [ 'resize', 'rounded', 'shadow', ];
// Card Theme Changer
newClassNames.push('border-[1px] border-[#283F5D]');
@ -25,12 +25,12 @@ export const NitroCardView: FC<NitroCardViewProps> = props =>
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [theme, classNames]);
}, [ classNames ]);
return (
<NitroCardContextProvider value={{ theme }}>
<DraggableWindow disableDrag={disableDrag} handleSelector={handleSelector} uniqueKey={uniqueKey} windowPosition={windowPosition}>
<Column classNames={getClassNames} gap={gap} innerRef={elementRef} overflow={overflow} position={position} {...rest} />
<NitroCardContextProvider value={ { theme } }>
<DraggableWindow disableDrag={ disableDrag } handleSelector={ handleSelector } uniqueKey={ uniqueKey } windowPosition={ windowPosition }>
<Column classNames={ getClassNames } gap={ gap } innerRef={ elementRef } overflow={ overflow } position={ position } { ...rest } />
</DraggableWindow>
</NitroCardContextProvider>
);

View File

@ -14,23 +14,23 @@ export const NitroCardTabsItemView: FC<NitroCardTabsItemViewProps> = props =>
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['overflow-hidden relative cursor-pointer rounded-t-md flex bg-card-tab-item px-3 py-1 z-[1] border-card-border border-t border-l border-r before:absolute before:w-[93%] before:h-[3px] before:rounded-md before:top-[1.5px] before:left-0 before:right-0 before:m-auto before:z-[1] before:bg-[#C2C9D1]',
isActive && 'bg-card-tab-item-active -mb-[1px] before:bg-white'];
const newClassNames: string[] = [ 'overflow-hidden relative cursor-pointer rounded-t-md flex bg-card-tab-item px-3 py-1 z-[1] border-card-border border-t border-l border-r before:absolute before:w-[93%] before:h-[3px] before:rounded-md before:top-[1.5px] before:left-0 before:right-0 before:m-auto before:z-[1] before:bg-[#C2C9D1]',
isActive && 'bg-card-tab-item-active -mb-[1px] before:bg-white' ];
//if (isActive) newClassNames.push('bg-[#dfdfdf] border-b-[1px_solid_black]');
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [isActive, classNames]);
}, [ isActive, classNames ]);
return (
<Flex classNames={getClassNames} overflow={overflow} pointer={pointer} position={position} {...rest}>
<Flex classNames={ getClassNames } overflow={ overflow } pointer={ pointer } position={ position } { ...rest }>
<Flex center shrink>
{children}
{ children }
</Flex>
{(count > 0) &&
<LayoutItemCountView count={count} />}
{ (count > 0) &&
<LayoutItemCountView count={ count } /> }
</Flex>
);
}

View File

@ -7,16 +7,16 @@ export const NitroCardTabsView: FC<FlexProps> = props =>
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['justify-center gap-0.5 flex bg-card-tabs min-h-card-tabs max-h-card-tabs pt-1 border-b border-card-border px-2'];
const newClassNames: string[] = [ 'justify-center gap-0.5 flex bg-card-tabs min-h-card-tabs max-h-card-tabs pt-1 border-b border-card-border px-2' ];
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [classNames]);
}, [ classNames ]);
return (
<Flex classNames={getClassNames} gap={gap} justifyContent={justifyContent} {...rest}>
{children}
<Flex classNames={ getClassNames } gap={ gap } justifyContent={ justifyContent } { ...rest }>
{ children }
</Flex>
);
}

View File

@ -16,28 +16,28 @@ export interface LayoutAvatarImageViewProps extends BaseProps<HTMLDivElement>
export const LayoutAvatarImageView: FC<LayoutAvatarImageViewProps> = props =>
{
const { figure = '', gender = 'M', headOnly = false, direction = 0, scale = 1, classNames = [], style = {}, ...rest } = props;
const [avatarUrl, setAvatarUrl] = useState<string>(null);
const [isReady, setIsReady] = useState<boolean>(false);
const [ avatarUrl, setAvatarUrl ] = useState<string>(null);
const [ isReady, setIsReady ] = useState<boolean>(false);
const isDisposed = useRef(false);
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['avatar-image relative w-[90px] h-[130px] bg-no-repeat bg-[center_-8px] pointer-events-none'];
const newClassNames: string[] = [ 'avatar-image relative w-[90px] h-[130px] bg-no-repeat bg-[center_-8px] pointer-events-none' ];
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [classNames]);
}, [ classNames ]);
const getStyle = useMemo(() =>
{
let newStyle: CSSProperties = {};
if (avatarUrl && avatarUrl.length) newStyle.backgroundImage = `url('${avatarUrl}')`;
if (avatarUrl && avatarUrl.length) newStyle.backgroundImage = `url('${ avatarUrl }')`;
if (scale !== 1)
{
newStyle.transform = `scale(${scale})`;
newStyle.transform = `scale(${ scale })`;
if (!(scale % 1)) newStyle.imageRendering = 'pixelated';
}
@ -45,13 +45,13 @@ export const LayoutAvatarImageView: FC<LayoutAvatarImageViewProps> = props =>
if (Object.keys(style).length) newStyle = { ...newStyle, ...style };
return newStyle;
}, [avatarUrl, scale, style]);
}, [ avatarUrl, scale, style ]);
useEffect(() =>
{
if (!isReady) return;
const figureKey = [figure, gender, direction, headOnly].join('-');
const figureKey = [ figure, gender, direction, headOnly ].join('-');
if (AVATAR_IMAGE_CACHE.has(figureKey))
{
@ -85,7 +85,7 @@ export const LayoutAvatarImageView: FC<LayoutAvatarImageViewProps> = props =>
resetFigure(figure);
}
}, [figure, gender, direction, headOnly, isReady]);
}, [ figure, gender, direction, headOnly, isReady ]);
useEffect(() =>
{
@ -99,5 +99,5 @@ export const LayoutAvatarImageView: FC<LayoutAvatarImageViewProps> = props =>
}
}, []);
return <Base classNames={getClassNames} style={getStyle} {...rest} />;
return <Base classNames={ getClassNames } style={ getStyle } { ...rest } />;
}

View File

@ -16,11 +16,11 @@ export interface LayoutBadgeImageViewProps extends BaseProps<HTMLDivElement>
export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
{
const { badgeCode = null, isGroup = false, showInfo = false, customTitle = null, isGrayscale = false, scale = 1, classNames = [], style = {}, children = null, ...rest } = props;
const [imageElement, setImageElement] = useState<HTMLImageElement>(null);
const [ imageElement, setImageElement ] = useState<HTMLImageElement>(null);
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['relative w-[40px] h-[40px] bg-no-repeat bg-center'];
const newClassNames: string[] = [ 'relative w-[40px] h-[40px] bg-no-repeat bg-center' ];
if (isGroup) newClassNames.push('group-badge');
@ -29,7 +29,7 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [classNames, isGroup, isGrayscale]);
}, [ classNames, isGroup, isGrayscale ]);
const getStyle = useMemo(() =>
{
@ -37,13 +37,13 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
if (imageElement)
{
newStyle.backgroundImage = `url(${(isGroup) ? imageElement.src : GetConfigurationValue<string>('badge.asset.url').replace('%badgename%', badgeCode.toString())})`;
newStyle.backgroundImage = `url(${ (isGroup) ? imageElement.src : GetConfigurationValue<string>('badge.asset.url').replace('%badgename%', badgeCode.toString()) })`;
newStyle.width = imageElement.width;
newStyle.height = imageElement.height;
if (scale !== 1)
{
newStyle.transform = `scale(${scale})`;
newStyle.transform = `scale(${ scale })`;
if (!(scale % 1)) newStyle.imageRendering = 'pixelated';
@ -55,7 +55,7 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
if (Object.keys(style).length) newStyle = { ...newStyle, ...style };
return newStyle;
}, [badgeCode, isGroup, imageElement, scale, style]);
}, [ badgeCode, isGroup, imageElement, scale, style ]);
useEffect(() =>
{
@ -91,16 +91,16 @@ export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
}
return () => GetEventDispatcher().removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
}, [badgeCode, isGroup]);
}, [ badgeCode, isGroup ]);
return (
<Base className='group' classNames={getClassNames} style={getStyle} {...rest}>
{(showInfo && GetConfigurationValue<boolean>('badge.descriptions.enabled', true)) &&
<Base className="group" classNames={ getClassNames } style={ getStyle } { ...rest }>
{ (showInfo && GetConfigurationValue<boolean>('badge.descriptions.enabled', true)) &&
<Base className="hidden group-hover:block before:absolute before:content-['_'] before:w-[0] before:h-[0] before:!border-l-[10px] before:!border-b-[10px] before:!border-t-[10px] before:top-[10px] before:-right-[10px] before:[border-left-color:white] before:[border-top-color:transparent] before:[border-bottom-color:transparent] z-50 absolute pointer-events-none select-none w-[210px] rounded-[.25rem] bg-[#fff] -left-[220px] text-black py-1 px-2 small">
<div className="font-bold mb-1">{isGroup ? customTitle : LocalizeBadgeName(badgeCode)}</div>
<div>{isGroup ? LocalizeText('group.badgepopup.body') : LocalizeBadgeDescription(badgeCode)}</div>
</Base>}
{children}
<div className="font-bold mb-1">{ isGroup ? customTitle : LocalizeBadgeName(badgeCode) }</div>
<div>{ isGroup ? LocalizeText('group.badgepopup.body') : LocalizeBadgeDescription(badgeCode) }</div>
</Base> }
{ children }
</Base>
);
}

View File

@ -13,12 +13,12 @@ export const LayoutCurrencyIcon: FC<CurrencyIconProps> = props =>
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['nitro-currency-icon', 'bg-center bg-no-repeat w-[15px] h-[15px]'];
const newClassNames: string[] = [ 'nitro-currency-icon', 'bg-center bg-no-repeat w-[15px] h-[15px]' ];
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [classNames]);
}, [ classNames ]);
const urlString = useMemo(() =>
{
@ -26,8 +26,8 @@ export const LayoutCurrencyIcon: FC<CurrencyIconProps> = props =>
url = url.replace('%type%', type.toString());
return `url(${url})`;
}, [type]);
return `url(${ url })`;
}, [ type ]);
const getStyle = useMemo(() =>
{
@ -38,7 +38,7 @@ export const LayoutCurrencyIcon: FC<CurrencyIconProps> = props =>
if (Object.keys(style).length) newStyle = { ...newStyle, ...style };
return newStyle;
}, [style, urlString]);
}, [ style, urlString ]);
return <Base classNames={getClassNames} style={getStyle} {...rest} />
return <Base classNames={ getClassNames } style={ getStyle } { ...rest } />
}

View File

@ -21,19 +21,19 @@ export const LayoutGiftTagView: FC<LayoutGiftTagViewProps> = props =>
return (
<Flex className="nitro-gift-card text-black" overflow="hidden">
<div className="flex items-center justify-center gift-face flex-shrink-0">
{!userName && <div className="gift-incognito"></div>}
{figure && <div className="gift-avatar">
<LayoutAvatarImageView direction={2} figure={figure} headOnly={true} />
</div>}
{ !userName && <div className="gift-incognito"></div> }
{ figure && <div className="gift-avatar">
<LayoutAvatarImageView direction={ 2 } figure={ figure } headOnly={ true } />
</div> }
</div>
<Flex className="w-full pt-4 pb-4 pe-4 ps-3" overflow="hidden">
<Column className="!flex-grow" justifyContent="between" overflow="auto">
{!editable &&
<Text textBreak className="gift-message">{message}</Text>}
{editable && (onChange !== null) &&
<textarea className="gift-message h-full" maxLength={140} placeholder={LocalizeText('catalog.gift_wrapping_new.message_hint')} value={message} onChange={(e) => onChange(e.target.value)}></textarea>}
{userName &&
<Text italics textEnd className="pe-1">{LocalizeText('catalog.gift_wrapping_new.message_from', ['name'], [userName])}</Text>}
{ !editable &&
<Text textBreak className="gift-message">{ message }</Text> }
{ editable && (onChange !== null) &&
<textarea className="gift-message h-full" maxLength={ 140 } placeholder={ LocalizeText('catalog.gift_wrapping_new.message_hint') } value={ message } onChange={ (e) => onChange(e.target.value) }></textarea> }
{ userName &&
<Text italics textEnd className="pe-1">{ LocalizeText('catalog.gift_wrapping_new.message_from', [ 'name' ], [ userName ]) }</Text> }
</Column>
</Flex>
</Flex>

View File

@ -24,7 +24,7 @@ export const LayoutGridItem: FC<LayoutGridItemProps> = props =>
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['layout-grid-item', 'border', 'border-2', 'border-muted', 'rounded'];
const newClassNames: string[] = [ 'layout-grid-item', 'border', 'border-2', 'border-muted', 'rounded' ];
if (itemActive) newClassNames.push('!bg-[#ececec] !border-[#fff]');
@ -44,33 +44,33 @@ export const LayoutGridItem: FC<LayoutGridItemProps> = props =>
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [itemActive, itemUniqueSoldout, itemUniqueNumber, itemUnseen, itemHighlight, disabled, itemImage, classNames]);
}, [ itemActive, itemUniqueSoldout, itemUniqueNumber, itemUnseen, itemHighlight, disabled, itemImage, classNames ]);
const getStyle = useMemo(() =>
{
let newStyle = { ...style };
if (itemImage && !(itemUniqueSoldout || (itemUniqueNumber > 0))) newStyle.backgroundImage = `url(${itemImage})`;
if (itemImage && !(itemUniqueSoldout || (itemUniqueNumber > 0))) newStyle.backgroundImage = `url(${ itemImage })`;
if (itemColor) newStyle.backgroundColor = itemColor;
if (Object.keys(style).length) newStyle = { ...newStyle, ...style };
return newStyle;
}, [style, itemImage, itemColor, itemUniqueSoldout, itemUniqueNumber]);
}, [ style, itemImage, itemColor, itemUniqueSoldout, itemUniqueNumber ]);
return (
<Column pointer center={center} classNames={getClassNames} column={column} overflow={overflow} position={position} style={getStyle} {...rest}>
{(itemCount > itemCountMinimum) &&
<LayoutItemCountView count={itemCount} />}
{(itemUniqueNumber > 0) &&
<Column pointer center={ center } classNames={ getClassNames } column={ column } overflow={ overflow } position={ position } style={ getStyle } { ...rest }>
{ (itemCount > itemCountMinimum) &&
<LayoutItemCountView count={ itemCount } /> }
{ (itemUniqueNumber > 0) &&
<>
<Base fit className="unique-bg-override" style={{ backgroundImage: `url(${itemImage})` }} />
<Base fit className="unique-bg-override" style={ { backgroundImage: `url(${ itemImage })` } } />
<div className="absolute bottom-0 unique-item-counter">
<LayoutLimitedEditionStyledNumberView value={itemUniqueNumber} />
<LayoutLimitedEditionStyledNumberView value={ itemUniqueNumber } />
</div>
</>}
{children}
</> }
{ children }
</Column>
);
}

View File

@ -12,17 +12,17 @@ export const LayoutItemCountView: FC<LayoutItemCountViewProps> = props =>
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['inline-block px-[.65em] py-[.35em] text-[.75em] font-bold leading-none text-[#fff] text-center whitespace-nowrap align-baseline rounded-[.25rem]', '!border-[1px] !border-[solid] !border-[#283F5D]', 'border-black', 'bg-danger', 'px-1', 'top-[2px] right-[2px] text-[9.5px] px-[3px] py-[2px] '];
const newClassNames: string[] = [ 'inline-block px-[.65em] py-[.35em] text-[.75em] font-bold leading-none text-[#fff] text-center whitespace-nowrap align-baseline rounded-[.25rem]', '!border-[1px] !border-[solid] !border-[#283F5D]', 'border-black', 'bg-danger', 'px-1', 'top-[2px] right-[2px] text-[9.5px] px-[3px] py-[2px] ' ];
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [classNames]);
}, [ classNames ]);
return (
<Base classNames={getClassNames} position="absolute" {...rest}>
{count}
{children}
<Base classNames={ getClassNames } position="absolute" { ...rest }>
{ count }
{ children }
</Base>
);
}

View File

@ -33,10 +33,10 @@ export const LayoutMiniCameraView: FC<LayoutMiniCameraViewProps> = props =>
return (
<DraggableWindow handleSelector=".nitro-room-thumbnail-camera">
<div className="nitro-room-thumbnail-camera px-2">
<div ref={elementRef} className={'camera-frame'} />
<div ref={ elementRef } className={ 'camera-frame' } />
<div className="flex align-items-end h-full pb-2">
<button className="btn btn-sm btn-danger w-full mb-1 me-2" onClick={onClose}>{LocalizeText('cancel')}</button>
<button className="btn btn-sm btn-success w-full mb-1" onClick={takePicture}>{LocalizeText('navigator.thumbeditor.save')}</button>
<button className="btn btn-sm btn-danger w-full mb-1 me-2" onClick={ onClose }>{ LocalizeText('cancel') }</button>
<button className="btn btn-sm btn-success w-full mb-1" onClick={ takePicture }>{ LocalizeText('navigator.thumbeditor.save') }</button>
</div>
</div>
</DraggableWindow>

View File

@ -12,16 +12,16 @@ export interface LayoutNotificationBubbleViewProps extends FlexProps
export const LayoutNotificationBubbleView: FC<LayoutNotificationBubbleViewProps> = props =>
{
const { fadesOut = true, timeoutMs = 8000, onClose = null, overflow = 'hidden', classNames = [], ...rest } = props;
const [isVisible, setIsVisible] = useState(false);
const [ isVisible, setIsVisible ] = useState(false);
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['text-sm bg-[#1c1c20f2] px-[5px] py-[6px] [box-shadow:inset_0_5px_#22222799,_inset_0_-4px_#12121599] ', 'rounded'];
const newClassNames: string[] = [ 'text-sm bg-[#1c1c20f2] px-[5px] py-[6px] [box-shadow:inset_0_5px_#22222799,_inset_0_-4px_#12121599] ', 'rounded' ];
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [classNames]);
}, [ classNames ]);
useEffect(() =>
{
@ -42,11 +42,11 @@ export const LayoutNotificationBubbleView: FC<LayoutNotificationBubbleViewProps>
}, timeoutMs);
return () => clearTimeout(timeout);
}, [fadesOut, timeoutMs, onClose]);
}, [ fadesOut, timeoutMs, onClose ]);
return (
<TransitionAnimation inProp={isVisible} timeout={300} type={TransitionAnimationTypes.FADE_IN}>
<Flex classNames={getClassNames} overflow={overflow} onClick={onClose} {...rest} />
<TransitionAnimation inProp={ isVisible } timeout={ 300 } type={ TransitionAnimationTypes.FADE_IN }>
<Flex classNames={ getClassNames } overflow={ overflow } onClick={ onClose } { ...rest } />
</TransitionAnimation>
);
}

View File

@ -14,19 +14,19 @@ export const LayoutProgressBar: FC<LayoutProgressBarProps> = props =>
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['border-[1px] border-[solid] border-[#fff] p-[2px] h-[20px] rounded-[.25rem] overflow-hidden bg-[#1E7295] ', 'text-white'];
const newClassNames: string[] = [ 'border-[1px] border-[solid] border-[#fff] p-[2px] h-[20px] rounded-[.25rem] overflow-hidden bg-[#1E7295] ', 'text-white' ];
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [classNames]);
}, [ classNames ]);
return (
<Column position={position} justifyContent={justifyContent} classNames={getClassNames} {...rest}>
{text && (text.length > 0) &&
<Flex fit center position="absolute" className="[text-shadow:0px_4px_4px_rgba(0,_0,_0,_.25)] z-20">{text}</Flex>}
<Base className="h-full z-10 [transition:all_1s] rounded-[.125rem] bg-[repeating-linear-gradient(#2DABC2,_#2DABC2_50%,_#2B91A7_50%,_#2B91A7_100%)]" style={{ width: (~~((((progress - 0) * (100 - 0)) / (maxProgress - 0)) + 0) + '%') }} />
{children}
<Column classNames={ getClassNames } justifyContent={ justifyContent } position={ position } { ...rest }>
{ text && (text.length > 0) &&
<Flex center fit className="[text-shadow:0px_4px_4px_rgba(0,_0,_0,_.25)] z-20" position="absolute">{ text }</Flex> }
<Base className="h-full z-10 [transition:all_1s] rounded-[.125rem] bg-[repeating-linear-gradient(#2DABC2,_#2DABC2_50%,_#2B91A7_50%,_#2B91A7_100%)]" style={ { width: (~~((((progress - 0) * (100 - 0)) / (maxProgress - 0)) + 0) + '%') } } />
{ children }
</Column>
);
}

View File

@ -14,24 +14,24 @@ export const LayoutRoomThumbnailView: FC<LayoutRoomThumbnailViewProps> = props =
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['relative w-[110px] h-[110px] bg-[url("@/assets/images/navigator/thumbnail_placeholder.png")] bg-no-repeat bg-center', 'rounded', '!border-[1px] !border-[solid] !border-[#283F5D]'];
const newClassNames: string[] = [ 'relative w-[110px] h-[110px] bg-[url("@/assets/images/navigator/thumbnail_placeholder.png")] bg-no-repeat bg-center', 'rounded', '!border-[1px] !border-[solid] !border-[#283F5D]' ];
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [classNames]);
}, [ classNames ]);
const getImageUrl = useMemo(() =>
{
if (customUrl && customUrl.length) return (GetConfigurationValue<string>('image.library.url') + customUrl);
return (GetConfigurationValue<string>('thumbnails.url').replace('%thumbnail%', roomId.toString()));
}, [customUrl, roomId]);
}, [ customUrl, roomId ]);
return (
<Base classNames={getClassNames} overflow={overflow} shrink={shrink} {...rest}>
{getImageUrl && <img alt="" src={getImageUrl} />}
{children}
<Base classNames={ getClassNames } overflow={ overflow } shrink={ shrink } { ...rest }>
{ getImageUrl && <img alt="" src={ getImageUrl } /> }
{ children }
</Base>
);
}

View File

@ -14,16 +14,16 @@ export const UserProfileIconView: FC<UserProfileIconViewProps> = props =>
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['bg-[url("@/assets/images/friends/friends-spritesheet.png")]', 'w-[13px] h-[11px] bg-[-51px_-91px]'];
const newClassNames: string[] = [ 'bg-[url("@/assets/images/friends/friends-spritesheet.png")]', 'w-[13px] h-[11px] bg-[-51px_-91px]' ];
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [classNames]);
}, [ classNames ]);
return (
<Base classNames={getClassNames} pointer={pointer} onClick={event => GetUserProfile(userId)} {...rest}>
{children}
<Base classNames={ getClassNames } pointer={ pointer } onClick={ event => GetUserProfile(userId) } { ...rest }>
{ children }
</Base>
);
}

View File

@ -16,23 +16,23 @@ export const LayoutLimitedEditionCompletePlateView: FC<LayoutLimitedEditionCompl
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = ['unique-complete-plate'];
const newClassNames: string[] = [ 'unique-complete-plate' ];
if (classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [classNames]);
}, [ classNames ]);
return (
<Base classNames={getClassNames} {...rest}>
<Column className="plate-container" gap={0}>
<Base classNames={ getClassNames } { ...rest }>
<Column className="plate-container" gap={ 0 }>
<div className="flex justify-between items-center">
{LocalizeText('unique.items.left')}
<div><LayoutLimitedEditionStyledNumberView value={uniqueLimitedItemsLeft} /></div>
{ LocalizeText('unique.items.left') }
<div><LayoutLimitedEditionStyledNumberView value={ uniqueLimitedItemsLeft } /></div>
</div>
<div className="flex justify-between items-center">
{LocalizeText('unique.items.number.sold')}
<div><LayoutLimitedEditionStyledNumberView value={uniqueLimitedSeriesSize} /></div>
{ LocalizeText('unique.items.number.sold') }
<div><LayoutLimitedEditionStyledNumberView value={ uniqueLimitedSeriesSize } /></div>
</div>
</Column>
</Base>

View File

@ -15,12 +15,12 @@ export function getTransitionAnimationStyle(type: string, transition: Transition
case ENTERING:
return {
animationName: 'bounceIn',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
case EXITING:
return {
animationName: 'bounceOut',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
}
case TransitionAnimationTypes.SLIDE_LEFT:
@ -31,12 +31,12 @@ export function getTransitionAnimationStyle(type: string, transition: Transition
case ENTERING:
return {
animationName: 'slideInLeft',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
case EXITING:
return {
animationName: 'slideOutLeft',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
}
case TransitionAnimationTypes.SLIDE_RIGHT:
@ -47,12 +47,12 @@ export function getTransitionAnimationStyle(type: string, transition: Transition
case ENTERING:
return {
animationName: 'slideInRight',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
case EXITING:
return {
animationName: 'slideOutRight',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
}
case TransitionAnimationTypes.FLIP_X:
@ -63,12 +63,12 @@ export function getTransitionAnimationStyle(type: string, transition: Transition
case ENTERING:
return {
animationName: 'flipInX',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
case EXITING:
return {
animationName: 'flipOutX',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
}
case TransitionAnimationTypes.FADE_UP:
@ -79,12 +79,12 @@ export function getTransitionAnimationStyle(type: string, transition: Transition
case ENTERING:
return {
animationName: 'fadeInUp',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
case EXITING:
return {
animationName: 'fadeOutDown',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
}
case TransitionAnimationTypes.FADE_IN:
@ -95,12 +95,12 @@ export function getTransitionAnimationStyle(type: string, transition: Transition
case ENTERING:
return {
animationName: 'fadeIn',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
case EXITING:
return {
animationName: 'fadeOut',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
}
case TransitionAnimationTypes.FADE_DOWN:
@ -111,12 +111,12 @@ export function getTransitionAnimationStyle(type: string, transition: Transition
case ENTERING:
return {
animationName: 'fadeInDown',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
case EXITING:
return {
animationName: 'fadeOutUp',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
}
case TransitionAnimationTypes.HEAD_SHAKE:
@ -127,7 +127,7 @@ export function getTransitionAnimationStyle(type: string, transition: Transition
case ENTERING:
return {
animationName: 'headShake',
animationDuration: `${timeout}ms`
animationDuration: `${ timeout }ms`
}
}
}

View File

@ -8,7 +8,7 @@ import { AchievementsCategoryListView } from './views/category-list/Achievements
export const AchievementsView: FC<{}> = props =>
{
const [isVisible, setIsVisible] = useState(false);
const [ isVisible, setIsVisible ] = useState(false);
const { achievementCategories = [], selectedCategoryCode = null, setSelectedCategoryCode = null, achievementScore = 0, getProgress = 0, getMaxProgress = 0, selectedCategory = null } = useAchievements();
useEffect(() =>
@ -45,27 +45,27 @@ export const AchievementsView: FC<{}> = props =>
return (
<NitroCardView className="w-[375px] h-[405px]" theme="primary-slim" uniqueKey="achievements">
<NitroCardHeaderView headerText={LocalizeText('inventory.achievements')} onCloseClick={event => setIsVisible(false)} />
{selectedCategory &&
<NitroCardHeaderView headerText={ LocalizeText('inventory.achievements') } onCloseClick={ event => setIsVisible(false) } />
{ selectedCategory &&
<div className="flex relative gap-3 justify-center container-fluid p-1 bg-muted items-center cursor-pointer">
<div className="bg-[url('@/assets/images/achievements/back-arrow.png')] bg-center no-repeat w-[33px] h-[34px]" onClick={event => setSelectedCategoryCode(null)} />
<Column className="!flex-grow" gap={0}>
<Text className="text-small" fontSize={4} fontWeight="bold">{LocalizeText(`quests.${selectedCategory.code}.name`)}</Text>
<Text>{LocalizeText('achievements.details.categoryprogress', ['progress', 'limit'], [selectedCategory.getProgress().toString(), selectedCategory.getMaxProgress().toString()])}</Text>
<div className="bg-[url('@/assets/images/achievements/back-arrow.png')] bg-center no-repeat w-[33px] h-[34px]" onClick={ event => setSelectedCategoryCode(null) } />
<Column className="!flex-grow" gap={ 0 }>
<Text className="text-small" fontSize={ 4 } fontWeight="bold">{ LocalizeText(`quests.${ selectedCategory.code }.name`) }</Text>
<Text>{ LocalizeText('achievements.details.categoryprogress', [ 'progress', 'limit' ], [ selectedCategory.getProgress().toString(), selectedCategory.getMaxProgress().toString() ]) }</Text>
</Column>
<LayoutImage imageUrl={AchievementUtilities.getAchievementCategoryImageUrl(selectedCategory, null, true)} />
</div>}
<NitroCardContentView gap={1}>
{!selectedCategory &&
<LayoutImage imageUrl={ AchievementUtilities.getAchievementCategoryImageUrl(selectedCategory, null, true) } />
</div> }
<NitroCardContentView gap={ 1 }>
{ !selectedCategory &&
<>
<AchievementsCategoryListView categories={achievementCategories} selectedCategoryCode={selectedCategoryCode} setSelectedCategoryCode={setSelectedCategoryCode} />
<Column className="!flex-grow" gap={1} justifyContent="end">
<Text center small>{LocalizeText('achievements.categories.score', ['score'], [achievementScore.toString()])}</Text>
<LayoutProgressBar maxProgress={getMaxProgress} progress={getProgress} text={LocalizeText('achievements.categories.totalprogress', ['progress', 'limit'], [getProgress.toString(), getMaxProgress.toString()])} />
<AchievementsCategoryListView categories={ achievementCategories } selectedCategoryCode={ selectedCategoryCode } setSelectedCategoryCode={ setSelectedCategoryCode } />
<Column className="!flex-grow" gap={ 1 } justifyContent="end">
<Text center small>{ LocalizeText('achievements.categories.score', [ 'score' ], [ achievementScore.toString() ]) }</Text>
<LayoutProgressBar maxProgress={ getMaxProgress } progress={ getProgress } text={ LocalizeText('achievements.categories.totalprogress', [ 'progress', 'limit' ], [ getProgress.toString(), getMaxProgress.toString() ]) } />
</Column>
</>}
{selectedCategory &&
<AchievementCategoryView category={selectedCategory} />}
</> }
{ selectedCategory &&
<AchievementCategoryView category={ selectedCategory } /> }
</NitroCardContentView>
</NitroCardView>
);

View File

@ -16,37 +16,37 @@ export const AchievementDetailsView: FC<AchievementDetailsViewProps> = props =>
if (!achievement) return null;
return (
<Flex shrink className="bg-muted rounded p-2 text-black" gap={2} overflow="hidden">
<Column center gap={1}>
<AchievementBadgeView achievement={achievement} className="nitro-achievements-relative w-[40px] h-[40px] bg-no-repeat bg-center" scale={2} />
<Flex shrink className="bg-muted rounded p-2 text-black" gap={ 2 } overflow="hidden">
<Column center gap={ 1 }>
<AchievementBadgeView achievement={ achievement } className="nitro-achievements-relative w-[40px] h-[40px] bg-no-repeat bg-center" scale={ 2 } />
<Text fontWeight="bold">
{LocalizeText('achievements.details.level', ['level', 'limit'], [AchievementUtilities.getAchievementLevel(achievement).toString(), achievement.levelCount.toString()])}
{ LocalizeText('achievements.details.level', [ 'level', 'limit' ], [ AchievementUtilities.getAchievementLevel(achievement).toString(), achievement.levelCount.toString() ]) }
</Text>
</Column>
<Column fullWidth justifyContent="center" overflow="hidden">
<div className="flex flex-col gap-1">
<Text truncate fontWeight="bold">
{LocalizeBadgeName(AchievementUtilities.getAchievementBadgeCode(achievement))}
{ LocalizeBadgeName(AchievementUtilities.getAchievementBadgeCode(achievement)) }
</Text>
<Text textBreak>
{LocalizeBadgeDescription(AchievementUtilities.getAchievementBadgeCode(achievement))}
{ LocalizeBadgeDescription(AchievementUtilities.getAchievementBadgeCode(achievement)) }
</Text>
</div>
{((achievement.levelRewardPoints > 0) || (achievement.scoreLimit > 0)) &&
{ ((achievement.levelRewardPoints > 0) || (achievement.scoreLimit > 0)) &&
<div className="flex flex-col gap-1">
{(achievement.levelRewardPoints > 0) &&
{ (achievement.levelRewardPoints > 0) &&
<div className="flex items-center gap-1">
<Text truncate className="small">
{LocalizeText('achievements.details.reward')}
{ LocalizeText('achievements.details.reward') }
</Text>
<Flex center className="font-bold small" gap={1}>
{achievement.levelRewardPoints}
<LayoutCurrencyIcon type={achievement.levelRewardPointType} />
<Flex center className="font-bold small" gap={ 1 }>
{ achievement.levelRewardPoints }
<LayoutCurrencyIcon type={ achievement.levelRewardPointType } />
</Flex>
</div>}
{(achievement.scoreLimit > 0) &&
<LayoutProgressBar maxProgress={(achievement.scoreLimit + achievement.scoreAtStartOfLevel)} progress={(achievement.currentPoints + achievement.scoreAtStartOfLevel)} text={LocalizeText('achievements.details.progress', ['progress', 'limit'], [(achievement.currentPoints + achievement.scoreAtStartOfLevel).toString(), (achievement.scoreLimit + achievement.scoreAtStartOfLevel).toString()])} />}
</div>}
</div> }
{ (achievement.scoreLimit > 0) &&
<LayoutProgressBar maxProgress={ (achievement.scoreLimit + achievement.scoreAtStartOfLevel) } progress={ (achievement.currentPoints + achievement.scoreAtStartOfLevel) } text={ LocalizeText('achievements.details.progress', [ 'progress', 'limit' ], [ (achievement.currentPoints + achievement.scoreAtStartOfLevel).toString(), (achievement.scoreLimit + achievement.scoreAtStartOfLevel).toString() ]) } /> }
</div> }
</Column>
</Flex>
)

View File

@ -15,8 +15,8 @@ export const AchievementsCategoryListView: FC<AchievementsCategoryListViewProps>
const { categories = null, selectedCategoryCode = null, setSelectedCategoryCode = null } = props;
return (
<AutoGrid columnCount={3} columnMinWidth={90} columnMinHeight={100}>
{categories && (categories.length > 0) && categories.map((category, index) => <AchievementsCategoryListItemView key={index} category={category} selectedCategoryCode={selectedCategoryCode} setSelectedCategoryCode={setSelectedCategoryCode} />)}
<AutoGrid columnCount={ 3 } columnMinHeight={ 100 } columnMinWidth={ 90 }>
{ categories && (categories.length > 0) && categories.map((category, index) => <AchievementsCategoryListItemView key={ index } category={ category } selectedCategoryCode={ selectedCategoryCode } setSelectedCategoryCode={ setSelectedCategoryCode } />) }
</AutoGrid>
);
};

View File

@ -8,7 +8,7 @@ const DEFAULT_DIRECTION: number = 4;
export const AvatarEditorFigurePreviewView: FC<{}> = props =>
{
const [direction, setDirection] = useState<number>(DEFAULT_DIRECTION);
const [ direction, setDirection ] = useState<number>(DEFAULT_DIRECTION);
const { getFigureString = null } = useAvatarEditor();
const rotateFigure = (newDirection: number) =>
@ -28,12 +28,12 @@ export const AvatarEditorFigurePreviewView: FC<{}> = props =>
return (
<div className="flex flex-col figure-preview-container overflow-hidden relative">
<LayoutAvatarImageView direction={direction} figure={getFigureString} scale={2} />
<LayoutAvatarImageView direction={ direction } figure={ getFigureString } scale={ 2 } />
<AvatarEditorIcon className="avatar-spotlight" icon="spotlight" />
<div className="avatar-shadow" />
<div className="arrow-container">
<AvatarEditorIcon icon="arrow-left" onClick={event => rotateFigure(direction + 1)} />
<AvatarEditorIcon icon="arrow-right" onClick={event => rotateFigure(direction - 1)} />
<AvatarEditorIcon icon="arrow-left" onClick={ event => rotateFigure(direction + 1) } />
<AvatarEditorIcon icon="arrow-right" onClick={ event => rotateFigure(direction - 1) } />
</div>
</div>
);

View File

@ -30,16 +30,16 @@ export const AvatarEditorIcon = forwardRef<HTMLDivElement, PropsWithChildren<{
*/
return (
<div
ref={ref}
ref={ ref }
className={classNames(
className={ classNames(
'nitro-avatar-editor-spritesheet',
'cursor-pointer',
`${icon}-icon`,
`${ icon }-icon`,
selected && 'selected',
className
)}
{...rest} />
) }
{ ...rest } />
);
});

View File

@ -10,71 +10,71 @@ export const AvatarEditorModelView: FC<{
name: string,
categories: IAvatarEditorCategory[]
}> = props =>
{
const { name = '', categories = [] } = props;
const [ didChange, setDidChange ] = useState<boolean>(false);
const [ activeSetType, setActiveSetType ] = useState<string>('');
const { maxPaletteCount = 1, gender = null, setGender = null, selectedColorParts = null, getFirstSelectableColor = null, selectEditorColor = null } = useAvatarEditor();
const activeCategory = useMemo(() =>
{
const { name = '', categories = [] } = props;
const [didChange, setDidChange] = useState<boolean>(false);
const [activeSetType, setActiveSetType] = useState<string>('');
const { maxPaletteCount = 1, gender = null, setGender = null, selectedColorParts = null, getFirstSelectableColor = null, selectEditorColor = null } = useAvatarEditor();
return categories.find(category => category.setType === activeSetType) ?? null;
}, [ categories, activeSetType ]);
const activeCategory = useMemo(() =>
{
return categories.find(category => category.setType === activeSetType) ?? null;
}, [categories, activeSetType]);
const selectSet = useCallback((setType: string) =>
{
const selectedPalettes = selectedColorParts[setType];
const selectSet = useCallback((setType: string) =>
{
const selectedPalettes = selectedColorParts[setType];
if (!selectedPalettes || !selectedPalettes.length) selectEditorColor(setType, 0, getFirstSelectableColor(setType));
if (!selectedPalettes || !selectedPalettes.length) selectEditorColor(setType, 0, getFirstSelectableColor(setType));
setActiveSetType(setType);
}, [ getFirstSelectableColor, selectEditorColor, selectedColorParts ]);
setActiveSetType(setType);
}, [getFirstSelectableColor, selectEditorColor, selectedColorParts]);
useEffect(() =>
{
if (!categories || !categories.length || !didChange) return;
useEffect(() =>
{
if (!categories || !categories.length || !didChange) return;
selectSet(categories[0]?.setType);
setDidChange(false);
}, [ categories, didChange, selectSet ]);
selectSet(categories[0]?.setType);
setDidChange(false);
}, [categories, didChange, selectSet]);
useEffect(() =>
{
setDidChange(true);
}, [ categories ]);
useEffect(() =>
{
setDidChange(true);
}, [categories]);
if (!activeCategory) return null;
if (!activeCategory) return null;
return (
<div className="grid grid-cols-12 gap-2 overflow-hidden">
<div className="flex flex-col col-span-2">
{(name === AvatarEditorFigureCategory.GENERIC) &&
return (
<div className="grid grid-cols-12 gap-2 overflow-hidden">
<div className="flex flex-col col-span-2">
{ (name === AvatarEditorFigureCategory.GENERIC) &&
<>
<div className="category-item items-center justify-center cursor-pointer flex" onClick={event => setGender(AvatarFigurePartType.MALE)}>
<AvatarEditorIcon icon="male" selected={gender === FigureDataContainer.MALE} />
<div className="category-item items-center justify-center cursor-pointer flex" onClick={ event => setGender(AvatarFigurePartType.MALE) }>
<AvatarEditorIcon icon="male" selected={ gender === FigureDataContainer.MALE } />
</div>
<div className="category-item items-center justify-center cursor-pointer flex" onClick={event => setGender(AvatarFigurePartType.FEMALE)}>
<AvatarEditorIcon icon="female" selected={gender === FigureDataContainer.FEMALE} />
<div className="category-item items-center justify-center cursor-pointer flex" onClick={ event => setGender(AvatarFigurePartType.FEMALE) }>
<AvatarEditorIcon icon="female" selected={ gender === FigureDataContainer.FEMALE } />
</div>
</>}
{(name !== AvatarEditorFigureCategory.GENERIC) && (categories.length > 0) && categories.map(category =>
{
return (
<div key={category.setType} className="category-item items-center justify-center cursor-pointer flex" onClick={event => selectSet(category.setType)}>
<AvatarEditorIcon icon={category.setType} selected={(activeSetType === category.setType)} />
</div>
);
})}
</div>
<div className="flex flex-col overflow-hidden col-span-5">
<AvatarEditorFigureSetView category={activeCategory} columnCount={3} />
</div>
<div className="flex flex-col overflow-hidden col-span-5">
{(maxPaletteCount >= 1) &&
<AvatarEditorPaletteSetView category={activeCategory} columnCount={5} paletteIndex={0} />}
{(maxPaletteCount === 2) &&
<AvatarEditorPaletteSetView category={activeCategory} columnCount={5} paletteIndex={1} />}
</div>
</> }
{ (name !== AvatarEditorFigureCategory.GENERIC) && (categories.length > 0) && categories.map(category =>
{
return (
<div key={ category.setType } className="category-item items-center justify-center cursor-pointer flex" onClick={ event => selectSet(category.setType) }>
<AvatarEditorIcon icon={ category.setType } selected={ (activeSetType === category.setType) } />
</div>
);
}) }
</div>
);
}
<div className="flex flex-col overflow-hidden col-span-5">
<AvatarEditorFigureSetView category={ activeCategory } columnCount={ 3 } />
</div>
<div className="flex flex-col overflow-hidden col-span-5">
{ (maxPaletteCount >= 1) &&
<AvatarEditorPaletteSetView category={ activeCategory } columnCount={ 5 } paletteIndex={ 0 } /> }
{ (maxPaletteCount === 2) &&
<AvatarEditorPaletteSetView category={ activeCategory } columnCount={ 5 } paletteIndex={ 1 } /> }
</div>
</div>
);
}

View File

@ -13,7 +13,7 @@ const DEFAULT_FEMALE_FIGURE: string = 'hr-515-33.hd-600-1.ch-635-70.lg-716-66-62
export const AvatarEditorView: FC<{}> = props =>
{
const [isVisible, setIsVisible] = useState(false);
const [ isVisible, setIsVisible ] = useState(false);
const { setIsVisible: setEditorVisibility, avatarModels, activeModelKey, setActiveModelKey, loadAvatarData, getFigureStringWithFace, gender, figureSetIds = [], randomizeCurrentFigure = null, getFigureString = null } = useAvatarEditor();
const processAction = (action: string) =>
@ -69,49 +69,49 @@ export const AvatarEditorView: FC<{}> = props =>
useEffect(() =>
{
setEditorVisibility(isVisible)
}, [isVisible, setEditorVisibility]);
}, [ isVisible, setEditorVisibility ]);
if (!isVisible) return null;
return (
<NitroCardView className="w-[620px] h-[374px] nitro-avatar-editor" uniqueKey="avatar-editor">
<NitroCardHeaderView headerText={LocalizeText('avatareditor.title')} onCloseClick={event => setIsVisible(false)} />
<NitroCardHeaderView headerText={ LocalizeText('avatareditor.title') } onCloseClick={ event => setIsVisible(false) } />
<NitroCardTabsView>
{Object.keys(avatarModels).map(modelKey =>
{ Object.keys(avatarModels).map(modelKey =>
{
const isActive = (activeModelKey === modelKey);
return (
<NitroCardTabsItemView key={modelKey} isActive={isActive} onClick={event => setActiveModelKey(modelKey)}>
{LocalizeText(`avatareditor.category.${modelKey}`)}
<NitroCardTabsItemView key={ modelKey } isActive={ isActive } onClick={ event => setActiveModelKey(modelKey) }>
{ LocalizeText(`avatareditor.category.${ modelKey }`) }
</NitroCardTabsItemView>
);
})}
}) }
</NitroCardTabsView>
<NitroCardContentView>
<Grid className="grid gap-2 overflow-hidden">
<div className="flex flex-col col-span-9 overflow-hidden">
{((activeModelKey.length > 0) && (activeModelKey !== AvatarEditorFigureCategory.WARDROBE)) &&
<AvatarEditorModelView categories={avatarModels[activeModelKey]} name={activeModelKey} />}
{(activeModelKey === AvatarEditorFigureCategory.WARDROBE) &&
<AvatarEditorWardrobeView />}
{ ((activeModelKey.length > 0) && (activeModelKey !== AvatarEditorFigureCategory.WARDROBE)) &&
<AvatarEditorModelView categories={ avatarModels[activeModelKey] } name={ activeModelKey } /> }
{ (activeModelKey === AvatarEditorFigureCategory.WARDROBE) &&
<AvatarEditorWardrobeView /> }
</div>
<div className="flex flex-col col-span-3 overflow-hidden gap-1">
<AvatarEditorFigurePreviewView />
<div className="flex flex-col !flex-grow gap-1">
<div className="relative inline-flex align-middle">
<Button className='flex-auto ' variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_RESET)}>
<Button className="flex-auto " variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RESET) }>
<FaRedo className="fa-icon" />
</Button>
<Button className='flex-auto' variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_CLEAR)}>
<Button className="flex-auto" variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_CLEAR) }>
<FaTrash className="fa-icon" />
</Button>
<Button className='flex-auto' variant="secondary" onClick={event => processAction(AvatarEditorAction.ACTION_RANDOMIZE)}>
<Button className="flex-auto" variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RANDOMIZE) }>
<FaDice className="fa-icon" />
</Button>
</div>
<Button className="w-full" variant="success" onClick={event => processAction(AvatarEditorAction.ACTION_SAVE)}>
{LocalizeText('avatareditor.save')}
<Button className="w-full" variant="success" onClick={ event => processAction(AvatarEditorAction.ACTION_SAVE) }>
{ LocalizeText('avatareditor.save') }
</Button>
</div>
</div>

View File

@ -10,16 +10,16 @@ export const AvatarEditorPaletteSetItem: FC<{
isSelected: boolean;
width?: string;
} & LayoutGridItemProps> = props =>
{
const { setType = null, partColor = null, isSelected = false, width = '100%', ...rest } = props;
{
const { setType = null, partColor = null, isSelected = false, width = '100%', ...rest } = props;
if (!partColor) return null;
if (!partColor) return null;
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && (partColor.clubLevel > 0);
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && (partColor.clubLevel > 0);
return (
<InfiniteGrid.Item itemHighlight className="clear-bg" itemActive={isSelected} itemColor={ColorConverter.int2rgb(partColor.rgb)} {...rest}>
{isHC && <LayoutCurrencyIcon className="absolute end-1 bottom-1" type="hc" />}
</InfiniteGrid.Item>
);
}
return (
<InfiniteGrid.Item itemHighlight className="clear-bg" itemActive={ isSelected } itemColor={ ColorConverter.int2rgb(partColor.rgb) } { ...rest }>
{ isHC && <LayoutCurrencyIcon className="absolute end-1 bottom-1" type="hc" /> }
</InfiniteGrid.Item>
);
}

View File

@ -10,27 +10,27 @@ export const AvatarEditorPaletteSetView: FC<{
paletteIndex: number;
columnCount: number;
}> = props =>
{
const { category = null, paletteIndex = -1, columnCount = 3 } = props;
const { selectedColorParts = null, selectEditorColor = null } = useAvatarEditor();
const isPartColorSelected = (partColor: IPartColor) =>
{
const { category = null, paletteIndex = -1, columnCount = 3 } = props;
const { selectedColorParts = null, selectEditorColor = null } = useAvatarEditor();
if (!category || !category.setType || !selectedColorParts || !selectedColorParts[category.setType] || !selectedColorParts[category.setType][paletteIndex]) return false;
const isPartColorSelected = (partColor: IPartColor) =>
{
if (!category || !category.setType || !selectedColorParts || !selectedColorParts[category.setType] || !selectedColorParts[category.setType][paletteIndex]) return false;
const selectedColorPart = selectedColorParts[category.setType][paletteIndex];
const selectedColorPart = selectedColorParts[category.setType][paletteIndex];
return (selectedColorPart.id === partColor.id);
}
return (
<InfiniteGrid<IPartColor> columnCount={columnCount} itemRender={(item: IPartColor) =>
{
if (!item) return null;
return (
<AvatarEditorPaletteSetItem isSelected={isPartColorSelected(item)} partColor={item} setType={category.setType} width={`calc(100% / ${columnCount}`} onClick={event => selectEditorColor(category.setType, paletteIndex, item.id)} />
)
}} items={category.colorItems[paletteIndex]} overscan={columnCount} />
);
return (selectedColorPart.id === partColor.id);
}
return (
<InfiniteGrid<IPartColor> columnCount={ columnCount } itemRender={ (item: IPartColor) =>
{
if (!item) return null;
return (
<AvatarEditorPaletteSetItem isSelected={ isPartColorSelected(item) } partColor={ item } setType={ category.setType } width={ `calc(100% / ${ columnCount }` } onClick={ event => selectEditorColor(category.setType, paletteIndex, item.id) } />
)
} } items={ category.colorItems[paletteIndex] } overscan={ columnCount } />
);
}

View File

@ -12,9 +12,9 @@ const MODE_CHECKOUT: number = 3;
export const CameraWidgetView: FC<{}> = props =>
{
const [mode, setMode] = useState<number>(MODE_NONE);
const [base64Url, setSavedPictureUrl] = useState<string>(null);
const { availableEffects = [], selectedPictureIndex = -1, cameraRoll = [], setCameraRoll = null, myLevel = 0, price = { credits: 0, duckets: 0, publishDucketPrice: 0 } } = useCamera();
const [ mode, setMode ] = useState<number>(MODE_NONE);
const [ base64Url, setSavedPictureUrl ] = useState<string>(null);
const { availableEffects = [], selectedPictureIndex = -1, cameraRoll = [], setCameraRoll = null, myLevel = 0, price = { credits: 0, duckets: 0, publishDucketPrice: 0 }} = useCamera();
const processAction = (type: string) =>
@ -30,7 +30,7 @@ export const CameraWidgetView: FC<{}> = props =>
case 'delete':
setCameraRoll(prevValue =>
{
const clone = [...prevValue];
const clone = [ ...prevValue ];
clone.splice(selectedPictureIndex, 1);
@ -89,9 +89,9 @@ export const CameraWidgetView: FC<{}> = props =>
return (
<>
{(mode === MODE_CAPTURE) && <CameraWidgetCaptureView onClose={() => processAction('close')} onDelete={() => processAction('delete')} onEdit={() => processAction('edit')} />}
{(mode === MODE_EDITOR) && <CameraWidgetEditorView availableEffects={availableEffects} myLevel={myLevel} picture={cameraRoll[selectedPictureIndex]} onCancel={() => processAction('editor_cancel')} onCheckout={checkoutPictureUrl} onClose={() => processAction('close')} />}
{(mode === MODE_CHECKOUT) && <CameraWidgetCheckoutView base64Url={base64Url} price={price} onCancelClick={() => processAction('editor_cancel')} onCloseClick={() => processAction('close')}></CameraWidgetCheckoutView>}
{ (mode === MODE_CAPTURE) && <CameraWidgetCaptureView onClose={ () => processAction('close') } onDelete={ () => processAction('delete') } onEdit={ () => processAction('edit') } /> }
{ (mode === MODE_EDITOR) && <CameraWidgetEditorView availableEffects={ availableEffects } myLevel={ myLevel } picture={ cameraRoll[selectedPictureIndex] } onCancel={ () => processAction('editor_cancel') } onCheckout={ checkoutPictureUrl } onClose={ () => processAction('close') } /> }
{ (mode === MODE_CHECKOUT) && <CameraWidgetCheckoutView base64Url={ base64Url } price={ price } onCancelClick={ () => processAction('editor_cancel') } onCloseClick={ () => processAction('close') }></CameraWidgetCheckoutView> }
</>
);
}

View File

@ -42,7 +42,7 @@ export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props =
const texture = GetRoomEngine().createTextureFromRoom(GetRoomSession().roomId, 1, getCameraBounds());
const clone = [...cameraRoll];
const clone = [ ...cameraRoll ];
if (clone.length >= CAMERA_ROLL_LIMIT)
{
@ -59,31 +59,31 @@ export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props =
return (
<DraggableWindow uniqueKey="nitro-camera-capture">
<Column center className="relative" gap={0}>
{selectedPicture && <img alt="" className="absolute top-[37px] left-[10px] w-[320px] h-[320px]" src={selectedPicture.imageUrl} />}
<Column center className="relative" gap={ 0 }>
{ selectedPicture && <img alt="" className="absolute top-[37px] left-[10px] w-[320px] h-[320px]" src={ selectedPicture.imageUrl } /> }
<div className="relative w-[340px] h-[462px] bg-[url('@/assets/images/room-widgets/camera-widget/camera-spritesheet.png')] bg-[-1px_-1px] drag-handler">
<div className="absolute top-[8px] right-[8px] rounded-[.25rem] [box-shadow:0_0_0_1.5px_#fff] border-[2px] border-[solid] border-[#921911] bg-[repeating-linear-gradient(rgb(245,_80,_65),_rgb(245,_80,_65)_50%,_rgb(194,_48,_39)_50%,_rgb(194,_48,_39)_100%)] cursor-pointer leading-none px-[3px] py-px" onClick={onClose}>
<div className="absolute top-[8px] right-[8px] rounded-[.25rem] [box-shadow:0_0_0_1.5px_#fff] border-[2px] border-[solid] border-[#921911] bg-[repeating-linear-gradient(rgb(245,_80,_65),_rgb(245,_80,_65)_50%,_rgb(194,_48,_39)_50%,_rgb(194,_48,_39)_100%)] cursor-pointer leading-none px-[3px] py-px" onClick={ onClose }>
<FaTimes className="fa-icon" />
</div>
{!selectedPicture && <div ref={elementRef} className="absolute top-[37px] left-[10px] w-[320px] h-[320px] bg-[url('@/assets/images/room-widgets/camera-widget/camera-spritesheet.png')] bg-[-343px_-1px]" />}
{selectedPicture &&
{ !selectedPicture && <div ref={ elementRef } className="absolute top-[37px] left-[10px] w-[320px] h-[320px] bg-[url('@/assets/images/room-widgets/camera-widget/camera-spritesheet.png')] bg-[-343px_-1px]" /> }
{ selectedPicture &&
<div className="absolute top-[37px] left-[10px] w-[320px] h-[320px] ">
<div className="bg-[rgba(0,_0,_0,_0.5)] w-full absolute bottom-0 py-2 text-center inline-flex justify-center">
<Button variant='success' className="me-3" title={LocalizeText('camera.editor.button.tooltip')} onClick={onEdit}>{LocalizeText('camera.editor.button.text')}</Button>
<Button variant='danger' onClick={onDelete}>{LocalizeText('camera.delete.button.text')}</Button>
<Button className="me-3" title={ LocalizeText('camera.editor.button.tooltip') } variant="success" onClick={ onEdit }>{ LocalizeText('camera.editor.button.text') }</Button>
<Button variant="danger" onClick={ onDelete }>{ LocalizeText('camera.delete.button.text') }</Button>
</div>
</div>}
</div> }
<div className="flex justify-center">
<div className="w-[94px] h-[94px] cursor-pointer mt-[362px] bg-[url('@/assets/images/room-widgets/camera-widget/camera-spritesheet.png')] bg-[-343px_-321px]" title={LocalizeText('camera.take.photo.button.tooltip')} onClick={takePicture} />
<div className="w-[94px] h-[94px] cursor-pointer mt-[362px] bg-[url('@/assets/images/room-widgets/camera-widget/camera-spritesheet.png')] bg-[-343px_-321px]" title={ LocalizeText('camera.take.photo.button.tooltip') } onClick={ takePicture } />
</div>
</div>
{(cameraRoll.length > 0) &&
{ (cameraRoll.length > 0) &&
<div className="w-[330px] bg-[#bab8b4] rounded-bl-[10px] rounded-br-[10px] border-[1px] border-[solid] border-[black] [box-shadow:inset_1px_0px_white,_inset_-1px_-1px_white] flex justify-center py-2">
{cameraRoll.map((picture, index) =>
{ cameraRoll.map((picture, index) =>
{
return <img className='w-[56px] h-[56px] border-[1px] border-[solid] border-[black] object-contain [image-rendering:initial]' key={index} alt="" src={picture.imageUrl} onClick={event => setSelectedPictureIndex(index)} />;
})}
</div>}
return <img key={ index } alt="" className="w-[56px] h-[56px] border-[1px] border-[solid] border-[black] object-contain [image-rendering:initial]" src={ picture.imageUrl } onClick={ event => setSelectedPictureIndex(index) } />;
}) }
</div> }
</Column>
</DraggableWindow>
);

View File

@ -15,12 +15,12 @@ export interface CameraWidgetCheckoutViewProps
export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props =>
{
const { base64Url = null, onCloseClick = null, onCancelClick = null, price = null } = props;
const [pictureUrl, setPictureUrl] = useState<string>(null);
const [publishUrl, setPublishUrl] = useState<string>(null);
const [picturesBought, setPicturesBought] = useState(0);
const [wasPicturePublished, setWasPicturePublished] = useState(false);
const [isWaiting, setIsWaiting] = useState(false);
const [publishCooldown, setPublishCooldown] = useState(0);
const [ pictureUrl, setPictureUrl ] = useState<string>(null);
const [ publishUrl, setPublishUrl ] = useState<string>(null);
const [ picturesBought, setPicturesBought ] = useState(0);
const [ wasPicturePublished, setWasPicturePublished ] = useState(false);
const [ isWaiting, setIsWaiting ] = useState(false);
const [ publishCooldown, setPublishCooldown ] = useState(0);
const publishDisabled = useMemo(() => GetConfigurationValue<boolean>('camera.publish.disabled', false), []);
@ -77,81 +77,81 @@ export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props
if (!base64Url) return;
GetRoomEngine().saveBase64AsScreenshot(base64Url);
}, [base64Url]);
}, [ base64Url ]);
if (!price) return null;
return (
<NitroCardView className="nitro-camera-checkout" theme="primary-slim">
<NitroCardHeaderView headerText={LocalizeText('camera.confirm_phase.title')} onCloseClick={event => processAction('close')} />
<NitroCardHeaderView headerText={ LocalizeText('camera.confirm_phase.title') } onCloseClick={ event => processAction('close') } />
<NitroCardContentView>
<div className="flex items-center justify-center">
{(pictureUrl && pictureUrl.length) &&
<LayoutImage className="picture-preview border" imageUrl={pictureUrl} />}
{(!pictureUrl || !pictureUrl.length) &&
{ (pictureUrl && pictureUrl.length) &&
<LayoutImage className="picture-preview border" imageUrl={ pictureUrl } /> }
{ (!pictureUrl || !pictureUrl.length) &&
<div className="flex items-center justify-center picture-preview border">
<Text bold>{LocalizeText('camera.loading')}</Text>
</div>}
<Text bold>{ LocalizeText('camera.loading') }</Text>
</div> }
</div>
<div className="flex items-center bg-muted rounded p-2 justify-between">
<Column gap={1} size={publishDisabled ? 10 : 6}>
<Column gap={ 1 } size={ publishDisabled ? 10 : 6 }>
<Text bold>
{LocalizeText('camera.purchase.header')}
{ LocalizeText('camera.purchase.header') }
</Text>
{((price.credits > 0) || (price.duckets > 0)) &&
{ ((price.credits > 0) || (price.duckets > 0)) &&
<div className="flex gap-1">
<Text>{LocalizeText('catalog.purchase.confirmation.dialog.cost')}</Text>
{(price.credits > 0) &&
<Text>{ LocalizeText('catalog.purchase.confirmation.dialog.cost') }</Text>
{ (price.credits > 0) &&
<div className="flex gap-1">
<Text bold>{price.credits}</Text>
<LayoutCurrencyIcon type={-1} />
</div>}
{(price.duckets > 0) &&
<Text bold>{ price.credits }</Text>
<LayoutCurrencyIcon type={ -1 } />
</div> }
{ (price.duckets > 0) &&
<div className="flex gap-1">
<Text bold>{price.duckets}</Text>
<LayoutCurrencyIcon type={5} />
</div>}
</div>}
{(picturesBought > 0) &&
<Text bold>{ price.duckets }</Text>
<LayoutCurrencyIcon type={ 5 } />
</div> }
</div> }
{ (picturesBought > 0) &&
<Text>
<Text bold>{LocalizeText('camera.purchase.count.info')}</Text> {picturesBought}
<u className="ms-1 cursor-pointer" onClick={() => CreateLinkEvent('inventory/toggle')}>{LocalizeText('camera.open.inventory')}</u>
</Text>}
<Text bold>{ LocalizeText('camera.purchase.count.info') }</Text> { picturesBought }
<u className="ms-1 cursor-pointer" onClick={ () => CreateLinkEvent('inventory/toggle') }>{ LocalizeText('camera.open.inventory') }</u>
</Text> }
</Column>
<div className="flex items-center">
<Button disabled={isWaiting} variant="success" onClick={event => processAction('buy')}>{LocalizeText(!picturesBought ? 'buy' : 'camera.buy.another.button.text')}</Button>
<Button disabled={ isWaiting } variant="success" onClick={ event => processAction('buy') }>{ LocalizeText(!picturesBought ? 'buy' : 'camera.buy.another.button.text') }</Button>
</div>
</div>
{!publishDisabled &&
{ !publishDisabled &&
<div className="flex items-center justify-between bg-muted rounded p-2">
<div className="flex flex-col gap-1">
<Text bold>
{LocalizeText(wasPicturePublished ? 'camera.publish.successful' : 'camera.publish.explanation')}
{ LocalizeText(wasPicturePublished ? 'camera.publish.successful' : 'camera.publish.explanation') }
</Text>
<Text>
{LocalizeText(wasPicturePublished ? 'camera.publish.success.short.info' : 'camera.publish.detailed.explanation')}
{ LocalizeText(wasPicturePublished ? 'camera.publish.success.short.info' : 'camera.publish.detailed.explanation') }
</Text>
{wasPicturePublished && <a href={publishUrl} rel="noreferrer" target="_blank">{LocalizeText('camera.link.to.published')}</a>}
{!wasPicturePublished && (price.publishDucketPrice > 0) &&
{ wasPicturePublished && <a href={ publishUrl } rel="noreferrer" target="_blank">{ LocalizeText('camera.link.to.published') }</a> }
{ !wasPicturePublished && (price.publishDucketPrice > 0) &&
<div className="flex gap-1">
<Text>{LocalizeText('catalog.purchase.confirmation.dialog.cost')}</Text>
<Text>{ LocalizeText('catalog.purchase.confirmation.dialog.cost') }</Text>
<div className="flex gap-1">
<Text bold>{price.publishDucketPrice}</Text>
<LayoutCurrencyIcon type={5} />
<Text bold>{ price.publishDucketPrice }</Text>
<LayoutCurrencyIcon type={ 5 } />
</div>
</div>}
{(publishCooldown > 0) && <div className="mt-1 text-center font-bold ">{LocalizeText('camera.publish.wait', ['minutes'], [Math.ceil(publishCooldown / 60).toString()])}</div>}
</div> }
{ (publishCooldown > 0) && <div className="mt-1 text-center font-bold ">{ LocalizeText('camera.publish.wait', [ 'minutes' ], [ Math.ceil(publishCooldown / 60).toString() ]) }</div> }
</div>
{!wasPicturePublished &&
{ !wasPicturePublished &&
<div className="flex align-items-end">
<Button disabled={(isWaiting || (publishCooldown > 0))} variant="success" onClick={event => processAction('publish')}>
{LocalizeText('camera.publish.button.text')}
<Button disabled={ (isWaiting || (publishCooldown > 0)) } variant="success" onClick={ event => processAction('publish') }>
{ LocalizeText('camera.publish.button.text') }
</Button>
</div>}
</div>}
<Text center>{LocalizeText('camera.warning.disclaimer')}</Text>
</div> }
</div> }
<Text center>{ LocalizeText('camera.warning.disclaimer') }</Text>
<div className="flex justify-end">
<Button onClick={event => processAction('cancel')}>{LocalizeText('generic.cancel')}</Button>
<Button onClick={ event => processAction('cancel') }>{ LocalizeText('generic.cancel') }</Button>
</div>
</NitroCardContentView>
</NitroCardView>

View File

@ -12,7 +12,7 @@ export interface CameraWidgetShowPhotoViewProps
export const CameraWidgetShowPhotoView: FC<CameraWidgetShowPhotoViewProps> = props =>
{
const { currentIndex = -1, currentPhotos = null } = props;
const [imageIndex, setImageIndex] = useState(0);
const [ imageIndex, setImageIndex ] = useState(0);
const currentImage = (currentPhotos && currentPhotos.length) ? currentPhotos[imageIndex] : null;
@ -43,27 +43,27 @@ export const CameraWidgetShowPhotoView: FC<CameraWidgetShowPhotoViewProps> = pro
useEffect(() =>
{
setImageIndex(currentIndex);
}, [currentIndex]);
}, [ currentIndex ]);
if (!currentImage) return null;
return (
<Grid style={{ display: 'flex', flexDirection: 'column' }}>
<Flex center className="picture-preview border border-black" style={currentImage.w ? { backgroundImage: 'url(' + currentImage.w + ')' } : {}}>
{!currentImage.w &&
<Text bold>{LocalizeText('camera.loading')}</Text>}
<Grid style={ { display: 'flex', flexDirection: 'column' } }>
<Flex center className="picture-preview border border-black" style={ currentImage.w ? { backgroundImage: 'url(' + currentImage.w + ')' } : {} }>
{ !currentImage.w &&
<Text bold>{ LocalizeText('camera.loading') }</Text> }
</Flex>
{currentImage.m && currentImage.m.length &&
<Text center>{currentImage.m}</Text>}
{ currentImage.m && currentImage.m.length &&
<Text center>{ currentImage.m }</Text> }
<div className="flex items-center justify-between">
<Text>{(currentImage.n || '')}</Text>
<Text>{new Date(currentImage.t * 1000).toLocaleDateString()}</Text>
<Text>{ (currentImage.n || '') }</Text>
<Text>{ new Date(currentImage.t * 1000).toLocaleDateString() }</Text>
</div>
{(currentPhotos.length > 1) &&
{ (currentPhotos.length > 1) &&
<Flex className="picture-preview-buttons">
<FaArrowLeft className="cursor-pointer picture-preview-buttons-previous fa-icon" onClick={previous} />
<Text underline className="cursor-pointer" onClick={event => GetUserProfile(currentImage.oi)}>{currentImage.o}</Text>
<FaArrowRight className="cursor-pointer picture-preview-buttons-next fa-icon" onClick={next} />
<FaArrowLeft className="cursor-pointer picture-preview-buttons-previous fa-icon" onClick={ previous } />
<Text underline className="cursor-pointer" onClick={ event => GetUserProfile(currentImage.oi) }>{ currentImage.o }</Text>
<FaArrowRight className="cursor-pointer picture-preview-buttons-next fa-icon" onClick={ next } />
</Flex>
}
</Grid>

View File

@ -16,27 +16,27 @@ export interface CameraWidgetEditorViewProps
onCheckout: (pictureUrl: string) => void;
}
const TABS: string[] = [CameraEditorTabs.COLORMATRIX, CameraEditorTabs.COMPOSITE];
const TABS: string[] = [ CameraEditorTabs.COLORMATRIX, CameraEditorTabs.COMPOSITE ];
export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
{
const { picture = null, availableEffects = null, myLevel = 1, onClose = null, onCancel = null, onCheckout = null } = props;
const [currentTab, setCurrentTab] = useState(TABS[0]);
const [selectedEffectName, setSelectedEffectName] = useState<string>(null);
const [selectedEffects, setSelectedEffects] = useState<IRoomCameraWidgetSelectedEffect[]>([]);
const [effectsThumbnails, setEffectsThumbnails] = useState<CameraPictureThumbnail[]>([]);
const [isZoomed, setIsZoomed] = useState(false);
const [currentPictureUrl, setCurrentPictureUrl] = useState<string>('');
const [ currentTab, setCurrentTab ] = useState(TABS[0]);
const [ selectedEffectName, setSelectedEffectName ] = useState<string>(null);
const [ selectedEffects, setSelectedEffects ] = useState<IRoomCameraWidgetSelectedEffect[]>([]);
const [ effectsThumbnails, setEffectsThumbnails ] = useState<CameraPictureThumbnail[]>([]);
const [ isZoomed, setIsZoomed ] = useState(false);
const [ currentPictureUrl, setCurrentPictureUrl ] = useState<string>('');
const getColorMatrixEffects = useMemo(() =>
{
return availableEffects.filter(effect => effect.colorMatrix);
}, [availableEffects]);
}, [ availableEffects ]);
const getCompositeEffects = useMemo(() =>
{
return availableEffects.filter(effect => effect.texture);
}, [availableEffects]);
}, [ availableEffects ]);
const getEffectList = useCallback(() =>
{
@ -46,26 +46,26 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
}
return getCompositeEffects;
}, [currentTab, getColorMatrixEffects, getCompositeEffects]);
}, [ currentTab, getColorMatrixEffects, getCompositeEffects ]);
const getSelectedEffectIndex = useCallback((name: string) =>
{
if (!name || !name.length || !selectedEffects || !selectedEffects.length) return -1;
return selectedEffects.findIndex(effect => (effect.effect.name === name));
}, [selectedEffects])
}, [ selectedEffects ])
const getCurrentEffectIndex = useMemo(() =>
{
return getSelectedEffectIndex(selectedEffectName)
}, [selectedEffectName, getSelectedEffectIndex])
}, [ selectedEffectName, getSelectedEffectIndex ])
const getCurrentEffect = useMemo(() =>
{
if (!selectedEffectName) return null;
return (selectedEffects[getCurrentEffectIndex] || null);
}, [selectedEffectName, getCurrentEffectIndex, selectedEffects]);
}, [ selectedEffectName, getCurrentEffectIndex, selectedEffects ]);
const setSelectedEffectAlpha = useCallback((alpha: number) =>
{
@ -75,14 +75,14 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
setSelectedEffects(prevValue =>
{
const clone = [...prevValue];
const clone = [ ...prevValue ];
const currentEffect = clone[index];
clone[getCurrentEffectIndex] = new RoomCameraWidgetSelectedEffect(currentEffect.effect, alpha);
return clone;
});
}, [getCurrentEffectIndex, setSelectedEffects]);
}, [ getCurrentEffectIndex, setSelectedEffects ]);
const processAction = useCallback((type: string, effectName: string = null) =>
{
@ -111,7 +111,7 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
setSelectedEffects(prevValue =>
{
return [...prevValue, new RoomCameraWidgetSelectedEffect(effect, 1)];
return [ ...prevValue, new RoomCameraWidgetSelectedEffect(effect, 1) ];
});
setSelectedEffectName(effect.name);
@ -124,7 +124,7 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
setSelectedEffects(prevValue =>
{
const clone = [...prevValue];
const clone = [ ...prevValue ];
clone.splice(existingIndex, 1);
@ -154,7 +154,7 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
setIsZoomed(!isZoomed);
return;
}
}, [isZoomed, availableEffects, selectedEffectName, currentPictureUrl, getSelectedEffectIndex, onCancel, onCheckout, onClose, setIsZoomed, setSelectedEffects]);
}, [ isZoomed, availableEffects, selectedEffectName, currentPictureUrl, getSelectedEffectIndex, onCancel, onCheckout, onClose, setIsZoomed, setSelectedEffects ]);
useEffect(() =>
{
@ -164,14 +164,14 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
for await (const effect of availableEffects)
{
const image = await GetRoomCameraWidgetManager().applyEffects(picture.texture, [new RoomCameraWidgetSelectedEffect(effect, 1)], false);
const image = await GetRoomCameraWidgetManager().applyEffects(picture.texture, [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false);
thumbnails.push(new CameraPictureThumbnail(effect.name, image.src));
}
setEffectsThumbnails(thumbnails);
})();
}, [picture, availableEffects]);
}, [ picture, availableEffects ]);
useEffect(() =>
{
@ -183,57 +183,57 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
setCurrentPictureUrl(imageUrl.src);
})();
}, [picture, selectedEffects, isZoomed]);
}, [ picture, selectedEffects, isZoomed ]);
return (
<NitroCardView className="w-[600px] h-[500px]">
<NitroCardHeaderView headerText={LocalizeText('camera.editor.button.text')} onCloseClick={event => processAction('close')} />
<NitroCardHeaderView headerText={ LocalizeText('camera.editor.button.text') } onCloseClick={ event => processAction('close') } />
<NitroCardTabsView>
{TABS.map(tab =>
{ TABS.map(tab =>
{
return <NitroCardTabsItemView key={tab} isActive={currentTab === tab} onClick={event => processAction('change_tab', tab)}><i className={'nitro-icon icon-camera-' + tab}></i></NitroCardTabsItemView>
})}
return <NitroCardTabsItemView key={ tab } isActive={ currentTab === tab } onClick={ event => processAction('change_tab', tab) }><i className={ 'nitro-icon icon-camera-' + tab }></i></NitroCardTabsItemView>
}) }
</NitroCardTabsView>
<NitroCardContentView>
<Grid>
<Column overflow="hidden" size={5}>
<CameraWidgetEffectListView effects={getEffectList()} myLevel={myLevel} processAction={processAction} selectedEffects={selectedEffects} thumbnails={effectsThumbnails} />
<Column overflow="hidden" size={ 5 }>
<CameraWidgetEffectListView effects={ getEffectList() } myLevel={ myLevel } processAction={ processAction } selectedEffects={ selectedEffects } thumbnails={ effectsThumbnails } />
</Column>
<Column justifyContent="between" overflow="hidden" size={7}>
<Column justifyContent="between" overflow="hidden" size={ 7 }>
<Column center>
<LayoutImage className="w-[320px] h-[320px]" imageUrl={currentPictureUrl} />
{selectedEffectName &&
<Column center fullWidth gap={1}>
<Text>{LocalizeText('camera.effect.name.' + selectedEffectName)}</Text>
<LayoutImage className="w-[320px] h-[320px]" imageUrl={ currentPictureUrl } />
{ selectedEffectName &&
<Column center fullWidth gap={ 1 }>
<Text>{ LocalizeText('camera.effect.name.' + selectedEffectName) }</Text>
<ReactSlider
className={'nitro-slider'}
max={1}
min={0}
renderThumb={(props, state) => <div {...props}>{state.valueNow}</div>}
step={0.01}
value={getCurrentEffect.alpha}
onChange={event => setSelectedEffectAlpha(event)} />
</Column>}
className={ 'nitro-slider' }
max={ 1 }
min={ 0 }
renderThumb={ (props, state) => <div { ...props }>{ state.valueNow }</div> }
step={ 0.01 }
value={ getCurrentEffect.alpha }
onChange={ event => setSelectedEffectAlpha(event) } />
</Column> }
</Column>
<div className="flex justify-between">
<div className="relative inline-flex align-middle">
<Button onClick={event => processAction('clear_effects')}>
<Button onClick={ event => processAction('clear_effects') }>
<FaTrash className="fa-icon" />
</Button>
<Button onClick={event => processAction('download')}>
<Button onClick={ event => processAction('download') }>
<FaSave className="fa-icon" />
</Button>
<Button onClick={event => processAction('zoom')}>
{isZoomed && <FaSearchMinus className="fa-icon" />}
{!isZoomed && <FaSearchPlus className="fa-icon" />}
<Button onClick={ event => processAction('zoom') }>
{ isZoomed && <FaSearchMinus className="fa-icon" /> }
{ !isZoomed && <FaSearchPlus className="fa-icon" /> }
</Button>
</div>
<div className="flex gap-1">
<Button onClick={event => processAction('cancel')}>
{LocalizeText('generic.cancel')}
<Button onClick={ event => processAction('cancel') }>
{ LocalizeText('generic.cancel') }
</Button>
<Button onClick={event => processAction('checkout')}>
{LocalizeText('camera.preview.button.text')}
<Button onClick={ event => processAction('checkout') }>
{ LocalizeText('camera.preview.button.text') }
</Button>
</div>
</div>

View File

@ -22,22 +22,22 @@ export const CameraWidgetEffectListItemView: FC<CameraWidgetEffectListItemViewPr
<>
asassa
<LayoutGridItem itemActive={isActive} title={LocalizeText(!isLocked ? (`camera.effect.name.${effect.name}`) : `camera.effect.required.level ${effect.minLevel}`)} onClick={event => (!isActive && selectEffect())}>
{isActive &&
<Button className="rounded-circle remove-effect" variant="danger" onClick={removeEffect}>
<LayoutGridItem itemActive={ isActive } title={ LocalizeText(!isLocked ? (`camera.effect.name.${ effect.name }`) : `camera.effect.required.level ${ effect.minLevel }`) } onClick={ event => (!isActive && selectEffect()) }>
{ isActive &&
<Button className="rounded-circle remove-effect" variant="danger" onClick={ removeEffect }>
<FaTimes className="fa-icon" />
</Button>}
{!isLocked && (thumbnailUrl && thumbnailUrl.length > 0) &&
</Button> }
{ !isLocked && (thumbnailUrl && thumbnailUrl.length > 0) &&
<div className="effect-thumbnail-image border">
<img alt="" src={thumbnailUrl} />
</div>}
{isLocked &&
<img alt="" src={ thumbnailUrl } />
</div> }
{ isLocked &&
<Text bold center>
<div>
<FaLock className="fa-icon" />
</div>
{effect.minLevel}
</Text>}
{ effect.minLevel }
</Text> }
</LayoutGridItem>
</>
);

View File

@ -18,14 +18,14 @@ export const CameraWidgetEffectListView: FC<CameraWidgetEffectListViewProps> = p
const { myLevel = 0, selectedEffects = [], effects = [], thumbnails = [], processAction = null } = props;
return (
<Grid columnCount={3} overflow="auto">
{effects && (effects.length > 0) && effects.map((effect, index) =>
<Grid columnCount={ 3 } overflow="auto">
{ effects && (effects.length > 0) && effects.map((effect, index) =>
{
const thumbnailUrl = (thumbnails.find(thumbnail => (thumbnail.effectName === effect.name)));
const isActive = (selectedEffects.findIndex(selectedEffect => (selectedEffect.effect.name === effect.name)) > -1);
return <CameraWidgetEffectListItemView key={index} effect={effect} isActive={isActive} isLocked={(effect.minLevel > myLevel)} removeEffect={() => processAction('remove_effect', effect.name)} selectEffect={() => processAction('select_effect', effect.name)} thumbnailUrl={((thumbnailUrl && thumbnailUrl.thumbnailUrl) || null)} />
})}
return <CameraWidgetEffectListItemView key={ index } effect={ effect } isActive={ isActive } isLocked={ (effect.minLevel > myLevel) } removeEffect={ () => processAction('remove_effect', effect.name) } selectEffect={ () => processAction('select_effect', effect.name) } thumbnailUrl={ ((thumbnailUrl && thumbnailUrl.thumbnailUrl) || null) } />
}) }
</Grid>
);
}

View File

@ -21,8 +21,8 @@ const TOTAL_SHOWN_ITEMS = 5;
export const CalendarView: FC<CalendarViewProps> = props =>
{
const { onClose = null, campaignName = null, currentDay = null, numDays = null, missedDays = null, openedDays = null, openPackage = null, receivedProducts = null } = props;
const [selectedDay, setSelectedDay] = useState(currentDay);
const [index, setIndex] = useState(Math.max(0, (selectedDay - 1)));
const [ selectedDay, setSelectedDay ] = useState(currentDay);
const [ index, setIndex ] = useState(Math.max(0, (selectedDay - 1)));
const getDayState = (day: number) =>
{
@ -98,44 +98,44 @@ export const CalendarView: FC<CalendarViewProps> = props =>
return (
<NitroCardView className="nitro-campaign-calendar" theme="primary-slim">
<NitroCardHeaderView headerText={LocalizeText(`campaign.calendar.${campaignName}.title`)} onCloseClick={onClose} />
<NitroCardHeaderView headerText={ LocalizeText(`campaign.calendar.${ campaignName }.title`) } onCloseClick={ onClose } />
<NitroCardContentView>
<Grid alignItems="center" fullHeight={false} justifyContent="between">
<Column size={1} />
<Column size={10}>
<Grid alignItems="center" fullHeight={ false } justifyContent="between">
<Column size={ 1 } />
<Column size={ 10 }>
<div className="flex items-center gap-1 justify-between">
<div className="flex flex-col gap-1">
<Text fontSize={3}>{LocalizeText('campaign.calendar.heading.day', ['number'], [(selectedDay + 1).toString()])}</Text>
<Text>{dayMessage(selectedDay)}</Text>
<Text fontSize={ 3 }>{ LocalizeText('campaign.calendar.heading.day', [ 'number' ], [ (selectedDay + 1).toString() ]) }</Text>
<Text>{ dayMessage(selectedDay) }</Text>
</div>
<div>
{GetSessionDataManager().isModerator &&
<Button variant="danger" onClick={forceOpen}>Force open</Button>}
{ GetSessionDataManager().isModerator &&
<Button variant="danger" onClick={ forceOpen }>Force open</Button> }
</div>
</div>
</Column>
<Column size={1} />
<Column size={ 1 } />
</Grid>
<div className="flex h-full gap-2">
<div className="flex items-center justify-center">
<div className="campaign-spritesheet prev cursor-pointer" onClick={onClickPrev} />
<div className="campaign-spritesheet prev cursor-pointer" onClick={ onClickPrev } />
</div>
<Column center fullWidth>
<Grid fit columnCount={TOTAL_SHOWN_ITEMS} gap={1}>
{[...Array(TOTAL_SHOWN_ITEMS)].map((e, i) =>
<Grid fit columnCount={ TOTAL_SHOWN_ITEMS } gap={ 1 }>
{ [ ...Array(TOTAL_SHOWN_ITEMS) ].map((e, i) =>
{
const day = (index + i);
return (
<Column key={i} overflow="hidden">
<CalendarItemView active={(selectedDay === day)} itemId={day} product={receivedProducts.has(day) ? receivedProducts.get(day) : null} state={getDayState(day)} onClick={onClickItem} />
<Column key={ i } overflow="hidden">
<CalendarItemView active={ (selectedDay === day) } itemId={ day } product={ receivedProducts.has(day) ? receivedProducts.get(day) : null } state={ getDayState(day) } onClick={ onClickItem } />
</Column>
);
})}
}) }
</Grid>
</Column>
<div className="flex items-center justify-center">
<div className="campaign-spritesheet next cursor-pointer" onClick={onClickNext} />
<div className="campaign-spritesheet next cursor-pointer" onClick={ onClickNext } />
</div>
</div>
</NitroCardContentView>

View File

@ -64,46 +64,46 @@ export const CatalogView: FC<{}> = props =>
AddLinkEventTracker(linkTracker);
return () => RemoveLinkEventTracker(linkTracker);
}, [setIsVisible, openPageByOfferId, openPageByName]);
}, [ setIsVisible, openPageByOfferId, openPageByName ]);
return (
<>
{isVisible &&
<NitroCardView className="w-[630px] h-[400px]" style={GetConfigurationValue('catalog.headers') ? { width: 710 } : {}} uniqueKey="catalog">
<NitroCardHeaderView headerText={LocalizeText('catalog.title')} onCloseClick={event => setIsVisible(false)} />
{ isVisible &&
<NitroCardView className="w-[630px] h-[400px]" style={ GetConfigurationValue('catalog.headers') ? { width: 710 } : {} } uniqueKey="catalog">
<NitroCardHeaderView headerText={ LocalizeText('catalog.title') } onCloseClick={ event => setIsVisible(false) } />
<NitroCardTabsView>
{rootNode && (rootNode.children.length > 0) && rootNode.children.map(child =>
{ rootNode && (rootNode.children.length > 0) && rootNode.children.map(child =>
{
if (!child.isVisible) return null;
return (
<NitroCardTabsItemView key={child.pageId} isActive={child.isActive} onClick={event =>
<NitroCardTabsItemView key={ child.pageId } isActive={ child.isActive } onClick={ event =>
{
if (searchResult) setSearchResult(null);
activateNode(child);
}} >
<div className={`flex items-center gap-${GetConfigurationValue('catalog.tab.icons') ? 1 : 0}`}>
{GetConfigurationValue('catalog.tab.icons') && <CatalogIconView icon={child.iconId} />}
{child.localization}
} } >
<div className={ `flex items-center gap-${ GetConfigurationValue('catalog.tab.icons') ? 1 : 0 }` }>
{ GetConfigurationValue('catalog.tab.icons') && <CatalogIconView icon={ child.iconId } /> }
{ child.localization }
</div>
</NitroCardTabsItemView>
);
})}
}) }
</NitroCardTabsView>
<NitroCardContentView>
<Grid>
{!navigationHidden &&
<Column overflow="hidden" size={3}>
{activeNodes && (activeNodes.length > 0) &&
<CatalogNavigationView node={activeNodes[0]} />}
</Column>}
<Column overflow="hidden" size={!navigationHidden ? 9 : 12}>
{GetCatalogLayout(currentPage, () => setNavigationHidden(true))}
{ !navigationHidden &&
<Column overflow="hidden" size={ 3 }>
{ activeNodes && (activeNodes.length > 0) &&
<CatalogNavigationView node={ activeNodes[0] } /> }
</Column> }
<Column overflow="hidden" size={ !navigationHidden ? 9 : 12 }>
{ GetCatalogLayout(currentPage, () => setNavigationHidden(true)) }
</Column>
</Grid>
</NitroCardContentView>
</NitroCardView>}
</NitroCardView> }
<CatalogGiftView />
<MarketplacePostOfferView />
</>

View File

@ -9,17 +9,17 @@ export interface CatalogHeaderViewProps
export const CatalogHeaderView: FC<CatalogHeaderViewProps> = props =>
{
const { imageUrl = null } = props;
const [displayImageUrl, setDisplayImageUrl] = useState('');
const [ displayImageUrl, setDisplayImageUrl ] = useState('');
useEffect(() =>
{
setDisplayImageUrl(imageUrl ?? GetConfigurationValue<string>('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder'));
}, [imageUrl]);
}, [ imageUrl ]);
return <div className="flex justify-center items-center w-full nitro-catalog-header">
<img src={displayImageUrl} onError={({ currentTarget }) =>
<img src={ displayImageUrl } onError={ ({ currentTarget }) =>
{
currentTarget.src = GetConfigurationValue<string>('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder');
}} />
} } />
</div>;
}

View File

@ -9,26 +9,26 @@ import { classNames } from '../../../../layout';
export const CatalogGiftView: FC<{}> = props =>
{
const [isVisible, setIsVisible] = useState<boolean>(false);
const [pageId, setPageId] = useState<number>(0);
const [offerId, setOfferId] = useState<number>(0);
const [extraData, setExtraData] = useState<string>('');
const [receiverName, setReceiverName] = useState<string>('');
const [showMyFace, setShowMyFace] = useState<boolean>(true);
const [message, setMessage] = useState<string>('');
const [colors, setColors] = useState<{ id: number, color: string }[]>([]);
const [selectedBoxIndex, setSelectedBoxIndex] = useState<number>(0);
const [selectedRibbonIndex, setSelectedRibbonIndex] = useState<number>(0);
const [selectedColorId, setSelectedColorId] = useState<number>(0);
const [maxBoxIndex, setMaxBoxIndex] = useState<number>(0);
const [maxRibbonIndex, setMaxRibbonIndex] = useState<number>(0);
const [receiverNotFound, setReceiverNotFound] = useState<boolean>(false);
const [ isVisible, setIsVisible ] = useState<boolean>(false);
const [ pageId, setPageId ] = useState<number>(0);
const [ offerId, setOfferId ] = useState<number>(0);
const [ extraData, setExtraData ] = useState<string>('');
const [ receiverName, setReceiverName ] = useState<string>('');
const [ showMyFace, setShowMyFace ] = useState<boolean>(true);
const [ message, setMessage ] = useState<string>('');
const [ colors, setColors ] = useState<{ id: number, color: string }[]>([]);
const [ selectedBoxIndex, setSelectedBoxIndex ] = useState<number>(0);
const [ selectedRibbonIndex, setSelectedRibbonIndex ] = useState<number>(0);
const [ selectedColorId, setSelectedColorId ] = useState<number>(0);
const [ maxBoxIndex, setMaxBoxIndex ] = useState<number>(0);
const [ maxRibbonIndex, setMaxRibbonIndex ] = useState<number>(0);
const [ receiverNotFound, setReceiverNotFound ] = useState<boolean>(false);
const { catalogOptions = null } = useCatalog();
const { friends } = useFriends();
const { giftConfiguration = null } = catalogOptions;
const [boxTypes, setBoxTypes] = useState<number[]>([]);
const [suggestions, setSuggestions] = useState([]);
const [isAutocompleteVisible, setIsAutocompleteVisible] = useState(true);
const [ boxTypes, setBoxTypes ] = useState<number[]>([]);
const [ suggestions, setSuggestions ] = useState([]);
const [ isAutocompleteVisible, setIsAutocompleteVisible ] = useState(true);
const onClose = useCallback(() =>
{
@ -45,19 +45,19 @@ export const CatalogGiftView: FC<{}> = props =>
setSuggestions([]);
if (colors.length) setSelectedColorId(colors[0].id);
}, [colors]);
}, [ colors ]);
const isBoxDefault = useMemo(() =>
{
return giftConfiguration ? (giftConfiguration.defaultStuffTypes.findIndex(s => (s === boxTypes[selectedBoxIndex])) > -1) : false;
}, [boxTypes, giftConfiguration, selectedBoxIndex]);
}, [ boxTypes, giftConfiguration, selectedBoxIndex ]);
const boxExtraData = useMemo(() =>
{
if (!giftConfiguration) return '';
return ((boxTypes[selectedBoxIndex] * 1000) + giftConfiguration.ribbonTypes[selectedRibbonIndex]).toString();
}, [giftConfiguration, selectedBoxIndex, selectedRibbonIndex, boxTypes]);
}, [ giftConfiguration, selectedBoxIndex, selectedRibbonIndex, boxTypes ]);
const isColorable = useMemo(() =>
{
@ -68,12 +68,12 @@ export const CatalogGiftView: FC<{}> = props =>
const boxType = boxTypes[selectedBoxIndex];
return (boxType === 8 || (boxType >= 3 && boxType <= 6)) ? false : true;
}, [giftConfiguration, selectedBoxIndex, isBoxDefault, boxTypes]);
}, [ giftConfiguration, selectedBoxIndex, isBoxDefault, boxTypes ]);
const colourId = useMemo(() =>
{
return isBoxDefault ? boxTypes[selectedBoxIndex] : selectedColorId;
}, [isBoxDefault, boxTypes, selectedBoxIndex, selectedColorId])
}, [ isBoxDefault, boxTypes, selectedBoxIndex, selectedColorId ])
const allFriends = friends.filter((friend: MessengerFriend) => friend.id !== -1);
@ -125,13 +125,13 @@ export const CatalogGiftView: FC<{}> = props =>
SendMessageComposer(new PurchaseFromCatalogAsGiftComposer(pageId, offerId, extraData, receiverName, message, colourId, selectedBoxIndex, selectedRibbonIndex, showMyFace));
return;
}
}, [colourId, extraData, maxBoxIndex, maxRibbonIndex, message, offerId, pageId, receiverName, selectedBoxIndex, selectedRibbonIndex, showMyFace]);
}, [ colourId, extraData, maxBoxIndex, maxRibbonIndex, message, offerId, pageId, receiverName, selectedBoxIndex, selectedRibbonIndex, showMyFace ]);
useMessageEvent<GiftReceiverNotFoundEvent>(GiftReceiverNotFoundEvent, event => setReceiverNotFound(true));
useUiEvent([
CatalogPurchasedEvent.PURCHASE_SUCCESS,
CatalogEvent.INIT_GIFT], event =>
CatalogEvent.INIT_GIFT ], event =>
{
switch (event.type)
{
@ -154,7 +154,7 @@ export const CatalogGiftView: FC<{}> = props =>
useEffect(() =>
{
setReceiverNotFound(false);
}, [receiverName]);
}, [ receiverName ]);
const createBoxTypes = useCallback(() =>
{
@ -162,7 +162,7 @@ export const CatalogGiftView: FC<{}> = props =>
setBoxTypes(prev =>
{
let newPrev = [...giftConfiguration.boxTypes];
let newPrev = [ ...giftConfiguration.boxTypes ];
newPrev.push(giftConfiguration.defaultStuffTypes[Math.floor((Math.random() * (giftConfiguration.defaultStuffTypes.length - 1)))]);
@ -171,7 +171,7 @@ export const CatalogGiftView: FC<{}> = props =>
return newPrev;
})
}, [giftConfiguration])
}, [ giftConfiguration ])
useEffect(() =>
{
@ -195,93 +195,93 @@ export const CatalogGiftView: FC<{}> = props =>
setSelectedColorId(newColors[0].id);
setColors(newColors);
}
}, [giftConfiguration, createBoxTypes]);
}, [ giftConfiguration, createBoxTypes ]);
useEffect(() =>
{
if (!isVisible) return;
createBoxTypes();
}, [createBoxTypes, isVisible])
}, [ createBoxTypes, isVisible ])
if (!giftConfiguration || !giftConfiguration.isEnabled || !isVisible) return null;
const boxName = 'catalog.gift_wrapping_new.box.' + (isBoxDefault ? 'default' : boxTypes[selectedBoxIndex]);
const ribbonName = `catalog.gift_wrapping_new.ribbon.${selectedRibbonIndex}`;
const ribbonName = `catalog.gift_wrapping_new.ribbon.${ selectedRibbonIndex }`;
const priceText = 'catalog.gift_wrapping_new.' + (isBoxDefault ? 'freeprice' : 'price');
return (
<NitroCardView className="nitro-catalog-gift" theme="primary-slim" uniqueKey="catalog-gift">
<NitroCardHeaderView headerText={LocalizeText('catalog.gift_wrapping.title')} onCloseClick={onClose} />
<NitroCardHeaderView headerText={ LocalizeText('catalog.gift_wrapping.title') } onCloseClick={ onClose } />
<NitroCardContentView className="text-black">
<FormGroup column>
<Text>{LocalizeText('catalog.gift_wrapping.receiver')}</Text>
<input className={classNames('form-control form-control-sm', receiverNotFound && 'is-invalid')} type="text" value={receiverName} onChange={(e) => onTextChanged(e)} />
{(suggestions.length > 0 && isAutocompleteVisible) &&
<Text>{ LocalizeText('catalog.gift_wrapping.receiver') }</Text>
<input className={ classNames('form-control form-control-sm', receiverNotFound && 'is-invalid') } type="text" value={ receiverName } onChange={ (e) => onTextChanged(e) } />
{ (suggestions.length > 0 && isAutocompleteVisible) &&
<Column className="autocomplete-gift-container">
{suggestions.map((friend: MessengerFriend) => (
<div key={friend.id} className="autocomplete-gift-item" onClick={(e) => selectedReceiverName(friend.name)}>{friend.name}</div>
))}
{ suggestions.map((friend: MessengerFriend) => (
<div key={ friend.id } className="autocomplete-gift-item" onClick={ (e) => selectedReceiverName(friend.name) }>{ friend.name }</div>
)) }
</Column>
}
{receiverNotFound &&
<div className="invalid-feedback">{LocalizeText('catalog.gift_wrapping.receiver_not_found.title')}</div>}
{ receiverNotFound &&
<div className="invalid-feedback">{ LocalizeText('catalog.gift_wrapping.receiver_not_found.title') }</div> }
</FormGroup>
<LayoutGiftTagView editable={true} figure={GetSessionDataManager().figure} message={message} userName={GetSessionDataManager().userName} onChange={(value) => setMessage(value)} />
<LayoutGiftTagView editable={ true } figure={ GetSessionDataManager().figure } message={ message } userName={ GetSessionDataManager().userName } onChange={ (value) => setMessage(value) } />
<div className="form-check">
<input checked={showMyFace} className="form-check-input" name="showMyFace" type="checkbox" onChange={(e) => setShowMyFace(value => !value)} />
<label className="form-check-label">{LocalizeText('catalog.gift_wrapping.show_face.title')}</label>
<input checked={ showMyFace } className="form-check-input" name="showMyFace" type="checkbox" onChange={ (e) => setShowMyFace(value => !value) } />
<label className="form-check-label">{ LocalizeText('catalog.gift_wrapping.show_face.title') }</label>
</div>
<div className="items-center gap-2">
{selectedColorId &&
{ selectedColorId &&
<div className="gift-preview">
<LayoutFurniImageView extraData={boxExtraData} productClassId={colourId} productType={ProductTypeEnum.FLOOR} />
</div>}
<LayoutFurniImageView extraData={ boxExtraData } productClassId={ colourId } productType={ ProductTypeEnum.FLOOR } />
</div> }
<div className="flex flex-col gap-1">
<div className="flex gap-2">
<div className="relative inline-flex align-middle">
<Button variant="primary" onClick={() => handleAction('prev_box')}>
<Button variant="primary" onClick={ () => handleAction('prev_box') }>
<FaChevronLeft className="fa-icon" />
</Button>
<Button variant="primary" onClick={() => handleAction('next_box')}>
<Button variant="primary" onClick={ () => handleAction('next_box') }>
<FaChevronRight className="fa-icon" />
</Button>
</div>
<div className="flex flex-col gap-1">
<Text fontWeight="bold">{LocalizeText(boxName)}</Text>
<Text fontWeight="bold">{ LocalizeText(boxName) }</Text>
<div className="flex items-center gap-1">
{LocalizeText(priceText, ['price'], [giftConfiguration.price.toString()])}
<LayoutCurrencyIcon type={-1} />
{ LocalizeText(priceText, [ 'price' ], [ giftConfiguration.price.toString() ]) }
<LayoutCurrencyIcon type={ -1 } />
</div>
</div>
</div>
<Flex alignItems="center" className={isColorable ? '' : 'opacity-50 pointer-events-none'} gap={2}>
<Flex alignItems="center" className={ isColorable ? '' : 'opacity-50 pointer-events-none' } gap={ 2 }>
<div className="relative inline-flex align-middle">
<Button variant="primary" onClick={() => handleAction('prev_ribbon')}>
<Button variant="primary" onClick={ () => handleAction('prev_ribbon') }>
<FaChevronLeft className="fa-icon" />
</Button>
<Button variant="primary" onClick={() => handleAction('next_ribbon')}>
<Button variant="primary" onClick={ () => handleAction('next_ribbon') }>
<FaChevronRight className="fa-icon" />
</Button>
</div>
<Text fontWeight="bold">{LocalizeText(ribbonName)}</Text>
<Text fontWeight="bold">{ LocalizeText(ribbonName) }</Text>
</Flex>
</div>
</div>
<Column className={isColorable ? '' : 'opacity-50 pointer-events-none'} gap={1}>
<Column className={ isColorable ? '' : 'opacity-50 pointer-events-none' } gap={ 1 }>
<Text fontWeight="bold">
{LocalizeText('catalog.gift_wrapping.pick_color')}
{ LocalizeText('catalog.gift_wrapping.pick_color') }
</Text>
<div className="relative inline-flex align-middle w-full">
{colors.map(color => <Button key={color.id} active={(color.id === selectedColorId)} disabled={!isColorable} style={{ backgroundColor: color.color }} variant="dark" onClick={() => setSelectedColorId(color.id)} />)}
{ colors.map(color => <Button key={ color.id } active={ (color.id === selectedColorId) } disabled={ !isColorable } style={ { backgroundColor: color.color } } variant="dark" onClick={ () => setSelectedColorId(color.id) } />) }
</div>
</Column>
<div className="flex items-center justify-between">
<Button className="text-black" variant="link" onClick={onClose}>
{LocalizeText('cancel')}
<Button className="text-black" variant="link" onClick={ onClose }>
{ LocalizeText('cancel') }
</Button>
<Button variant="success" onClick={() => handleAction('buy')}>
{LocalizeText('catalog.gift_wrapping.give_gift')}
<Button variant="success" onClick={ () => handleAction('buy') }>
{ LocalizeText('catalog.gift_wrapping.give_gift') }
</Button>
</div>
</NitroCardContentView>

View File

@ -18,19 +18,18 @@ export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = pro
const { activateNode = null } = useCatalog();
return (
<div className={child ? 'pl-[5px] border-s-2 border-[#b6bec5]' : ''}>
<LayoutGridItem className={' !h-[23px] bg-[#cdd3d9] !border-[0] px-[3px] py-px text-sm'} column={false} gap={1} itemActive={node.isActive} onClick={event => activateNode(node)}>
<CatalogIconView icon={node.iconId} />
<Text truncate className="!flex-grow">{node.localization}</Text>
{node.isBranch &&
<div className={ child ? 'pl-[5px] border-s-2 border-[#b6bec5]' : '' }>
<LayoutGridItem className={ ' !h-[23px] bg-[#cdd3d9] !border-[0] px-[3px] py-px text-sm' } column={ false } gap={ 1 } itemActive={ node.isActive } onClick={ event => activateNode(node) }>
<CatalogIconView icon={ node.iconId } />
<Text truncate className="!flex-grow">{ node.localization }</Text>
{ node.isBranch &&
<>
{node.isOpen && <FaCaretUp className="fa-icon" />}
{!node.isOpen && <FaCaretDown className="fa-icon" />}
</>}
{ node.isOpen && <FaCaretUp className="fa-icon" /> }
{ !node.isOpen && <FaCaretDown className="fa-icon" /> }
</> }
</LayoutGridItem>
{node.isOpen && node.isBranch &&
<CatalogNavigationSetView child={true} node={node} />}
{ node.isOpen && node.isBranch &&
<CatalogNavigationSetView child={ true } node={ node } /> }
</div>
);
}

View File

@ -20,13 +20,13 @@ export const CatalogNavigationView: FC<CatalogNavigationViewProps> = props =>
<>
<CatalogSearchView />
<Column fullHeight className="!border-[#b6bec5] bg-[#cdd3d9] border-[2px] border-[solid] rounded p-1" overflow="hidden">
<AutoGrid columnCount={1} gap={1} id="nitro-catalog-main-navigation">
{searchResult && (searchResult.filteredNodes.length > 0) && searchResult.filteredNodes.map((n, index) =>
<AutoGrid columnCount={ 1 } gap={ 1 } id="nitro-catalog-main-navigation">
{ searchResult && (searchResult.filteredNodes.length > 0) && searchResult.filteredNodes.map((n, index) =>
{
return <CatalogNavigationItemView key={index} node={n} />;
})}
{!searchResult &&
<CatalogNavigationSetView node={node} />}
return <CatalogNavigationItemView key={ index } node={ n } />;
}) }
{ !searchResult &&
<CatalogNavigationSetView node={ node } /> }
</AutoGrid>
</Column>
</>

View File

@ -14,8 +14,8 @@ export interface CatalogRedeemVoucherViewProps
export const CatalogRedeemVoucherView: FC<CatalogRedeemVoucherViewProps> = props =>
{
const { text = null } = props;
const [voucher, setVoucher] = useState<string>('');
const [isWaiting, setIsWaiting] = useState(false);
const [ voucher, setVoucher ] = useState<string>('');
const [ isWaiting, setIsWaiting ] = useState(false);
const { simpleAlert = null } = useNotification();
const redeemVoucher = () =>
@ -33,7 +33,7 @@ export const CatalogRedeemVoucherView: FC<CatalogRedeemVoucherViewProps> = props
let message = LocalizeText('catalog.alert.voucherredeem.ok.description');
if (parser.productName) message = LocalizeText('catalog.alert.voucherredeem.ok.description.furni', ['productName', 'productDescription'], [parser.productName, parser.productDescription]);
if (parser.productName) message = LocalizeText('catalog.alert.voucherredeem.ok.description.furni', [ 'productName', 'productDescription' ], [ parser.productName, parser.productDescription ]);
simpleAlert(message, null, null, null, LocalizeText('catalog.alert.voucherredeem.ok.title'));
@ -45,7 +45,7 @@ export const CatalogRedeemVoucherView: FC<CatalogRedeemVoucherViewProps> = props
{
const parser = event.getParser();
simpleAlert(LocalizeText(`catalog.alert.voucherredeem.error.description.${parser.errorCode}`), null, null, null, LocalizeText('catalog.alert.voucherredeem.error.title'));
simpleAlert(LocalizeText(`catalog.alert.voucherredeem.error.description.${ parser.errorCode }`), null, null, null, LocalizeText('catalog.alert.voucherredeem.error.title'));
setIsWaiting(false);
});
@ -56,11 +56,10 @@ export const CatalogRedeemVoucherView: FC<CatalogRedeemVoucherViewProps> = props
<NitroInput
placeholder={text}
value={voucher}
onChange={event => setVoucher(event.target.value)} />
<Button disabled={isWaiting} variant="primary" onClick={redeemVoucher}>
placeholder={ text }
value={ voucher }
onChange={ event => setVoucher(event.target.value) } />
<Button disabled={ isWaiting } variant="primary" onClick={ redeemVoucher }>
<FaTag className="fa-icon" />
</Button>
</div>

View File

@ -8,7 +8,7 @@ import { NitroInput } from '../../../../../layout';
export const CatalogSearchView: FC<{}> = props =>
{
const [searchValue, setSearchValue] = useState('');
const [ searchValue, setSearchValue ] = useState('');
const { currentType = null, rootNode = null, offersToNodes = null, searchResult = null, setSearchResult = null, setCurrentPage = null } = useCatalog();
useEffect(() =>
@ -37,7 +37,7 @@ export const CatalogSearchView: FC<{}> = props =>
if ((currentType === CatalogType.NORMAL) && furniture.excludeDynamic) continue;
const searchValues = [furniture.className, furniture.name, furniture.description].join(' ').replace(/ /gi, '').toLowerCase();
const searchValues = [ furniture.className, furniture.name, furniture.description ].join(' ').replace(/ /gi, '').toLowerCase();
if ((currentType === CatalogType.BUILDER) && (furniture.purchaseOfferId === -1) && (furniture.rentOfferId === -1))
{
@ -75,7 +75,7 @@ export const CatalogSearchView: FC<{}> = props =>
}, 300);
return () => clearTimeout(timeout);
}, [offersToNodes, currentType, rootNode, searchValue, setCurrentPage, setSearchResult]);
}, [ offersToNodes, currentType, rootNode, searchValue, setCurrentPage, setSearchResult ]);
return (
<div className="flex gap-1">
@ -87,20 +87,20 @@ export const CatalogSearchView: FC<{}> = props =>
<NitroInput
placeholder={LocalizeText('generic.search')}
value={searchValue}
onChange={event => setSearchValue(event.target.value)} />
placeholder={ LocalizeText('generic.search') }
value={ searchValue }
onChange={ event => setSearchValue(event.target.value) } />
</Flex>
{(!searchValue || !searchValue.length) &&
{ (!searchValue || !searchValue.length) &&
<Button className="catalog-search-button" variant="primary">
<FaSearch className="fa-icon" />
</Button>}
{searchValue && !!searchValue.length &&
<Button className="catalog-search-button" variant="primary" onClick={event => setSearchValue('')}>
</Button> }
{ searchValue && !!searchValue.length &&
<Button className="catalog-search-button" variant="primary" onClick={ event => setSearchValue('') }>
<FaTimes className="fa-icon" />
</Button>}
</Button> }
</div>
);
}

View File

@ -20,33 +20,33 @@ export const CatalogLayoutBadgeDisplayView: FC<CatalogLayoutProps> = props =>
<>
<CatalogFirstProductSelectorWidgetView />
<Grid>
<Column overflow="hidden" size={7}>
<Column overflow="hidden" size={ 7 }>
<CatalogItemGridWidgetView shrink />
<Column gap={1} overflow="hidden">
<Text shrink truncate fontWeight="bold">{LocalizeText('catalog_selectbadge')}</Text>
<Column gap={ 1 } overflow="hidden">
<Text shrink truncate fontWeight="bold">{ LocalizeText('catalog_selectbadge') }</Text>
<CatalogBadgeSelectorWidgetView />
</Column>
</Column>
<Column center={!currentOffer} overflow="hidden" size={5}>
{!currentOffer &&
<Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer &&
<>
{!!page.localization.getImage(1) && <img alt="" src={page.localization.getImage(1)} />}
<Text center dangerouslySetInnerHTML={{ __html: page.localization.getText(0) }} />
</>}
{currentOffer &&
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
</> }
{ currentOffer &&
<>
<div className="relative overflow-hidden">
<CatalogViewProductWidgetView />
</div>
<Column className="!flex-grow" gap={1}>
<Column className="!flex-grow" gap={ 1 }>
<CatalogLimitedItemWidgetView />
<Text truncate className="!flex-grow">{currentOffer.localizationName}</Text>
<Text truncate className="!flex-grow">{ currentOffer.localizationName }</Text>
<div className="flex justify-end">
<CatalogTotalPriceWidget alignItems="end" />
</div>
<CatalogPurchaseWidgetView />
</Column>
</>}
</> }
</Column>
</Grid>
</>

View File

@ -21,9 +21,9 @@ export interface CatalogLayoutColorGroupViewProps extends CatalogLayoutProps
export const CatalogLayoutColorGroupingView: FC<CatalogLayoutColorGroupViewProps> = props =>
{
const { page = null } = props;
const [colorableItems, setColorableItems] = useState<Map<string, number[]>>(new Map<string, number[]>());
const [ colorableItems, setColorableItems ] = useState<Map<string, number[]>>(new Map<string, number[]>());
const { currentOffer = null, setCurrentOffer = null } = useCatalog();
const [colorsShowing, setColorsShowing] = useState<boolean>(false);
const [ colorsShowing, setColorsShowing ] = useState<boolean>(false);
const sortByColorIndex = (a: IPurchasableOffer, b: IPurchasableOffer) =>
{
@ -63,7 +63,7 @@ export const CatalogLayoutColorGroupingView: FC<CatalogLayoutColorGroupViewProps
const selectColor = (colorIndex: number, productName: string) =>
{
const fullName = `${productName}*${colorIndex}`;
const fullName = `${ productName }*${ colorIndex }`;
const index = page.offers.findIndex(offer => offer.product.furnitureData.fullName === fullName);
if (index > -1)
{
@ -128,39 +128,39 @@ export const CatalogLayoutColorGroupingView: FC<CatalogLayoutColorGroupViewProps
offers.sort(sortyByFurnitureClassName);
setColorableItems(updatedColorableItems);
return offers;
}, [page.offers]);
}, [ page.offers ]);
return (
<Grid>
<Column overflow="hidden" size={7}>
<AutoGrid columnCount={5}>
{(!colorsShowing || !currentOffer || !colorableItems.has(currentOffer.product.furnitureData.className)) &&
offers.map((offer, index) => <CatalogGridOfferView key={index} itemActive={(currentOffer && (currentOffer.product.furnitureData.hasIndexedColor ? currentOffer.product.furnitureData.className === offer.product.furnitureData.className : currentOffer.offerId === offer.offerId))} offer={offer} selectOffer={selectOffer} />)
<Column overflow="hidden" size={ 7 }>
<AutoGrid columnCount={ 5 }>
{ (!colorsShowing || !currentOffer || !colorableItems.has(currentOffer.product.furnitureData.className)) &&
offers.map((offer, index) => <CatalogGridOfferView key={ index } itemActive={ (currentOffer && (currentOffer.product.furnitureData.hasIndexedColor ? currentOffer.product.furnitureData.className === offer.product.furnitureData.className : currentOffer.offerId === offer.offerId)) } offer={ offer } selectOffer={ selectOffer } />)
}
{(colorsShowing && currentOffer && colorableItems.has(currentOffer.product.furnitureData.className)) &&
colorableItems.get(currentOffer.product.furnitureData.className).map((color, index) => <LayoutGridItem key={index} itemHighlight className="clear-bg" itemActive={(currentOffer.product.furnitureData.colorIndex === index)} itemColor={ColorConverter.int2rgb(color)} onClick={event => selectColor(index, currentOffer.product.furnitureData.className)} />)
{ (colorsShowing && currentOffer && colorableItems.has(currentOffer.product.furnitureData.className)) &&
colorableItems.get(currentOffer.product.furnitureData.className).map((color, index) => <LayoutGridItem key={ index } itemHighlight className="clear-bg" itemActive={ (currentOffer.product.furnitureData.colorIndex === index) } itemColor={ ColorConverter.int2rgb(color) } onClick={ event => selectColor(index, currentOffer.product.furnitureData.className) } />)
}
</AutoGrid>
</Column>
<Column center={!currentOffer} overflow="hidden" size={5}>
{!currentOffer &&
<Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer &&
<>
{!!page.localization.getImage(1) && <img alt="" src={page.localization.getImage(1)} />}
<Text center dangerouslySetInnerHTML={{ __html: page.localization.getText(0) }} />
</>}
{currentOffer &&
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
</> }
{ currentOffer &&
<>
<div className="relative overflow-hidden">
<CatalogViewProductWidgetView />
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 end-1" position="absolute" />
{currentOffer.product.furnitureData.hasIndexedColor &&
<Button className="bottom-1 start-1" position="absolute" onClick={event => setColorsShowing(prev => !prev)}>
{ currentOffer.product.furnitureData.hasIndexedColor &&
<Button className="bottom-1 start-1" position="absolute" onClick={ event => setColorsShowing(prev => !prev) }>
<FaFillDrip className="fa-icon" />
</Button>}
</Button> }
</div>
<Column className="!flex-grow" gap={1}>
<Column className="!flex-grow" gap={ 1 }>
<CatalogLimitedItemWidgetView />
<Text truncate className="!flex-grow">{currentOffer.localizationName}</Text>
<Text truncate className="!flex-grow">{ currentOffer.localizationName }</Text>
<div className="flex justify-between">
<div className="flex flex-col gap-1">
<CatalogSpinnerWidgetView />
@ -169,7 +169,7 @@ export const CatalogLayoutColorGroupingView: FC<CatalogLayoutColorGroupViewProps
</div>
<CatalogPurchaseWidgetView />
</Column>
</>}
</> }
</Column>
</Grid>
);

View File

@ -20,31 +20,31 @@ export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
return (
<>
<Grid>
<Column overflow="hidden" size={7}>
{GetConfigurationValue('catalog.headers') &&
<CatalogHeaderView imageUrl={currentPage.localization.getImage(0)} />}
<Column overflow="hidden" size={ 7 }>
{ GetConfigurationValue('catalog.headers') &&
<CatalogHeaderView imageUrl={ currentPage.localization.getImage(0) } /> }
<CatalogItemGridWidgetView />
</Column>
<Column center={!currentOffer} overflow="hidden" size={5}>
{!currentOffer &&
<Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer &&
<>
{!!page.localization.getImage(1) &&
<LayoutImage imageUrl={page.localization.getImage(1)} />}
<Text center dangerouslySetInnerHTML={{ __html: page.localization.getText(0) }} />
</>}
{currentOffer &&
{ !!page.localization.getImage(1) &&
<LayoutImage imageUrl={ page.localization.getImage(1) } /> }
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
</> }
{ currentOffer &&
<>
<Flex center overflow="hidden" style={{ height: 140 }}>
{(currentOffer.product.productType !== ProductTypeEnum.BADGE) &&
<Flex center overflow="hidden" style={ { height: 140 } }>
{ (currentOffer.product.productType !== ProductTypeEnum.BADGE) &&
<>
<CatalogViewProductWidgetView />
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 end-1" />
</>}
{(currentOffer.product.productType === ProductTypeEnum.BADGE) && <CatalogAddOnBadgeWidgetView className="scale-2" />}
</> }
{ (currentOffer.product.productType === ProductTypeEnum.BADGE) && <CatalogAddOnBadgeWidgetView className="scale-2" /> }
</Flex>
<Column grow gap={1}>
<Column grow gap={ 1 }>
<CatalogLimitedItemWidgetView />
<Text grow truncate>{currentOffer.localizationName}</Text>
<Text grow truncate>{ currentOffer.localizationName }</Text>
<div className="flex justify-between">
<div className="flex flex-col gap-1">
<CatalogSpinnerWidgetView />
@ -53,7 +53,7 @@ export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
</div>
<CatalogPurchaseWidgetView />
</Column>
</>}
</> }
</Column>
</Grid>
</>

View File

@ -16,23 +16,23 @@ export const CatalogLayouGuildCustomFurniView: FC<CatalogLayoutProps> = props =>
return (
<Grid>
<Column overflow="hidden" size={7}>
<Column overflow="hidden" size={ 7 }>
<CatalogItemGridWidgetView />
</Column>
<Column center={!currentOffer} overflow="hidden" size={5}>
{!currentOffer &&
<Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer &&
<>
{!!page.localization.getImage(1) && <img alt="" src={page.localization.getImage(1)} />}
<Text center dangerouslySetInnerHTML={{ __html: page.localization.getText(0) }} />
</>}
{currentOffer &&
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
</> }
{ currentOffer &&
<>
<div className="relative overflow-hidden">
<CatalogViewProductWidgetView />
<CatalogGuildBadgeWidgetView className="bottom-1 end-1" position="absolute" />
</div>
<Column grow gap={1}>
<Text truncate>{currentOffer.localizationName}</Text>
<Column grow gap={ 1 }>
<Text truncate>{ currentOffer.localizationName }</Text>
<div className="!flex-grow">
<CatalogGuildSelectorWidgetView />
</div>
@ -41,7 +41,7 @@ export const CatalogLayouGuildCustomFurniView: FC<CatalogLayoutProps> = props =>
</div>
<CatalogPurchaseWidgetView />
</Column>
</>}
</> }
</Column>
</Grid>
);

View File

@ -12,36 +12,36 @@ import { CatalogLayoutProps } from './CatalogLayout.types';
export const CatalogLayouGuildForumView: FC<CatalogLayoutProps> = props =>
{
const { page = null } = props;
const [selectedGroupIndex, setSelectedGroupIndex] = useState<number>(0);
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState<number>(0);
const { currentOffer = null, setCurrentOffer = null, catalogOptions = null } = useCatalog();
const { groups = null } = catalogOptions;
useEffect(() =>
{
SendMessageComposer(new CatalogGroupsComposer());
}, [page]);
}, [ page ]);
return (
<>
<CatalogFirstProductSelectorWidgetView />
<Grid>
<Column className="bg-muted rounded p-2 text-black" overflow="hidden" size={7}>
<div className="overflow-auto" dangerouslySetInnerHTML={{ __html: page.localization.getText(1) }} />
<Column className="bg-muted rounded p-2 text-black" overflow="hidden" size={ 7 }>
<div className="overflow-auto" dangerouslySetInnerHTML={ { __html: page.localization.getText(1) } } />
</Column>
<Column gap={1} overflow="hidden" size={5}>
{!!currentOffer &&
<Column gap={ 1 } overflow="hidden" size={ 5 }>
{ !!currentOffer &&
<>
<Column grow gap={1}>
<Text truncate>{currentOffer.localizationName}</Text>
<Column grow gap={ 1 }>
<Text truncate>{ currentOffer.localizationName }</Text>
<div className="!flex-grow">
<CatalogGuildSelectorWidgetView />
</div>
<div className="flex justify-end">
<CatalogTotalPriceWidget alignItems="end" />
</div>
<CatalogPurchaseWidgetView noGiftOption={true} />
<CatalogPurchaseWidgetView noGiftOption={ true } />
</Column>
</>}
</> }
</Column>
</Grid>
</>

View File

@ -8,7 +8,7 @@ export const CatalogLayoutInfoLoyaltyView: FC<CatalogLayoutProps> = props =>
return (
<div className="h-full nitro-catalog-layout-info-loyalty text-black flex flex-row">
<div className="overflow-auto h-full flex flex-col info-loyalty-content">
<div dangerouslySetInnerHTML={{ __html: page.localization.getText(0) }} />
<div dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
</div>
</div>
);

View File

@ -11,14 +11,14 @@ export const CatalogLayoutPets3View: FC<CatalogLayoutProps> = props =>
return (
<Column grow className="bg-muted rounded text-black p-2" overflow="hidden">
<div className="items-center gap-2">
{imageUrl && <img alt="" src={imageUrl} />}
<div className="fs-5" dangerouslySetInnerHTML={{ __html: page.localization.getText(1) }} />
{ imageUrl && <img alt="" src={ imageUrl } /> }
<div className="fs-5" dangerouslySetInnerHTML={ { __html: page.localization.getText(1) } } />
</div>
<Column grow alignItems="center" overflow="auto">
<div dangerouslySetInnerHTML={{ __html: page.localization.getText(2) }} />
<div dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } />
</Column>
<div className="flex items-center">
<div className="font-bold " dangerouslySetInnerHTML={{ __html: page.localization.getText(3) }} />
<div className="font-bold " dangerouslySetInnerHTML={ { __html: page.localization.getText(3) } } />
</div>
</Column>
);

View File

@ -9,12 +9,12 @@ import { CatalogLayoutProps } from './CatalogLayout.types';
export const CatalogLayoutRoomAdsView: FC<CatalogLayoutProps> = props =>
{
const { page = null } = props;
const [eventName, setEventName] = useState<string>('');
const [eventDesc, setEventDesc] = useState<string>('');
const [roomId, setRoomId] = useState<number>(-1);
const [availableRooms, setAvailableRooms] = useState<RoomEntryData[]>([]);
const [extended, setExtended] = useState<boolean>(false);
const [categoryId, setCategoryId] = useState<number>(1);
const [ eventName, setEventName ] = useState<string>('');
const [ eventDesc, setEventDesc ] = useState<string>('');
const [ roomId, setRoomId ] = useState<number>(-1);
const [ availableRooms, setAvailableRooms ] = useState<RoomEntryData[]>([]);
const [ extended, setExtended ] = useState<boolean>(false);
const [ categoryId, setCategoryId ] = useState<number>(1);
const { categories = null } = useNavigator();
const { setIsVisible = null } = useCatalog();
const { promoteInformation, isExtended, setIsExtended } = useRoomPromote();
@ -31,7 +31,7 @@ export const CatalogLayoutRoomAdsView: FC<CatalogLayoutProps> = props =>
setIsExtended(false); // This is from hook useRoomPromotte
}
}, [isExtended, eventName, eventDesc, categoryId]);
}, [ isExtended, eventName, eventDesc, categoryId, promoteInformation.data, setIsExtended ]);
const resetData = () =>
{
@ -73,38 +73,34 @@ export const CatalogLayoutRoomAdsView: FC<CatalogLayoutProps> = props =>
}, []);
return (<>
<Text bold center>{LocalizeText('roomad.catalog_header')}</Text>
<Column className="text-black" overflow="hidden" size={12}>
<div>{LocalizeText('roomad.catalog_text', ['duration'], ['120'])}</div>
<div className="bg-muted rounded p-1">
<Column gap={2}>
<Text bold>{LocalizeText('navigator.category')}</Text>
<select className="form-select form-select-sm" disabled={extended} value={categoryId} onChange={event => setCategoryId(parseInt(event.target.value))}>
{categories && categories.map((cat, index) => <option key={index} value={cat.id}>{LocalizeText(cat.name)}</option>)}
<Text bold center>{ LocalizeText('roomad.catalog_header') }</Text>
<Column className="text-black" overflow="hidden" size={ 12 }>
<div>{ LocalizeText('roomad.catalog_text', [ 'duration' ], [ '120' ]) }</div>
<div className="p-1 rounded bg-muted">
<Column gap={ 2 }>
<Text bold>{ LocalizeText('navigator.category') }</Text>
<select className="form-select form-select-sm" disabled={ extended } value={ categoryId } onChange={ event => setCategoryId(parseInt(event.target.value)) }>
{ categories && categories.map((cat, index) => <option key={ index } value={ cat.id }>{ LocalizeText(cat.name) }</option>) }
</select>
</Column>
<div className="flex flex-col gap-1">
<Text bold>{LocalizeText('roomad.catalog_name')}</Text>
<NitroInput maxLength={64} readOnly={extended} value={eventName} onChange={event => setEventName(event.target.value)} />
<Text bold>{ LocalizeText('roomad.catalog_name') }</Text>
<NitroInput maxLength={ 64 } readOnly={ extended } value={ eventName } onChange={ event => setEventName(event.target.value) } />
</div>
<div className="flex flex-col gap-1">
<Text bold>{LocalizeText('roomad.catalog_description')}</Text>
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm" maxLength={64} readOnly={extended} value={eventDesc} onChange={event => setEventDesc(event.target.value)} />
<Text bold>{ LocalizeText('roomad.catalog_description') }</Text>
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm" maxLength={ 64 } readOnly={ extended } value={ eventDesc } onChange={ event => setEventDesc(event.target.value) } />
</div>
<div className="flex flex-col gap-1">
<Text bold>{LocalizeText('roomad.catalog_roomname')}</Text>
<select className="form-select form-select-sm" disabled={extended} value={roomId} onChange={event => setRoomId(parseInt(event.target.value))}>
<option disabled value={-1}>{LocalizeText('roomad.catalog_roomname')}</option>
{availableRooms && availableRooms.map((room, index) => <option key={index} value={room.roomId}>{room.roomName}</option>)}
<Text bold>{ LocalizeText('roomad.catalog_roomname') }</Text>
<select className="form-select form-select-sm" disabled={ extended } value={ roomId } onChange={ event => setRoomId(parseInt(event.target.value)) }>
<option disabled value={ -1 }>{ LocalizeText('roomad.catalog_roomname') }</option>
{ availableRooms && availableRooms.map((room, index) => <option key={ index } value={ room.roomId }>{ room.roomName }</option>) }
</select>
</div>
<div className="flex flex-col gap-1">
<Button disabled={(!eventName || !eventDesc || roomId === -1)} variant={(!eventName || !eventDesc || roomId === -1) ? 'danger' : 'success'} onClick={purchaseAd}>{extended ? LocalizeText('roomad.extend.event') : LocalizeText('buy')}</Button>
<Button disabled={ (!eventName || !eventDesc || roomId === -1) } variant={ (!eventName || !eventDesc || roomId === -1) ? 'danger' : 'success' } onClick={ purchaseAd }>{ extended ? LocalizeText('roomad.extend.event') : LocalizeText('buy') }</Button>
</div>
</div>
</Column>

View File

@ -15,19 +15,19 @@ export const CatalogLayoutRoomBundleView: FC<CatalogLayoutProps> = props =>
<>
<CatalogFirstProductSelectorWidgetView />
<Grid>
<Column overflow="hidden" size={7}>
{!!page.localization.getText(2) &&
<Text dangerouslySetInnerHTML={{ __html: page.localization.getText(2) }} />}
<Column overflow="hidden" size={ 7 }>
{ !!page.localization.getText(2) &&
<Text dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } /> }
<Column grow className="bg-muted p-2 rounded" overflow="hidden">
<CatalogBundleGridWidgetView fullWidth className="nitro-catalog-layout-bundle-grid" />
</Column>
</Column>
<Column gap={1} overflow="hidden" size={5}>
{!!page.localization.getText(1) &&
<Text center small overflow="auto">{page.localization.getText(1)}</Text>}
<Column grow gap={0} overflow="hidden" position="relative">
{!!page.localization.getImage(1) &&
<img alt="" className="!flex-grow" src={page.localization.getImage(1)} />}
<Column gap={ 1 } overflow="hidden" size={ 5 }>
{ !!page.localization.getText(1) &&
<Text center small overflow="auto">{ page.localization.getText(1) }</Text> }
<Column grow gap={ 0 } overflow="hidden" position="relative">
{ !!page.localization.getImage(1) &&
<img alt="" className="!flex-grow" src={ page.localization.getImage(1) } /> }
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-0 start-0" position="absolute" />
<CatalogSimplePriceWidgetView />
</Column>

View File

@ -15,19 +15,19 @@ export const CatalogLayoutSingleBundleView: FC<CatalogLayoutProps> = props =>
<>
<CatalogFirstProductSelectorWidgetView />
<Grid>
<Column overflow="hidden" size={7}>
{!!page.localization.getText(2) &&
<Text dangerouslySetInnerHTML={{ __html: page.localization.getText(2) }} />}
<Column overflow="hidden" size={ 7 }>
{ !!page.localization.getText(2) &&
<Text dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } /> }
<Column grow className="bg-muted p-2 rounded" overflow="hidden">
<CatalogBundleGridWidgetView fullWidth className="nitro-catalog-layout-bundle-grid" />
</Column>
</Column>
<Column gap={1} overflow="hidden" size={5}>
{!!page.localization.getText(1) &&
<Text center small overflow="auto">{page.localization.getText(1)}</Text>}
<Column grow gap={0} overflow="hidden" position="relative">
{!!page.localization.getImage(1) &&
<img alt="" className="!flex-grow" src={page.localization.getImage(1)} />}
<Column gap={ 1 } overflow="hidden" size={ 5 }>
{ !!page.localization.getText(1) &&
<Text center small overflow="auto">{ page.localization.getText(1) }</Text> }
<Column grow gap={ 0 } overflow="hidden" position="relative">
{ !!page.localization.getImage(1) &&
<img alt="" className="!flex-grow" src={ page.localization.getImage(1) } /> }
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-0 start-0" position="absolute" />
<CatalogSimplePriceWidgetView />
</Column>

View File

@ -16,8 +16,8 @@ import { CatalogLayoutProps } from './CatalogLayout.types';
export const CatalogLayoutSoundMachineView: FC<CatalogLayoutProps> = props =>
{
const { page = null } = props;
const [songId, setSongId] = useState(-1);
const [officialSongId, setOfficialSongId] = useState('');
const [ songId, setSongId ] = useState(-1);
const [ officialSongId, setOfficialSongId ] = useState('');
const { currentOffer = null, currentPage = null } = useCatalog();
const previewSong = (previewSongId: number) => GetSoundManager().musicController?.playSong(previewSongId, MusicPriorities.PRIORITY_PURCHASE_PREVIEW, 15, 0, 0, 0);
@ -60,7 +60,7 @@ export const CatalogLayoutSoundMachineView: FC<CatalogLayoutProps> = props =>
}
return () => GetSoundManager().musicController?.stop(MusicPriorities.PRIORITY_PURCHASE_PREVIEW);
}, [currentOffer]);
}, [ currentOffer ]);
useEffect(() =>
{
@ -70,32 +70,32 @@ export const CatalogLayoutSoundMachineView: FC<CatalogLayoutProps> = props =>
return (
<>
<Grid>
<Column overflow="hidden" size={7}>
{GetConfigurationValue('catalog.headers') &&
<CatalogHeaderView imageUrl={currentPage.localization.getImage(0)} />}
<Column overflow="hidden" size={ 7 }>
{ GetConfigurationValue('catalog.headers') &&
<CatalogHeaderView imageUrl={ currentPage.localization.getImage(0) } /> }
<CatalogItemGridWidgetView />
</Column>
<Column center={!currentOffer} overflow="hidden" size={5}>
{!currentOffer &&
<Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer &&
<>
{!!page.localization.getImage(1) &&
<LayoutImage imageUrl={page.localization.getImage(1)} />}
<Text center dangerouslySetInnerHTML={{ __html: page.localization.getText(0) }} />
</>}
{currentOffer &&
{ !!page.localization.getImage(1) &&
<LayoutImage imageUrl={ page.localization.getImage(1) } /> }
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
</> }
{ currentOffer &&
<>
<div className="flex items-center justify-center overflow-hidden" style={{ height: 140 }}>
{(currentOffer.product.productType !== ProductTypeEnum.BADGE) &&
<div className="flex items-center justify-center overflow-hidden" style={ { height: 140 } }>
{ (currentOffer.product.productType !== ProductTypeEnum.BADGE) &&
<>
<CatalogViewProductWidgetView />
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 end-1" />
</>}
{(currentOffer.product.productType === ProductTypeEnum.BADGE) && <CatalogAddOnBadgeWidgetView className="scale-2" />}
</> }
{ (currentOffer.product.productType === ProductTypeEnum.BADGE) && <CatalogAddOnBadgeWidgetView className="scale-2" /> }
</div>
<Column grow gap={1}>
<Column grow gap={ 1 }>
<CatalogLimitedItemWidgetView />
<Text grow truncate>{currentOffer.localizationName}</Text>
{songId > -1 && <Button onClick={() => previewSong(songId)}>{LocalizeText('play_preview_button')}</Button>
<Text grow truncate>{ currentOffer.localizationName }</Text>
{ songId > -1 && <Button onClick={ () => previewSong(songId) }>{ LocalizeText('play_preview_button') }</Button>
}
<div className="flex justify-between">
<div className="flex flex-col gap-1">
@ -105,7 +105,7 @@ export const CatalogLayoutSoundMachineView: FC<CatalogLayoutProps> = props =>
</div>
<CatalogPurchaseWidgetView />
</Column>
</>}
</> }
</Column>
</Grid>
</>

View File

@ -15,32 +15,32 @@ export const CatalogLayoutSpacesView: FC<CatalogLayoutProps> = props =>
useEffect(() =>
{
roomPreviewer.updatePreviewObjectBoundingRectangle();
}, [roomPreviewer]);
}, [ roomPreviewer ]);
return (
<Grid>
<Column overflow="hidden" size={7}>
<Column overflow="hidden" size={ 7 }>
<CatalogSpacesWidgetView />
</Column>
<Column center={!currentOffer} overflow="hidden" size={5}>
{!currentOffer &&
<Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer &&
<>
{!!page.localization.getImage(1) && <img alt="" src={page.localization.getImage(1)} />}
<Text center dangerouslySetInnerHTML={{ __html: page.localization.getText(0) }} />
</>}
{currentOffer &&
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
</> }
{ currentOffer &&
<>
<div className="relative overflow-hidden">
<CatalogViewProductWidgetView />
</div>
<Column grow gap={1}>
<Text grow truncate>{currentOffer.localizationName}</Text>
<Column grow gap={ 1 }>
<Text grow truncate>{ currentOffer.localizationName }</Text>
<div className="flex justify-end">
<CatalogTotalPriceWidget alignItems="end" />
</div>
<CatalogPurchaseWidgetView />
</Column>
</>}
</> }
</Column>
</Grid>
);

View File

@ -10,7 +10,7 @@ import { CatalogLayoutProps } from './CatalogLayout.types';
export const CatalogLayoutTrophiesView: FC<CatalogLayoutProps> = props =>
{
const { page = null } = props;
const [trophyText, setTrophyText] = useState<string>('');
const [ trophyText, setTrophyText ] = useState<string>('');
const { currentOffer = null, setPurchaseOptions = null } = useCatalog();
useEffect(() =>
@ -25,31 +25,31 @@ export const CatalogLayoutTrophiesView: FC<CatalogLayoutProps> = props =>
return newValue;
});
}, [currentOffer, trophyText, setPurchaseOptions]);
}, [ currentOffer, trophyText, setPurchaseOptions ]);
return (
<Grid>
<Column overflow="hidden" size={7}>
<Column overflow="hidden" size={ 7 }>
<CatalogItemGridWidgetView />
<textarea className="!flex-grow form-control w-full" defaultValue={trophyText || ''} onChange={event => setTrophyText(event.target.value)} />
<textarea className="!flex-grow form-control w-full" defaultValue={ trophyText || '' } onChange={ event => setTrophyText(event.target.value) } />
</Column>
<Column center={!currentOffer} overflow="hidden" size={5}>
{!currentOffer &&
<Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer &&
<>
{!!page.localization.getImage(1) && <img alt="" src={page.localization.getImage(1)} />}
<Text center dangerouslySetInnerHTML={{ __html: page.localization.getText(0) }} />
</>}
{currentOffer &&
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
</> }
{ currentOffer &&
<>
<CatalogViewProductWidgetView />
<Column grow gap={1}>
<Text grow truncate>{currentOffer.localizationName}</Text>
<Column grow gap={ 1 }>
<Text grow truncate>{ currentOffer.localizationName }</Text>
<div className="flex justify-end">
<CatalogTotalPriceWidget alignItems="end" />
</div>
<CatalogPurchaseWidgetView />
</Column>
</>}
</> }
</Column>
</Grid>
);

View File

@ -7,9 +7,9 @@ import { CatalogLayoutProps } from '../CatalogLayout.types';
import { CatalogLayoutMarketplaceItemView, PUBLIC_OFFER } from './CatalogLayoutMarketplaceItemView';
import { SearchFormView } from './CatalogLayoutMarketplaceSearchFormView';
const SORT_TYPES_VALUE = [1, 2];
const SORT_TYPES_ACTIVITY = [3, 4, 5, 6];
const SORT_TYPES_ADVANCED = [1, 2, 3, 4, 5, 6];
const SORT_TYPES_VALUE = [ 1, 2 ];
const SORT_TYPES_ACTIVITY = [ 3, 4, 5, 6 ];
const SORT_TYPES_ADVANCED = [ 1, 2, 3, 4, 5, 6 ];
export interface CatalogLayoutMarketplacePublicItemsViewProps extends CatalogLayoutProps
{
@ -17,10 +17,10 @@ export interface CatalogLayoutMarketplacePublicItemsViewProps extends CatalogLay
export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplacePublicItemsViewProps> = props =>
{
const [searchType, setSearchType] = useState(MarketplaceSearchType.BY_ACTIVITY);
const [totalItemsFound, setTotalItemsFound] = useState(0);
const [offers, setOffers] = useState(new Map<number, MarketplaceOfferData>());
const [lastSearch, setLastSearch] = useState<IMarketplaceSearchOptions>({ minPrice: -1, maxPrice: -1, query: '', type: 3 });
const [ searchType, setSearchType ] = useState(MarketplaceSearchType.BY_ACTIVITY);
const [ totalItemsFound, setTotalItemsFound ] = useState(0);
const [ offers, setOffers ] = useState(new Map<number, MarketplaceOfferData>());
const [ lastSearch, setLastSearch ] = useState<IMarketplaceSearchOptions>({ minPrice: -1, maxPrice: -1, query: '', type: 3 });
const { getCurrencyAmount = null } = usePurse();
const { simpleAlert = null, showConfirm = null } = useNotification();
@ -42,7 +42,7 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
return SORT_TYPES_ADVANCED;
}
return [];
}, [searchType]);
}, [ searchType ]);
const purchaseItem = useCallback((offerData: MarketplaceOfferData) =>
{
@ -58,8 +58,8 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
{
SendMessageComposer(new BuyMarketplaceOfferMessageComposer(offerId));
},
null, null, null, LocalizeText('catalog.marketplace.confirm_title'));
}, [getCurrencyAmount, simpleAlert, showConfirm]);
null, null, null, LocalizeText('catalog.marketplace.confirm_title'));
}, [ getCurrencyAmount, simpleAlert, showConfirm ]);
useMessageEvent<MarketPlaceOffersEvent>(MarketPlaceOffersEvent, event =>
{
@ -120,11 +120,11 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
});
showConfirm(LocalizeText('catalog.marketplace.confirm_higher_header') +
'\n' + LocalizeText('catalog.marketplace.confirm_price', ['price'], [parser.newPrice.toString()]), () =>
'\n' + LocalizeText('catalog.marketplace.confirm_price', [ 'price' ], [ parser.newPrice.toString() ]), () =>
{
SendMessageComposer(new BuyMarketplaceOfferMessageComposer(parser.offerId));
},
null, null, null, LocalizeText('catalog.marketplace.confirm_higher_title'));
null, null, null, LocalizeText('catalog.marketplace.confirm_higher_title'));
break;
case 4:
simpleAlert(LocalizeText('catalog.alert.notenough.credits.description'), NotificationAlertType.DEFAULT, null, null, LocalizeText('catalog.alert.notenough.title'));
@ -135,24 +135,24 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
return (
<>
<div className="relative inline-flex align-middle">
<Button active={(searchType === MarketplaceSearchType.BY_ACTIVITY)} onClick={() => setSearchType(MarketplaceSearchType.BY_ACTIVITY)}>
{LocalizeText('catalog.marketplace.search_by_activity')}
<Button active={ (searchType === MarketplaceSearchType.BY_ACTIVITY) } onClick={ () => setSearchType(MarketplaceSearchType.BY_ACTIVITY) }>
{ LocalizeText('catalog.marketplace.search_by_activity') }
</Button>
<Button active={(searchType === MarketplaceSearchType.BY_VALUE)} onClick={() => setSearchType(MarketplaceSearchType.BY_VALUE)}>
{LocalizeText('catalog.marketplace.search_by_value')}
<Button active={ (searchType === MarketplaceSearchType.BY_VALUE) } onClick={ () => setSearchType(MarketplaceSearchType.BY_VALUE) }>
{ LocalizeText('catalog.marketplace.search_by_value') }
</Button>
<Button active={(searchType === MarketplaceSearchType.ADVANCED)} onClick={() => setSearchType(MarketplaceSearchType.ADVANCED)}>
{LocalizeText('catalog.marketplace.search_advanced')}
<Button active={ (searchType === MarketplaceSearchType.ADVANCED) } onClick={ () => setSearchType(MarketplaceSearchType.ADVANCED) }>
{ LocalizeText('catalog.marketplace.search_advanced') }
</Button>
</div>
<SearchFormView searchType={searchType} sortTypes={getSortTypes} onSearch={requestOffers} />
<Column gap={1} overflow="hidden">
<SearchFormView searchType={ searchType } sortTypes={ getSortTypes } onSearch={ requestOffers } />
<Column gap={ 1 } overflow="hidden">
<Text shrink truncate fontWeight="bold">
{LocalizeText('catalog.marketplace.items_found', ['count'], [offers.size.toString()])}
{ LocalizeText('catalog.marketplace.items_found', [ 'count' ], [ offers.size.toString() ]) }
</Text>
<Column className="nitro-catalog-layout-marketplace-grid" overflow="auto">
{
Array.from(offers.values()).map((entry, index) => <CatalogLayoutMarketplaceItemView key={index} offerData={entry} type={PUBLIC_OFFER} onClick={purchaseItem} />)
Array.from(offers.values()).map((entry, index) => <CatalogLayoutMarketplaceItemView key={ index } offerData={ entry } type={ PUBLIC_OFFER } onClick={ purchaseItem } />)
}
</Column>
</Column>

View File

@ -13,17 +13,17 @@ export interface SearchFormViewProps
export const SearchFormView: FC<SearchFormViewProps> = props =>
{
const { searchType = null, sortTypes = null, onSearch = null } = props;
const [sortType, setSortType] = useState(sortTypes ? sortTypes[0] : 3); // first item of SORT_TYPES_ACTIVITY
const [searchQuery, setSearchQuery] = useState('');
const [min, setMin] = useState(0);
const [max, setMax] = useState(0);
const [ sortType, setSortType ] = useState(sortTypes ? sortTypes[0] : 3); // first item of SORT_TYPES_ACTIVITY
const [ searchQuery, setSearchQuery ] = useState('');
const [ min, setMin ] = useState(0);
const [ max, setMax ] = useState(0);
const onSortTypeChange = useCallback((sortType: number) =>
{
setSortType(sortType);
if ((searchType === MarketplaceSearchType.BY_ACTIVITY) || (searchType === MarketplaceSearchType.BY_VALUE)) onSearch({ minPrice: -1, maxPrice: -1, query: '', type: sortType });
}, [onSearch, searchType]);
}, [ onSearch, searchType ]);
const onClickSearch = useCallback(() =>
{
@ -31,7 +31,7 @@ export const SearchFormView: FC<SearchFormViewProps> = props =>
const maxPrice = ((max > 0) ? max : -1);
onSearch({ minPrice: minPrice, maxPrice: maxPrice, type: sortType, query: searchQuery })
}, [max, min, onSearch, searchQuery, sortType]);
}, [ max, min, onSearch, searchQuery, sortType ]);
useEffect(() =>
{
@ -42,48 +42,45 @@ export const SearchFormView: FC<SearchFormViewProps> = props =>
setSortType(sortType);
if (searchType === MarketplaceSearchType.BY_ACTIVITY || MarketplaceSearchType.BY_VALUE === searchType) onSearch({ minPrice: -1, maxPrice: -1, query: '', type: sortType });
}, [onSearch, searchType, sortTypes]);
}, [ onSearch, searchType, sortTypes ]);
return (
<div className="flex flex-col gap-1">
<div className="flex items-center gap-1">
<Text className="col-span-3">{LocalizeText('catalog.marketplace.sort_order')}</Text>
<select className="form-select form-select-sm" value={sortType} onChange={event => onSortTypeChange(parseInt(event.target.value))}>
{sortTypes.map(type => <option key={type} value={type}>{LocalizeText(`catalog.marketplace.sort.${type}`)}</option>)}
<Text className="col-span-3">{ LocalizeText('catalog.marketplace.sort_order') }</Text>
<select className="form-select form-select-sm" value={ sortType } onChange={ event => onSortTypeChange(parseInt(event.target.value)) }>
{ sortTypes.map(type => <option key={ type } value={ type }>{ LocalizeText(`catalog.marketplace.sort.${ type }`) }</option>) }
</select>
</div>
{searchType === MarketplaceSearchType.ADVANCED &&
{ searchType === MarketplaceSearchType.ADVANCED &&
<>
<div className="flex items-center gap-1">
<Text className="col-span-3">{LocalizeText('catalog.marketplace.search_name')}</Text>
<Text className="col-span-3">{ LocalizeText('catalog.marketplace.search_name') }</Text>
<NitroInput
value={searchQuery}
onChange={event => setSearchQuery(event.target.value)} />
value={ searchQuery }
onChange={ event => setSearchQuery(event.target.value) } />
</div>
<div className="flex items-center gap-1">
<Text className="col-span-3">{LocalizeText('catalog.marketplace.search_price')}</Text>
<Text className="col-span-3">{ LocalizeText('catalog.marketplace.search_price') }</Text>
<div className="flex w-full gap-1">
<NitroInput
min={ 0 }
type="number"
min={0}
value={min}
onChange={event => setMin(event.target.valueAsNumber)} />
value={ min }
onChange={ event => setMin(event.target.valueAsNumber) } />
<NitroInput
min={ 0 }
type="number"
min={0}
value={max}
onChange={event => setMax(event.target.valueAsNumber)} />
value={ max }
onChange={ event => setMax(event.target.valueAsNumber) } />
</div>
</div>
<Button className="mx-auto" variant="secondary" onClick={onClickSearch}>{LocalizeText('generic.search')}</Button>
</>}
<Button className="mx-auto" variant="secondary" onClick={ onClickSearch }>{ LocalizeText('generic.search') }</Button>
</> }
</div>
);
}

View File

@ -8,9 +8,9 @@ import { NitroInput } from '../../../../../../layout';
export const MarketplacePostOfferView: FC<{}> = props =>
{
const [item, setItem] = useState<FurnitureItem>(null);
const [askingPrice, setAskingPrice] = useState(0);
const [tempAskingPrice, setTempAskingPrice] = useState('0');
const [ item, setItem ] = useState<FurnitureItem>(null);
const [ askingPrice, setAskingPrice ] = useState(0);
const [ tempAskingPrice, setTempAskingPrice ] = useState('0');
const { catalogOptions = null, setCatalogOptions = null } = useCatalog();
const { marketplaceConfiguration = null } = catalogOptions;
const { showConfirm = null } = useNotification();
@ -47,14 +47,14 @@ export const MarketplacePostOfferView: FC<{}> = props =>
if (!item || marketplaceConfiguration) return;
SendMessageComposer(new GetMarketplaceConfigurationMessageComposer());
}, [item, marketplaceConfiguration]);
}, [ item, marketplaceConfiguration ]);
useEffect(() =>
{
if (!item) return;
return () => setAskingPrice(0);
}, [item]);
}, [ item ]);
if (!marketplaceConfiguration || !item) return null;
@ -67,54 +67,53 @@ export const MarketplacePostOfferView: FC<{}> = props =>
{
if (!item || (askingPrice < marketplaceConfiguration.minimumPrice)) return;
showConfirm(LocalizeText('inventory.marketplace.confirm_offer.info', ['furniname', 'price'], [getFurniTitle, askingPrice.toString()]), () =>
showConfirm(LocalizeText('inventory.marketplace.confirm_offer.info', [ 'furniname', 'price' ], [ getFurniTitle, askingPrice.toString() ]), () =>
{
SendMessageComposer(new MakeOfferMessageComposer(askingPrice, item.isWallItem ? 2 : 1, item.id));
setItem(null);
},
() =>
{
setItem(null)
}, null, null, LocalizeText('inventory.marketplace.confirm_offer.title'));
() =>
{
setItem(null)
}, null, null, LocalizeText('inventory.marketplace.confirm_offer.title'));
}
return (
<NitroCardView className="nitro-catalog-layout-marketplace-post-offer" theme="primary-slim">
<NitroCardHeaderView headerText={LocalizeText('inventory.marketplace.make_offer.title')} onCloseClick={event => setItem(null)} />
<NitroCardHeaderView headerText={ LocalizeText('inventory.marketplace.make_offer.title') } onCloseClick={ event => setItem(null) } />
<NitroCardContentView overflow="hidden">
<Grid fullHeight>
<Column center className="bg-muted rounded p-2" overflow="hidden" size={4}>
<LayoutFurniImageView extraData={item.extra.toString()} productClassId={item.type} productType={item.isWallItem ? ProductTypeEnum.WALL : ProductTypeEnum.FLOOR} />
<Column center className="bg-muted rounded p-2" overflow="hidden" size={ 4 }>
<LayoutFurniImageView extraData={ item.extra.toString() } productClassId={ item.type } productType={ item.isWallItem ? ProductTypeEnum.WALL : ProductTypeEnum.FLOOR } />
</Column>
<Column justifyContent="between" overflow="hidden" size={8}>
<Column grow gap={1}>
<Text fontWeight="bold">{getFurniTitle}</Text>
<Text shrink truncate>{getFurniDescription}</Text>
<Column justifyContent="between" overflow="hidden" size={ 8 }>
<Column grow gap={ 1 }>
<Text fontWeight="bold">{ getFurniTitle }</Text>
<Text shrink truncate>{ getFurniDescription }</Text>
</Column>
<Column overflow="auto">
<Text italics>
{LocalizeText('inventory.marketplace.make_offer.expiration_info', ['time'], [marketplaceConfiguration.offerTime.toString()])}
{ LocalizeText('inventory.marketplace.make_offer.expiration_info', [ 'time' ], [ marketplaceConfiguration.offerTime.toString() ]) }
</Text>
<div className="input-group has-validation">
<NitroInput min={0} placeholder={LocalizeText('inventory.marketplace.make_offer.price_request')} type="number" value={tempAskingPrice} onChange={event => updateAskingPrice(event.target.value)} />
{((askingPrice < marketplaceConfiguration.minimumPrice) || isNaN(askingPrice)) &&
<NitroInput min={ 0 } placeholder={ LocalizeText('inventory.marketplace.make_offer.price_request') } type="number" value={ tempAskingPrice } onChange={ event => updateAskingPrice(event.target.value) } />
{ ((askingPrice < marketplaceConfiguration.minimumPrice) || isNaN(askingPrice)) &&
<div className="invalid-feedback d-block">
{LocalizeText('inventory.marketplace.make_offer.min_price', ['minprice'], [marketplaceConfiguration.minimumPrice.toString()])}
</div>}
{((askingPrice > marketplaceConfiguration.maximumPrice) && !isNaN(askingPrice)) &&
{ LocalizeText('inventory.marketplace.make_offer.min_price', [ 'minprice' ], [ marketplaceConfiguration.minimumPrice.toString() ]) }
</div> }
{ ((askingPrice > marketplaceConfiguration.maximumPrice) && !isNaN(askingPrice)) &&
<div className="invalid-feedback d-block">
{LocalizeText('inventory.marketplace.make_offer.max_price', ['maxprice'], [marketplaceConfiguration.maximumPrice.toString()])}
</div>}
{(!((askingPrice < marketplaceConfiguration.minimumPrice) || (askingPrice > marketplaceConfiguration.maximumPrice) || isNaN(askingPrice))) &&
{ LocalizeText('inventory.marketplace.make_offer.max_price', [ 'maxprice' ], [ marketplaceConfiguration.maximumPrice.toString() ]) }
</div> }
{ (!((askingPrice < marketplaceConfiguration.minimumPrice) || (askingPrice > marketplaceConfiguration.maximumPrice) || isNaN(askingPrice))) &&
<div className="invalid-feedback d-block">
{LocalizeText('inventory.marketplace.make_offer.final_price', ['commission', 'finalprice'], [getCommission().toString(), (askingPrice + getCommission()).toString()])}
</div>}
{ LocalizeText('inventory.marketplace.make_offer.final_price', [ 'commission', 'finalprice' ], [ getCommission().toString(), (askingPrice + getCommission()).toString() ]) }
</div> }
</div>
<Button disabled={((askingPrice < marketplaceConfiguration.minimumPrice) || (askingPrice > marketplaceConfiguration.maximumPrice) || isNaN(askingPrice))} onClick={postItem}>
{LocalizeText('inventory.marketplace.make_offer.post')}
<Button disabled={ ((askingPrice < marketplaceConfiguration.minimumPrice) || (askingPrice > marketplaceConfiguration.maximumPrice) || isNaN(askingPrice)) } onClick={ postItem }>
{ LocalizeText('inventory.marketplace.make_offer.post') }
</Button>
</Column>
</Column>

View File

@ -14,15 +14,15 @@ import { CatalogLayoutProps } from '../CatalogLayout.types';
export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
{
const { page = null } = props;
const [petIndex, setPetIndex] = useState(-1);
const [sellablePalettes, setSellablePalettes] = useState<SellablePetPaletteData[]>([]);
const [selectedPaletteIndex, setSelectedPaletteIndex] = useState(-1);
const [sellableColors, setSellableColors] = useState<number[][]>([]);
const [selectedColorIndex, setSelectedColorIndex] = useState(-1);
const [colorsShowing, setColorsShowing] = useState(false);
const [petName, setPetName] = useState('');
const [approvalPending, setApprovalPending] = useState(true);
const [approvalResult, setApprovalResult] = useState(-1);
const [ petIndex, setPetIndex ] = useState(-1);
const [ sellablePalettes, setSellablePalettes ] = useState<SellablePetPaletteData[]>([]);
const [ selectedPaletteIndex, setSelectedPaletteIndex ] = useState(-1);
const [ sellableColors, setSellableColors ] = useState<number[][]>([]);
const [ selectedColorIndex, setSelectedColorIndex ] = useState(-1);
const [ colorsShowing, setColorsShowing ] = useState(false);
const [ petName, setPetName ] = useState('');
const [ approvalPending, setApprovalPending ] = useState(true);
const [ approvalResult, setApprovalResult ] = useState(-1);
const { currentOffer = null, setCurrentOffer = null, setPurchaseOptions = null, catalogOptions = null, roomPreviewer = null } = useCatalog();
const { petPalettes = null } = catalogOptions;
@ -31,14 +31,14 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
if (!sellableColors.length || (selectedColorIndex === -1)) return 0xFFFFFF;
return sellableColors[selectedColorIndex][0];
}, [sellableColors, selectedColorIndex]);
}, [ sellableColors, selectedColorIndex ]);
const petBreedName = useMemo(() =>
{
if ((petIndex === -1) || !sellablePalettes.length || (selectedPaletteIndex === -1)) return '';
return LocalizeText(`pet.breed.${petIndex}.${sellablePalettes[selectedPaletteIndex].breedId}`);
}, [petIndex, sellablePalettes, selectedPaletteIndex]);
return LocalizeText(`pet.breed.${ petIndex }.${ sellablePalettes[selectedPaletteIndex].breedId }`);
}, [ petIndex, sellablePalettes, selectedPaletteIndex ]);
const petPurchaseString = useMemo(() =>
{
@ -59,8 +59,8 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
while (colorString.length < 6) colorString = ('0' + colorString);
return `${paletteId}\n${colorString}`;
}, [sellablePalettes, selectedPaletteIndex, petIndex, sellableColors, selectedColorIndex]);
return `${ paletteId }\n${ colorString }`;
}, [ sellablePalettes, selectedPaletteIndex, petIndex, sellableColors, selectedColorIndex ]);
const validationErrorMessage = useMemo(() =>
{
@ -85,7 +85,7 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
if (!key || !key.length) return '';
return LocalizeText(key);
}, [approvalResult]);
}, [ approvalResult ]);
const purchasePet = useCallback(() =>
{
@ -98,11 +98,11 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
if (approvalResult === 0)
{
SendMessageComposer(new PurchaseFromCatalogComposer(page.pageId, currentOffer.offerId, `${petName}\n${petPurchaseString}`, 1));
SendMessageComposer(new PurchaseFromCatalogComposer(page.pageId, currentOffer.offerId, `${ petName }\n${ petPurchaseString }`, 1));
return;
}
}, [page, currentOffer, petName, petPurchaseString, approvalResult]);
}, [ page, currentOffer, petName, petPurchaseString, approvalResult ]);
useMessageEvent<ApproveNameMessageEvent>(ApproveNameMessageEvent, event =>
{
@ -123,7 +123,7 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
setCurrentOffer(offer);
setPetIndex(GetPetIndexFromLocalization(offer.localizationId));
setColorsShowing(false);
}, [page, setCurrentOffer]);
}, [ page, setCurrentOffer ]);
useEffect(() =>
{
@ -159,7 +159,7 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
setSellablePalettes([]);
SendMessageComposer(new GetSellablePetPalettesComposer(productData.type));
}, [currentOffer, petPalettes]);
}, [ currentOffer, petPalettes ]);
useEffect(() =>
{
@ -169,7 +169,7 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
setSelectedColorIndex((colors.length ? 0 : -1));
setSellableColors(colors);
}, [petIndex, sellablePalettes]);
}, [ petIndex, sellablePalettes ]);
useEffect(() =>
{
@ -179,64 +179,64 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
if ((petIndex === -1) || !sellablePalettes.length || (selectedPaletteIndex === -1)) return;
let petFigureString = `${petIndex} ${sellablePalettes[selectedPaletteIndex].paletteId}`;
let petFigureString = `${ petIndex } ${ sellablePalettes[selectedPaletteIndex].paletteId }`;
if (petIndex <= 7) petFigureString += ` ${getColor.toString(16)}`;
if (petIndex <= 7) petFigureString += ` ${ getColor.toString(16) }`;
roomPreviewer.addPetIntoRoom(petFigureString);
}, [roomPreviewer, petIndex, sellablePalettes, selectedPaletteIndex, getColor]);
}, [ roomPreviewer, petIndex, sellablePalettes, selectedPaletteIndex, getColor ]);
useEffect(() =>
{
setApprovalResult(-1);
}, [petName]);
}, [ petName ]);
if (!currentOffer) return null;
return (
<Grid>
<Column overflow="hidden" size={7}>
<AutoGrid columnCount={5}>
{!colorsShowing && (sellablePalettes.length > 0) && sellablePalettes.map((palette, index) =>
<Column overflow="hidden" size={ 7 }>
<AutoGrid columnCount={ 5 }>
{ !colorsShowing && (sellablePalettes.length > 0) && sellablePalettes.map((palette, index) =>
{
return (
<LayoutGridItem key={index} itemActive={(selectedPaletteIndex === index)} onClick={event => setSelectedPaletteIndex(index)}>
<LayoutPetImageView direction={2} headOnly={true} paletteId={palette.paletteId} typeId={petIndex} />
<LayoutGridItem key={ index } itemActive={ (selectedPaletteIndex === index) } onClick={ event => setSelectedPaletteIndex(index) }>
<LayoutPetImageView direction={ 2 } headOnly={ true } paletteId={ palette.paletteId } typeId={ petIndex } />
</LayoutGridItem>
);
})}
{colorsShowing && (sellableColors.length > 0) && sellableColors.map((colorSet, index) => <LayoutGridItem key={index} itemHighlight className="clear-bg" itemActive={(selectedColorIndex === index)} itemColor={ColorConverter.int2rgb(colorSet[0])} onClick={event => setSelectedColorIndex(index)} />)}
}) }
{ colorsShowing && (sellableColors.length > 0) && sellableColors.map((colorSet, index) => <LayoutGridItem key={ index } itemHighlight className="clear-bg" itemActive={ (selectedColorIndex === index) } itemColor={ ColorConverter.int2rgb(colorSet[0]) } onClick={ event => setSelectedColorIndex(index) } />) }
</AutoGrid>
</Column>
<Column center={!currentOffer} overflow="hidden" size={5}>
{!currentOffer &&
<Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer &&
<>
{!!page.localization.getImage(1) && <img alt="" src={page.localization.getImage(1)} />}
<Text center dangerouslySetInnerHTML={{ __html: page.localization.getText(0) }} />
</>}
{currentOffer &&
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
</> }
{ currentOffer &&
<>
<div className="relative overflow-hidden">
<CatalogViewProductWidgetView />
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 end-1" position="absolute" />
{((petIndex > -1) && (petIndex <= 7)) &&
<Button className="bottom-1 start-1" position="absolute" onClick={event => setColorsShowing(!colorsShowing)}>
{ ((petIndex > -1) && (petIndex <= 7)) &&
<Button className="bottom-1 start-1" position="absolute" onClick={ event => setColorsShowing(!colorsShowing) }>
<FaFillDrip className="fa-icon" />
</Button>}
</Button> }
</div>
<Column grow gap={1}>
<Text truncate>{petBreedName}</Text>
<Column grow gap={1}>
<input className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm w-full" placeholder={LocalizeText('widgets.petpackage.name.title')} type="text" value={petName} onChange={event => setPetName(event.target.value)} />
{(approvalResult > 0) &&
<div className="invalid-feedback d-block m-0">{validationErrorMessage}</div>}
<Column grow gap={ 1 }>
<Text truncate>{ petBreedName }</Text>
<Column grow gap={ 1 }>
<input className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm w-full" placeholder={ LocalizeText('widgets.petpackage.name.title') } type="text" value={ petName } onChange={ event => setPetName(event.target.value) } />
{ (approvalResult > 0) &&
<div className="invalid-feedback d-block m-0">{ validationErrorMessage }</div> }
</Column>
<div className="flex justify-end">
<CatalogTotalPriceWidget alignItems="end" justifyContent="end" />
</div>
<CatalogPurchaseWidgetView purchaseCallback={purchasePet} />
<CatalogPurchaseWidgetView purchaseCallback={ purchasePet } />
</Column>
</>}
</> }
</Column>
</Grid>
);

View File

@ -6,7 +6,7 @@ import { useCatalog } from '../../../../../hooks';
export const CatalogGuildSelectorWidgetView: FC<{}> = props =>
{
const [selectedGroupIndex, setSelectedGroupIndex] = useState<number>(0);
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState<number>(0);
const { currentOffer = null, catalogOptions = null, setPurchaseOptions = null } = useCatalog();
const { groups = null } = catalogOptions;
@ -20,10 +20,10 @@ export const CatalogGuildSelectorWidgetView: FC<{}> = props =>
const stuffData = new StringDataType();
stuffData.setValue(['0', group.groupId.toString(), group.badgeCode, group.colorA, group.colorB]);
stuffData.setValue([ '0', group.groupId.toString(), group.badgeCode, group.colorA, group.colorB ]);
return stuffData;
}, [selectedGroupIndex, groups]);
}, [ selectedGroupIndex, groups ]);
useEffect(() =>
{
@ -39,7 +39,7 @@ export const CatalogGuildSelectorWidgetView: FC<{}> = props =>
return newValue;
});
}, [currentOffer, previewStuffData, setPurchaseOptions]);
}, [ currentOffer, previewStuffData, setPurchaseOptions ]);
useEffect(() =>
{
@ -50,9 +50,9 @@ export const CatalogGuildSelectorWidgetView: FC<{}> = props =>
{
return (
<div className="bg-muted rounded p-1 text-black text-center">
{LocalizeText('catalog.guild_selector.members_only')}
{ LocalizeText('catalog.guild_selector.members_only') }
<Button className="mt-1">
{LocalizeText('catalog.guild_selector.find_groups')}
{ LocalizeText('catalog.guild_selector.find_groups') }
</Button>
</div>
);
@ -62,13 +62,13 @@ export const CatalogGuildSelectorWidgetView: FC<{}> = props =>
return (
<div className="flex gap-1">
{!!selectedGroup &&
{ !!selectedGroup &&
<Flex className="rounded border" overflow="hidden">
<div className="h-full" style={{ width: '20px', backgroundColor: '#' + selectedGroup.colorA }} />
<div className="h-full" style={{ width: '20px', backgroundColor: '#' + selectedGroup.colorB }} />
</Flex>}
<select className="form-select form-select-sm" value={selectedGroupIndex} onChange={event => setSelectedGroupIndex(parseInt(event.target.value))}>
{groups.map((group, index) => <option key={index} value={index}>{group.groupName}</option>)}
<div className="h-full" style={ { width: '20px', backgroundColor: '#' + selectedGroup.colorA } } />
<div className="h-full" style={ { width: '20px', backgroundColor: '#' + selectedGroup.colorB } } />
</Flex> }
<select className="form-select form-select-sm" value={ selectedGroupIndex } onChange={ event => setSelectedGroupIndex(parseInt(event.target.value)) }>
{ groups.map((group, index) => <option key={ index } value={ index }>{ group.groupName }</option>) }
</select>
</div>
);

View File

@ -11,7 +11,7 @@ export const CatalogLimitedItemWidgetView: FC = props =>
return (
<div className="w-full">
<LayoutLimitedEditionCompletePlateView className="mx-auto" uniqueLimitedItemsLeft={currentOffer.product.uniqueLimitedItemsLeft} uniqueLimitedSeriesSize={currentOffer.product.uniqueLimitedItemSeriesSize} />
<LayoutLimitedEditionCompletePlateView className="mx-auto" uniqueLimitedItemsLeft={ currentOffer.product.uniqueLimitedItemsLeft } uniqueLimitedSeriesSize={ currentOffer.product.uniqueLimitedItemSeriesSize } />
</div>
);
}

View File

@ -9,14 +9,14 @@ interface CatalogSpacesWidgetViewProps extends AutoGridProps
}
const SPACES_GROUP_NAMES = ['floors', 'walls', 'views'];
const SPACES_GROUP_NAMES = [ 'floors', 'walls', 'views' ];
export const CatalogSpacesWidgetView: FC<CatalogSpacesWidgetViewProps> = props =>
{
const { columnCount = 5, children = null, ...rest } = props;
const [groupedOffers, setGroupedOffers] = useState<IPurchasableOffer[][]>(null);
const [selectedGroupIndex, setSelectedGroupIndex] = useState(-1);
const [selectedOfferForGroup, setSelectedOfferForGroup] = useState<IPurchasableOffer[]>(null);
const [ groupedOffers, setGroupedOffers ] = useState<IPurchasableOffer[][]>(null);
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState(-1);
const [ selectedOfferForGroup, setSelectedOfferForGroup ] = useState<IPurchasableOffer[]>(null);
const { currentPage = null, currentOffer = null, setCurrentOffer = null, setPurchaseOptions = null } = useCatalog();
const elementRef = useRef<HTMLDivElement>();
@ -26,7 +26,7 @@ export const CatalogSpacesWidgetView: FC<CatalogSpacesWidgetViewProps> = props =
setSelectedOfferForGroup(prevValue =>
{
const newValue = [...prevValue];
const newValue = [ ...prevValue ];
newValue[selectedGroupIndex] = offer;
@ -38,7 +38,7 @@ export const CatalogSpacesWidgetView: FC<CatalogSpacesWidgetViewProps> = props =
{
if (!currentPage) return;
const groupedOffers: IPurchasableOffer[][] = [[], [], []];
const groupedOffers: IPurchasableOffer[][] = [ [], [], [] ];
for (const offer of currentPage.offers)
{
@ -66,8 +66,8 @@ export const CatalogSpacesWidgetView: FC<CatalogSpacesWidgetViewProps> = props =
setGroupedOffers(groupedOffers);
setSelectedGroupIndex(0);
setSelectedOfferForGroup([groupedOffers[0][0], groupedOffers[1][0], groupedOffers[2][0]]);
}, [currentPage]);
setSelectedOfferForGroup([ groupedOffers[0][0], groupedOffers[1][0], groupedOffers[2][0] ]);
}, [ currentPage ]);
useEffect(() =>
{
@ -75,7 +75,7 @@ export const CatalogSpacesWidgetView: FC<CatalogSpacesWidgetViewProps> = props =
setCurrentOffer(selectedOfferForGroup[selectedGroupIndex]);
}, [selectedGroupIndex, selectedOfferForGroup, setCurrentOffer]);
}, [ selectedGroupIndex, selectedOfferForGroup, setCurrentOffer ]);
useEffect(() =>
{
@ -90,12 +90,12 @@ export const CatalogSpacesWidgetView: FC<CatalogSpacesWidgetViewProps> = props =
return newValue;
});
}, [currentOffer, selectedGroupIndex, selectedOfferForGroup, setPurchaseOptions]);
}, [ currentOffer, selectedGroupIndex, selectedOfferForGroup, setPurchaseOptions ]);
useEffect(() =>
{
if (elementRef && elementRef.current) elementRef.current.scrollTop = 0;
}, [selectedGroupIndex]);
}, [ selectedGroupIndex ]);
if (!groupedOffers || (selectedGroupIndex === -1)) return null;
@ -104,11 +104,11 @@ export const CatalogSpacesWidgetView: FC<CatalogSpacesWidgetViewProps> = props =
return (
<>
<div className="relative inline-flex align-middle">
{SPACES_GROUP_NAMES.map((name, index) => <Button key={index} active={(selectedGroupIndex === index)} onClick={event => setSelectedGroupIndex(index)}>{LocalizeText(`catalog.spaces.tab.${name}`)}</Button>)}
{ SPACES_GROUP_NAMES.map((name, index) => <Button key={ index } active={ (selectedGroupIndex === index) } onClick={ event => setSelectedGroupIndex(index) }>{ LocalizeText(`catalog.spaces.tab.${ name }`) }</Button>) }
</div>
<AutoGrid columnCount={columnCount} innerRef={elementRef} {...rest}>
{offers && (offers.length > 0) && offers.map((offer, index) => <CatalogGridOfferView key={index} itemActive={(currentOffer && (currentOffer === offer))} offer={offer} selectOffer={offer => setSelectedOffer(offer)} />)}
{children}
<AutoGrid columnCount={ columnCount } innerRef={ elementRef } { ...rest }>
{ offers && (offers.length > 0) && offers.map((offer, index) => <CatalogGridOfferView key={ index } itemActive={ (currentOffer && (currentOffer === offer)) } offer={ offer } selectOffer={ offer => setSelectedOffer(offer) } />) }
{ children }
</AutoGrid>
</>
);

View File

@ -35,11 +35,11 @@ export const CatalogSpinnerWidgetView: FC<{}> = props =>
return (
<>
<Text>{LocalizeText('catalog.bundlewidget.spinner.select.amount')}</Text>
<Text>{ LocalizeText('catalog.bundlewidget.spinner.select.amount') }</Text>
<div className="flex items-center gap-1">
<FaCaretLeft className="text-black cursor-pointer fa-icon" onClick={event => updateQuantity(quantity - 1)} />
<input className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none min-h-[17px] h-[17px] w-[28px] px-[4px] py-[0] text-right rounded-[.2rem]" type="number" value={quantity} onChange={event => updateQuantity(event.target.valueAsNumber)} />
<FaCaretRight className="text-black cursor-pointer fa-icon" onClick={event => updateQuantity(quantity + 1)} />
<FaCaretLeft className="text-black cursor-pointer fa-icon" onClick={ event => updateQuantity(quantity - 1) } />
<input className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none min-h-[17px] h-[17px] w-[28px] px-[4px] py-[0] text-right rounded-[.2rem]" type="number" value={ quantity } onChange={ event => updateQuantity(event.target.valueAsNumber) } />
<FaCaretRight className="text-black cursor-pointer fa-icon" onClick={ event => updateQuantity(quantity + 1) } />
</div>
</>
);

View File

@ -10,7 +10,7 @@ export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Disp
const { getCurrencyAmount } = usePurse();
const [amount, setAmount] = useState<number>(1);
const [ amount, setAmount ] = useState<number>(1);
const canPurchase = useMemo(() =>
{
@ -26,7 +26,7 @@ export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Disp
if (offer.purchaseLimit > 0) limit = true;
return (credits && points && limit);
}, [offer, getCurrencyAmount])
}, [ offer, getCurrencyAmount ])
const expirationTime = () =>
{
@ -44,41 +44,41 @@ export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Disp
if (!offer) return;
return <NitroCardView className="nitro-targeted-offer" theme="primary-slim" uniqueKey="targeted-offer">
<NitroCardHeaderView headerText={LocalizeText(offer.title)} onCloseClick={event => setOpen(false)} />
<NitroCardHeaderView headerText={ LocalizeText(offer.title) } onCloseClick={ event => setOpen(false) } />
<div className="container-fluid p-1 relative justify-center items-center cursor-pointer gap-3 bg-danger">
{LocalizeText('targeted.offer.timeleft', ['timeleft'], [expirationTime()])}
{ LocalizeText('targeted.offer.timeleft', [ 'timeleft' ], [ expirationTime() ]) }
</div>
<NitroCardContentView gap={1}>
<Flex fullHeight gap={1}>
<Flex column className="w-75 text-black" gap={1}>
<NitroCardContentView gap={ 1 }>
<Flex fullHeight gap={ 1 }>
<Flex column className="w-75 text-black" gap={ 1 }>
<Column fullHeight className="bg-warning p-2">
<h4>
{LocalizeText(offer.title)}
{ LocalizeText(offer.title) }
</h4>
<div dangerouslySetInnerHTML={{ __html: offer.description }} />
<div dangerouslySetInnerHTML={ { __html: offer.description } } />
</Column>
<Flex alignItems="center" alignSelf="center" gap={2} justifyContent="center">
{offer.purchaseLimit > 1 &&
<Flex alignItems="center" alignSelf="center" gap={ 2 } justifyContent="center">
{ offer.purchaseLimit > 1 &&
<div className="flex gap-1">
<Text variant="muted">{LocalizeText('catalog.bundlewidget.quantity')}</Text>
<input max={offer.purchaseLimit} min={1} type="number" value={amount} onChange={evt => setAmount(parseInt(evt.target.value))} />
</div>}
<Button disabled={!canPurchase} variant="primary" onClick={() => buyOffer()}>{LocalizeText('targeted.offer.button.buy')}</Button>
<Text variant="muted">{ LocalizeText('catalog.bundlewidget.quantity') }</Text>
<input max={ offer.purchaseLimit } min={ 1 } type="number" value={ amount } onChange={ evt => setAmount(parseInt(evt.target.value)) } />
</div> }
<Button disabled={ !canPurchase } variant="primary" onClick={ () => buyOffer() }>{ LocalizeText('targeted.offer.button.buy') }</Button>
</Flex>
</Flex>
<div className="w-50 h-full" style={{ background: `url(${GetConfigurationValue('image.library.url') + offer.imageUrl}) no-repeat center` }} />
<div className="w-50 h-full" style={ { background: `url(${ GetConfigurationValue('image.library.url') + offer.imageUrl }) no-repeat center` } } />
</Flex>
<Flex column alignItems="center" className="price-ray absolute" justifyContent="center">
<Text>{LocalizeText('targeted.offer.price.label')}</Text>
{offer.priceInCredits > 0 &&
<Text>{ LocalizeText('targeted.offer.price.label') }</Text>
{ offer.priceInCredits > 0 &&
<div className="flex gap-1">
<Text variant="light">{offer.priceInCredits}</Text>
<LayoutCurrencyIcon type={-1} />
</div>}
{offer.priceInActivityPoints > 0 &&
<Text variant="light">{ offer.priceInCredits }</Text>
<LayoutCurrencyIcon type={ -1 } />
</div> }
{ offer.priceInActivityPoints > 0 &&
<div className="flex gap-1">
<Text className="ubuntu-bold" variant="light">+{offer.priceInActivityPoints}</Text> <LayoutCurrencyIcon type={offer.activityPointType} />
</div>}
<Text className="ubuntu-bold" variant="light">+{ offer.priceInActivityPoints }</Text> <LayoutCurrencyIcon type={ offer.activityPointType } />
</div> }
</Flex>
</NitroCardContentView>
</NitroCardView>;

View File

@ -3,11 +3,12 @@ import { FC, useEffect, useMemo, useRef, useState } from 'react';
import { ChatEntryType, LocalizeText } from '../../api';
import { Flex, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
import { useChatHistory } from '../../hooks';
import { NitroInput } from '../../layout';
export const ChatHistoryView: FC<{}> = props =>
{
const [isVisible, setIsVisible] = useState(false);
const [searchText, setSearchText] = useState<string>('');
const [ isVisible, setIsVisible ] = useState(false);
const [ searchText, setSearchText ] = useState<string>('');
const { chatHistory = [] } = useChatHistory();
const elementRef = useRef<HTMLDivElement>(null);
@ -18,12 +19,12 @@ export const ChatHistoryView: FC<{}> = props =>
let text = searchText.toLowerCase();
return chatHistory.filter(entry => ((entry.message && entry.message.toLowerCase().includes(text))) || (entry.name && entry.name.toLowerCase().includes(text)));
}, [chatHistory, searchText]);
}, [ chatHistory, searchText ]);
useEffect(() =>
{
if (elementRef && elementRef.current && isVisible) elementRef.current.scrollTop = elementRef.current.scrollHeight;
}, [isVisible]);
}, [ isVisible ]);
useEffect(() =>
{
@ -59,37 +60,37 @@ export const ChatHistoryView: FC<{}> = props =>
return (
<NitroCardView className="nitro-chat-history" theme="primary-slim" uniqueKey="chat-history">
<NitroCardHeaderView headerText={LocalizeText('room.chathistory.button.text')} onCloseClick={event => setIsVisible(false)} />
<NitroCardContentView gap={2} innerRef={elementRef} overflow="hidden">
<NitroInput placeholder={LocalizeText('generic.search')} type="text" value={searchText} onChange={event => setSearchText(event.target.value)} />
<InfiniteScroll rowRender={row =>
<NitroCardHeaderView headerText={ LocalizeText('room.chathistory.button.text') } onCloseClick={ event => setIsVisible(false) } />
<NitroCardContentView gap={ 2 } innerRef={ elementRef } overflow="hidden">
<NitroInput placeholder={ LocalizeText('generic.search') } type="text" value={ searchText } onChange={ event => setSearchText(event.target.value) } />
<InfiniteScroll rowRender={ row =>
{
return (
<Flex alignItems="center" className="p-1" gap={2}>
<Text variant="muted">{row.timestamp}</Text>
{(row.type === ChatEntryType.TYPE_CHAT) &&
<div className="bubble-container" style={{ position: 'relative' }}>
{(row.style === 0) &&
<div className="user-container-bg" style={{ backgroundColor: row.color }} />}
<div className={`chat-bubble bubble-${row.style} type-${row.chatType}`} style={{ maxWidth: '100%' }}>
<Flex alignItems="center" className="p-1" gap={ 2 }>
<Text variant="muted">{ row.timestamp }</Text>
{ (row.type === ChatEntryType.TYPE_CHAT) &&
<div className="bubble-container" style={ { position: 'relative' } }>
{ (row.style === 0) &&
<div className="user-container-bg" style={ { backgroundColor: row.color } } /> }
<div className={ `chat-bubble bubble-${ row.style } type-${ row.chatType }` } style={ { maxWidth: '100%' } }>
<div className="user-container">
{row.imageUrl && (row.imageUrl.length > 0) &&
<div className="user-image" style={{ backgroundImage: `url(${row.imageUrl})` }} />}
{ row.imageUrl && (row.imageUrl.length > 0) &&
<div className="user-image" style={ { backgroundImage: `url(${ row.imageUrl })` } } /> }
</div>
<div className="chat-content">
<b className="username mr-1" dangerouslySetInnerHTML={{ __html: `${row.name}: ` }} />
<span className="message" dangerouslySetInnerHTML={{ __html: `${row.message}` }} />
<b className="mr-1 username" dangerouslySetInnerHTML={ { __html: `${ row.name }: ` } } />
<span className="message" dangerouslySetInnerHTML={ { __html: `${ row.message }` } } />
</div>
</div>
</div>}
{(row.type === ChatEntryType.TYPE_ROOM_INFO) &&
</div> }
{ (row.type === ChatEntryType.TYPE_ROOM_INFO) &&
<>
<i className="nitro-icon icon-small-room" />
<Text grow textBreak wrap>{row.name}</Text>
</>}
<Text grow textBreak wrap>{ row.name }</Text>
</> }
</Flex>
)
}} rows={filteredChatHistory} scrollToBottom={true} />
} } rows={ filteredChatHistory } scrollToBottom={ true } />
</NitroCardContentView>
</NitroCardView>
);

View File

@ -14,18 +14,18 @@ import { FloorplanOptionsView } from './views/FloorplanOptionsView';
export const FloorplanEditorView: FC<{}> = props =>
{
const [isVisible, setIsVisible] = useState(false);
const [importExportVisible, setImportExportVisible] = useState(false);
const [originalFloorplanSettings, setOriginalFloorplanSettings] = useState<IFloorplanSettings>({
const [ isVisible, setIsVisible ] = useState(false);
const [ importExportVisible, setImportExportVisible ] = useState(false);
const [ originalFloorplanSettings, setOriginalFloorplanSettings ] = useState<IFloorplanSettings>({
tilemap: '',
reservedTiles: [],
entryPoint: [0, 0],
entryPoint: [ 0, 0 ],
entryPointDir: 2,
wallHeight: -1,
thicknessWall: 1,
thicknessFloor: 1
});
const [visualizationSettings, setVisualizationSettings] = useState<IVisualizationSettings>({
const [ visualizationSettings, setVisualizationSettings ] = useState<IVisualizationSettings>({
entryPointDir: 2,
wallHeight: -1,
thicknessWall: 1,
@ -136,25 +136,25 @@ export const FloorplanEditorView: FC<{}> = props =>
}, []);
return (
<FloorplanEditorContextProvider value={{ originalFloorplanSettings: originalFloorplanSettings, setOriginalFloorplanSettings: setOriginalFloorplanSettings, visualizationSettings: visualizationSettings, setVisualizationSettings: setVisualizationSettings }}>
{isVisible &&
<FloorplanEditorContextProvider value={ { originalFloorplanSettings: originalFloorplanSettings, setOriginalFloorplanSettings: setOriginalFloorplanSettings, visualizationSettings: visualizationSettings, setVisualizationSettings: setVisualizationSettings } }>
{ isVisible &&
<NitroCardView className="w-[760px] h-[500px]" theme="primary-slim" uniqueKey="floorpan-editor">
<NitroCardHeaderView headerText={LocalizeText('floor.plan.editor.title')} onCloseClick={() => setIsVisible(false)} />
<NitroCardHeaderView headerText={ LocalizeText('floor.plan.editor.title') } onCloseClick={ () => setIsVisible(false) } />
<NitroCardContentView overflow="hidden">
<FloorplanOptionsView />
<FloorplanCanvasView overflow="hidden" />
<div className="flex justify-between">
<Button onClick={revertChanges}>{LocalizeText('floor.plan.editor.reload')}</Button>
<Button onClick={ revertChanges }>{ LocalizeText('floor.plan.editor.reload') }</Button>
<div className="relative inline-flex align-middle">
<Button disabled={true}>{LocalizeText('floor.plan.editor.preview')}</Button>
<Button onClick={event => setImportExportVisible(true)}>{LocalizeText('floor.plan.editor.import.export')}</Button>
<Button onClick={saveFloorChanges}>{LocalizeText('floor.plan.editor.save')}</Button>
<Button disabled={ true }>{ LocalizeText('floor.plan.editor.preview') }</Button>
<Button onClick={ event => setImportExportVisible(true) }>{ LocalizeText('floor.plan.editor.import.export') }</Button>
<Button onClick={ saveFloorChanges }>{ LocalizeText('floor.plan.editor.save') }</Button>
</div>
</div>
</NitroCardContentView>
</NitroCardView>}
{importExportVisible &&
<FloorplanImportExportView onCloseClick={() => setImportExportVisible(false)} />}
</NitroCardView> }
{ importExportVisible &&
<FloorplanImportExportView onCloseClick={ () => setImportExportVisible(false) } /> }
</FloorplanEditorContextProvider>
);
}

View File

@ -10,8 +10,8 @@ import { FloorplanEditor } from '../common/FloorplanEditor';
export const FloorplanCanvasView: FC<ColumnProps> = props =>
{
const { gap = 1, children = null, ...rest } = props;
const [occupiedTilesReceived, setOccupiedTilesReceived] = useState(false);
const [entryTileReceived, setEntryTileReceived] = useState(false);
const [ occupiedTilesReceived, setOccupiedTilesReceived ] = useState(false);
const [ entryTileReceived, setEntryTileReceived ] = useState(false);
const { originalFloorplanSettings = null, setOriginalFloorplanSettings = null, setVisualizationSettings = null } = useFloorplanEditorContext();
const elementRef = useRef<HTMLDivElement>(null);
@ -43,7 +43,7 @@ export const FloorplanCanvasView: FC<ColumnProps> = props =>
{
const newValue = { ...prevValue };
newValue.entryPoint = [parser.x, parser.y];
newValue.entryPoint = [ parser.x, parser.y ];
newValue.entryPointDir = parser.direction;
return newValue;
@ -121,14 +121,14 @@ export const FloorplanCanvasView: FC<ColumnProps> = props =>
}
});
}
}, [originalFloorplanSettings.thicknessFloor, originalFloorplanSettings.thicknessWall, originalFloorplanSettings.wallHeight, setVisualizationSettings]);
}, [ originalFloorplanSettings.thicknessFloor, originalFloorplanSettings.thicknessWall, originalFloorplanSettings.wallHeight, setVisualizationSettings ]);
useEffect(() =>
{
if (!entryTileReceived || !occupiedTilesReceived) return;
FloorplanEditor.instance.renderTiles();
}, [entryTileReceived, occupiedTilesReceived]);
}, [ entryTileReceived, occupiedTilesReceived ]);
useEffect(() =>
{
@ -167,33 +167,33 @@ export const FloorplanCanvasView: FC<ColumnProps> = props =>
const isSmallScreen = () => window.innerWidth < 768;
return (
<Column gap={gap} {...rest}>
<Grid gap={1} overflow="hidden">
<Column center className="hidden" size={1}>
<Button className="hidden" onClick={event => onClickArrowButton('left')}>
<Column gap={ gap } { ...rest }>
<Grid gap={ 1 } overflow="hidden">
<Column center className="hidden" size={ 1 }>
<Button className="hidden" onClick={ event => onClickArrowButton('left') }>
<FaArrowLeft className="fa-icon" />
</Button>
</Column>
<Column gap={1} overflow="hidden" size={isSmallScreen() ? 10 : 12}>
<Column gap={ 1 } overflow="hidden" size={ isSmallScreen() ? 10 : 12 }>
<div className="flex hidden justify-content-enter" >
<Button shrink onClick={event => onClickArrowButton('up')}>
<Button shrink onClick={ event => onClickArrowButton('up') }>
<FaArrowUp className="fa-icon" />
</Button>
</div>
<div ref={elementRef} className="overflow-auto" />
<div ref={ elementRef } className="overflow-auto" />
<div className="flex hidden justify-center">
<Button shrink onClick={event => onClickArrowButton('down')}>
<Button shrink onClick={ event => onClickArrowButton('down') }>
<FaArrowDown className="fa-icon" />
</Button>
</div>
</Column>
<Column center className="hidden" size={1}>
<Button className="hidden" onClick={event => onClickArrowButton('right')}>
<Column center className="hidden" size={ 1 }>
<Button className="hidden" onClick={ event => onClickArrowButton('right') }>
<FaArrowRight className="fa-icon" />
</Button>
</Column>
</Grid>
{children}
{ children }
</Column>
);
}

View File

@ -15,7 +15,7 @@ interface FloorplanImportExportViewProps
export const FloorplanImportExportView: FC<FloorplanImportExportViewProps> = props =>
{
const { onCloseClick = null } = props;
const [map, setMap] = useState<string>('');
const [ map, setMap ] = useState<string>('');
const { originalFloorplanSettings = null } = useFloorplanEditorContext();
const saveFloorChanges = () =>
@ -38,15 +38,15 @@ export const FloorplanImportExportView: FC<FloorplanImportExportViewProps> = pro
return (
<NitroCardView className="floorplan-import-export" theme="primary-slim">
<NitroCardHeaderView headerText={LocalizeText('floor.plan.editor.import.export')} onCloseClick={onCloseClick} />
<NitroCardHeaderView headerText={ LocalizeText('floor.plan.editor.import.export') } onCloseClick={ onCloseClick } />
<NitroCardContentView>
<textarea className="h-full" value={map} onChange={event => setMap(event.target.value)} />
<textarea className="h-full" value={ map } onChange={ event => setMap(event.target.value) } />
<div className="flex justify-between">
<Button onClick={event => setMap(ConvertTileMapToString(originalFloorplanSettings.tilemap))}>
{LocalizeText('floor.plan.editor.revert.to.last.received.map')}
<Button onClick={ event => setMap(ConvertTileMapToString(originalFloorplanSettings.tilemap)) }>
{ LocalizeText('floor.plan.editor.revert.to.last.received.map') }
</Button>
<Button onClick={saveFloorChanges}>
{LocalizeText('floor.plan.editor.save')}
<Button onClick={ saveFloorChanges }>
{ LocalizeText('floor.plan.editor.save') }
</Button>
</div>
</NitroCardContentView>

View File

@ -16,8 +16,8 @@ const MAX_FLOOR_HEIGHT: number = 26;
export const FloorplanOptionsView: FC<{}> = props =>
{
const { visualizationSettings = null, setVisualizationSettings = null } = useFloorplanEditorContext();
const [floorAction, setFloorAction] = useState(FloorAction.SET);
const [floorHeight, setFloorHeight] = useState(0);
const [ floorAction, setFloorAction ] = useState(FloorAction.SET);
const [ floorHeight, setFloorHeight ] = useState(0);
const selectAction = (action: number) =>
{
@ -117,69 +117,69 @@ export const FloorplanOptionsView: FC<{}> = props =>
return (
<div className="flex flex-col">
<Grid>
<Column gap={1} size={5}>
<Text bold>{LocalizeText('floor.plan.editor.draw.mode')}</Text>
<Flex gap={3}>
<Column gap={ 1 } size={ 5 }>
<Text bold>{ LocalizeText('floor.plan.editor.draw.mode') }</Text>
<Flex gap={ 3 }>
<div className="flex gap-1">
<LayoutGridItem itemActive={(floorAction === FloorAction.SET)} onClick={event => selectAction(FloorAction.SET)}>
<LayoutGridItem itemActive={ (floorAction === FloorAction.SET) } onClick={ event => selectAction(FloorAction.SET) }>
<i className="nitro-icon icon-set-tile" />
</LayoutGridItem>
<LayoutGridItem itemActive={(floorAction === FloorAction.UNSET)} onClick={event => selectAction(FloorAction.UNSET)}>
<LayoutGridItem itemActive={ (floorAction === FloorAction.UNSET) } onClick={ event => selectAction(FloorAction.UNSET) }>
<i className="nitro-icon icon-unset-tile" />
</LayoutGridItem>
</div>
<div className="flex gap-1">
<LayoutGridItem itemActive={(floorAction === FloorAction.UP)} onClick={event => selectAction(FloorAction.UP)}>
<LayoutGridItem itemActive={ (floorAction === FloorAction.UP) } onClick={ event => selectAction(FloorAction.UP) }>
<i className="nitro-icon icon-increase-height" />
</LayoutGridItem>
<LayoutGridItem itemActive={(floorAction === FloorAction.DOWN)} onClick={event => selectAction(FloorAction.DOWN)}>
<LayoutGridItem itemActive={ (floorAction === FloorAction.DOWN) } onClick={ event => selectAction(FloorAction.DOWN) }>
<i className="nitro-icon icon-decrease-height" />
</LayoutGridItem>
</div>
<LayoutGridItem itemActive={(floorAction === FloorAction.DOOR)} onClick={event => selectAction(FloorAction.DOOR)}>
<LayoutGridItem itemActive={ (floorAction === FloorAction.DOOR) } onClick={ event => selectAction(FloorAction.DOOR) }>
<i className="nitro-icon icon-set-door" />
</LayoutGridItem>
</Flex>
</Column>
<Column alignItems="center" size={4}>
<Text bold>{LocalizeText('floor.plan.editor.enter.direction')}</Text>
<i className={`nitro-icon icon-door-direction-${visualizationSettings.entryPointDir} cursor-pointer`} onClick={changeDoorDirection} />
<Column alignItems="center" size={ 4 }>
<Text bold>{ LocalizeText('floor.plan.editor.enter.direction') }</Text>
<i className={ `nitro-icon icon-door-direction-${ visualizationSettings.entryPointDir } cursor-pointer` } onClick={ changeDoorDirection } />
</Column>
<Column size={3}>
<Text bold>{LocalizeText('floor.editor.wall.height')}</Text>
<Column size={ 3 }>
<Text bold>{ LocalizeText('floor.editor.wall.height') }</Text>
<div className="flex items-center gap-1">
<FaCaretLeft className="cursor-pointer fa-icon" onClick={decreaseWallHeight} />
<input className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm quantity-input" type="number" value={visualizationSettings.wallHeight} onChange={event => onWallHeightChange(event.target.valueAsNumber)} />
<FaCaretRight className="cursor-pointer fa-icon" onClick={increaseWallHeight} />
<FaCaretLeft className="cursor-pointer fa-icon" onClick={ decreaseWallHeight } />
<input className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm quantity-input" type="number" value={ visualizationSettings.wallHeight } onChange={ event => onWallHeightChange(event.target.valueAsNumber) } />
<FaCaretRight className="cursor-pointer fa-icon" onClick={ increaseWallHeight } />
</div>
</Column>
</Grid>
<Grid>
<Column size={6}>
<Text bold>{LocalizeText('floor.plan.editor.tile.height')}: {floorHeight}</Text>
<Column size={ 6 }>
<Text bold>{ LocalizeText('floor.plan.editor.tile.height') }: { floorHeight }</Text>
<ReactSlider
className="nitro-slider"
max={MAX_FLOOR_HEIGHT}
min={MIN_FLOOR_HEIGHT}
renderThumb={({ style, ...rest }, state) => <div style={{ backgroundColor: `#${COLORMAP[state.valueNow.toString(33)]}`, ...style }} {...rest}>{state.valueNow}</div>}
step={1}
value={floorHeight}
onChange={event => onFloorHeightChange(event)} />
max={ MAX_FLOOR_HEIGHT }
min={ MIN_FLOOR_HEIGHT }
renderThumb={ ({ style, ...rest }, state) => <div style={ { backgroundColor: `#${ COLORMAP[state.valueNow.toString(33)] }`, ...style } } { ...rest }>{ state.valueNow }</div> }
step={ 1 }
value={ floorHeight }
onChange={ event => onFloorHeightChange(event) } />
</Column>
<Column size={6}>
<Text bold>{LocalizeText('floor.plan.editor.room.options')}</Text>
<Column size={ 6 }>
<Text bold>{ LocalizeText('floor.plan.editor.room.options') }</Text>
<Flex className="items-center">
<select className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm" value={visualizationSettings.thicknessWall} onChange={event => onWallThicknessChange(parseInt(event.target.value))}>
<option value={0}>{LocalizeText('navigator.roomsettings.wall_thickness.thinnest')}</option>
<option value={1}>{LocalizeText('navigator.roomsettings.wall_thickness.thin')}</option>
<option value={2}>{LocalizeText('navigator.roomsettings.wall_thickness.normal')}</option>
<option value={3}>{LocalizeText('navigator.roomsettings.wall_thickness.thick')}</option>
<select className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm" value={ visualizationSettings.thicknessWall } onChange={ event => onWallThicknessChange(parseInt(event.target.value)) }>
<option value={ 0 }>{ LocalizeText('navigator.roomsettings.wall_thickness.thinnest') }</option>
<option value={ 1 }>{ LocalizeText('navigator.roomsettings.wall_thickness.thin') }</option>
<option value={ 2 }>{ LocalizeText('navigator.roomsettings.wall_thickness.normal') }</option>
<option value={ 3 }>{ LocalizeText('navigator.roomsettings.wall_thickness.thick') }</option>
</select>
<select className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm" value={visualizationSettings.thicknessFloor} onChange={event => onFloorThicknessChange(parseInt(event.target.value))}>
<option value={0}>{LocalizeText('navigator.roomsettings.floor_thickness.thinnest')}</option>
<option value={1}>{LocalizeText('navigator.roomsettings.floor_thickness.thin')}</option>
<option value={2}>{LocalizeText('navigator.roomsettings.floor_thickness.normal')}</option>
<option value={3}>{LocalizeText('navigator.roomsettings.floor_thickness.thick')}</option>
<select className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm" value={ visualizationSettings.thicknessFloor } onChange={ event => onFloorThicknessChange(parseInt(event.target.value)) }>
<option value={ 0 }>{ LocalizeText('navigator.roomsettings.floor_thickness.thinnest') }</option>
<option value={ 1 }>{ LocalizeText('navigator.roomsettings.floor_thickness.thin') }</option>
<option value={ 2 }>{ LocalizeText('navigator.roomsettings.floor_thickness.normal') }</option>
<option value={ 3 }>{ LocalizeText('navigator.roomsettings.floor_thickness.thick') }</option>
</select>
</Flex>
</Column>

View File

@ -7,7 +7,7 @@ import { useFriends } from '../../../../hooks';
export const FriendBarItemView: FC<{ friend: MessengerFriend }> = props =>
{
const { friend = null } = props;
const [isVisible, setVisible] = useState(false);
const [ isVisible, setVisible ] = useState(false);
const { followFriend = null } = useFriends();
const elementRef = useRef<HTMLDivElement>();
@ -33,27 +33,27 @@ export const FriendBarItemView: FC<{ friend: MessengerFriend }> = props =>
if (!friend)
{
return (
<Button size='md' justifyContent='start' className="border w-[130px] mx-[3px] my-[0] z-0 relative pl-[37px] text-left friend-bar-search">
<Button className="border w-[130px] mx-[3px] my-[0] z-0 relative pl-[37px] text-left friend-bar-search" justifyContent="start" size="md">
<div className="absolute -top-[3px] left-[5px] w-[31px] h-[34px] bg-[url('@/assets/images/toolbar/friend-search.png')]" />
<div className="truncate">{LocalizeText('friend.bar.find.title')}</div>
<div className="truncate">{ LocalizeText('friend.bar.find.title') }</div>
</Button>
);
}
return (
<Button size='md' justifyContent='start' variant={'success'} className={' block w-[130px] mx-[3px] my-[0] z-0 relative pl-[37px] text-left' + (isVisible ? 'mb-[21px]' : '')} onClick={event => setVisible(prevValue => !prevValue)}>
<div className={`friend-bar-item-head absolute ${friend.id > 0 ? '-top-[30px] -left-[30px]' : '-top-[5px] -left-[3.5px]'}`}>
{(friend.id > 0) && <LayoutAvatarImageView direction={2} figure={friend.figure} headOnly={true} />}
{(friend.id <= 0) && <LayoutBadgeImageView badgeCode={friend.figure} isGroup={true} />}
<Button className={ ' block w-[130px] mx-[3px] my-[0] z-0 relative pl-[37px] text-left' + (isVisible ? 'mb-[21px]' : '') } justifyContent="start" size="md" variant={ 'success' } onClick={ event => setVisible(prevValue => !prevValue) }>
<div className={ `friend-bar-item-head absolute ${ friend.id > 0 ? '-top-[30px] -left-[30px]' : '-top-[5px] -left-[3.5px]' }` }>
{ (friend.id > 0) && <LayoutAvatarImageView direction={ 2 } figure={ friend.figure } headOnly={ true } /> }
{ (friend.id <= 0) && <LayoutBadgeImageView badgeCode={ friend.figure } isGroup={ true } /> }
</div>
<div className="truncate">{friend.name}</div>
{isVisible &&
<div className="truncate">{ friend.name }</div>
{ isVisible &&
<div className="flex justify-between">
<div className="cursor-pointer nitro-friends-spritesheet icon-friendbar-chat" onClick={event => OpenMessengerChat(friend.id)} />
{friend.followingAllowed &&
<div className="cursor-pointer nitro-friends-spritesheet icon-friendbar-visit" onClick={event => followFriend(friend)} />}
<div className="cursor-pointer nitro-friends-spritesheet icon-profile" onClick={event => GetUserProfile(friend.id)} />
</div>}
<div className="cursor-pointer nitro-friends-spritesheet icon-friendbar-chat" onClick={ event => OpenMessengerChat(friend.id) } />
{ friend.followingAllowed &&
<div className="cursor-pointer nitro-friends-spritesheet icon-friendbar-visit" onClick={ event => followFriend(friend) } /> }
<div className="cursor-pointer nitro-friends-spritesheet icon-profile" onClick={ event => GetUserProfile(friend.id) } />
</div> }
</Button>
);
}

View File

@ -9,16 +9,16 @@ const MAX_DISPLAY_COUNT = 3;
export const FriendBarView: FC<{ onlineFriends: MessengerFriend[] }> = props =>
{
const { onlineFriends = null } = props;
const [indexOffset, setIndexOffset] = useState(0);
const [ indexOffset, setIndexOffset ] = useState(0);
const elementRef = useRef<HTMLDivElement>();
return (
<div ref={elementRef} className="flex items-center ">
<Button className="z-[2] cursor-pointer" disabled={(indexOffset <= 0)} variant="black" onClick={event => setIndexOffset(indexOffset - 1)}>
<div ref={ elementRef } className="flex items-center ">
<Button className="z-[2] cursor-pointer" disabled={ (indexOffset <= 0) } variant="black" onClick={ event => setIndexOffset(indexOffset - 1) }>
<FaChevronLeft className="fa-icon" />
</Button>
{Array.from(Array(MAX_DISPLAY_COUNT), (e, i) => <FriendBarItemView key={i} friend={(onlineFriends[indexOffset + i] || null)} />)}
<Button className="z-[2] cursor-pointer" disabled={!((onlineFriends.length > MAX_DISPLAY_COUNT) && ((indexOffset + MAX_DISPLAY_COUNT) <= (onlineFriends.length - 1)))} variant="black" onClick={event => setIndexOffset(indexOffset + 1)}>
{ Array.from(Array(MAX_DISPLAY_COUNT), (e, i) => <FriendBarItemView key={ i } friend={ (onlineFriends[indexOffset + i] || null) } />) }
<Button className="z-[2] cursor-pointer" disabled={ !((onlineFriends.length > MAX_DISPLAY_COUNT) && ((indexOffset + MAX_DISPLAY_COUNT) <= (onlineFriends.length - 1))) } variant="black" onClick={ event => setIndexOffset(indexOffset + 1) }>
<FaChevronRight className="fa-icon" />
</Button>
</div>

View File

@ -12,18 +12,18 @@ interface FriendsRoomInviteViewProps
export const FriendsRoomInviteView: FC<FriendsRoomInviteViewProps> = props =>
{
const { selectedFriendsIds = null, onCloseClick = null, sendRoomInvite = null } = props;
const [roomInviteMessage, setRoomInviteMessage] = useState<string>('');
const [ roomInviteMessage, setRoomInviteMessage ] = useState<string>('');
return (
<NitroCardView className="nitro-friends-room-invite" theme="primary-slim" uniqueKey="nitro-friends-room-invite">
<NitroCardHeaderView headerText={LocalizeText('friendlist.invite.title')} onCloseClick={onCloseClick} />
<NitroCardHeaderView headerText={ LocalizeText('friendlist.invite.title') } onCloseClick={ onCloseClick } />
<NitroCardContentView className="text-black">
{LocalizeText('friendlist.invite.summary', ['count'], [selectedFriendsIds.length.toString()])}
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem]" maxLength={255} value={roomInviteMessage} onChange={event => setRoomInviteMessage(event.target.value)}></textarea>
<Text center className="bg-muted rounded p-1">{LocalizeText('friendlist.invite.note')}</Text>
{ LocalizeText('friendlist.invite.summary', [ 'count' ], [ selectedFriendsIds.length.toString() ]) }
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem]" maxLength={ 255 } value={ roomInviteMessage } onChange={ event => setRoomInviteMessage(event.target.value) }></textarea>
<Text center className="bg-muted rounded p-1">{ LocalizeText('friendlist.invite.note') }</Text>
<div className="flex gap-1">
<Button fullWidth disabled={((roomInviteMessage.length === 0) || (selectedFriendsIds.length === 0))} variant="success" onClick={() => sendRoomInvite(roomInviteMessage)}>{LocalizeText('friendlist.invite.send')}</Button>
<Button fullWidth onClick={onCloseClick}>{LocalizeText('generic.cancel')}</Button>
<Button fullWidth disabled={ ((roomInviteMessage.length === 0) || (selectedFriendsIds.length === 0)) } variant="success" onClick={ () => sendRoomInvite(roomInviteMessage) }>{ LocalizeText('friendlist.invite.send') }</Button>
<Button fullWidth onClick={ onCloseClick }>{ LocalizeText('generic.cancel') }</Button>
</div>
</NitroCardContentView>
</NitroCardView>

View File

@ -12,9 +12,9 @@ interface FriendsSearchViewProps extends NitroCardAccordionSetViewProps
export const FriendsSearchView: FC<FriendsSearchViewProps> = props =>
{
const { ...rest } = props;
const [searchValue, setSearchValue] = useState('');
const [friendResults, setFriendResults] = useState<HabboSearchResultData[]>(null);
const [otherResults, setOtherResults] = useState<HabboSearchResultData[]>(null);
const [ searchValue, setSearchValue ] = useState('');
const [ friendResults, setFriendResults ] = useState<HabboSearchResultData[]>(null);
const [ otherResults, setOtherResults ] = useState<HabboSearchResultData[]>(null);
const { canRequestFriend = null, requestFriend = null } = useFriends();
useMessageEvent<HabboSearchResultEvent>(HabboSearchResultEvent, event =>
@ -37,66 +37,66 @@ export const FriendsSearchView: FC<FriendsSearchViewProps> = props =>
}, 500);
return () => clearTimeout(timeout);
}, [searchValue]);
}, [ searchValue ]);
return (
<NitroCardAccordionSetView {...rest}>
<input className="search-input form-control form-control-sm w-full rounded-0" maxLength={50} placeholder={LocalizeText('generic.search')} type="text" value={searchValue} onChange={event => setSearchValue(event.target.value)} />
<NitroCardAccordionSetView { ...rest }>
<input className="search-input form-control form-control-sm w-full rounded-0" maxLength={ 50 } placeholder={ LocalizeText('generic.search') } type="text" value={ searchValue } onChange={ event => setSearchValue(event.target.value) } />
<div className="flex flex-col">
{friendResults &&
{ friendResults &&
<>
{(friendResults.length === 0) &&
<Text bold small className="px-2 py-1">{LocalizeText('friendlist.search.nofriendsfound')}</Text>}
{(friendResults.length > 0) &&
<Column gap={0}>
<Text bold small className="px-2 py-1">{LocalizeText('friendlist.search.friendscaption', ['cnt'], [friendResults.length.toString()])}</Text>
{ (friendResults.length === 0) &&
<Text bold small className="px-2 py-1">{ LocalizeText('friendlist.search.nofriendsfound') }</Text> }
{ (friendResults.length > 0) &&
<Column gap={ 0 }>
<Text bold small className="px-2 py-1">{ LocalizeText('friendlist.search.friendscaption', [ 'cnt' ], [ friendResults.length.toString() ]) }</Text>
<hr className="mx-2 mt-0 mb-1 text-black" />
<Column gap={0}>
{friendResults.map(result =>
<Column gap={ 0 }>
{ friendResults.map(result =>
{
return (
<NitroCardAccordionItemView key={result.avatarId} className="px-2 py-1" justifyContent="between">
<NitroCardAccordionItemView key={ result.avatarId } className="px-2 py-1" justifyContent="between">
<div className="flex items-center gap-1">
<UserProfileIconView userId={result.avatarId} />
<div>{result.avatarName}</div>
<UserProfileIconView userId={ result.avatarId } />
<div>{ result.avatarName }</div>
</div>
<div className="flex items-center gap-1">
{result.isAvatarOnline &&
<div className="nitro-friends-spritesheet icon-chat cursor-pointer" title={LocalizeText('friendlist.tip.im')} onClick={event => OpenMessengerChat(result.avatarId)} />}
{ result.isAvatarOnline &&
<div className="nitro-friends-spritesheet icon-chat cursor-pointer" title={ LocalizeText('friendlist.tip.im') } onClick={ event => OpenMessengerChat(result.avatarId) } /> }
</div>
</NitroCardAccordionItemView>
)
})}
}) }
</Column>
</Column>}
</>}
{otherResults &&
</Column> }
</> }
{ otherResults &&
<>
{(otherResults.length === 0) &&
<Text bold small className="px-2 py-1">{LocalizeText('friendlist.search.noothersfound')}</Text>}
{(otherResults.length > 0) &&
<Column gap={0}>
<Text bold small className="px-2 py-1">{LocalizeText('friendlist.search.otherscaption', ['cnt'], [otherResults.length.toString()])}</Text>
{ (otherResults.length === 0) &&
<Text bold small className="px-2 py-1">{ LocalizeText('friendlist.search.noothersfound') }</Text> }
{ (otherResults.length > 0) &&
<Column gap={ 0 }>
<Text bold small className="px-2 py-1">{ LocalizeText('friendlist.search.otherscaption', [ 'cnt' ], [ otherResults.length.toString() ]) }</Text>
<hr className="mx-2 mt-0 mb-1 text-black" />
<Column gap={0}>
{otherResults.map(result =>
<Column gap={ 0 }>
{ otherResults.map(result =>
{
return (
<NitroCardAccordionItemView key={result.avatarId} className="px-2 py-1" justifyContent="between">
<NitroCardAccordionItemView key={ result.avatarId } className="px-2 py-1" justifyContent="between">
<div className="flex items-center gap-1">
<UserProfileIconView userId={result.avatarId} />
<div>{result.avatarName}</div>
<UserProfileIconView userId={ result.avatarId } />
<div>{ result.avatarName }</div>
</div>
<div className="flex items-center gap-1">
{canRequestFriend(result.avatarId) &&
<div className="nitro-friends-spritesheet icon-add cursor-pointer" title={LocalizeText('friendlist.tip.addfriend')} onClick={event => requestFriend(result.avatarId, result.avatarName)} />}
{ canRequestFriend(result.avatarId) &&
<div className="nitro-friends-spritesheet icon-add cursor-pointer" title={ LocalizeText('friendlist.tip.addfriend') } onClick={ event => requestFriend(result.avatarId, result.avatarName) } /> }
</div>
</NitroCardAccordionItemView>
)
})}
}) }
</Column>
</Column>}
</>}
</Column> }
</> }
</div>
</NitroCardAccordionSetView>
);

View File

@ -9,9 +9,9 @@ import { FriendsMessengerThreadView } from './messenger-thread/FriendsMessengerT
export const FriendsMessengerView: FC<{}> = props =>
{
const [isVisible, setIsVisible] = useState(false);
const [lastThreadId, setLastThreadId] = useState(-1);
const [messageText, setMessageText] = useState('');
const [ isVisible, setIsVisible ] = useState(false);
const [ lastThreadId, setLastThreadId ] = useState(-1);
const [ messageText, setMessageText ] = useState('');
const { visibleThreads = [], activeThread = null, getMessageThread = null, sendMessage = null, setActiveThreadId = null, closeThread = null } = useMessenger();
const { report = null } = useHelp();
const messagesBox = useRef<HTMLDivElement>();
@ -72,14 +72,14 @@ export const FriendsMessengerView: FC<{}> = props =>
AddLinkEventTracker(linkTracker);
return () => RemoveLinkEventTracker(linkTracker);
}, [getMessageThread, setActiveThreadId]);
}, [ getMessageThread, setActiveThreadId ]);
useEffect(() =>
{
if (!isVisible || !activeThread) return;
messagesBox.current.scrollTop = messagesBox.current.scrollHeight;
}, [isVisible, activeThread]);
}, [ isVisible, activeThread ]);
useEffect(() =>
{
@ -102,74 +102,74 @@ export const FriendsMessengerView: FC<{}> = props =>
setLastThreadId(activeThread.threadId);
setActiveThreadId(-1);
}
}, [isVisible, activeThread, lastThreadId, visibleThreads, setActiveThreadId]);
}, [ isVisible, activeThread, lastThreadId, visibleThreads, setActiveThreadId ]);
if (!isVisible) return null;
return (
<NitroCardView className="nitro-friends-messenger" theme="primary-slim" uniqueKey="nitro-friends-messenger">
<NitroCardHeaderView headerText={LocalizeText('messenger.window.title', ['OPEN_CHAT_COUNT'], [visibleThreads.length.toString()])} onCloseClick={event => setIsVisible(false)} />
<NitroCardHeaderView headerText={ LocalizeText('messenger.window.title', [ 'OPEN_CHAT_COUNT' ], [ visibleThreads.length.toString() ]) } onCloseClick={ event => setIsVisible(false) } />
<NitroCardContentView>
<Grid overflow="hidden">
<Column overflow="hidden" size={4}>
<Text bold>{LocalizeText('toolbar.icon.label.messenger')}</Text>
<Column overflow="hidden" size={ 4 }>
<Text bold>{ LocalizeText('toolbar.icon.label.messenger') }</Text>
<Column fit overflow="auto">
<div className="flex flex-col">
{visibleThreads && (visibleThreads.length > 0) && visibleThreads.map(thread =>
{ visibleThreads && (visibleThreads.length > 0) && visibleThreads.map(thread =>
{
return (
<LayoutGridItem key={thread.threadId} itemActive={(activeThread === thread)} onClick={event => setActiveThreadId(thread.threadId)}>
{thread.unread &&
<LayoutItemCountView count={thread.unreadCount} />}
<LayoutGridItem key={ thread.threadId } itemActive={ (activeThread === thread) } onClick={ event => setActiveThreadId(thread.threadId) }>
{ thread.unread &&
<LayoutItemCountView count={ thread.unreadCount } /> }
<div className="flex w-full items-center gap-1">
<div className="flex items-center friend-head px-1">
{(thread.participant.id > 0) &&
<LayoutAvatarImageView direction={3} figure={thread.participant.figure} headOnly={true} />}
{(thread.participant.id <= 0) &&
<LayoutBadgeImageView badgeCode={thread.participant.figure} isGroup={true} />}
{ (thread.participant.id > 0) &&
<LayoutAvatarImageView direction={ 3 } figure={ thread.participant.figure } headOnly={ true } /> }
{ (thread.participant.id <= 0) &&
<LayoutBadgeImageView badgeCode={ thread.participant.figure } isGroup={ true } /> }
</div>
<Text grow truncate>{thread.participant.name}</Text>
<Text grow truncate>{ thread.participant.name }</Text>
</div>
</LayoutGridItem>
);
})}
}) }
</div>
</Column>
</Column>
<Column overflow="hidden" size={8}>
{activeThread &&
<Column overflow="hidden" size={ 8 }>
{ activeThread &&
<>
<Text bold center>{LocalizeText('messenger.window.separator', ['FRIEND_NAME'], [activeThread.participant.name])}</Text>
<Flex alignItems="center" gap={1} justifyContent="between">
<Text bold center>{ LocalizeText('messenger.window.separator', [ 'FRIEND_NAME' ], [ activeThread.participant.name ]) }</Text>
<Flex alignItems="center" gap={ 1 } justifyContent="between">
<div className="flex gap-1">
<div className="relative inline-flex align-middle">
<Button onClick={followFriend}>
<Button onClick={ followFriend }>
<div className="nitro-friends-spritesheet icon-follow" />
</Button>
<Button onClick={openProfile}>
<Button onClick={ openProfile }>
<div className="nitro-friends-spritesheet icon-profile-sm" />
</Button>
</div>
<Button variant="danger" onClick={() => report(ReportType.IM, { reportedUserId: activeThread.participant.id })}>
{LocalizeText('messenger.window.button.report')}
<Button variant="danger" onClick={ () => report(ReportType.IM, { reportedUserId: activeThread.participant.id }) }>
{ LocalizeText('messenger.window.button.report') }
</Button>
</div>
<Button onClick={event => closeThread(activeThread.threadId)}>
<Button onClick={ event => closeThread(activeThread.threadId) }>
<FaTimes className="fa-icon" />
</Button>
</Flex>
<Column fit className="bg-muted p-2 rounded chat-messages">
<Column innerRef={messagesBox} overflow="auto">
<FriendsMessengerThreadView thread={activeThread} />
<Column innerRef={ messagesBox } overflow="auto">
<FriendsMessengerThreadView thread={ activeThread } />
</Column>
</Column>
<div className="flex gap-1">
<NitroInput maxLength={255} placeholder={LocalizeText('messenger.window.input.default', ['FRIEND_NAME'], [activeThread.participant.name])} type="text" value={messageText} onChange={event => setMessageText(event.target.value)} onKeyDown={onKeyDown} />
<Button variant="success" onClick={send}>
{LocalizeText('widgets.chatinput.say')}
<NitroInput maxLength={ 255 } placeholder={ LocalizeText('messenger.window.input.default', [ 'FRIEND_NAME' ], [ activeThread.participant.name ]) } type="text" value={ messageText } onChange={ event => setMessageText(event.target.value) } onKeyDown={ onKeyDown } />
<Button variant="success" onClick={ send }>
{ LocalizeText('widgets.chatinput.say') }
</Button>
</div>
</>}
</> }
</Column>
</Grid>
</NitroCardContentView>

View File

@ -7,7 +7,7 @@ export const FriendsMessengerThreadGroup: FC<{ thread: MessengerThread, group: M
{
const { thread = null, group = null } = props;
const groupChatData = useMemo(() => ((group.type === MessengerGroupType.GROUP_CHAT) && GetGroupChatData(group.chats[0].extraData)), [group]);
const groupChatData = useMemo(() => ((group.type === MessengerGroupType.GROUP_CHAT) && GetGroupChatData(group.chats[0].extraData)), [ group ]);
const isOwnChat = useMemo(() =>
{
@ -18,7 +18,7 @@ export const FriendsMessengerThreadGroup: FC<{ thread: MessengerThread, group: M
if (groupChatData && group.chats.length && (groupChatData.userId === GetSessionDataManager().userId)) return true;
return false;
}, [thread, group, groupChatData]);
}, [ thread, group, groupChatData ]);
if (!thread || !group) return null;
@ -26,48 +26,48 @@ export const FriendsMessengerThreadGroup: FC<{ thread: MessengerThread, group: M
{
return (
<>
{group.chats.map((chat, index) =>
{ group.chats.map((chat, index) =>
{
return (
<Flex key={index} fullWidth gap={2} justifyContent="start">
<Flex key={ index } fullWidth gap={ 2 } justifyContent="start">
<Base className="w-full text-break">
{(chat.type === MessengerThreadChat.SECURITY_NOTIFICATION) &&
<Flex alignItems="center" className="bg-light rounded mb-2 px-2 py-1 small text-muted" gap={2}>
{ (chat.type === MessengerThreadChat.SECURITY_NOTIFICATION) &&
<Flex alignItems="center" className="bg-light rounded mb-2 px-2 py-1 small text-muted" gap={ 2 }>
<Base className="nitro-friends-spritesheet icon-warning flex-shrink-0" />
<Base>{chat.message}</Base>
</Flex>}
{(chat.type === MessengerThreadChat.ROOM_INVITE) &&
<Flex alignItems="center" className="bg-light rounded mb-2 px-2 py-1 small text-black" gap={2}>
<Base>{ chat.message }</Base>
</Flex> }
{ (chat.type === MessengerThreadChat.ROOM_INVITE) &&
<Flex alignItems="center" className="bg-light rounded mb-2 px-2 py-1 small text-black" gap={ 2 }>
<Base className="messenger-notification-icon flex-shrink-0" />
<Base>{(LocalizeText('messenger.invitation') + ' ')}{chat.message}</Base>
</Flex>}
<Base>{ (LocalizeText('messenger.invitation') + ' ') }{ chat.message }</Base>
</Flex> }
</Base>
</Flex>
);
})}
}) }
</>
);
}
return (
<Flex fullWidth gap={2} justifyContent={isOwnChat ? 'end' : 'start'}>
<Flex fullWidth gap={ 2 } justifyContent={ isOwnChat ? 'end' : 'start' }>
<Base shrink className="message-avatar">
{((group.type === MessengerGroupType.PRIVATE_CHAT) && !isOwnChat) &&
<LayoutAvatarImageView direction={2} figure={thread.participant.figure} />}
{(groupChatData && !isOwnChat) &&
<LayoutAvatarImageView direction={2} figure={groupChatData.figure} />}
{ ((group.type === MessengerGroupType.PRIVATE_CHAT) && !isOwnChat) &&
<LayoutAvatarImageView direction={ 2 } figure={ thread.participant.figure } /> }
{ (groupChatData && !isOwnChat) &&
<LayoutAvatarImageView direction={ 2 } figure={ groupChatData.figure } /> }
</Base>
<Base className={'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (isOwnChat ? 'right' : 'left')}>
<Base className={ 'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (isOwnChat ? 'right' : 'left') }>
<Base className="font-bold ">
{isOwnChat && GetSessionDataManager().userName}
{!isOwnChat && (groupChatData ? groupChatData.username : thread.participant.name)}
{ isOwnChat && GetSessionDataManager().userName }
{ !isOwnChat && (groupChatData ? groupChatData.username : thread.participant.name) }
</Base>
{group.chats.map((chat, index) => <Base key={index} className="text-break">{chat.message}</Base>)}
{ group.chats.map((chat, index) => <Base key={ index } className="text-break">{ chat.message }</Base>) }
</Base>
{isOwnChat &&
{ isOwnChat &&
<Base shrink className="message-avatar">
<LayoutAvatarImageView direction={4} figure={GetSessionDataManager().figure} />
</Base>}
<LayoutAvatarImageView direction={ 4 } figure={ GetSessionDataManager().figure } />
</Base> }
</Flex>
);
}

View File

@ -8,7 +8,7 @@ export const GameListView = () =>
const getClasses = (game: GameConfigurationData) =>
{
let classes = ['game-icon'];
let classes = [ 'game-icon' ];
if (selectedGame === game) classes.push('selected');
@ -17,15 +17,15 @@ export const GameListView = () =>
const getIconImage = (game: GameConfigurationData): string =>
{
return `url(${game.assetUrl}${game.gameNameId}_icon.png)`
return `url(${ game.assetUrl }${ game.gameNameId }_icon.png)`
}
return <div className="gameList-container bg-dark p-1 w-full">
{LocalizeText('gamecenter.game_list_title')}
{ LocalizeText('gamecenter.game_list_title') }
<div className="flex gap-3">
{games && games.map((game, index) =>
<div key={index} className={getClasses(game)} style={{ backgroundImage: getIconImage(game) }} onClick={evt => setSelectedGame(game)} />
)}
{ games && games.map((game, index) =>
<div key={ index } className={ getClasses(game) } style={ { backgroundImage: getIconImage(game) } } onClick={ evt => setSelectedGame(game) } />
) }
</div>
</div>
}

View File

@ -6,7 +6,7 @@ import { useGameCenter } from '../../../hooks';
export const GameStageView = () =>
{
const { gameURL, setGameURL } = useGameCenter();
const [loadTimes, setLoadTimes] = useState<number>(0);
const [ loadTimes, setLoadTimes ] = useState<number>(0);
const ref = useRef<HTMLDivElement>();
useEffect(() =>
@ -29,7 +29,7 @@ export const GameStageView = () =>
ref.current.innerHTML = '';
ref.current.appendChild(frame);
}, [ref, gameURL]);
}, [ ref, gameURL ]);
useEffect(() =>
{
@ -38,9 +38,9 @@ export const GameStageView = () =>
setGameURL(null);
SendMessageComposer(new Game2ExitGameMessageComposer());
}
}, [loadTimes, setGameURL])
}, [ loadTimes, setGameURL ])
if (!gameURL) return null;
return <div ref={ref} className="game-center-stage" />
return <div ref={ ref } className="game-center-stage" />
}

View File

@ -10,17 +10,17 @@ interface GroupBadgeCreatorViewProps
setBadgeParts: Dispatch<SetStateAction<GroupBadgePart[]>>;
}
const POSITIONS: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8];
const POSITIONS: number[] = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ];
export const GroupBadgeCreatorView: FC<GroupBadgeCreatorViewProps> = props =>
{
const { badgeParts = [], setBadgeParts = null } = props;
const [selectedIndex, setSelectedIndex] = useState<number>(-1);
const [ selectedIndex, setSelectedIndex ] = useState<number>(-1);
const { groupCustomize = null } = useGroup();
const setPartProperty = (partIndex: number, property: string, value: number) =>
{
const newBadgeParts = [...badgeParts];
const newBadgeParts = [ ...badgeParts ];
newBadgeParts[partIndex][property] = value;
@ -33,51 +33,51 @@ export const GroupBadgeCreatorView: FC<GroupBadgeCreatorViewProps> = props =>
return (
<>
{((selectedIndex < 0) && badgeParts && (badgeParts.length > 0)) && badgeParts.map((part, index) =>
{ ((selectedIndex < 0) && badgeParts && (badgeParts.length > 0)) && badgeParts.map((part, index) =>
{
return (
<Flex key={index} alignItems="center" className="bg-muted rounded px-2 py-1" gap={2} justifyContent="between">
<Flex center pointer className="bg-muted rounded p-1" onClick={event => setSelectedIndex(index)}>
{(badgeParts[index].code && (badgeParts[index].code.length > 0)) &&
<LayoutBadgeImageView badgeCode={badgeParts[index].code} isGroup={true} />}
{(!badgeParts[index].code || !badgeParts[index].code.length) &&
<Flex key={ index } alignItems="center" className="bg-muted rounded px-2 py-1" gap={ 2 } justifyContent="between">
<Flex center pointer className="bg-muted rounded p-1" onClick={ event => setSelectedIndex(index) }>
{ (badgeParts[index].code && (badgeParts[index].code.length > 0)) &&
<LayoutBadgeImageView badgeCode={ badgeParts[index].code } isGroup={ true } /> }
{ (!badgeParts[index].code || !badgeParts[index].code.length) &&
<Flex center className="relative w-[40px] h-[40px] bg-no-repeat bg-center group-badge">
<FaPlus className="fa-icon" />
</Flex>}
</Flex> }
</Flex>
{(part.type !== GroupBadgePart.BASE) &&
<Grid columnCount={3} gap={1}>
{POSITIONS.map((position, posIndex) =>
{ (part.type !== GroupBadgePart.BASE) &&
<Grid columnCount={ 3 } gap={ 1 }>
{ POSITIONS.map((position, posIndex) =>
{
return <div key={posIndex} className={`relative rounded-[.25rem] w-[16px] h-[16px] bg-[#fff] border-[2px] border-[solid] border-[#fff] [box-shadow:inset_3px_3px_#0000001a] cursor-pointer ${(badgeParts[index].position === position) ? 'bg-primary [box-shadow:none]' : ''}`} onClick={event => setPartProperty(index, 'position', position)} />
})}
</Grid>}
<Grid columnCount={8} gap={1}>
{(groupCustomize.badgePartColors.length > 0) && groupCustomize.badgePartColors.map((item, colorIndex) =>
return <div key={ posIndex } className={ `relative rounded-[.25rem] w-[16px] h-[16px] bg-[#fff] border-[2px] border-[solid] border-[#fff] [box-shadow:inset_3px_3px_#0000001a] cursor-pointer ${ (badgeParts[index].position === position) ? 'bg-primary [box-shadow:none]' : '' }` } onClick={ event => setPartProperty(index, 'position', position) } />
}) }
</Grid> }
<Grid columnCount={ 8 } gap={ 1 }>
{ (groupCustomize.badgePartColors.length > 0) && groupCustomize.badgePartColors.map((item, colorIndex) =>
{
return <div key={colorIndex} className={`relative [box-shadow:inset_2px_2px_#0003] rounded-[.25rem] w-[16px] h-[16px] bg-[#fff] border-[2px] border-[solid] border-[#fff] [box-shadow:inset_3px_3px_#0000001a]cursor-pointer ${(badgeParts[index].color === (colorIndex + 1)) ? 'bg-primary [box-shadow:none]' : ''}`} style={{ backgroundColor: '#' + item.color }} onClick={event => setPartProperty(index, 'color', (colorIndex + 1))} />
})}
return <div key={ colorIndex } className={ `relative [box-shadow:inset_2px_2px_#0003] rounded-[.25rem] w-[16px] h-[16px] bg-[#fff] border-[2px] border-[solid] border-[#fff] [box-shadow:inset_3px_3px_#0000001a]cursor-pointer ${ (badgeParts[index].color === (colorIndex + 1)) ? 'bg-primary [box-shadow:none]' : '' }` } style={ { backgroundColor: '#' + item.color } } onClick={ event => setPartProperty(index, 'color', (colorIndex + 1)) } />
}) }
</Grid>
</Flex>
);
})}
{(selectedIndex >= 0) &&
<Grid columnCount={5} gap={1}>
{(badgeParts[selectedIndex].type === GroupBadgePart.SYMBOL) &&
<Column center pointer className="bg-muted rounded p-1" onClick={event => setPartProperty(selectedIndex, 'key', 0)}>
}) }
{ (selectedIndex >= 0) &&
<Grid columnCount={ 5 } gap={ 1 }>
{ (badgeParts[selectedIndex].type === GroupBadgePart.SYMBOL) &&
<Column center pointer className="bg-muted rounded p-1" onClick={ event => setPartProperty(selectedIndex, 'key', 0) }>
<Flex center className="relative w-[40px] h-[40px] bg-no-repeat bg-center group-badge">
<FaTimes className="fa-icon" />
</Flex>
</Column>}
{((badgeParts[selectedIndex].type === GroupBadgePart.BASE) ? groupCustomize.badgeBases : groupCustomize.badgeSymbols).map((item, index) =>
</Column> }
{ ((badgeParts[selectedIndex].type === GroupBadgePart.BASE) ? groupCustomize.badgeBases : groupCustomize.badgeSymbols).map((item, index) =>
{
return (
<Column key={index} center pointer className="bg-muted rounded p-1" onClick={event => setPartProperty(selectedIndex, 'key', item.id)}>
<LayoutBadgeImageView badgeCode={GroupBadgePart.getCode(badgeParts[selectedIndex].type, item.id, badgeParts[selectedIndex].color, 4)} isGroup={true} />
<Column key={ index } center pointer className="bg-muted rounded p-1" onClick={ event => setPartProperty(selectedIndex, 'key', item.id) }>
<LayoutBadgeImageView badgeCode={ GroupBadgePart.getCode(badgeParts[selectedIndex].type, item.id, badgeParts[selectedIndex].color, 4) } isGroup={ true } />
</Column>
);
})}
</Grid>}
}) }
</Grid> }
</>
);
}

View File

@ -13,16 +13,16 @@ interface GroupCreatorViewProps
onClose: () => void;
}
const TABS: number[] = [1, 2, 3, 4];
const TABS: number[] = [ 1, 2, 3, 4 ];
export const GroupCreatorView: FC<GroupCreatorViewProps> = props =>
{
const { onClose = null } = props;
const [currentTab, setCurrentTab] = useState<number>(1);
const [closeAction, setCloseAction] = useState<{ action: () => boolean }>(null);
const [groupData, setGroupData] = useState<IGroupData>(null);
const [availableRooms, setAvailableRooms] = useState<{ id: number, name: string }[]>(null);
const [purchaseCost, setPurchaseCost] = useState<number>(0);
const [ currentTab, setCurrentTab ] = useState<number>(1);
const [ closeAction, setCloseAction ] = useState<{ action: () => boolean }>(null);
const [ groupData, setGroupData ] = useState<IGroupData>(null);
const [ availableRooms, setAvailableRooms ] = useState<{ id: number, name: string }[]>(null);
const [ purchaseCost, setPurchaseCost ] = useState<number>(0);
const onCloseClose = () =>
{
@ -113,49 +113,49 @@ export const GroupCreatorView: FC<GroupCreatorViewProps> = props =>
});
SendMessageComposer(new GroupBuyDataComposer());
}, [setGroupData]);
}, [ setGroupData ]);
if (!groupData) return null;
return (
<NitroCardView className="h-[355px] w-[390px] border-[1px] border-[solid] border-[#283F5D] " theme="primary-slim">
<NitroCardHeaderView headerText={LocalizeText('group.create.title')} onCloseClick={onCloseClose} />
<NitroCardHeaderView headerText={ LocalizeText('group.create.title') } onCloseClick={ onCloseClose } />
<NitroCardContentView>
<div className="flex items-center justify-center creator-tabs">
{TABS.map((tab, index) =>
{ TABS.map((tab, index) =>
{
return (
<Flex key={index} center className={`relative -ml-[6px] bg-[url('@/assets/images/groups/creator_tabs.png')] bg-no-repeat ${((tab === 1) ? 'w-[84px] h-[24px] bg-[0px_0px]' : (tab === 4) ? 'w-[133px] h-[28px] bg-[0px_-104px]' : 'w-[83px] h-[24px] bg-[0px_-52px]')} ${(currentTab === tab) ? 'active' : ''}`}>
<Text variant="white">{LocalizeText(`group.create.steplabel.${tab}`)}</Text>
<Flex key={ index } center className={ `relative -ml-[6px] bg-[url('@/assets/images/groups/creator_tabs.png')] bg-no-repeat ${ ((tab === 1) ? 'w-[84px] h-[24px] bg-[0px_0px]' : (tab === 4) ? 'w-[133px] h-[28px] bg-[0px_-104px]' : 'w-[83px] h-[24px] bg-[0px_-52px]') } ${ (currentTab === tab) ? 'active' : '' }` }>
<Text variant="white">{ LocalizeText(`group.create.steplabel.${ tab }`) }</Text>
</Flex>
);
})}
}) }
</div>
<Column overflow="hidden">
<div className="flex items-center gap-2">
<div className={`bg-no-repeat w-[122px] h-[68px] bg-[url('@/assets/images/groups/creator_images.png')] ${currentTab === 1 && 'bg-[0px_0px] !w-[99px] !h-[50px]'}
${currentTab == 2 && '!bg-[-99px_0px] !w-[98px] !h-[62px]'} ${currentTab === 3 && '!bg-[0px_-50px] !w-[96px] !h-[45px]'} ${currentTab === 4 || currentTab === 5 && '!bg-[0px_-95px] !w-[114px] !h-[61px]'} `} />
<Column grow gap={0}>
<Text bold fontSize={4}>{LocalizeText(`group.create.stepcaption.${currentTab}`)}</Text>
<Text>{LocalizeText(`group.create.stepdesc.${currentTab}`)}</Text>
<div className={ `bg-no-repeat w-[122px] h-[68px] bg-[url('@/assets/images/groups/creator_images.png')] ${ currentTab === 1 && 'bg-[0px_0px] !w-[99px] !h-[50px]' }
${ currentTab == 2 && '!bg-[-99px_0px] !w-[98px] !h-[62px]' } ${ currentTab === 3 && '!bg-[0px_-50px] !w-[96px] !h-[45px]' } ${ currentTab === 4 || currentTab === 5 && '!bg-[0px_-95px] !w-[114px] !h-[61px]' } ` } />
<Column grow gap={ 0 }>
<Text bold fontSize={ 4 }>{ LocalizeText(`group.create.stepcaption.${ currentTab }`) }</Text>
<Text>{ LocalizeText(`group.create.stepdesc.${ currentTab }`) }</Text>
</Column>
</div>
<Column overflow="hidden">
{(currentTab === 1) &&
<GroupTabIdentityView availableRooms={availableRooms} groupData={groupData} isCreator={true} setCloseAction={setCloseAction} setGroupData={setGroupData} onClose={null} />}
{(currentTab === 2) &&
<GroupTabBadgeView groupData={groupData} setCloseAction={setCloseAction} setGroupData={setGroupData} />}
{(currentTab === 3) &&
<GroupTabColorsView groupData={groupData} setCloseAction={setCloseAction} setGroupData={setGroupData} />}
{(currentTab === 4) &&
<GroupTabCreatorConfirmationView groupData={groupData} purchaseCost={purchaseCost} setGroupData={setGroupData} />}
{ (currentTab === 1) &&
<GroupTabIdentityView availableRooms={ availableRooms } groupData={ groupData } isCreator={ true } setCloseAction={ setCloseAction } setGroupData={ setGroupData } onClose={ null } /> }
{ (currentTab === 2) &&
<GroupTabBadgeView groupData={ groupData } setCloseAction={ setCloseAction } setGroupData={ setGroupData } /> }
{ (currentTab === 3) &&
<GroupTabColorsView groupData={ groupData } setCloseAction={ setCloseAction } setGroupData={ setGroupData } /> }
{ (currentTab === 4) &&
<GroupTabCreatorConfirmationView groupData={ groupData } purchaseCost={ purchaseCost } setGroupData={ setGroupData } /> }
</Column>
<div className="flex justify-between">
<Button className="text-black" variant="link" onClick={previousStep}>
{LocalizeText(currentTab === 1 ? 'generic.cancel' : 'group.create.previousstep')}
<Button className="text-black" variant="link" onClick={ previousStep }>
{ LocalizeText(currentTab === 1 ? 'generic.cancel' : 'group.create.previousstep') }
</Button>
<Button disabled={((currentTab === 4) && !HasHabboClub())} variant={((currentTab === 4) ? HasHabboClub() ? 'success' : 'danger' : 'primary')} onClick={nextStep}>
{LocalizeText((currentTab === 4) ? HasHabboClub() ? 'group.create.confirm.buy' : 'group.create.confirm.viprequired' : 'group.create.nextstep')}
<Button disabled={ ((currentTab === 4) && !HasHabboClub()) } variant={ ((currentTab === 4) ? HasHabboClub() ? 'success' : 'danger' : 'primary') } onClick={ nextStep }>
{ LocalizeText((currentTab === 4) ? HasHabboClub() ? 'group.create.confirm.buy' : 'group.create.confirm.viprequired' : 'group.create.nextstep') }
</Button>
</div>
</Column>

View File

@ -4,7 +4,7 @@ import { CatalogPageName, GetGroupManager, GetGroupMembers, GroupMembershipType,
import { Button, Column, Grid, GridProps, LayoutBadgeImageView, Text } from '../../../common';
import { useNotification } from '../../../hooks';
const STATES: string[] = ['regular', 'exclusive', 'private'];
const STATES: string[] = [ 'regular', 'exclusive', 'private' ];
interface GroupInformationViewProps extends GridProps
{
@ -36,11 +36,11 @@ export const GroupInformationView: FC<GroupInformationViewProps> = props =>
{
if (groupInformation.membershipType === GroupMembershipType.NOT_MEMBER || groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) return null;
if (isRealOwner) return <i className="nitro-icon icon-group-owner" title={LocalizeText('group.youareowner')} />;
if (isRealOwner) return <i className="nitro-icon icon-group-owner" title={ LocalizeText('group.youareowner') } />;
if (groupInformation.isAdmin) return <i className="nitro-icon icon-group-admin" title={LocalizeText('group.youareadmin')} />;
if (groupInformation.isAdmin) return <i className="nitro-icon icon-group-admin" title={ LocalizeText('group.youareadmin') } />;
return <i className="nitro-icon icon-group-member" title={LocalizeText('group.youaremember')} />;
return <i className="nitro-icon icon-group-member" title={ LocalizeText('group.youaremember') } />;
}
const getButtonText = () =>
@ -100,45 +100,45 @@ export const GroupInformationView: FC<GroupInformationViewProps> = props =>
if (!groupInformation) return null;
return (
<Grid overflow={overflow} {...rest}>
<Column center overflow="hidden" size={3}>
<Grid overflow={ overflow } { ...rest }>
<Column center overflow="hidden" size={ 3 }>
<div className="flex items-center overflow-hidden group-badge">
<LayoutBadgeImageView badgeCode={groupInformation.badge} isGroup={true} scale={2} />
<LayoutBadgeImageView badgeCode={ groupInformation.badge } isGroup={ true } scale={ 2 } />
</div>
<Column alignItems="center" gap={1}>
<Text pointer small underline onClick={() => handleAction('members')}>{LocalizeText('group.membercount', ['totalMembers'], [groupInformation.membersCount.toString()])}</Text>
{(groupInformation.pendingRequestsCount > 0) &&
<Text pointer small underline onClick={() => handleAction('members_pending')}>{LocalizeText('group.pendingmembercount', ['amount'], [groupInformation.pendingRequestsCount.toString()])}</Text>}
{groupInformation.isOwner &&
<Text pointer small underline onClick={() => handleAction('manage')}>{LocalizeText('group.manage')}</Text>}
<Column alignItems="center" gap={ 1 }>
<Text pointer small underline onClick={ () => handleAction('members') }>{ LocalizeText('group.membercount', [ 'totalMembers' ], [ groupInformation.membersCount.toString() ]) }</Text>
{ (groupInformation.pendingRequestsCount > 0) &&
<Text pointer small underline onClick={ () => handleAction('members_pending') }>{ LocalizeText('group.pendingmembercount', [ 'amount' ], [ groupInformation.pendingRequestsCount.toString() ]) }</Text> }
{ groupInformation.isOwner &&
<Text pointer small underline onClick={ () => handleAction('manage') }>{ LocalizeText('group.manage') }</Text> }
</Column>
{getRoleIcon()}
{ getRoleIcon() }
</Column>
<div className="flex flex-col justify-between overflow-auto col-span-9">
<div className="flex flex-col overflow-hidden">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<Text bold>{groupInformation.title}</Text>
<Text bold>{ groupInformation.title }</Text>
<div className="flex gap-1">
<i className={'nitro-icon icon-group-type-' + groupInformation.type} title={LocalizeText(`group.edit.settings.type.${STATES[groupInformation.type]}.help`)} />
{groupInformation.canMembersDecorate &&
<i className="nitro-icon icon-group-decorate" title={LocalizeText('group.memberscandecorate')} />}
<i className={ 'nitro-icon icon-group-type-' + groupInformation.type } title={ LocalizeText(`group.edit.settings.type.${ STATES[groupInformation.type] }.help`) } />
{ groupInformation.canMembersDecorate &&
<i className="nitro-icon icon-group-decorate" title={ LocalizeText('group.memberscandecorate') } /> }
</div>
</div>
<Text small>{LocalizeText('group.created', ['date', 'owner'], [groupInformation.createdAt, groupInformation.ownerName])}</Text>
<Text small>{ LocalizeText('group.created', [ 'date', 'owner' ], [ groupInformation.createdAt, groupInformation.ownerName ]) }</Text>
</div>
<Text small className="group-description" overflow="auto">{groupInformation.description}</Text>
<Text small className="group-description" overflow="auto">{ groupInformation.description }</Text>
</div>
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-1">
<Text pointer small underline onClick={() => handleAction('homeroom')}>{LocalizeText('group.linktobase')}</Text>
<Text pointer small underline onClick={() => handleAction('furniture')}>{LocalizeText('group.buyfurni')}</Text>
<Text pointer small underline onClick={() => handleAction('popular_groups')}>{LocalizeText('group.showgroups')}</Text>
<Text pointer small underline onClick={ () => handleAction('homeroom') }>{ LocalizeText('group.linktobase') }</Text>
<Text pointer small underline onClick={ () => handleAction('furniture') }>{ LocalizeText('group.buyfurni') }</Text>
<Text pointer small underline onClick={ () => handleAction('popular_groups') }>{ LocalizeText('group.showgroups') }</Text>
</div>
{(groupInformation.type !== GroupType.PRIVATE || groupInformation.type === GroupType.PRIVATE && groupInformation.membershipType === GroupMembershipType.MEMBER) &&
<Button disabled={(groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) || isRealOwner} onClick={handleButtonClick}>
{LocalizeText(getButtonText())}
</Button>}
{ (groupInformation.type !== GroupType.PRIVATE || groupInformation.type === GroupType.PRIVATE && groupInformation.membershipType === GroupMembershipType.MEMBER) &&
<Button disabled={ (groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) || isRealOwner } onClick={ handleButtonClick }>
{ LocalizeText(getButtonText()) }
</Button> }
</div>
</div>
</Grid>

View File

@ -8,13 +8,13 @@ import { classNames } from '../../../layout';
export const GroupMembersView: FC<{}> = props =>
{
const [groupId, setGroupId] = useState<number>(-1);
const [levelId, setLevelId] = useState<number>(-1);
const [membersData, setMembersData] = useState<GroupMembersParser>(null);
const [pageId, setPageId] = useState<number>(-1);
const [totalPages, setTotalPages] = useState<number>(0);
const [searchQuery, setSearchQuery] = useState<string>('');
const [removingMemberName, setRemovingMemberName] = useState<string>(null);
const [ groupId, setGroupId ] = useState<number>(-1);
const [ levelId, setLevelId ] = useState<number>(-1);
const [ membersData, setMembersData ] = useState<GroupMembersParser>(null);
const [ pageId, setPageId ] = useState<number>(-1);
const [ totalPages, setTotalPages ] = useState<number>(0);
const [ searchQuery, setSearchQuery ] = useState<string>('');
const [ removingMemberName, setRemovingMemberName ] = useState<string>(null);
const { showConfirm = null } = useNotification();
const getRankDescription = (member: GroupMemberParser) =>
@ -36,7 +36,7 @@ export const GroupMembersView: FC<{}> = props =>
if ((groupId === -1) || (levelId === -1) || (pageId === -1)) return;
SendMessageComposer(new GroupMembersComposer(groupId, pageId, searchQuery, levelId));
}, [groupId, levelId, pageId, searchQuery]);
}, [ groupId, levelId, pageId, searchQuery ]);
const toggleAdmin = (member: GroupMemberParser) =>
{
@ -87,7 +87,7 @@ export const GroupMembersView: FC<{}> = props =>
{
const parser = event.getParser();
showConfirm(LocalizeText(((parser.furnitureCount > 0) ? 'group.kickconfirm.desc' : 'group.kickconfirm_nofurni.desc'), ['user', 'amount'], [removingMemberName, parser.furnitureCount.toString()]), () =>
showConfirm(LocalizeText(((parser.furnitureCount > 0) ? 'group.kickconfirm.desc' : 'group.kickconfirm_nofurni.desc'), [ 'user', 'amount' ], [ removingMemberName, parser.furnitureCount.toString() ]), () =>
{
SendMessageComposer(new GroupRemoveMemberComposer(membersData.groupId, parser.userId));
@ -124,14 +124,14 @@ export const GroupMembersView: FC<{}> = props =>
useEffect(() =>
{
setPageId(0);
}, [groupId, levelId, searchQuery]);
}, [ groupId, levelId, searchQuery ]);
useEffect(() =>
{
if ((groupId === -1) || (levelId === -1) || (pageId === -1)) return;
SendMessageComposer(new GroupMembersComposer(groupId, pageId, searchQuery, levelId));
}, [groupId, levelId, pageId, searchQuery]);
}, [ groupId, levelId, pageId, searchQuery ]);
useEffect(() =>
{
@ -142,66 +142,66 @@ export const GroupMembersView: FC<{}> = props =>
setTotalPages(0);
setSearchQuery('');
setRemovingMemberName(null);
}, [groupId]);
}, [ groupId ]);
if ((groupId === -1) || !membersData) return null;
return (
<NitroCardView className="w-[400px] max-h-[380px] " theme="primary-slim">
<NitroCardHeaderView headerText={LocalizeText('group.members.title', ['groupName'], [membersData ? membersData.groupTitle : ''])} onCloseClick={event => setGroupId(-1)} />
<NitroCardHeaderView headerText={ LocalizeText('group.members.title', [ 'groupName' ], [ membersData ? membersData.groupTitle : '' ]) } onCloseClick={ event => setGroupId(-1) } />
<NitroCardContentView overflow="hidden">
<div className="flex gap-2">
<Flex center className="group-badge">
<LayoutBadgeImageView badgeCode={membersData.badge} className="mx-auto block" isGroup={true} />
<LayoutBadgeImageView badgeCode={ membersData.badge } className="mx-auto block" isGroup={ true } />
</Flex>
<Column fullWidth gap={1}>
<input className="min-h-[calc(1.5em+.5rem+2px)] px-[.5rem] py-[.25rem] text-[.7875rem] rounded-[.2rem] w-full" placeholder={LocalizeText('group.members.searchinfo')} type="text" value={searchQuery} onChange={event => setSearchQuery(event.target.value)} />
<select className="form-select form-select-sm w-full" value={levelId} onChange={event => setLevelId(parseInt(event.target.value))}>
<option value="0">{LocalizeText('group.members.search.all')}</option>
<option value="1">{LocalizeText('group.members.search.admins')}</option>
<option value="2">{LocalizeText('group.members.search.pending')}</option>
<Column fullWidth gap={ 1 }>
<input className="min-h-[calc(1.5em+.5rem+2px)] px-[.5rem] py-[.25rem] text-[.7875rem] rounded-[.2rem] w-full" placeholder={ LocalizeText('group.members.searchinfo') } type="text" value={ searchQuery } onChange={ event => setSearchQuery(event.target.value) } />
<select className="form-select form-select-sm w-full" value={ levelId } onChange={ event => setLevelId(parseInt(event.target.value)) }>
<option value="0">{ LocalizeText('group.members.search.all') }</option>
<option value="1">{ LocalizeText('group.members.search.admins') }</option>
<option value="2">{ LocalizeText('group.members.search.pending') }</option>
</select>
</Column>
</div>
<Grid className="nitro-group-members-list-grid" columnCount={2} overflow="auto">
{membersData.result.map((member, index) =>
<Grid className="nitro-group-members-list-grid" columnCount={ 2 } overflow="auto">
{ membersData.result.map((member, index) =>
{
return (
<Flex key={index} alignItems="center" className="p-2 bg-white rounded h-[50px] max-h-[50px]" gap={2} overflow="hidden">
<div className="cursor-pointer relative overflow-hidden w-[40px] h-[50px]" onClick={() => GetUserProfile(member.id)}>
<LayoutAvatarImageView className='absolute -left-[25px] -top-[20px]' direction={2} figure={member.figure} headOnly={true} />
<Flex key={ index } alignItems="center" className="p-2 bg-white rounded h-[50px] max-h-[50px]" gap={ 2 } overflow="hidden">
<div className="cursor-pointer relative overflow-hidden w-[40px] h-[50px]" onClick={ () => GetUserProfile(member.id) }>
<LayoutAvatarImageView className="absolute -left-[25px] -top-[20px]" direction={ 2 } figure={ member.figure } headOnly={ true } />
</div>
<Column grow gap={1}>
<Text bold pointer small onClick={event => GetUserProfile(member.id)}>{member.name}</Text>
{(member.rank !== GroupRank.REQUESTED) &&
<Text italics small variant="muted">{LocalizeText('group.members.since', ['date'], [member.joinedAt])}</Text>}
<Column grow gap={ 1 }>
<Text bold pointer small onClick={ event => GetUserProfile(member.id) }>{ member.name }</Text>
{ (member.rank !== GroupRank.REQUESTED) &&
<Text italics small variant="muted">{ LocalizeText('group.members.since', [ 'date' ], [ member.joinedAt ]) }</Text> }
</Column>
<div className="flex flex-col gap-1">
{(member.rank !== GroupRank.REQUESTED) &&
{ (member.rank !== GroupRank.REQUESTED) &&
<div className="flex items-center justify-center">
<div className={classNames(`nitro-icon icon-group-small-${((member.rank === GroupRank.OWNER) ? 'owner' : (member.rank === GroupRank.ADMIN) ? 'admin' : (membersData.admin && (member.rank === GroupRank.MEMBER)) ? 'not-admin' : '')}`, membersData.admin && 'cursor-pointer')} title={LocalizeText(getRankDescription(member))} onClick={event => toggleAdmin(member)} />
</div>}
{membersData.admin && (member.rank === GroupRank.REQUESTED) &&
<div className={ classNames(`nitro-icon icon-group-small-${ ((member.rank === GroupRank.OWNER) ? 'owner' : (member.rank === GroupRank.ADMIN) ? 'admin' : (membersData.admin && (member.rank === GroupRank.MEMBER)) ? 'not-admin' : '') }`, membersData.admin && 'cursor-pointer') } title={ LocalizeText(getRankDescription(member)) } onClick={ event => toggleAdmin(member) } />
</div> }
{ membersData.admin && (member.rank === GroupRank.REQUESTED) &&
<Flex alignItems="center">
<div className="cursor-pointer nitro-friends-spritesheet icon-accept" title={LocalizeText('group.members.accept')} onClick={event => acceptMembership(member)} />
</Flex>}
{membersData.admin && (member.rank !== GroupRank.OWNER) && (member.id !== GetSessionDataManager().userId) &&
<div className="cursor-pointer nitro-friends-spritesheet icon-accept" title={ LocalizeText('group.members.accept') } onClick={ event => acceptMembership(member) } />
</Flex> }
{ membersData.admin && (member.rank !== GroupRank.OWNER) && (member.id !== GetSessionDataManager().userId) &&
<Flex alignItems="center">
<div className="cursor-pointer nitro-friends-spritesheet icon-deny" title={LocalizeText(member.rank === GroupRank.REQUESTED ? 'group.members.reject' : 'group.members.kick')} onClick={event => removeMemberOrDeclineMembership(member)} />
</Flex>}
<div className="cursor-pointer nitro-friends-spritesheet icon-deny" title={ LocalizeText(member.rank === GroupRank.REQUESTED ? 'group.members.reject' : 'group.members.kick') } onClick={ event => removeMemberOrDeclineMembership(member) } />
</Flex> }
</div>
</Flex>
);
})}
}) }
</Grid>
<Flex alignItems="center" gap={1} justifyContent="between">
<Button disabled={(membersData.pageIndex === 0)} onClick={event => setPageId(prevValue => (prevValue - 1))}>
<Flex alignItems="center" gap={ 1 } justifyContent="between">
<Button disabled={ (membersData.pageIndex === 0) } onClick={ event => setPageId(prevValue => (prevValue - 1)) }>
<FaChevronLeft className="fa-icon" />
</Button>
<Text small>
{LocalizeText('group.members.pageinfo', ['amount', 'page', 'totalPages'], [membersData.totalMembersCount.toString(), (membersData.pageIndex + 1).toString(), totalPages.toString()])}
{ LocalizeText('group.members.pageinfo', [ 'amount', 'page', 'totalPages' ], [ membersData.totalMembersCount.toString(), (membersData.pageIndex + 1).toString(), totalPages.toString() ]) }
</Text>
<Button disabled={(membersData.pageIndex === (totalPages - 1))} onClick={event => setPageId(prevValue => (prevValue + 1))}>
<Button disabled={ (membersData.pageIndex === (totalPages - 1)) } onClick={ event => setPageId(prevValue => (prevValue + 1)) }>
<FaChevronRight className="fa-icon" />
</Button>
</Flex>

View File

@ -7,9 +7,9 @@ import { useMessageEvent, useNotification } from '../../../hooks';
export const GroupRoomInformationView: FC<{}> = props =>
{
const [expectedGroupId, setExpectedGroupId] = useState<number>(0);
const [groupInformation, setGroupInformation] = useState<GroupInformationParser>(null);
const [isOpen, setIsOpen] = useState<boolean>(true);
const [ expectedGroupId, setExpectedGroupId ] = useState<number>(0);
const [ groupInformation, setGroupInformation ] = useState<GroupInformationParser>(null);
const [ isOpen, setIsOpen ] = useState<boolean>(true);
const { showConfirm = null } = useNotification();
useMessageEvent<DesktopViewEvent>(DesktopViewEvent, event =>
@ -107,25 +107,25 @@ export const GroupRoomInformationView: FC<{}> = props =>
return (
<div className="pointer-events-auto px-[5px] py-[6px] [box-shadow:inset_0_5px_#22222799,_inset_0_-4px_#12121599] bg-[#1c1c20f2] rounded text-sm">
<div className="flex flex-col gap-2">
<Flex pointer alignItems="center" justifyContent="between" onClick={event => setIsOpen(value => !value)}>
<Text variant="white">{LocalizeText('group.homeroominfo.title')}</Text>
{isOpen && <FaChevronUp className="fa-icon" />}
{!isOpen && <FaChevronDown className="fa-icon" />}
<Flex pointer alignItems="center" justifyContent="between" onClick={ event => setIsOpen(value => !value) }>
<Text variant="white">{ LocalizeText('group.homeroominfo.title') }</Text>
{ isOpen && <FaChevronUp className="fa-icon" /> }
{ !isOpen && <FaChevronDown className="fa-icon" /> }
</Flex>
{isOpen &&
{ isOpen &&
<>
<Flex pointer alignItems="center" gap={2} onClick={event => GetGroupInformation(groupInformation.id)}>
<Flex pointer alignItems="center" gap={ 2 } onClick={ event => GetGroupInformation(groupInformation.id) }>
<div className="group-badge">
<LayoutBadgeImageView badgeCode={groupInformation.badge} isGroup={true} />
<LayoutBadgeImageView badgeCode={ groupInformation.badge } isGroup={ true } />
</div>
<Text variant="white">{groupInformation.title}</Text>
<Text variant="white">{ groupInformation.title }</Text>
</Flex>
{(groupInformation.type !== GroupType.PRIVATE || isRealOwner) &&
<Button fullWidth disabled={(groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING)} variant="success" onClick={handleButtonClick}>
{LocalizeText(getButtonText())}
{ (groupInformation.type !== GroupType.PRIVATE || isRealOwner) &&
<Button fullWidth disabled={ (groupInformation.membershipType === GroupMembershipType.REQUEST_PENDING) } variant="success" onClick={ handleButtonClick }>
{ LocalizeText(getButtonText()) }
</Button>
}
</>}
</> }
</div>
</div>
);

View File

@ -15,7 +15,7 @@ interface GroupTabColorsViewProps
export const GroupTabColorsView: FC<GroupTabColorsViewProps> = props =>
{
const { groupData = null, setGroupData = null, setCloseAction = null } = props;
const [colors, setColors] = useState<number[]>(null);
const [ colors, setColors ] = useState<number[]>(null);
const { groupCustomize = null } = useGroup();
const getGroupColor = (colorIndex: number) =>
@ -29,7 +29,7 @@ export const GroupTabColorsView: FC<GroupTabColorsViewProps> = props =>
{
setColors(prevValue =>
{
const newColors = [...prevValue];
const newColors = [ ...prevValue ];
newColors[colorIndex] = colorId;
@ -49,7 +49,7 @@ export const GroupTabColorsView: FC<GroupTabColorsViewProps> = props =>
{
const newValue = { ...prevValue };
newValue.groupColors = [...colors];
newValue.groupColors = [ ...colors ];
return newValue;
});
@ -60,67 +60,67 @@ export const GroupTabColorsView: FC<GroupTabColorsViewProps> = props =>
SendMessageComposer(new GroupSaveColorsComposer(groupData.groupId, colors[0], colors[1]));
return true;
}, [groupData, colors, setGroupData]);
}, [ groupData, colors, setGroupData ]);
useEffect(() =>
{
if (!groupCustomize.groupColorsA || !groupCustomize.groupColorsB || groupData.groupColors) return;
const groupColors = [groupCustomize.groupColorsA[0].id, groupCustomize.groupColorsB[0].id];
const groupColors = [ groupCustomize.groupColorsA[0].id, groupCustomize.groupColorsB[0].id ];
setGroupData(prevValue =>
{
return { ...prevValue, groupColors };
});
}, [groupCustomize, groupData.groupColors, setGroupData]);
}, [ groupCustomize, groupData.groupColors, setGroupData ]);
useEffect(() =>
{
if (groupData.groupId <= 0)
{
setColors(groupData.groupColors ? [...groupData.groupColors] : null);
setColors(groupData.groupColors ? [ ...groupData.groupColors ] : null);
return;
}
setColors(groupData.groupColors);
}, [groupData]);
}, [ groupData ]);
useEffect(() =>
{
setCloseAction({ action: saveColors });
return () => setCloseAction(null);
}, [setCloseAction, saveColors]);
}, [ setCloseAction, saveColors ]);
if (!colors) return null;
return (
<Grid overflow="hidden">
<Column gap={1} size={2}>
<Text bold>{LocalizeText('group.edit.color.guild.color')}</Text>
{groupData.groupColors && (groupData.groupColors.length > 0) &&
<Column gap={ 1 } size={ 2 }>
<Text bold>{ LocalizeText('group.edit.color.guild.color') }</Text>
{ groupData.groupColors && (groupData.groupColors.length > 0) &&
<div className="flex overflow-hidden border rounded">
<div className="w-[30px] h-[40px]" style={{ backgroundColor: '#' + getGroupColor(0) }} />
<div className="w-[30px] h-[40px]" style={{ backgroundColor: '#' + getGroupColor(1) }} />
</div>}
<div className="w-[30px] h-[40px]" style={ { backgroundColor: '#' + getGroupColor(0) } } />
<div className="w-[30px] h-[40px]" style={ { backgroundColor: '#' + getGroupColor(1) } } />
</div> }
</Column>
<Column gap={1} overflow="hidden" size={5}>
<Text bold>{LocalizeText('group.edit.color.primary.color')}</Text>
<AutoGrid columnCount={7} columnMinHeight={16} columnMinWidth={16} gap={1}>
{groupData.groupColors && groupCustomize.groupColorsA && groupCustomize.groupColorsA.map((item, index) =>
<Column gap={ 1 } overflow="hidden" size={ 5 }>
<Text bold>{ LocalizeText('group.edit.color.primary.color') }</Text>
<AutoGrid columnCount={ 7 } columnMinHeight={ 16 } columnMinWidth={ 16 } gap={ 1 }>
{ groupData.groupColors && groupCustomize.groupColorsA && groupCustomize.groupColorsA.map((item, index) =>
{
return <div key={index} className={classNames('relative rounded-[.25rem] w-[16px] h-[16px] bg-[#fff] border-[2px] border-[solid] border-[#fff] [box-shadow:inset_3px_3px_#0000001a] [box-shadow:inset_2px_2px_#0003] cursor-pointer', ((groupData.groupColors[0] === item.id) && 'bg-primary [box-shadow:none]'))} style={{ backgroundColor: '#' + item.color }} onClick={() => selectColor(0, item.id)}></div>
})}
return <div key={ index } className={ classNames('relative rounded-[.25rem] w-[16px] h-[16px] bg-[#fff] border-[2px] border-[solid] border-[#fff] [box-shadow:inset_3px_3px_#0000001a] [box-shadow:inset_2px_2px_#0003] cursor-pointer', ((groupData.groupColors[0] === item.id) && 'bg-primary [box-shadow:none]')) } style={ { backgroundColor: '#' + item.color } } onClick={ () => selectColor(0, item.id) }></div>
}) }
</AutoGrid>
</Column>
<Column gap={1} overflow="hidden" size={5}>
<Text bold>{LocalizeText('group.edit.color.secondary.color')}</Text>
<AutoGrid columnCount={7} columnMinHeight={16} columnMinWidth={16} gap={1}>
{groupData.groupColors && groupCustomize.groupColorsB && groupCustomize.groupColorsB.map((item, index) =>
<Column gap={ 1 } overflow="hidden" size={ 5 }>
<Text bold>{ LocalizeText('group.edit.color.secondary.color') }</Text>
<AutoGrid columnCount={ 7 } columnMinHeight={ 16 } columnMinWidth={ 16 } gap={ 1 }>
{ groupData.groupColors && groupCustomize.groupColorsB && groupCustomize.groupColorsB.map((item, index) =>
{
return <div key={index} className={classNames('relative rounded-[.25rem] w-[16px] h-[16px] bg-[#fff] border-[2px] border-[solid] border-[#fff] [box-shadow:inset_3px_3px_#0000001a] [box-shadow:inset_2px_2px_#0003] cursor-pointer', ((groupData.groupColors[1] === item.id) && 'bg-primary [box-shadow:none]'))} style={{ backgroundColor: '#' + item.color }} onClick={() => selectColor(1, item.id)}></div>
})}
return <div key={ index } className={ classNames('relative rounded-[.25rem] w-[16px] h-[16px] bg-[#fff] border-[2px] border-[solid] border-[#fff] [box-shadow:inset_3px_3px_#0000001a] [box-shadow:inset_2px_2px_#0003] cursor-pointer', ((groupData.groupColors[1] === item.id) && 'bg-primary [box-shadow:none]')) } style={ { backgroundColor: '#' + item.color } } onClick={ () => selectColor(1, item.id) }></div>
}) }
</AutoGrid>
</Column>
</Grid>

View File

@ -36,30 +36,30 @@ export const GroupTabCreatorConfirmationView: FC<GroupTabCreatorConfirmationView
if (!groupData) return null;
return (
<Grid gap={1} overflow="hidden">
<Column size={3}>
<Column center className="bg-muted rounded p-1" gap={2}>
<Text bold center>{LocalizeText('group.create.confirm.guildbadge')}</Text>
<LayoutBadgeImageView badgeCode={getCompleteBadgeCode()} isGroup={true} />
<Grid gap={ 1 } overflow="hidden">
<Column size={ 3 }>
<Column center className="bg-muted rounded p-1" gap={ 2 }>
<Text bold center>{ LocalizeText('group.create.confirm.guildbadge') }</Text>
<LayoutBadgeImageView badgeCode={ getCompleteBadgeCode() } isGroup={ true } />
</Column>
<Column center className="bg-muted rounded p-1" gap={2}>
<Text bold center>{LocalizeText('group.edit.color.guild.color')}</Text>
<Column center className="bg-muted rounded p-1" gap={ 2 }>
<Text bold center>{ LocalizeText('group.edit.color.guild.color') }</Text>
<Flex className="rounded border" overflow="hidden">
<div className="w-[30px] h-[40px]" style={{ backgroundColor: '#' + getGroupColor(0) }} />
<div className="w-[30px] h-[40px]" style={{ backgroundColor: '#' + getGroupColor(1) }} />
<div className="w-[30px] h-[40px]" style={ { backgroundColor: '#' + getGroupColor(0) } } />
<div className="w-[30px] h-[40px]" style={ { backgroundColor: '#' + getGroupColor(1) } } />
</Flex>
</Column>
</Column>
<Column justifyContent="between" size={9}>
<Column justifyContent="between" size={ 9 }>
<div className="flex flex-col">
<div className="flex flex-col gap-1">
<Text bold>{groupData.groupName}</Text>
<Text>{groupData.groupDescription}</Text>
<Text bold>{ groupData.groupName }</Text>
<Text>{ groupData.groupDescription }</Text>
</div>
<Text overflow="auto">{LocalizeText('group.create.confirm.info')}</Text>
<Text overflow="auto">{ LocalizeText('group.create.confirm.info') }</Text>
</div>
<Text center className="bg-primary rounded p-1" variant="white">
{LocalizeText('group.create.confirm.buyinfo', ['amount'], [purchaseCost.toString()])}
{ LocalizeText('group.create.confirm.buyinfo', [ 'amount' ], [ purchaseCost.toString() ]) }
</Text>
</Column>
</Grid>

View File

@ -18,9 +18,9 @@ interface GroupTabIdentityViewProps
export const GroupTabIdentityView: FC<GroupTabIdentityViewProps> = props =>
{
const { groupData = null, setGroupData = null, setCloseAction = null, onClose = null, isCreator = false, availableRooms = [] } = props;
const [groupName, setGroupName] = useState<string>('');
const [groupDescription, setGroupDescription] = useState<string>('');
const [groupHomeroomId, setGroupHomeroomId] = useState<number>(-1);
const [ groupName, setGroupName ] = useState<string>('');
const [ groupDescription, setGroupDescription ] = useState<string>('');
const [ groupHomeroomId, setGroupHomeroomId ] = useState<number>(-1);
const { showConfirm = null } = useNotification();
const deleteGroup = () =>
@ -62,21 +62,21 @@ export const GroupTabIdentityView: FC<GroupTabIdentityViewProps> = props =>
SendMessageComposer(new GroupSaveInformationComposer(groupData.groupId, groupName, (groupDescription || '')));
return true;
}, [groupData, groupName, groupDescription, groupHomeroomId, setGroupData]);
}, [ groupData, groupName, groupDescription, groupHomeroomId, setGroupData ]);
useEffect(() =>
{
setGroupName(groupData.groupName || '');
setGroupDescription(groupData.groupDescription || '');
setGroupHomeroomId(groupData.groupHomeroomId);
}, [groupData]);
}, [ groupData ]);
useEffect(() =>
{
setCloseAction({ action: saveIdentity });
return () => setCloseAction(null);
}, [setCloseAction, saveIdentity]);
}, [ setCloseAction, saveIdentity ]);
if (!groupData) return null;
@ -84,34 +84,34 @@ export const GroupTabIdentityView: FC<GroupTabIdentityViewProps> = props =>
<Column justifyContent="between" overflow="auto">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-1">
<Text center className="col-span-3">{LocalizeText('group.edit.name')}</Text>
<NitroInput maxLength={29} type="text" value={groupName} onChange={event => setGroupName(event.target.value)} />
<Text center className="col-span-3">{ LocalizeText('group.edit.name') }</Text>
<NitroInput maxLength={ 29 } type="text" value={ groupName } onChange={ event => setGroupName(event.target.value) } />
</div>
<div className="flex items-center gap-1">
<Text center className="col-span-3">{LocalizeText('group.edit.desc')}</Text>
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm" maxLength={254} value={groupDescription} onChange={event => setGroupDescription(event.target.value)} />
<Text center className="col-span-3">{ LocalizeText('group.edit.desc') }</Text>
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm" maxLength={ 254 } value={ groupDescription } onChange={ event => setGroupDescription(event.target.value) } />
</div>
{isCreator &&
{ isCreator &&
<>
<div className="flex items-center gap-1">
<Text center className="col-span-3">{LocalizeText('group.edit.base')}</Text>
<Column fullWidth gap={1}>
<select className="form-select form-select-sm" value={groupHomeroomId} onChange={event => setGroupHomeroomId(parseInt(event.target.value))}>
<option disabled value={-1}>{LocalizeText('group.edit.base.select.room')}</option>
{availableRooms && availableRooms.map((room, index) => <option key={index} value={room.id}>{room.name}</option>)}
<Text center className="col-span-3">{ LocalizeText('group.edit.base') }</Text>
<Column fullWidth gap={ 1 }>
<select className="form-select form-select-sm" value={ groupHomeroomId } onChange={ event => setGroupHomeroomId(parseInt(event.target.value)) }>
<option disabled value={ -1 }>{ LocalizeText('group.edit.base.select.room') }</option>
{ availableRooms && availableRooms.map((room, index) => <option key={ index } value={ room.id }>{ room.name }</option>) }
</select>
</Column>
</div>
<div className="flex gap-1">
<div className="col-span-3">&nbsp;</div>
<Text small>{LocalizeText('group.edit.base.warning')}</Text>
<Text small>{ LocalizeText('group.edit.base.warning') }</Text>
</div>
</>}
</> }
</div>
{!isCreator &&
<Button variant="danger" onClick={deleteGroup}>{LocalizeText('group.delete')}</Button>}
{isCreator &&
<Text center fullWidth pointer underline onClick={event => CreateLinkEvent('navigator/create')}>{LocalizeText('group.createroom')}</Text>}
{ !isCreator &&
<Button variant="danger" onClick={ deleteGroup }>{ LocalizeText('group.delete') }</Button> }
{ isCreator &&
<Text center fullWidth pointer underline onClick={ event => CreateLinkEvent('navigator/create') }>{ LocalizeText('group.createroom') }</Text> }
</Column>
);
};

View File

@ -35,41 +35,41 @@ export const GuideToolMenuView: FC<GuideToolMenuViewProps> = props =>
return (
<div className="flex flex-col">
<Flex alignItems="center" className="bg-muted p-2 rounded" gap={2}>
<div className={'duty-switch' + (isOnDuty ? '' : ' off')} onClick={event => processAction('toggle_duty')} />
<Column gap={0}>
<Text bold>{LocalizeText('guide.help.guide.tool.yourstatus')}</Text>
<Text>{LocalizeText(`guide.help.guide.tool.duty.${(isOnDuty ? 'on' : 'off')}`)}</Text>
<Flex alignItems="center" className="bg-muted p-2 rounded" gap={ 2 }>
<div className={ 'duty-switch' + (isOnDuty ? '' : ' off') } onClick={ event => processAction('toggle_duty') } />
<Column gap={ 0 }>
<Text bold>{ LocalizeText('guide.help.guide.tool.yourstatus') }</Text>
<Text>{ LocalizeText(`guide.help.guide.tool.duty.${ (isOnDuty ? 'on' : 'off') }`) }</Text>
</Column>
</Flex>
<div className="flex flex-col gap-1">
<Text bold>{LocalizeText('guide.help.guide.tool.tickettypeselection.caption')}</Text>
<Text bold>{ LocalizeText('guide.help.guide.tool.tickettypeselection.caption') }</Text>
<div className="flex items-center gap-1">
<input checked={isHandlingGuideRequests} className="form-check-input" disabled={isOnDuty} type="checkbox" onChange={event => setIsHandlingGuideRequests(event.target.checked)} />
<Text>{LocalizeText('guide.help.guide.tool.tickettypeselection.guiderequests')}</Text>
<input checked={ isHandlingGuideRequests } className="form-check-input" disabled={ isOnDuty } type="checkbox" onChange={ event => setIsHandlingGuideRequests(event.target.checked) } />
<Text>{ LocalizeText('guide.help.guide.tool.tickettypeselection.guiderequests') }</Text>
</div>
<div className="flex items-center gap-1">
<input checked={isHandlingHelpRequests} className="form-check-input" disabled={isOnDuty} type="checkbox" onChange={event => setIsHandlingHelpRequests(event.target.checked)} />
<Text>{LocalizeText('guide.help.guide.tool.tickettypeselection.onlyhelprequests')}</Text>
<input checked={ isHandlingHelpRequests } className="form-check-input" disabled={ isOnDuty } type="checkbox" onChange={ event => setIsHandlingHelpRequests(event.target.checked) } />
<Text>{ LocalizeText('guide.help.guide.tool.tickettypeselection.onlyhelprequests') }</Text>
</div>
<div className="flex items-center gap-1">
<input checked={isHandlingBullyReports} className="form-check-input" disabled={isOnDuty} type="checkbox" onChange={event => setIsHandlingBullyReports(event.target.checked)} />
<Text>{LocalizeText('guide.help.guide.tool.tickettypeselection.bullyreports')}</Text>
<input checked={ isHandlingBullyReports } className="form-check-input" disabled={ isOnDuty } type="checkbox" onChange={ event => setIsHandlingBullyReports(event.target.checked) } />
<Text>{ LocalizeText('guide.help.guide.tool.tickettypeselection.bullyreports') }</Text>
</div>
</div>
<hr className="bg-dark m-0" />
<div className="flex justify-enter items-center gap-2">
<div className="info-icon" />
<div className="flex flex-col gap-1">
<div dangerouslySetInnerHTML={{ __html: LocalizeText('guide.help.guide.tool.guidesonduty', ['amount'], [guidesOnDuty.toString()]) }} />
<div dangerouslySetInnerHTML={{ __html: LocalizeText('guide.help.guide.tool.helpersonduty', ['amount'], [helpersOnDuty.toString()]) }} />
<div dangerouslySetInnerHTML={{ __html: LocalizeText('guide.help.guide.tool.guardiansonduty', ['amount'], [guardiansOnDuty.toString()]) }} />
<div dangerouslySetInnerHTML={ { __html: LocalizeText('guide.help.guide.tool.guidesonduty', [ 'amount' ], [ guidesOnDuty.toString() ]) } } />
<div dangerouslySetInnerHTML={ { __html: LocalizeText('guide.help.guide.tool.helpersonduty', [ 'amount' ], [ helpersOnDuty.toString() ]) } } />
<div dangerouslySetInnerHTML={ { __html: LocalizeText('guide.help.guide.tool.guardiansonduty', [ 'amount' ], [ guardiansOnDuty.toString() ]) } } />
</div>
</div>
<hr className="bg-dark m-0" />
<Flex gap={2} justifyContent="between">
<Button disabled onClick={event => processAction('forum_link')}>{LocalizeText('guide.help.guide.tool.forum.link')}</Button>
<Button disabled>{LocalizeText('guide.help.guide.tool.skill.link')}</Button>
<Flex gap={ 2 } justifyContent="between">
<Button disabled onClick={ event => processAction('forum_link') }>{ LocalizeText('guide.help.guide.tool.forum.link') }</Button>
<Button disabled>{ LocalizeText('guide.help.guide.tool.skill.link') }</Button>
</Flex>
</div>
);

View File

@ -3,7 +3,7 @@ import { FC, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'rea
import { GuideToolMessageGroup, LocalizeText, SendMessageComposer, TryVisitRoom } from '../../../api';
import { Button, Column, Flex, LayoutAvatarImageView, Text } from '../../../common';
import { useMessageEvent } from '../../../hooks';
import { classNames } from '../../../layout';
import { NitroInput, classNames } from '../../../layout';
interface GuideToolOngoingViewProps
{
@ -21,13 +21,13 @@ export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
const { isGuide = false, userId = 0, userName = null, userFigure = null, isTyping = false, messageGroups = [] } = props;
const [messageText, setMessageText] = useState<string>('');
const [ messageText, setMessageText ] = useState<string>('');
useEffect(() =>
{
scrollDiv.current?.scrollIntoView({ block: 'end', behavior: 'smooth' });
}, [messageGroups]);
}, [ messageGroups ]);
const visit = useCallback(() =>
{
@ -57,14 +57,14 @@ export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
SendMessageComposer(new GuideSessionMessageMessageComposer(messageText));
setMessageText('');
}, [messageText]);
}, [ messageText ]);
const onKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) =>
{
if (event.key !== 'Enter') return;
sendMessage();
}, [sendMessage]);
}, [ sendMessage ]);
const isOwnChat = useCallback((userId: number) =>
{
@ -73,58 +73,58 @@ export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
return (
<Column fullHeight>
<Flex alignItems="center" className="p-2 rounded bg-muted" gap={1} justifyContent="between">
{isGuide &&
<Flex alignItems="center" className="p-2 rounded bg-muted" gap={ 1 } justifyContent="between">
{ isGuide &&
<div className="relative inline-flex align-middle">
<Button onClick={visit}>{LocalizeText('guide.help.request.guide.ongoing.visit.button')}</Button>
<Button onClick={invite}>{LocalizeText('guide.help.request.guide.ongoing.invite.button')}</Button>
</div>}
{!isGuide &&
<Column gap={0}>
<Text bold>{userName}</Text>
<Text>{LocalizeText('guide.help.request.user.ongoing.guide.desc')}</Text>
</Column>}
<Button disabled variant="danger">{LocalizeText('guide.help.common.report.link')}</Button>
<Button onClick={ visit }>{ LocalizeText('guide.help.request.guide.ongoing.visit.button') }</Button>
<Button onClick={ invite }>{ LocalizeText('guide.help.request.guide.ongoing.invite.button') }</Button>
</div> }
{ !isGuide &&
<Column gap={ 0 }>
<Text bold>{ userName }</Text>
<Text>{ LocalizeText('guide.help.request.user.ongoing.guide.desc') }</Text>
</Column> }
<Button disabled variant="danger">{ LocalizeText('guide.help.common.report.link') }</Button>
</Flex>
<Column className="p-2 rounded bg-muted chat-messages" gap={1} overflow="hidden">
<Column className="p-2 rounded bg-muted chat-messages" gap={ 1 } overflow="hidden">
<Column overflow="auto">
{messageGroups.map((group, index) =>
{ messageGroups.map((group, index) =>
{
return (
<Flex key={index} fullWidth gap={2} justifyContent={isOwnChat(group.userId) ? 'end' : 'start'}>
<Flex key={ index } fullWidth gap={ 2 } justifyContent={ isOwnChat(group.userId) ? 'end' : 'start' }>
<div className="flex-shrink-0 message-avatar">
{(!isOwnChat(group.userId)) &&
<LayoutAvatarImageView direction={2} figure={userFigure} />}
{ (!isOwnChat(group.userId)) &&
<LayoutAvatarImageView direction={ 2 } figure={ userFigure } /> }
</div>
<div className={'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (isOwnChat(group.userId) ? 'right' : 'left')}>
<div className={ 'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (isOwnChat(group.userId) ? 'right' : 'left') }>
<Text bold>
{(isOwnChat(group.userId)) && GetSessionDataManager().userName}
{(!isOwnChat(group.userId)) && userName}
{ (isOwnChat(group.userId)) && GetSessionDataManager().userName }
{ (!isOwnChat(group.userId)) && userName }
</Text>
{group.messages.map((chat, index) => <div key={index} className={classNames(chat.roomId ? 'text-break text-underline' : 'text-break', 'chat.roomId' && 'cursor-pointer')} onClick={() => chat.roomId ? TryVisitRoom(chat.roomId) : null}>{chat.message}</div>)}
{ group.messages.map((chat, index) => <div key={ index } className={ classNames(chat.roomId ? 'text-break text-underline' : 'text-break', 'chat.roomId' && 'cursor-pointer') } onClick={ () => chat.roomId ? TryVisitRoom(chat.roomId) : null }>{ chat.message }</div>) }
</div>
{(isOwnChat(group.userId)) &&
{ (isOwnChat(group.userId)) &&
<div className="flex-shrink-0 message-avatar">
<LayoutAvatarImageView direction={4} figure={GetSessionDataManager().figure} />
</div>}
<LayoutAvatarImageView direction={ 4 } figure={ GetSessionDataManager().figure } />
</div> }
</Flex>
);
})}
<div ref={scrollDiv} />
}) }
<div ref={ scrollDiv } />
</Column>
</Column>
<div className="flex flex-col gap-1">
<div className="flex gap-1">
<NitroInput placeholder={LocalizeText('guide.help.request.guide.ongoing.input.empty', ['name'], [userName])} type="text" value={messageText} onChange={event => setMessageText(event.target.value)} onKeyDown={onKeyDown} />
<Button variant="success" onClick={sendMessage}>
{LocalizeText('widgets.chatinput.say')}
<NitroInput placeholder={ LocalizeText('guide.help.request.guide.ongoing.input.empty', [ 'name' ], [ userName ]) } type="text" value={ messageText } onChange={ event => setMessageText(event.target.value) } onKeyDown={ onKeyDown } />
<Button variant="success" onClick={ sendMessage }>
{ LocalizeText('widgets.chatinput.say') }
</Button>
</div>
{isTyping &&
<Text variant="muted">{LocalizeText('guide.help.common.typing')}</Text>}
{ isTyping &&
<Text variant="muted">{ LocalizeText('guide.help.common.typing') }</Text> }
</div>
<Button fullWidth variant="success" onClick={resolve}>
{LocalizeText('guide.help.request.' + (isGuide ? 'guide' : 'user') + '.ongoing.close.link')}
<Button fullWidth variant="success" onClick={ resolve }>
{ LocalizeText('guide.help.request.' + (isGuide ? 'guide' : 'user') + '.ongoing.close.link') }
</Button>
</Column>
);

View File

@ -7,10 +7,10 @@ import { useInventoryBadges, useMessageEvent, usePurse, useSessionInfo } from '.
export const HcCenterView: FC<{}> = props =>
{
const [isVisible, setIsVisible] = useState(false);
const [kickbackData, setKickbackData] = useState<ScrKickbackData>(null);
const [unclaimedGifts, setUnclaimedGifts] = useState(0);
const [badgeCode, setBadgeCode] = useState(null);
const [ isVisible, setIsVisible ] = useState(false);
const [ kickbackData, setKickbackData ] = useState<ScrKickbackData>(null);
const [ unclaimedGifts, setUnclaimedGifts ] = useState(0);
const [ badgeCode, setBadgeCode ] = useState(null);
const { userFigure = null } = useSessionInfo();
const { purse = null, clubStatus = null } = usePurse();
const { badgeCodes = [], activate = null, deactivate = null } = useInventoryBadges();
@ -32,16 +32,16 @@ export const HcCenterView: FC<{}> = props =>
switch (clubStatus)
{
case ClubStatus.ACTIVE:
return LocalizeText(`hccenter.status.${clubStatus}.info`, ['timeleft', 'joindate', 'streakduration'], [getClubText(), kickbackData?.firstSubscriptionDate, FriendlyTime.shortFormat(kickbackData?.currentHcStreak * 86400)]);
return LocalizeText(`hccenter.status.${ clubStatus }.info`, [ 'timeleft', 'joindate', 'streakduration' ], [ getClubText(), kickbackData?.firstSubscriptionDate, FriendlyTime.shortFormat(kickbackData?.currentHcStreak * 86400) ]);
case ClubStatus.EXPIRED:
return LocalizeText(`hccenter.status.${clubStatus}.info`, ['joindate'], [kickbackData?.firstSubscriptionDate]);
return LocalizeText(`hccenter.status.${ clubStatus }.info`, [ 'joindate' ], [ kickbackData?.firstSubscriptionDate ]);
default:
return LocalizeText(`hccenter.status.${clubStatus}.info`);
return LocalizeText(`hccenter.status.${ clubStatus }.info`);
}
}
const getHcPaydayTime = () => (!kickbackData || kickbackData.timeUntilPayday < 60) ? LocalizeText('hccenter.special.time.soon') : FriendlyTime.shortFormat(kickbackData.timeUntilPayday * 60);
const getHcPaydayAmount = () => LocalizeText('hccenter.special.sum', ['credits'], [(kickbackData?.creditRewardForStreakBonus + kickbackData?.creditRewardForMonthlySpent).toString()]);
const getHcPaydayAmount = () => LocalizeText('hccenter.special.sum', [ 'credits' ], [ (kickbackData?.creditRewardForStreakBonus + kickbackData?.creditRewardForMonthlySpent).toString() ]);
useMessageEvent<ClubGiftInfoEvent>(ClubGiftInfoEvent, event =>
{
@ -92,7 +92,7 @@ export const HcCenterView: FC<{}> = props =>
useEffect(() =>
{
setBadgeCode(GetClubBadge(badgeCodes));
}, [badgeCodes]);
}, [ badgeCodes ]);
useEffect(() =>
{
@ -101,7 +101,7 @@ export const HcCenterView: FC<{}> = props =>
const id = activate();
return () => deactivate(id);
}, [isVisible, activate, deactivate]);
}, [ isVisible, activate, deactivate ]);
useEffect(() =>
{
@ -113,86 +113,86 @@ export const HcCenterView: FC<{}> = props =>
const popover = (
<>
<h5>{LocalizeText('hccenter.breakdown.title')}</h5>
<div>{LocalizeText('hccenter.breakdown.creditsspent', ['credits'], [kickbackData?.totalCreditsSpent.toString()])}</div>
<div>{LocalizeText('hccenter.breakdown.paydayfactor.percent', ['percent'], [(kickbackData?.kickbackPercentage * 100).toString()])}</div>
<div>{LocalizeText('hccenter.breakdown.streakbonus', ['credits'], [kickbackData?.creditRewardForStreakBonus.toString()])}</div>
<h5>{ LocalizeText('hccenter.breakdown.title') }</h5>
<div>{ LocalizeText('hccenter.breakdown.creditsspent', [ 'credits' ], [ kickbackData?.totalCreditsSpent.toString() ]) }</div>
<div>{ LocalizeText('hccenter.breakdown.paydayfactor.percent', [ 'percent' ], [ (kickbackData?.kickbackPercentage * 100).toString() ]) }</div>
<div>{ LocalizeText('hccenter.breakdown.streakbonus', [ 'credits' ], [ kickbackData?.creditRewardForStreakBonus.toString() ]) }</div>
<hr className="w-full text-black my-1" />
<div>{LocalizeText('hccenter.breakdown.total', ['credits', 'actual'], [getHcPaydayAmount(), ((((kickbackData?.kickbackPercentage * kickbackData?.totalCreditsSpent) + kickbackData?.creditRewardForStreakBonus) * 100) / 100).toString()])}</div>
<div className="btn btn-link text-primary p-0" onClick={() => CreateLinkEvent('habbopages/' + GetConfigurationValue('hc.center')['payday.habbopage'])}>
{LocalizeText('hccenter.special.infolink')}
<div>{ LocalizeText('hccenter.breakdown.total', [ 'credits', 'actual' ], [ getHcPaydayAmount(), ((((kickbackData?.kickbackPercentage * kickbackData?.totalCreditsSpent) + kickbackData?.creditRewardForStreakBonus) * 100) / 100).toString() ]) }</div>
<div className="btn btn-link text-primary p-0" onClick={ () => CreateLinkEvent('habbopages/' + GetConfigurationValue('hc.center')['payday.habbopage']) }>
{ LocalizeText('hccenter.special.infolink') }
</div>
</>
);
return (
<NitroCardView className="nitro-hc-center" theme="primary-slim">
<NitroCardHeaderView headerText={LocalizeText('generic.hccenter')} onCloseClick={() => setIsVisible(false)} />
<NitroCardHeaderView headerText={ LocalizeText('generic.hccenter') } onCloseClick={ () => setIsVisible(false) } />
<Flex className="bg-muted p-2" position="relative">
<div className="flex flex-col gap-1">
<div className="hc-logo" />
<Flex>
<Button variant="success" onClick={event => CreateLinkEvent('catalog/open/' + GetConfigurationValue('catalog.links')['hc.buy_hc'])}>
{LocalizeText((clubStatus === ClubStatus.ACTIVE) ? 'hccenter.btn.extend' : 'hccenter.btn.buy')}
<Button variant="success" onClick={ event => CreateLinkEvent('catalog/open/' + GetConfigurationValue('catalog.links')['hc.buy_hc']) }>
{ LocalizeText((clubStatus === ClubStatus.ACTIVE) ? 'hccenter.btn.extend' : 'hccenter.btn.buy') }
</Button>
</Flex>
</div>
<div className="end-0 p-4 top-0 habbo-avatar absolute">
<LayoutAvatarImageView direction={4} figure={userFigure} scale={2} />
<LayoutAvatarImageView direction={ 4 } figure={ userFigure } scale={ 2 } />
</div>
</Flex>
<NitroCardContentView>
<div className="flex gap-2">
<LayoutBadgeImageView badgeCode={badgeCode} className="align-self-center flex-shrink-0 me-1" />
<Column className="streak-info" gap={0} size={5}>
<Text>{LocalizeText('hccenter.status.' + clubStatus)}</Text>
<Text dangerouslySetInnerHTML={{ __html: getInfoText() }} />
<LayoutBadgeImageView badgeCode={ badgeCode } className="align-self-center flex-shrink-0 me-1" />
<Column className="streak-info" gap={ 0 } size={ 5 }>
<Text>{ LocalizeText('hccenter.status.' + clubStatus) }</Text>
<Text dangerouslySetInnerHTML={ { __html: getInfoText() } } />
</Column>
</div>
{GetConfigurationValue('hc.center')['payday.info'] &&
{ GetConfigurationValue('hc.center')['payday.info'] &&
<Flex alignItems="center">
<Column className="rounded-start bg-primary p-2 payday-special mb-1">
<h4 className="mb-1">{LocalizeText('hccenter.special.title')}</h4>
<div>{LocalizeText('hccenter.special.info')}</div>
<div className="btn btn-link text-white p-0 mt-auto align-self-baseline" onClick={() => CreateLinkEvent('habbopages/' + GetConfigurationValue('hc.center')['payday.habbopage'])}>{LocalizeText('hccenter.special.infolink')}</div>
<h4 className="mb-1">{ LocalizeText('hccenter.special.title') }</h4>
<div>{ LocalizeText('hccenter.special.info') }</div>
<div className="btn btn-link text-white p-0 mt-auto align-self-baseline" onClick={ () => CreateLinkEvent('habbopages/' + GetConfigurationValue('hc.center')['payday.habbopage']) }>{ LocalizeText('hccenter.special.infolink') }</div>
</Column>
<div className="payday flex-shrink-0 p-2">
<h5 className="mb-2 ms-2">{LocalizeText('hccenter.special.time.title')}</h5>
<h5 className="mb-2 ms-2">{ LocalizeText('hccenter.special.time.title') }</h5>
<div className="flex flex-row mb-2">
<div className="clock me-2" />
<h6 className="mb-0 align-self-center">{getHcPaydayTime()}</h6>
<h6 className="mb-0 align-self-center">{ getHcPaydayTime() }</h6>
</div>
{clubStatus === ClubStatus.ACTIVE &&
{ clubStatus === ClubStatus.ACTIVE &&
<div className="pe-3">
<h5 className="ms-2 mb-1 bolder">{LocalizeText('hccenter.special.amount.title')}</h5>
<h5 className="ms-2 mb-1 bolder">{ LocalizeText('hccenter.special.amount.title') }</h5>
<div className="flex flex-col">
<div className="w-full text-center ms-4n">{getHcPaydayAmount()}</div>
<div className="w-full text-center ms-4n">{ getHcPaydayAmount() }</div>
<div className="btn btn-link align-self-end text-primary">
{LocalizeText('hccenter.breakdown.infolink')}
{ LocalizeText('hccenter.breakdown.infolink') }
</div>
</div>
</div>}
</div> }
</div>
</Flex>}
{GetConfigurationValue('hc.center')['gift.info'] &&
</Flex> }
{ GetConfigurationValue('hc.center')['gift.info'] &&
<div className="rounded bg-success p-2 flex flex-row mb-0">
<div>
<h4 className="mb-1">{LocalizeText('hccenter.gift.title')}</h4>
<div dangerouslySetInnerHTML={{ __html: unclaimedGifts > 0 ? LocalizeText('hccenter.unclaimedgifts', ['unclaimedgifts'], [unclaimedGifts.toString()]) : LocalizeText('hccenter.gift.info') }}></div>
<h4 className="mb-1">{ LocalizeText('hccenter.gift.title') }</h4>
<div dangerouslySetInnerHTML={ { __html: unclaimedGifts > 0 ? LocalizeText('hccenter.unclaimedgifts', [ 'unclaimedgifts' ], [ unclaimedGifts.toString() ]) : LocalizeText('hccenter.gift.info') } }></div>
</div>
<button className="btn btn-primary btn-lg align-self-center ms-auto" onClick={() => CreateLinkEvent('catalog/open/' + GetConfigurationValue('catalog.links')['hc.hc_gifts'])}>
{LocalizeText(clubStatus === ClubStatus.ACTIVE ? 'hccenter.btn.gifts.redeem' : 'hccenter.btn.gifts.view')}
<button className="btn btn-primary btn-lg align-self-center ms-auto" onClick={ () => CreateLinkEvent('catalog/open/' + GetConfigurationValue('catalog.links')['hc.hc_gifts']) }>
{ LocalizeText(clubStatus === ClubStatus.ACTIVE ? 'hccenter.btn.gifts.redeem' : 'hccenter.btn.gifts.view') }
</button>
</div>}
{GetConfigurationValue('hc.center')['benefits.info'] &&
</div> }
{ GetConfigurationValue('hc.center')['benefits.info'] &&
<div className="benefits text-black py-2">
<h5 className="mb-1 text-primary">{LocalizeText('hccenter.general.title')}</h5>
<div className="mb-2" dangerouslySetInnerHTML={{ __html: LocalizeText('hccenter.general.info') }} />
<button className="btn btn-link p-0 text-primary" onClick={() => CreateLinkEvent('habbopages/' + GetConfigurationValue('hc.center')['benefits.habbopage'])}>
{LocalizeText('hccenter.general.infolink')}
<h5 className="mb-1 text-primary">{ LocalizeText('hccenter.general.title') }</h5>
<div className="mb-2" dangerouslySetInnerHTML={ { __html: LocalizeText('hccenter.general.info') } } />
<button className="btn btn-link p-0 text-primary" onClick={ () => CreateLinkEvent('habbopages/' + GetConfigurationValue('hc.center')['benefits.habbopage']) }>
{ LocalizeText('hccenter.general.infolink') }
</button>
</div>}
</div> }
</NitroCardContentView>
</NitroCardView>
);

View File

@ -5,7 +5,7 @@ import { useHelp } from '../../../hooks';
export const DescribeReportView: FC<{}> = props =>
{
const [message, setMessage] = useState('');
const [ message, setMessage ] = useState('');
const { activeReport = null, setActiveReport = null } = useHelp();
const submitMessage = () =>
@ -31,16 +31,16 @@ export const DescribeReportView: FC<{}> = props =>
return (
<>
<div className="flex flex-col gap-1">
<Text fontSize={4}>{LocalizeText('help.emergency.chat_report.subtitle')}</Text>
<Text>{LocalizeText('help.cfh.input.text')}</Text>
<Text fontSize={ 4 }>{ LocalizeText('help.emergency.chat_report.subtitle') }</Text>
<Text>{ LocalizeText('help.cfh.input.text') }</Text>
</div>
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] h-full" value={message} onChange={event => setMessage(event.target.value)} />
<Flex gap={2} justifyContent="between">
<Button disabled={!(activeReport.reportType === ReportType.BULLY || activeReport.reportType === ReportType.EMERGENCY)} variant="secondary" onClick={back}>
{LocalizeText('generic.back')}
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] h-full" value={ message } onChange={ event => setMessage(event.target.value) } />
<Flex gap={ 2 } justifyContent="between">
<Button disabled={ !(activeReport.reportType === ReportType.BULLY || activeReport.reportType === ReportType.EMERGENCY) } variant="secondary" onClick={ back }>
{ LocalizeText('generic.back') }
</Button>
<Button disabled={(message.length < 15)} onClick={submitMessage}>
{LocalizeText('help.emergency.main.submit.button')}
<Button disabled={ (message.length < 15) } onClick={ submitMessage }>
{ LocalizeText('help.emergency.main.submit.button') }
</Button>
</Flex>
</>

View File

@ -23,15 +23,15 @@ export const HelpIndexView: FC<{}> = props =>
return (
<>
<div className="flex flex-col justify-center alignp-items-enter !flex-grow gap-1">
<Text fontSize={3}>{LocalizeText('help.main.frame.title')}</Text>
<Text>{LocalizeText('help.main.self.description')}</Text>
<Text fontSize={ 3 }>{ LocalizeText('help.main.frame.title') }</Text>
<Text>{ LocalizeText('help.main.self.description') }</Text>
</div>
<div className="flex flex-col gap-1">
<Button onClick={onReportClick}>{LocalizeText('help.main.bully.subtitle')}</Button>
<Button disabled={!GetConfigurationValue('guides.enabled')} onClick={() => DispatchUiEvent(new GuideToolEvent(GuideToolEvent.CREATE_HELP_REQUEST))}>{LocalizeText('help.main.help.title')}</Button>
<Button disabled={true}>{LocalizeText('help.main.self.tips.title')}</Button>
<Button onClick={ onReportClick }>{ LocalizeText('help.main.bully.subtitle') }</Button>
<Button disabled={ !GetConfigurationValue('guides.enabled') } onClick={ () => DispatchUiEvent(new GuideToolEvent(GuideToolEvent.CREATE_HELP_REQUEST)) }>{ LocalizeText('help.main.help.title') }</Button>
<Button disabled={ true }>{ LocalizeText('help.main.self.tips.title') }</Button>
</div>
<Button textColor="black" variant="link" onClick={() => SendMessageComposer(new GetCfhStatusMessageComposer(false))}>{LocalizeText('help.main.my.sanction.status')}</Button>
<Button textColor="black" variant="link" onClick={ () => SendMessageComposer(new GetCfhStatusMessageComposer(false)) }>{ LocalizeText('help.main.my.sanction.status') }</Button>
</>
)
}

Some files were not shown because too many files have changed in this diff Show More