Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .idea/scopes/Backend.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions SteamOrganizer.Server/Controllers/Steam/WebApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ await parser
}

[HttpGet]
public async Task<PlayerGames?> GetGames(GamesRequest request)
public async Task<PlayerGames?> GetGames([FromQuery] GamesRequest request)
{
var response = await parser.SetCurrency(request.Currency).GetGames(
request.SteamId!.Value,
Tools.ToSteamId64(request.SteamId!.Value),
request.WithDetails
).ConfigureAwait(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public sealed class PlayerGameInfo
public float Playtime_forever { get; set; }
public required string Name { get; set; }
public string? FormattedPrice { get; set; }
public int? Price { get; set; }
}

public sealed class PlayerGames
Expand All @@ -32,7 +33,8 @@ public sealed class PlayerGamesResponse

public sealed class GamePrice
{
public decimal Initial { get; set; }
public int? Initial { get; set; }
public decimal Final { get; set; }
public string? Formatted { get; set; }
}

Expand Down
15 changes: 11 additions & 4 deletions SteamOrganizer.Server/Services/SteamParser/SteamParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ await Parallel.ForAsync(0, (int)Math.Ceiling(friends.Length / 100f),

if (details.Data?.Price_overview != null)
{
details.Data.Price_overview.Initial /= 100;
details.Data.Price_overview.Final = (decimal)details.Data.Price_overview.Initial! / 100u;
}
FormatPrice(games,info, details);

Expand Down Expand Up @@ -301,13 +301,20 @@ private void FormatPrice(PlayerGames games, PlayerGameInfo gameInfo, GameDetails
{
if (details.Data?.Price_overview == null)
{
gameInfo.FormattedPrice = details.Success ? "Free" : "Unknown";
if (details.Success)
{
gameInfo.Price = 0;
gameInfo.FormattedPrice = "Free";
return;
}
gameInfo.FormattedPrice = "Unknown";
return;
}

var price = details.Data.Price_overview;
games.GamesPrice += price.Initial;
gameInfo.FormattedPrice = price.Formatted ?? (price.Formatted = _currency.Format(price.Initial));
games.GamesPrice += price.Final;
gameInfo.Price = price.Initial;
gameInfo.FormattedPrice = price.Formatted ?? (price.Formatted = _currency.Format(price.Final));
games.PaidGames++;
}

Expand Down
4 changes: 4 additions & 0 deletions SteamOrganizer.Server/SteamOrganizer.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
</ItemGroup>

<ItemGroup>
<Folder Include="Cache\" />
</ItemGroup>

</Project>
3 changes: 1 addition & 2 deletions SteamOrganizer.Web/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
Expand All @@ -8,6 +7,6 @@
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script type="module" src="src/main.tsx"></script>
</body>
</html>
6 changes: 6 additions & 0 deletions SteamOrganizer.Web/public/sprites.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 18 additions & 18 deletions SteamOrganizer.Web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import Accounts from "./pages/Accounts/Accounts.tsx";
import {MainLayout} from "./layouts/MainLayout.tsx";
import NotFoundPage from "./pages/NotFoundPage.tsx";
import Accounts from "./pages/Accounts/Accounts";
import {MainLayout} from "./layouts/MainLayout";
import NotFoundPage from "./pages/NotFoundPage";
import {createBrowserRouter, Navigate, RouterProvider} from 'react-router-dom'
import Actions from "./pages/Actions.tsx";
import SignIn from "./pages/SignIn.tsx";
import Backups from "./pages/Backups/Backups.tsx";
import Actions from "./pages/Actions";
import SignIn from "./pages/SignIn";
import Backups from "./pages/Backups/Backups";
import {useEffect} from "react";
import {config, loadConfig} from "./store/config.ts";
import db from "./services/indexedDb.ts";
import {config, loadConfig} from "./store/config";
import db from "@/shared/services/indexedDb";
import {Defs} from "./defines"
import {ModalsHost} from "./components/primitives/Modal.tsx";
import {Profile} from "@/pages/Profile/Profile.tsx";
import {AuthProvider} from "@/providers/authProvider.tsx";
import {toast, ToastsHost, ToastVariant} from "@/components/primitives/Toast.tsx";
import {databaseKey, dbTimestamp, importAccounts, loadAccounts} from "@/store/accounts.ts";
import {isAuthorized} from "@/services/gAuth.ts";
import {getLatestBackup, loadBackup, restoreBackup} from "@/store/backups.ts";
import {decrypt} from "@/services/cryptography.ts";
import {flagStore} from "@/store/local.tsx";
import Logo from "@/components/elements/Logo.tsx";
import {ModalsHost} from "./shared/ui/Modal";
import {Profile} from "@/pages/Profile/Profile";
import {AuthProvider} from "@/providers/authProvider";
import {toast, ToastsHost, ToastVariant} from "@/shared/ui/Toast";
import {databaseKey, dbTimestamp, importAccounts, loadAccounts} from "@/store/accounts";
import {isAuthorized} from "@/shared/services/gAuth";
import {getLatestBackup, loadBackup, restoreBackup} from "@/store/backups";
import {decrypt} from "@/shared/services/cryptography";
import {flagStore} from "@/store/local";
import Logo from "@/components/Logo";

const router = createBrowserRouter([
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import {EPlacementX, EPlacementY, type IPopupProps, Popup} from "@/components/primitives/Popup.tsx";
import {type FC} from "react";
import Button, {EButtonVariant} from "@/components/primitives/Button.tsx";
import {type FC, ReactElement} from "react";
import Button, {EButtonVariant} from "@/shared/ui/Button";
import {Popup} from "@/shared/ui/Popup/Popup";
import {EPlacement} from "@/shared/ui/Popup/positioning";

interface IConfirmPopupProps extends IPopupProps {
interface IConfirmPopupProps {
text: string;
onYes?: () => void;
onNo?: () => void;
children: ReactElement;
}

export const ConfirmPopup: FC<IConfirmPopupProps> = ({ children, text, onYes, onNo }) => {
return (
<Popup alignY={EPlacementY.Bottom} alignX={EPlacementX.Center} offset={{ y: 5, x:0 }} content={ () => (
<Popup placement={EPlacement.BottomCenter} offset={{ y: 5, x:0 }} content={ () => (
<div className="max-w-60 text-wrap text-center py-2">
<span>{text}</span>
<div className="flex-center gap-5 mt-3">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {cloneElement, FC, forwardRef, ReactElement, type ReactNode, useImperativeHandle, useRef} from "react";
import clsx from "clsx";
import Input, {IInputProps} from "@/components/primitives/Input.tsx";
import {CheckBox} from "@/components/primitives/CheckBox";
import {config, delayedSaveConfig} from "@/store/config.ts";

export const enum ETitlePosition {
Top,
Expand Down Expand Up @@ -31,7 +27,7 @@ export const FieldWrapper:FC<IFieldWrapperProps> = ({className,title, titlePos,
<div className={className}>
{titlePos !== ETitlePosition.Inline &&
<div className="flex justify-between mb-0.5 ml-0.5 relative gap-3">
<p className="text-chip text-[13px] font-semibold text-nowrap">{title}</p>
<p className="text-chip text-2xs font-semibold text-nowrap">{title}</p>
{titleChildren}
</div>
}
Expand Down
65 changes: 65 additions & 0 deletions SteamOrganizer.Web/src/components/FilterInput/FilterInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {type FC, Fragment, useState} from "react";
import Input, {type IInputProps} from "@/shared/ui/Input";
import {Popup} from "@/shared/ui/Popup/Popup";
import {EPlacement} from "@/shared/ui/Popup/positioning";
import {ELabelVariant, Label} from "@/shared/ui/Label/Label";
import {EFilterType, FiltersDefinition, type IFilterCell, IFilterConfig} from "./types";
import {FlagsFilter, OrderFilter, SearchFilter} from "./Filters";
import styles from "./Filters.module.css"

interface ISearchInputProps extends IInputProps {
filters: FiltersDefinition;
config: IFilterConfig;
onFilterChanged: () => void;
}

const filterType = {
[EFilterType.Search]: SearchFilter,
[EFilterType.Order]: OrderFilter,
[EFilterType.Flags]: FlagsFilter
}

const FilterCell: FC<{items: IFilterCell[], callback: () => void, config: IFilterConfig}> = ({ items, config, callback }) => {
return <div>
{
items.map((item, index) => {
const Component = filterType[item.type];

return <Fragment key={index}>
{item.label && <Label children={item.label} variant={ELabelVariant.Colorful}/>}
{<Component fields={item.fields} config={config} callback={callback}/>}
</Fragment>
})
}
</div>
}


export const FilterInput: FC<ISearchInputProps> = ({ onFilterChanged, config, filters, ...props}) => {
const [isFocused,setFocused] = useState(undefined)

const content = <div className={styles.filterInput}>
{filters.map((item, index) => {
return <FilterCell config={config} items={item} key={index} callback={onFilterChanged}/>
})}
</div>


return (
<Popup content={content} exit={{opacity: 0, translateY: "10px"}}
placement={EPlacement.BottomFit} state={isFocused}
asToggle={isFocused !== undefined}
className={styles.popup}>
<Input defaultValue={config[EFilterType.Search].keyword}
onPointerDown={() => setFocused(undefined)}
onInput={(e) => {
setFocused(false)
config[EFilterType.Search].keyword = e.currentTarget.value;
onFilterChanged();
}}
className={"pr-24 h-full bg-primary placeholder:font-semibold rounded-lg"}
{...props}
/>
</Popup>
)
}
19 changes: 19 additions & 0 deletions SteamOrganizer.Web/src/components/FilterInput/Filters.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.filterInput {
@apply bg-primary mt-2 rounded-lg border-tertiary border p-3 flex flex-wrap gap-8 pointer-events-auto;
}

.popup {
@apply rounded-none rounded-b-lg bg-transparent p-0 flex-x-center gap-3 pointer-events-none;
}

.searchFilter {
@apply flex-x-center flex-wrap bg-accent rounded-lg my-3 gap-y-3;
}

.flagFilter {
@apply w-full justify-between flex-y-center mt-1;
}

.orderFilter {
@apply flex-x-center flex-wrap bg-accent rounded-lg gap-y-3;
}
69 changes: 69 additions & 0 deletions SteamOrganizer.Web/src/components/FilterInput/Filters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, {FC, useState} from "react";
import {RadioButton} from "@/shared/ui/RadioButton/RadioButton";
import {CheckBox} from "@/shared/ui/CheckBox/CheckBox";
import {EFilterType, IFilterConfig, IPropField} from "@/components/FilterInput/types";
import {Icon, SvgIcon} from "@/defines";
import styles from "./Filters.module.css"

type FilterComponent = FC<{ fields: IPropField[], config: IFilterConfig, callback: () => void }>

const OrderFilter: FilterComponent = ({ fields, config, callback }) => {
const node = config[EFilterType.Order];

return (
<>
<RadioButton layoutId="order"
initialState={node.by?.[1] ?? 0}
generator={["None", ...fields.map(field => field.name)]}
onStateChanged={(o) => {
node.by = o === 0 ? null : [fields[o - 1].prop, o];
callback();
}}
className={`my-3 ${styles.orderFilter}`}/>

<RadioButton layoutId="order-dir" generator={["Ascending", "Descending"]}
initialState={node.direction ?? 0}
onStateChanged={(o) => {
node.direction = o;
callback();
}}
className={`w-fit ${styles.orderFilter}`}/>
</>
)
}

const FlagsFilter: FilterComponent = ({ fields, config, callback }) => {
const node = config[EFilterType.Flags];

return fields.map((field, index) =>
<div key={index} className={styles.flagFilter}>
{field.name}
<CheckBox checkedSymbol={<SvgIcon icon={Icon.Plus} size={16} /> }
initialState={node[field.prop]}
onStateChanged={(o) => {
node[field.prop] = o
callback();
}} allowIndeterminate={true}/>
</div>
)
}

const SearchFilter: FilterComponent = ({ fields, config, callback }) => {
const node = config[EFilterType.Search];

if(fields.length == 1) {
return;
}

return (
<RadioButton layoutId="search" generator={fields.map(field => field.name)}
initialState={node.by[0]}
onStateChanged={(i) => {
node.by = [i, fields[i].prop];
callback();
}}
className={styles.searchFilter}/>
)
}

export { SearchFilter, OrderFilter, FlagsFilter }
38 changes: 38 additions & 0 deletions SteamOrganizer.Web/src/components/FilterInput/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export const enum EFilterType {
Search,
Order,
Flags
}


export interface IPropField {
name: string;
prop: string;
}

export interface IFilterCell {
label?: string;
type: EFilterType;
fields: IPropField[];
}

export type FiltersDefinition = IFilterCell[][]

export const enum EOrderDirection {
Ascending = 0,
Descending = 1
}

export interface IFilterConfig {
[EFilterType.Search]: {
keyword?: string,
by: [ number, string]
},
[EFilterType.Order]?: {
by?: [string, number] | null,
direction?: EOrderDirection
},
[EFilterType.Flags]?: {
[key: string]: boolean | null
}
}
Loading
Loading