Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/shared/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const HREF_GET_WORDPRESS_AUTH_PHP =
export const HREF_DOCUMENTATION_CUSTOM_ENDPOINT =
'https://wiki.recloud.tech/docs/gml-launcher/backend/authorization/custom';
export const HREF_DISCORD = 'https://discord.com/invite/b5xgqfWgNt';
export const HREF_YOUTRACK = 'https://youtrack.recloud.tech/agiles/173-5/current';
export const HREF_RECLOUD_PRO =
'https://market.recloud.tech/#:~:text=Gml%20Лаунчер%20%7C-,Тариф%20Pro,-(месяц)';

Expand Down
38 changes: 26 additions & 12 deletions src/shared/converters/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import quilt from '@/assets/logos/quilt.png';

export const convertGameLoaderImage = (loader: GameLoaderOption) => {
const converter: Record<GameLoaderOption, ReactElement> = {
[GameLoaderOption.VANILLA]: <Image src={vanilla} alt="Minecraft" width={24} height={24} />,
[GameLoaderOption.FORGE]: <Image src={forge} alt="Forge" width={24} height={24} />,
[GameLoaderOption.FABRIC]: <Image src={fabric} alt="Fabric" width={24} height={24} />,
[GameLoaderOption.NEOFORGE]: <Image src={neoForge} alt="NeoForge" width={18} height={18} />,
[GameLoaderOption.QUILT]: <Image src={quilt} alt="Quilt" width={18} height={18} />,
[GameLoaderOption.VANILLA]: <Image src={vanilla} alt="Minecraft" width={24} height={24}/>,
[GameLoaderOption.FORGE]: <Image src={forge} alt="Forge" width={24} height={24}/>,
[GameLoaderOption.FABRIC]: <Image src={fabric} alt="Fabric" width={24} height={24}/>,
[GameLoaderOption.NEOFORGE]: <Image src={neoForge} alt="NeoForge" width={18} height={18}/>,
[GameLoaderOption.QUILT]: <Image src={quilt} alt="Quilt" width={18} height={18}/>,
[GameLoaderOption.LITELOADER]: (
<Image src={liteLoader} alt="Liteloader" width={24} height={24} />
<Image src={liteLoader} alt="Liteloader" width={24} height={24}/>
),
};

Expand All @@ -27,24 +27,38 @@ export const convertGameLoaderImage = (loader: GameLoaderOption) => {
export const convertApiGameLoaderImage = (loader: GameLoaderType) => {
const converter: Record<GameLoaderType, ReactElement> = {
[GameLoaderType.VANILLA]: (
<Image src={vanilla} alt="Minecraft" className="min-w-4" width={24} height={24} />
<Image src={vanilla} alt="Minecraft" className="min-w-4" width={24} height={24}/>
),
[GameLoaderType.FORGE]: (
<Image src={forge} alt="Forge" className="min-w-6" width={24} height={24} />
<Image src={forge} alt="Forge" className="min-w-6" width={24} height={24}/>
),
[GameLoaderType.FABRIC]: (
<Image src={fabric} alt="Fabric" className="min-w-4" width={24} height={24} />
<Image src={fabric} alt="Fabric" className="min-w-4" width={24} height={24}/>
),
[GameLoaderType.NEOFORGE]: (
<Image src={neoForge} alt="NeoForge" className="min-w-4" width={18} height={18} />
<Image src={neoForge} alt="NeoForge" className="min-w-4" width={18} height={18}/>
),
[GameLoaderType.QUILT]: (
<Image src={quilt} alt="Quilt" className="min-w-4" width={18} height={18} />
<Image src={quilt} alt="Quilt" className="min-w-4" width={18} height={18}/>
),
[GameLoaderType.LITELOADER]: (
<Image src={liteLoader} alt="Liteloader" className="min-w-4" width={24} height={24} />
<Image src={liteLoader} alt="Liteloader" className="min-w-4" width={24} height={24}/>
),
};

return converter[loader];
};


export const convertApiGameLoaderName = (loader: GameLoaderType) => {
const converter: Record<GameLoaderType, string> = {
[GameLoaderType.VANILLA]: 'Minecraft',
[GameLoaderType.FORGE]: 'Forge',
[GameLoaderType.FABRIC]: 'Fabric',
[GameLoaderType.NEOFORGE]: 'NeoForge',
[GameLoaderType.QUILT]: 'Quilt',
[GameLoaderType.LITELOADER]: 'Liteloader',
};

return converter[loader];
};
26 changes: 13 additions & 13 deletions src/views/integrations/ui/Integrations.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LinkIcon } from 'lucide-react';
import { Lightbulb } from 'lucide-react';
import Link from 'next/link';

import { GenerateLauncherDialog } from '@/widgets/generate-launcher-dialog';
Expand All @@ -18,7 +18,7 @@ import {
DATA_TEST_ID_DIALOG_NEWS_PROVIDER,
} from '@/shared/constants/data';
import { Button } from '@/shared/ui/button';
import { HREF_DISCORD } from '@/shared/constants';
import { HREF_YOUTRACK } from '@/shared/constants';
import { NewsProviderDialog } from '@/widgets/news-provider-dialog';

export const IntegrationsPage = () => {
Expand All @@ -37,25 +37,25 @@ export const IntegrationsPage = () => {
<IntegrationCard
title="Аутентификация"
description="Синхронизация и управление данными о пользователях на платформе"
dialog={<ChooseAuthenticationMethodDialog />}
dialog={<ChooseAuthenticationMethodDialog/>}
testid={DATA_TEST_ID_DIALOG_AUTHENTICATION_METHOD}
/>
<IntegrationCard
title="Сервис скинов"
description="Добавь интеграцию со сервисом скинов, для отображения скинов и плащей в игре"
dialog={<ConnectTexturesDialog />}
dialog={<ConnectTexturesDialog/>}
testid={DATA_TEST_ID_DIALOG_CONNECT_TEXTURES}
/>
<IntegrationCard
title="Discord"
description="Синхронизация лаунчера и вашего Discord сервера"
dialog={<ConnectDiscordDialog />}
dialog={<ConnectDiscordDialog/>}
testid={DATA_TEST_ID_DIALOG_CONNECT_DISCORD}
/>
<IntegrationCard
title="Новости"
description="Выводите новости из социальных сетей Вконтакте, Telegram или вашего сайта"
dialog={<NewsProviderDialog />}
dialog={<NewsProviderDialog/>}
testid={DATA_TEST_ID_DIALOG_NEWS_PROVIDER}
/>
</div>
Expand All @@ -66,7 +66,7 @@ export const IntegrationsPage = () => {
<IntegrationCard
title="Сборка лаунчера"
description="Создайте лаунчер для платформ Windows, MacOS и Linux в пару кликов"
dialog={<GenerateLauncherDialog />}
dialog={<GenerateLauncherDialog/>}
testid={DATA_TEST_ID_DIALOG_GENERATE_LAUNCHER}
/>
</div>
Expand All @@ -77,17 +77,17 @@ export const IntegrationsPage = () => {
<IntegrationCard
title="Sentry"
description={'Подключение платформы для отслеживания ошибок и мониторинга приложений'}
dialog={<ConnectSentryDialog />}
dialog={<ConnectSentryDialog/>}
testid={DATA_TEST_ID_DIALOG_CONNECT_SENTRY}
/>
<IntegrationCard
title="Нужен сервис?"
description="Отправь заявку, а мы придумаем что-нибудь"
title="Предложите свою идею"
description="Ваше предложение может стать частью будущих обновлений"
dialog={
<Link target="_blank" href={HREF_DISCORD}>
<Link target="_blank" href={HREF_YOUTRACK}>
<Button size="sm" variant="outline" className="w-fit">
<LinkIcon className="mr-2" size={16} />
Поддержка
<Lightbulb className="mr-2" size={16}/>
Предложить идею
</Button>
</Link>
}
Expand Down
99 changes: 65 additions & 34 deletions src/widgets/profiles-table/lib/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { useQueryClient } from '@tanstack/react-query';
import { createColumnHelper } from '@tanstack/table-core';
import { Edit2Icon, Trash2Icon } from 'lucide-react';
import { Edit2Icon, Trash2Icon, X } from 'lucide-react';

import { ClientState } from '@/widgets/client-hub';
import { DataTableColumnHeader } from '@/entities/Table';
Expand All @@ -16,14 +16,14 @@ import { DASHBOARD_PAGES } from '@/shared/routes';
import { Icons } from '@/shared/ui/icons';
import { getFormatDate } from '@/shared/lib/utils';
import { profileKeys } from '@/shared/hooks';
import { convertApiGameLoaderImage } from '@/shared/converters';
import { convertApiGameLoaderImage, convertApiGameLoaderName } from '@/shared/converters';
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";

enum ColumnHeader {
ICON = '',
NAME = 'Название',
CREATED_AT = 'Дата создания',
VERSION_LAUNCHER = 'Запускаемая версия',
LOADER_LAUNCHER = '',
GAME_VERSION = 'Версия',
PRIORITY = 'Приоритет',
PROFILE_STATE = 'Статус',
Expand Down Expand Up @@ -71,86 +71,117 @@ export const useColumns = (props: UseColumnsProps) => {
enableSorting: false,
enableHiding: false,
}),


columnsHelper.accessor('iconBase64', {
size: 64,
header: ColumnHeader.ICON,
cell: ({ row }) =>
row.original.iconBase64 ? (
<Image
className="min-w-12 min-h-12 h-12 w-12"
src={`data:image/png;base64,${row.original.iconBase64}`}
alt={row.original.name || 'Profile Icon'}
width={48}
height={48}
/>
) : (
<div className="flex items-center justify-center min-w-12 min-h-12 h-12 w-12 bg-gray-200/5 rounded-xl">
{row.original.name.substring(0, 2).toUpperCase()}
cell: ({ row }) => {
const hasIcon = Boolean(row.original.iconBase64);
const hasLoader = Boolean(row.original.loader);

const loaderIcon = convertApiGameLoaderImage(row.original.loader);
const loaderName = convertApiGameLoaderName(row.original.loader);

return (
<div className="relative">
<div className="absolute bottom-[-.5rem] right-[-1rem] bg-primary rounded-full">
{hasLoader ? (
<Tooltip>
<TooltipTrigger asChild>
{loaderIcon}
</TooltipTrigger>
<TooltipContent>
{loaderName}: {row.original.launchVersion}
</TooltipContent>
</Tooltip>
) : (
<Tooltip>
<TooltipTrigger asChild>
<X/>
</TooltipTrigger>
<TooltipContent>
<p>Не загружен</p>
</TooltipContent>
</Tooltip>
)}
</div>


{hasIcon ? (
<Image
className="min-w-12 min-h-12 h-12 w-12"
src={`data:image/png;base64,${row.original.iconBase64}`}
alt={row.original.name || 'Profile Icon'}
width={48}
height={48}
/>
) : (
<div className="flex items-center justify-center min-w-12 min-h-12 h-12 w-12 bg-gray-200/5 rounded-xl">
{row.original.name.substring(0, 2).toUpperCase()}
</div>
)}
</div>
),


);
}

}),

columnsHelper.display({
size: 400,
id: 'name',
header: ({ column }) => <DataTableColumnHeader column={column} title={ColumnHeader.NAME} />,
header: ({ column }) => <DataTableColumnHeader column={column} title={ColumnHeader.NAME}/>,
cell: ({ row }) => (
<div className="flex flex-col gap-2">
<p className="text-sm text-muted-foreground">{row.original.name}</p>
<h3>{row.original.displayName}</h3>
</div>
),
}),
columnsHelper.accessor('loader', {
size: 70,
enableSorting: false,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={ColumnHeader.LOADER_LAUNCHER} />
),
cell: ({ getValue }) => (getValue() ? convertApiGameLoaderImage(getValue()) : 'Не загружен'),
}),
columnsHelper.accessor('launchVersion', {
size: 500,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={ColumnHeader.VERSION_LAUNCHER} />
<DataTableColumnHeader column={column} title={ColumnHeader.VERSION_LAUNCHER}/>
),
cell: ({ getValue }) => (getValue() ? getValue() : 'Не загружен'),
}),
columnsHelper.accessor('gameVersion', {
size: 100,

header: ({ column }) => (
<DataTableColumnHeader column={column} title={ColumnHeader.GAME_VERSION} />
<DataTableColumnHeader column={column} title={ColumnHeader.GAME_VERSION}/>
),
cell: ({ getValue }) => getValue(),
}),
columnsHelper.accessor('createDate', {
size: 500,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={ColumnHeader.CREATED_AT} />
<DataTableColumnHeader column={column} title={ColumnHeader.CREATED_AT}/>
),
cell: ({ getValue }) => getFormatDate(getValue()),
}),
columnsHelper.accessor('priority', {
size: 150,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={ColumnHeader.PRIORITY} />
<DataTableColumnHeader column={column} title={ColumnHeader.PRIORITY}/>
),
cell: ({ getValue }) => getValue(),
}),
columnsHelper.accessor('state', {
size: 270,
header: ({ column }) => (
<DataTableColumnHeader column={column} title={ColumnHeader.PROFILE_STATE} />
<DataTableColumnHeader column={column} title={ColumnHeader.PROFILE_STATE}/>
),
cell: ({ getValue }) => <ClientState state={getValue()} />,
cell: ({ getValue }) => <ClientState state={getValue()}/>,
}),
columnsHelper.display({
size: 48,
id: 'edit',
cell: ({ row }) => (
<Button variant="ghost" size="icon" onClick={onRedirectEditProfile(row.original.name)}>
<Edit2Icon size={16} />
<Edit2Icon size={16}/>
</Button>
),
}),
Expand All @@ -171,9 +202,9 @@ export const useColumns = (props: UseColumnsProps) => {
disabled={props.isPendingDelete}
>
{props.isPendingDelete ? (
<Icons.spinner className="h-4 w-4 animate-spin" />
<Icons.spinner className="h-4 w-4 animate-spin"/>
) : (
<Trash2Icon size={16} />
<Trash2Icon size={16}/>
)}
</Button>
);
Expand Down