Skip to content

Commit

Permalink
feat(ui): add GameStateCard, PolyButton components, extract hook useC…
Browse files Browse the repository at this point in the history
…onfirmModal, add SavePage
  • Loading branch information
GravityTwoG committed Apr 2, 2024
1 parent 48f107a commit 3ce2848
Show file tree
Hide file tree
Showing 37 changed files with 845 additions and 224 deletions.
2 changes: 2 additions & 0 deletions src/client/config/paths.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const profile = path("/");
const mySaves = path("/my-saves");
const localSaves = mySaves.path("/local");
const mySave = mySaves.path("/:gameStateId");
const save = path("/saves/:gameStateId");
const sharedSaves = path("/shared-saves");
const publicSaves = path("/public-saves");

Expand All @@ -28,6 +29,7 @@ export const paths = {
mySaves,
localSaves,
mySave,
save,
sharedSaves,
publicSaves,

Expand Down
11 changes: 9 additions & 2 deletions src/client/config/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { ResetPasswordPage } from "@/client/pages/ResetPassword/ResetPasswordPag
import { ProfilePage } from "@/client/pages/Profile/ProfilePage";
import { MySavesPage } from "@/client/pages/MySaves/MySavesPage";
import { MySavePage } from "@/client/pages/MySaves/MySave/MySavePage";
import { SharedSavesPage } from "@/client/pages/SharedSaves/SharedSavesPage";
import { PublicSavesPage } from "@/client/pages/PublicSaves/PublicSavesPage";
import { SavePage } from "../pages/saves/Save/SavePage";
import { SharedSavesPage } from "@/client/pages/saves/SharedSaves/SharedSavesPage";
import { PublicSavesPage } from "@/client/pages/saves/PublicSaves/PublicSavesPage";

import { GamesPage } from "@/client/pages/Games/GamesPage";
import { GamePage } from "@/client/pages/Games/Game/GamePage";
Expand Down Expand Up @@ -116,6 +117,12 @@ export const routes: RouteDescriptor[] = [
access: RouteAccess.AUTHENTICATED,
forRoles: [UserRole.USER],
},
{
path: paths.save.pattern,
component: SavePage,
access: RouteAccess.AUTHENTICATED,
forRoles: [UserRole.USER],
},
{
path: paths.sharedSaves.pattern,
component: SharedSavesPage,
Expand Down
60 changes: 60 additions & 0 deletions src/client/lib/components/GameStateCard/GameStateCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { GameState } from "@/types";

import classes from "./game-state-card.module.scss";
import { clsx } from "clsx";
import { Link } from "wouter";
import { ThreeDotsMenu } from "@/client/ui/molecules/ThreeDotsMenu";
import { useTranslation } from "react-i18next";
import { syncMap } from "@/client/pages/MySaves/utils";
import { useConfirmModal } from "@/client/ui/hooks/useConfirmModal/useConfirmModal";

export type GameStateCardProps = {
gameState: GameState;
className?: string;
onDelete?: (gameStateId: string) => void;
href: string;
};

export const GameStateCard = (props: GameStateCardProps) => {
const { t } = useTranslation(undefined, {
keyPrefix: "components.GameStateCard",
});

const { modal, onClick: onDelete } = useConfirmModal({
onConfirm: () => props.onDelete?.(props.gameState.id),
});

return (
<div
className={clsx(classes.GameStateCard, props.className)}
style={{
backgroundImage: `url(${props.gameState.gameIconURL})`,
}}
>
<Link href={props.href} className={classes.GameStateLink}>
<div className={classes.GameStateCardInner}>
{props.onDelete && (
<ThreeDotsMenu
className={classes.GameStateActions}
menuItems={[
{
onClick: () => onDelete(),
children: "Delete",
key: "delete",
},
]}
/>
)}
{modal}

<div className={classes.GameStateInfo}>
<p>{props.gameState.name}</p>
<p>
{t("sync")}: {t(syncMap[props.gameState.sync])}
</p>
</div>
</div>
</Link>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.GameStateCard {
min-width: 200px;
min-height: 250px;
display: flex;
flex-direction: column;
border-radius: 6px;
overflow: hidden;
}

.GameStateLink {
flex: 1;
display: flex;
flex-direction: column;
text-decoration: none;
}

.GameStateCardInner {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.25rem;
padding: 0.75rem 0.75rem 1rem;

background-image: linear-gradient(
180deg,
rgba(255, 255, 255, 0.2) 0%,
rgba(0, 0, 0, 0.8) 60%,
rgba(0, 0, 0, 0.9) 100%
);
background-position: 0% 50%;
background-size: 200% 200%;
transition: background-position 0.3s ease;

&:hover {
background-position: 0% 60%;
}
}

.GameStateActions {
margin-left: auto;
}

.GameStateInfo {
margin-top: auto;
color: white;
}
1 change: 1 addition & 0 deletions src/client/lib/components/GameStateCard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./GameStateCard";
61 changes: 61 additions & 0 deletions src/client/lib/components/ParametersView/ParametersView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import classes from "./parameters-view.module.scss";

import { GameStateValue } from "@/types";

type ParametersViewProps = {
gameStateValues: GameStateValue[];
};

export const ParametersView = (props: ParametersViewProps) => {
return (
<div>
{props.gameStateValues.map((field, idx) => (
<ParameterViewItem key={idx} {...field} />
))}
</div>
);
};

function formatTime(value: number, type: "seconds") {
if (type === "seconds") {
if (value < 60) {
return `${value} seconds`;
}
const minutes = Math.floor(value / 60);

if (minutes < 60) {
return `${minutes} minutes`;
}

const hours = Math.floor(minutes / 60);

return `${hours} hours`;
}

return `${value}`;
}

const ParameterViewItem = (props: {
label: string;
value: string;
type: string;
description: string;
}) => {
if (props.type === "seconds") {
return (
<div>
<span>{props.label}</span>:{" "}
<span className={classes.ParameterValue}>
{formatTime(parseFloat(props.value), props.type)}
</span>
</div>
);
}

return (
<div>
<span>{props.label}</span>:{" "}
<span className={classes.ParameterValue}>{props.value.toString()}</span>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.ParameterValue {
font-weight: bold;
}
8 changes: 8 additions & 0 deletions src/client/locales/en/components/GameStateCard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"sync": "Sync",
"every-hour": "Every hour",
"every-day": "Every day",
"every-week": "Every week",
"every-month": "Every month",
"no": "No"
}
6 changes: 0 additions & 6 deletions src/client/locales/en/pages/mySaves.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@
"home": "Home",
"choose-folder-to-list-files": "Choose folder to list files",
"upload": "Upload",
"sync": "Sync",
"every-hour": "Every hour",
"every-day": "Every day",
"every-week": "Every week",
"every-month": "Every month",
"no": "No",
"local-saves": "Local Saves",
"folder": "Folder",
"file": "File"
Expand Down
5 changes: 5 additions & 0 deletions src/client/locales/resources-en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import sharedSaves from "./en/pages/sharedSaves.json";
import publicSaves from "./en/pages/publicSaves.json";
import notFound from "./en/pages/notFound.json";

import GameStateCard from "./en/components/GameStateCard.json";

import common from "./en/common.json";

export const resourcesEN = {
Expand Down Expand Up @@ -43,6 +45,9 @@ export const resourcesEN = {
forms: {
gameForm,
},
components: {
GameStateCard,
},
common: common,
},
} as const;
5 changes: 5 additions & 0 deletions src/client/locales/resources-ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import sharedSaves from "./ru/pages/sharedSaves.json";
import publicSaves from "./ru/pages/publicSaves.json";
import notFound from "./ru/pages/notFound.json";

import GameStateCard from "./ru/components/GameStateCard.json";

import common from "./ru/common.json";

export const resourcesRU = {
Expand Down Expand Up @@ -43,6 +45,9 @@ export const resourcesRU = {
forms: {
gameForm,
},
components: {
GameStateCard,
},
common: common,
},
} as const;
8 changes: 8 additions & 0 deletions src/client/locales/ru/components/GameStateCard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"sync": "Синхронизация",
"every-hour": "Каждый час",
"every-day": "Каждый день",
"every-week": "Каждую неделю",
"every-month": "Каждый месяц",
"no": "Нет"
}
6 changes: 0 additions & 6 deletions src/client/locales/ru/pages/mySaves.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@
"home": "Главная",
"choose-folder-to-list-files": "Выберите папку для отображения списка файлов",
"upload": "Загрузить",
"sync": "Синхронизация",
"every-hour": "Каждый час",
"every-day": "Каждый день",
"every-week": "Каждую неделю",
"every-month": "Каждый месяц",
"no": "Нет",
"local-saves": "Локальные сохранения",
"folder": "Папка",
"file": "Файл"
Expand Down
76 changes: 15 additions & 61 deletions src/client/pages/MySaves/MySave/MySavePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useParams } from "wouter";

import classes from "./my-save-page.module.scss";

import { GameState, GameStateSync, GameStateValue } from "@/types";
import { GameState, GameStateSync } from "@/types";
import { useAPIContext } from "@/client/contexts/APIContext";
import { useUIContext } from "@/client/contexts/UIContext";
import { useAuthContext } from "@/client/contexts/AuthContext";
Expand All @@ -19,7 +19,9 @@ import { Button } from "@/client/ui/atoms/Button/Button";
import { Flex } from "@/client/ui/atoms/Flex";
import { Modal } from "@/client/ui/molecules/Modal/Modal";
import { ConfirmButton } from "@/client/ui/molecules/ConfirmButton/ConfirmButton";
import { PolyButton } from "@/client/ui/molecules/PolyButton/PolyButton";
import { SharesWidget } from "@/client/lib/components/SharesWidget";
import { ParametersView } from "@/client/lib/components/ParametersView/ParametersView";

export const MySavePage = () => {
const { gameStateAPI } = useAPIContext();
Expand Down Expand Up @@ -231,68 +233,20 @@ export const MySavePage = () => {
</span>

<div className={classes.Buttons}>
<Button onClick={downloadStateAs}>{t("download-as")} </Button>
<Button onClick={downloadState}>{t("download")} </Button>
<PolyButton
subActions={[
{
onClick: downloadStateAs,
children: t("download-as"),
key: "1",
},
]}
onClick={downloadState}
>
{t("download")}
</PolyButton>
</div>
</div>
</Container>
);
};

type ParametersViewProps = {
gameStateValues: GameStateValue[];
};

const ParametersView = (props: ParametersViewProps) => {
return (
<div>
{props.gameStateValues.map((field, idx) => (
<ParameterViewItem key={idx} {...field} />
))}
</div>
);
};

function formatTime(value: number, type: "seconds") {
if (type === "seconds") {
if (value < 60) {
return `${value} seconds`;
}
const minutes = Math.floor(value / 60);

if (minutes < 60) {
return `${minutes} minutes`;
}

const hours = Math.floor(minutes / 60);

return `${hours} hours`;
}

return `${value}`;
}

const ParameterViewItem = (props: {
label: string;
value: string;
type: string;
description: string;
}) => {
if (props.type === "seconds") {
return (
<div>
<span>{props.label}</span>:{" "}
<span className={classes.ParameterValue}>
{formatTime(parseFloat(props.value), props.type)}
</span>
</div>
);
}

return (
<div>
<span>{props.label}</span>:{" "}
<span className={classes.ParameterValue}>{props.value.toString()}</span>
</div>
);
};
Loading

0 comments on commit 3ce2848

Please sign in to comment.