Skip to content
This repository was archived by the owner on Feb 21, 2025. It is now read-only.

Commit b9d9d5f

Browse files
authored
Merge pull request #137 from game-node-app/dev
Enables PSN in the Importer system
2 parents bc2afd3 + e0e70a6 commit b9d9d5f

File tree

13 files changed

+276
-201
lines changed

13 files changed

+276
-201
lines changed

src/components/game/figure/GameFigureImage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const GameFigureImage = ({
4848
onClick={onClick}
4949
{...linkProps}
5050
>
51-
<AspectRatio ratio={264 / 354} pos="relative" h={"100%"} w={"auto"}>
51+
<AspectRatio ratio={264 / 354} pos="relative" w={"auto"}>
5252
<Image
5353
radius={"sm"}
5454
src={sizedCoverUrl ?? "/img/game_placeholder.jpeg"}

src/components/game/view/select/GameSelectViewContent.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ interface Props extends PropsWithChildren<SimpleGridProps & SelectedProps> {
2424
* @param checkIsSelected
2525
* @param onSelected
2626
* @param excludeItemsInLibrary
27+
* @param onExcludedItemClick
2728
* @param others
2829
* @constructor
2930
*/

src/components/game/view/select/GameSelectViewFigure.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useMemo, useState } from "react";
22
import GameFigureImage, {
33
IGameFigureProps,
44
} from "@/components/game/figure/GameFigureImage";
5-
import { Overlay, Stack, Text, ThemeIcon } from "@mantine/core";
5+
import { Box, Center, Overlay, Stack, Text, ThemeIcon } from "@mantine/core";
66
import { useDisclosure } from "@mantine/hooks";
77
import { IconCircleCheck, IconCircleCheckFilled } from "@tabler/icons-react";
88
import { useOwnCollectionEntryForGameId } from "@/components/collection/collection-entry/hooks/useOwnCollectionEntryForGameId";
@@ -77,11 +77,13 @@ const GameSelectViewFigure = ({
7777
backgroundOpacity={0.85}
7878
className={"z-10"}
7979
/>
80-
<IconCircleCheckFilled
80+
<Center
8181
className={
82-
"absolute left-1/2 right-1/2 -translate-x-1/2 -translate-y-1/2 top-1/2 bottom-1/2 w-8 h-8 z-20 text-brand-5"
82+
"absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-20"
8383
}
84-
/>
84+
>
85+
<IconCircleCheckFilled className={"text-brand-5"} />
86+
</Center>
8587
</>
8688
)}
8789
{isExcluded && (
@@ -91,16 +93,20 @@ const GameSelectViewFigure = ({
9193
backgroundOpacity={0.85}
9294
className={"z-10"}
9395
/>
94-
<div
96+
<Center
9597
className={
96-
"absolute flex flex-col left-1/2 right-1/2 -translate-x-1/2 -translate-y-1/2 top-1/2 bottom-1/2 z-20"
98+
"absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-20 items-center"
9799
}
98100
>
99-
<IconCircleCheckFilled
100-
className={"relative w-8 h-8 z-20 text-brand-5"}
101-
/>
102-
<Text className={"mt-2"}>In your library</Text>
103-
</div>
101+
<Stack className={"items-center gap-0.5"}>
102+
<IconCircleCheckFilled
103+
className={"w-8 h-8 z-20 text-brand-5"}
104+
/>
105+
<Text className={"text-center"}>
106+
In your library
107+
</Text>
108+
</Stack>
109+
</Center>
104110
</>
105111
)}
106112
</GameFigureImage>

src/components/importer/view/ImporterItem.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import React, { useMemo } from "react";
2-
import { UserConnection } from "@/wrapper/server";
2+
import { UserConnectionDto } from "@/wrapper/server";
33
import { Button, Image, Paper, Stack, Title } from "@mantine/core";
44
import { getServerStoredIcon } from "@/util/getServerStoredImages";
55
import Link from "next/link";
66

77
interface Props {
8-
connection: UserConnection;
8+
connection: UserConnectionDto;
99
}
1010

11-
const connectionTypeToName = (type: UserConnection.type) => {
11+
const connectionTypeToName = (type: UserConnectionDto.type) => {
1212
switch (type) {
13-
case UserConnection.type.STEAM:
13+
case UserConnectionDto.type.STEAM:
1414
return "Steam";
15+
case UserConnectionDto.type.PSN:
16+
return "Playstation Network";
1517
default:
1618
return "Name not available";
1719
}

src/components/notifications/item/ImporterWatchAggregatedNotification.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ const ImporterWatchAggregatedNotification = ({
4141
h={38}
4242
/>
4343
<Text>
44-
We've found {notificationQuery.data.games.length} new games
45-
ready to be imported from your {sourceName} connection.
44+
We've found {notificationQuery.data?.games?.length} new
45+
games ready to be imported from your {sourceName}{" "}
46+
connection.
4647
</Text>
4748
</Group>
4849
</Link>

src/components/preferences/handlers/connections/PreferencesConnectionItem.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useCallback, useMemo } from "react";
2-
import { UserConnection } from "@/wrapper/server";
3-
import type = UserConnection.type;
2+
import { UserConnectionDto } from "@/wrapper/server";
3+
import type = UserConnectionDto.type;
44
import { useOwnUserConnectionByType } from "@/components/connections/hooks/useOwnUserConnectionByType";
55
import { useDisclosure } from "@mantine/hooks";
66
import { Group, Image, Paper, Stack, Switch, Text, Title } from "@mantine/core";
@@ -31,7 +31,7 @@ const PreferencesConnectionItem = ({ type }: Props) => {
3131
onClose={modalUtils.close}
3232
/>
3333
<Image
34-
alt={"Steam icon"}
34+
alt={"Connection icon"}
3535
src={getServerStoredIcon(type.valueOf())}
3636
w={38}
3737
h={38}

src/components/preferences/handlers/connections/PreferencesConnectionModal.tsx

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
import React, { useMemo } from "react";
22
import { BaseModalProps } from "@/util/types/modal-props";
3-
import { UserConnection } from "@/wrapper/server";
3+
import { UserConnectionDto } from "@/wrapper/server";
44
import { Modal } from "@mantine/core";
5-
import PreferencesConnectionSteamForm from "@/components/preferences/handlers/connections/steam/PreferencesConnectionSteamForm";
5+
import PreferencesConnectionSetup from "@/components/preferences/handlers/connections/PreferencesConnectionSetup";
6+
67
interface Props extends BaseModalProps {
7-
type: UserConnection.type;
8+
type: UserConnectionDto.type;
89
}
910

1011
const PreferencesConnectionModal = ({ opened, onClose, type }: Props) => {
11-
const renderedConnectionForm = useMemo(() => {
12-
switch (type) {
13-
case UserConnection.type.STEAM:
14-
return <PreferencesConnectionSteamForm onClose={onClose} />;
15-
default:
16-
return null;
17-
}
18-
}, [onClose, type]);
1912
return (
2013
<Modal title={"Set up connection"} onClose={onClose} opened={opened}>
21-
<Modal.Body>{renderedConnectionForm}</Modal.Body>
14+
<PreferencesConnectionSetup type={type} onClose={onClose} />
2215
</Modal>
2316
);
2417
};
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import React, { useEffect, useMemo } from "react";
2+
import { BaseModalChildrenProps } from "@/util/types/modal-props";
3+
import { z } from "zod";
4+
import { useForm } from "react-hook-form";
5+
import { zodResolver } from "@hookform/resolvers/zod";
6+
import { ConnectionsService, UserConnectionDto } from "@/wrapper/server";
7+
import { useOwnUserConnectionByType } from "@/components/connections/hooks/useOwnUserConnectionByType";
8+
import { useMutation, useQueryClient } from "@tanstack/react-query";
9+
import { notifications } from "@mantine/notifications";
10+
import { getErrorMessage } from "@/util/getErrorMessage";
11+
import { getCapitalizedText } from "@/util/getCapitalizedText";
12+
import CenteredErrorMessage from "@/components/general/CenteredErrorMessage";
13+
import { Button, Stack, Switch, Text, TextInput } from "@mantine/core";
14+
import { useAvailableConnections } from "@/components/connections/hooks/useAvailableConnections";
15+
16+
const ConnectionSetupFormSchema = z.object({
17+
userIdentifier: z.string().min(1, "A username must be provided."),
18+
isImporterEnabled: z.boolean().default(true),
19+
});
20+
21+
type ConnectionSetupFormValues = z.infer<typeof ConnectionSetupFormSchema>;
22+
23+
export interface Props extends BaseModalChildrenProps {
24+
type: UserConnectionDto.type;
25+
}
26+
27+
const PreferencesConnectionSetup = ({ type, onClose }: Props) => {
28+
const {
29+
register,
30+
watch,
31+
setValue,
32+
handleSubmit,
33+
formState: { errors },
34+
} = useForm<ConnectionSetupFormValues>({
35+
mode: "onBlur",
36+
defaultValues: {
37+
isImporterEnabled: true,
38+
},
39+
resolver: zodResolver(ConnectionSetupFormSchema),
40+
});
41+
42+
const userConnection = useOwnUserConnectionByType(type);
43+
44+
const availableConnections = useAvailableConnections();
45+
46+
const queryClient = useQueryClient();
47+
48+
const connectionCreateMutation = useMutation({
49+
mutationFn: async (data: ConnectionSetupFormValues) => {
50+
await ConnectionsService.connectionsControllerCreateOrUpdateV1({
51+
type: type,
52+
userIdentifier: data.userIdentifier,
53+
isImporterEnabled: data.isImporterEnabled,
54+
});
55+
},
56+
onSuccess: () => {
57+
notifications.show({
58+
color: "green",
59+
message: `Successfully set up ${getCapitalizedText(type)} connection!`,
60+
});
61+
if (onClose) {
62+
onClose();
63+
}
64+
},
65+
onError: (err) => {
66+
notifications.show({
67+
color: "red",
68+
message: getErrorMessage(err),
69+
});
70+
},
71+
onSettled: () => {
72+
queryClient.resetQueries({
73+
queryKey: ["connections", "own"],
74+
});
75+
},
76+
});
77+
78+
const connectionDeleteMutation = useMutation({
79+
mutationFn: async () => {
80+
if (userConnection.data == undefined) {
81+
return;
82+
}
83+
84+
return ConnectionsService.connectionsControllerDeleteV1(
85+
userConnection.data.id,
86+
);
87+
},
88+
onSuccess: () => {
89+
notifications.show({
90+
color: "green",
91+
message: `Successfully removed ${getCapitalizedText(type)} connection!`,
92+
});
93+
if (onClose) {
94+
onClose();
95+
}
96+
},
97+
onSettled: () => {
98+
queryClient.resetQueries({
99+
queryKey: ["connections"],
100+
});
101+
},
102+
});
103+
104+
const isImporterViable = useMemo(() => {
105+
if (availableConnections.data != undefined) {
106+
return availableConnections.data.some((connection) => {
107+
return connection.type === type && connection.isImporterViable;
108+
});
109+
}
110+
111+
return false;
112+
}, [availableConnections.data, type]);
113+
114+
const identifierInfo = useMemo((): {
115+
label: string;
116+
description: string;
117+
} => {
118+
switch (type) {
119+
case UserConnectionDto.type.STEAM:
120+
return {
121+
label: "Your public Steam profile URL",
122+
description:
123+
"e.g.: https://steamcommunity.com/id/your-username/",
124+
};
125+
case UserConnectionDto.type.PSN:
126+
return {
127+
label: "Your PSN online id",
128+
description: "Usually, it's your username.",
129+
};
130+
}
131+
}, [type]);
132+
133+
/**
134+
* Effect to synchronize form state with user connection info.
135+
*/
136+
useEffect(() => {
137+
if (userConnection.data) {
138+
setValue(
139+
"isImporterEnabled",
140+
userConnection.data.isImporterEnabled,
141+
);
142+
}
143+
}, [setValue, userConnection.data]);
144+
145+
return (
146+
<form
147+
className={"w-full h-full"}
148+
onSubmit={handleSubmit((data) => {
149+
connectionCreateMutation.mutate(data);
150+
})}
151+
>
152+
<Stack className={"w-full h-full"}>
153+
{connectionCreateMutation.error && (
154+
<CenteredErrorMessage
155+
message={getErrorMessage(
156+
connectionCreateMutation.error,
157+
)}
158+
/>
159+
)}
160+
{connectionDeleteMutation.error && (
161+
<CenteredErrorMessage
162+
message={getErrorMessage(
163+
connectionDeleteMutation.error,
164+
)}
165+
/>
166+
)}
167+
<TextInput
168+
error={errors.userIdentifier?.message}
169+
label={identifierInfo.label}
170+
description={identifierInfo.description}
171+
defaultValue={userConnection.data?.sourceUsername}
172+
{...register("userIdentifier")}
173+
/>
174+
{isImporterViable && (
175+
<Stack>
176+
<Switch
177+
error={errors.isImporterEnabled?.message}
178+
label={"Allow importing"}
179+
labelPosition={"left"}
180+
defaultChecked={
181+
userConnection.data
182+
? userConnection.data.isImporterEnabled
183+
: true
184+
}
185+
description={
186+
"If this connection can be used by the Importer system to import games."
187+
}
188+
{...register("isImporterEnabled")}
189+
/>
190+
</Stack>
191+
)}
192+
193+
<Button
194+
type={"submit"}
195+
loading={connectionCreateMutation.isPending}
196+
>
197+
Submit
198+
</Button>
199+
{userConnection.data != undefined && (
200+
<Button
201+
color={"blue"}
202+
type={"button"}
203+
loading={connectionDeleteMutation.isPending}
204+
onClick={() => {
205+
connectionDeleteMutation.mutate();
206+
}}
207+
>
208+
Disconnect
209+
</Button>
210+
)}
211+
</Stack>
212+
</form>
213+
);
214+
};
215+
216+
export default PreferencesConnectionSetup;

0 commit comments

Comments
 (0)