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
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ interface ConnectionProps {
connection?: OAuthConnection;
connectionLink: string;
disconnectFunction: (data: userLinkedAccountDisconnectProviders) => void;
disabled: boolean;
}

export function Connection(props: ConnectionProps) {
const { connection, identifier, icon, name, connectionLink } = props;
const { connection, identifier, icon, name, connectionLink, disabled } =
props;

return (
<div className={`connection ${connection ? "" : "connection--disabled"}`}>
Expand All @@ -30,6 +32,7 @@ export function Connection(props: ConnectionProps) {
</div>
) : null}
<NewSwitch
disabled={disabled}
value={connection !== undefined}
onChange={() => {
if (connection) {
Expand Down
146 changes: 87 additions & 59 deletions apps/cyberstorm-remix/app/settings/user/Connections/Connections.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
import { type ReactElement, useRef } from "react";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faGithub, faDiscord } from "@fortawesome/free-brands-svg-icons";

import { useOutletContext, useRevalidator } from "react-router";

import { NewLink, OverwolfLogo, useToast } from "@thunderstore/cyberstorm";
import { ApiAction } from "@thunderstore/ts-api-react-actions";
import { ApiError } from "@thunderstore/thunderstore-api";

import { useOutletContext } from "react-router";
import { buildAuthLoginUrl } from "cyberstorm/utils/ThunderstoreAuth";

import { userLinkedAccountDisconnect } from "../../../../../../packages/thunderstore-api/src";
import { getPublicEnvVariables } from "cyberstorm/security/publicEnvVariables";

import { Connection } from "~/commonComponents/Connection/Connection";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faGithub, faDiscord } from "@fortawesome/free-brands-svg-icons";
import { type ReactElement } from "react";
import { type OutletContextShape } from "~/root";
import { ApiAction } from "@thunderstore/ts-api-react-actions";
import { NotLoggedIn } from "~/commonComponents/NotLoggedIn/NotLoggedIn";
import { getPublicEnvVariables } from "cyberstorm/security/publicEnvVariables";
import { type OutletContextShape } from "~/root";

import { userLinkedAccountDisconnect } from "../../../../../../packages/thunderstore-api/src";
import { useHydrated } from "remix-utils/use-hydrated";

type ProvidersType = {
name: string;
identifier: "discord" | "github" | "overwolf";
icon: ReactElement;
}[];
};

export const PROVIDERS: ProvidersType = [
export const PROVIDERS: ProvidersType[] = [
{
name: "Discord",
identifier: "discord",
Expand All @@ -36,31 +41,59 @@ export const PROVIDERS: ProvidersType = [

export default function Connections() {
const outletContext = useOutletContext() as OutletContextShape;
const isHydrated = useHydrated();
const revalidator = useRevalidator();
const toast = useToast();
const disconnectingProviderRef = useRef<string | null>(null);

if (!outletContext.currentUser || !outletContext.currentUser.username)
return <NotLoggedIn />;
const onlyOneConnected = () => {
const connectedProviders =
outletContext.currentUser?.connections?.length ?? 0;
return connectedProviders === 1;
};

const toast = useToast();
const getConnection = (provider: ProvidersType) =>
outletContext.currentUser?.connections?.find(
(c) => c.provider?.toLowerCase() === provider.identifier
);
Comment on lines +55 to +58
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix provider typing in getConnection.

Parameter is typed as ProvidersType (the whole array), so identifier is missing and the file won’t compile. Please type it as a single provider entry instead.

-  const getConnection = (provider: ProvidersType) =>
+  const getConnection = (provider: ProvidersType[number]) =>
🤖 Prompt for AI Agents
In apps/cyberstorm-remix/app/settings/user/Connections/Connections.tsx around
lines 54 to 57, the getConnection parameter is incorrectly typed as the whole
ProvidersType array; change the parameter to represent a single provider entry
(e.g. ProvidersType[number] or the specific Provider type used in the codebase)
so provider.identifier exists and the file compiles, then keep the comparison
logic (provider.identifier) as-is.


// eslint-disable-next-line @typescript-eslint/no-unused-vars
const onSubmitSuccess = (_r: unknown) => {
if (!outletContext.currentUser || !outletContext.currentUser.username)
throw new Error("User not logged in");
const username = outletContext.currentUser?.username;

revalidator.revalidate();

toast.addToast({
csVariant: "success",
children: (
<>
User {outletContext.currentUser.username} was disconnected from TODO
User {username} was disconnected from{" "}
{disconnectingProviderRef.current}
</>
),
duration: 30000,
});

disconnectingProviderRef.current = null;
};
Comment on lines 61 to 78
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Toast message shows provider identifier instead of display name.

Line 71 displays disconnectingProviderRef.current (e.g., "discord") instead of the user-friendly name (e.g., "Discord").

Apply this diff to show the proper provider name:

  const onSubmitSuccess = (_r: unknown) => {
    const username = outletContext.currentUser?.username;
+   const providerName = PROVIDERS.find(
+     p => p.identifier === disconnectingProviderRef.current
+   )?.name || disconnectingProviderRef.current;

    revalidator.revalidate();

    toast.addToast({
      csVariant: "success",
      children: (
        <>
-         User {username} was disconnected from{" "}
-         {disconnectingProviderRef.current}
+         User {username} was disconnected from {providerName}
        </>
      ),
      duration: 30000,
    });

    disconnectingProviderRef.current = null;
  };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/cyberstorm-remix/app/settings/user/Connections/Connections.tsx around
lines 61-78, the toast currently shows the raw provider identifier
(disconnectingProviderRef.current) instead of a user-friendly display name;
before calling toast.addToast, resolve the display name by looking up the
provider object for that identifier (e.g., from the providers list/context or a
helper getDisplayName function), fallback to a capitalized version of the
identifier if not found, and use that resolved display name in the toast
message; keep the rest of the flow (revalidator.revalidate and clearing
disconnectingProviderRef.current) unchanged.

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const onSubmitError = (_e: unknown) => {

const onSubmitError = (error: unknown) => {
let message = "Error when disconnecting account.";

if (error instanceof ApiError) {
const fieldErrors = error.getFieldErrors();
message =
fieldErrors.non_field_errors?.[0] ||
fieldErrors.detail?.[0] ||
fieldErrors.root?.[0] ||
error.message ||
message;
}

toast.addToast({
csVariant: "danger",
children: <>Unknown error occurred. The error has been logged</>,
children: <>{message}</>,
duration: 8000,
});
};

Expand All @@ -73,8 +106,17 @@ export default function Connections() {
const publicEnvVariables = getPublicEnvVariables([
"VITE_AUTH_BASE_URL",
"VITE_AUTH_RETURN_URL",
"VITE_BETA_SITE_URL",
]);

if (!isHydrated) {
return <div style={{ padding: "32px" }}>Loading...</div>;
}

if (!outletContext.currentUser) {
return <NotLoggedIn />;
}

return (
<div className="settings-items">
<div className="settings-items__item">
Expand All @@ -94,45 +136,31 @@ export default function Connections() {
</p>
</div>
<div className="settings-items__content">
{PROVIDERS.map((p) => {
if (
!outletContext.currentUser ||
!outletContext.currentUser.username
)
throw new Error("User not logged in");
return (
<Connection
key={p.name}
icon={p.icon}
name={p.name}
identifier={p.identifier}
connection={outletContext.currentUser.connections?.find(
(c) => c.provider.toLowerCase() === p.identifier
)}
connectionLink={buildAuthLoginUrl({
type: p.identifier,
authBaseDomain: publicEnvVariables.VITE_AUTH_BASE_URL || "",
authReturnDomain:
publicEnvVariables.VITE_AUTH_RETURN_URL || "",
})}
disconnectFunction={(p) => {
if (
!outletContext.currentUser ||
!outletContext.currentUser.username
)
throw new Error("User not logged in");
return onSubmit({
params: {
provider: p,
},
config: outletContext.requestConfig,
queryParams: {},
data: { provider: p },
});
}}
/>
);
})}
{PROVIDERS.map((provider) => (
<Connection
key={provider.name}
disabled={onlyOneConnected() && !!getConnection(provider)}
icon={provider.icon}
name={provider.name}
identifier={provider.identifier}
connection={getConnection(provider)}
connectionLink={buildAuthLoginUrl({
type: provider.identifier,
authBaseDomain: publicEnvVariables.VITE_AUTH_BASE_URL || "",
authReturnDomain: publicEnvVariables.VITE_AUTH_RETURN_URL || "",
nextUrl: `${publicEnvVariables.VITE_BETA_SITE_URL}/settings`,
})}
disconnectFunction={(provider) => {
disconnectingProviderRef.current = provider;
return onSubmit({
params: { provider },
config: outletContext.requestConfig,
queryParams: {},
data: { provider },
});
}}
/>
))}
</div>
</div>
</div>
Expand Down
61 changes: 13 additions & 48 deletions apps/cyberstorm-remix/app/settings/user/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,32 @@
import "./Settings.css";
import { Outlet, useLocation, useOutletContext } from "react-router";
import { NewLink, Tabs } from "@thunderstore/cyberstorm";
import { PageHeader } from "~/commonComponents/PageHeader/PageHeader";

import { type OutletContextShape } from "../../root";
import "./Settings.css";
import { NotLoggedIn } from "~/commonComponents/NotLoggedIn/NotLoggedIn";

// export async function clientLoader() {
// const _storage = new NamespacedStorageManager(SESSION_STORAGE_KEY);
// const currentUser = getSessionCurrentUser(_storage, true, undefined, () => {
// clearSession(_storage);
// throw new Response("Your session has expired, please log in again", {
// status: 401,
// });
// // redirect("/");
// });

// if (
// !currentUser.username ||
// (currentUser.username && currentUser.username === "")
// ) {
// clearSession(_storage);
// throw new Response("Not logged in.", { status: 401 });
// } else {
// return {
// currentUser: currentUser as typeof currentUserSchema._type,
// };
// }
// }

// export function HydrateFallback() {
// return <div style={{ padding: "32px" }}>Loading...</div>;
// }
export default function UserSettings() {
const context = useOutletContext<OutletContextShape>();
const { pathname } = useLocation();
const currentTab = pathname.endsWith("/account/") ? "account" : "settings";

export default function Community() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// const { currentUser } = useLoaderData<typeof clientLoader>();
const location = useLocation();
const outletContext = useOutletContext() as OutletContextShape;

if (!outletContext.currentUser || !outletContext.currentUser.username)
return <NotLoggedIn />;

const currentTab = location.pathname.endsWith("/account/")
? "account"
: "settings";
function tabClass(tab: string) {
return `tabs-item${currentTab === tab ? " tabs-item--current" : ""}`;
}

return (
<>
<PageHeader headingLevel="1" headingSize="2">
Settings
</PageHeader>

<div className="settings-user">
<Tabs>
<NewLink
key="settings"
primitiveType="cyberstormLink"
linkId="Settings"
aria-current={currentTab === "settings"}
rootClasses={`tabs-item${
currentTab === "settings" ? " tabs-item--current" : ""
}`}
rootClasses={tabClass("settings")}
>
Settings
</NewLink>
Expand All @@ -69,15 +35,14 @@ export default function Community() {
primitiveType="cyberstormLink"
linkId="SettingsAccount"
aria-current={currentTab === "account"}
rootClasses={`tabs-item${
currentTab === "account" ? " tabs-item--current" : ""
}`}
rootClasses={tabClass("account")}
>
Account
</NewLink>
</Tabs>

<section className="settings-user__body">
<Outlet context={outletContext} />
<Outlet context={context} />
</section>
</div>
</>
Expand Down
Loading