Refactor OIDC buttons: extract OIDCButton component, inline loops

Address PR review feedback:
- Remove OIDCButtons wrapper components that only loop, inline .map()
  directly in SignInPage and ConnectPage
- OIDCButton uses useTranslate() and useSafeContinueUrl() hooks
  internally instead of receiving hook results as props
- Move OIDCButton to local _components folders
- Fix eslint import grouping in SignInPage

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2026-03-23 13:57:50 +01:00
parent 0476cd2d87
commit 887e994ff1
4 changed files with 78 additions and 129 deletions

View File

@@ -2,8 +2,7 @@ import { formatError, type GraphQLError } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
import { Button, Field, useToast } from "@probo/ui";
import type { FormEventHandler } from "react";
import { useMutation, usePreloadedQuery } from "react-relay";
import type { PreloadedQuery } from "react-relay";
import { type PreloadedQuery, useMutation, usePreloadedQuery } from "react-relay";
import { Link, matchPath, useLocation } from "react-router";
import { graphql } from "relay-runtime";
@@ -12,7 +11,7 @@ import type { SignInPageQuery } from "#/__generated__/iam/SignInPageQuery.graphq
import { useSafeContinueUrl } from "#/hooks/useSafeContinueUrl";
import { Divider } from "./_components/Divider";
import { OIDCButtons } from "./_components/OIDCButtons";
import { OIDCButton } from "./_components/OIDCButton";
const signInMutation = graphql`
mutation SignInPageMutation($input: SignInInput!) {
@@ -27,7 +26,7 @@ const signInMutation = graphql`
export const signInPageQuery = graphql`
query SignInPageQuery {
oidcProviders {
...OIDCButtonsFragment
...OIDCButtonFragment
}
}
`;
@@ -140,10 +139,9 @@ export default function SignInPage(props: Props) {
<div className="mt-6 space-y-4">
<Divider>{__("Or")}</Divider>
<OIDCButtons
providers={data.oidcProviders}
safeContinueUrl={safeContinueUrl}
/>
{data.oidcProviders.map((providerRef, index) => (
<OIDCButton key={index} providerRef={providerRef} />
))}
<Button
variant="secondary"

View File

@@ -4,10 +4,11 @@ import type { ComponentProps } from "react";
import { useFragment } from "react-relay";
import { graphql } from "relay-runtime";
import type { OIDCButtonsFragment$key } from "#/__generated__/iam/OIDCButtonsFragment.graphql";
import type { OIDCButtonFragment$key } from "#/__generated__/iam/OIDCButtonFragment.graphql";
import { useSafeContinueUrl } from "#/hooks/useSafeContinueUrl";
const fragment = graphql`
fragment OIDCButtonsFragment on OIDCProviderInfo {
fragment OIDCButtonFragment on OIDCProviderInfo {
name
loginURL
}
@@ -21,38 +22,13 @@ const providerIcons: Record<
microsoft: Microsoft,
};
export function OIDCButtons({
providers,
safeContinueUrl,
export function OIDCButton({
providerRef,
}: {
providers: ReadonlyArray<OIDCButtonsFragment$key>;
safeContinueUrl: URL;
providerRef: OIDCButtonFragment$key;
}) {
const { __ } = useTranslate();
return (
<>
{providers.map((providerRef, index) => (
<OIDCButton
key={index}
providerRef={providerRef}
safeContinueUrl={safeContinueUrl}
__={__}
/>
))}
</>
);
}
function OIDCButton({
providerRef,
safeContinueUrl,
__,
}: {
providerRef: OIDCButtonsFragment$key;
safeContinueUrl: URL;
__: (s: string) => string;
}) {
const safeContinueUrl = useSafeContinueUrl();
const provider = useFragment(fragment, providerRef);
const Icon = providerIcons[provider.name];

View File

@@ -1,14 +1,9 @@
import type { GraphQLError } from "@probo/helpers";
import { usePageTitle } from "@probo/hooks";
import { useTranslate } from "@probo/i18n";
import { Button, Field, Google, Microsoft, useToast } from "@probo/ui";
import { type ComponentProps, useEffect, useRef, useState } from "react";
import {
type PreloadedQuery,
useFragment,
useMutation,
usePreloadedQuery,
} from "react-relay";
import { Button, Field, useToast } from "@probo/ui";
import { useEffect, useRef, useState } from "react";
import { type PreloadedQuery, useMutation, usePreloadedQuery } from "react-relay";
import { graphql } from "relay-runtime";
import { z } from "zod";
@@ -17,9 +12,9 @@ import { useSafeContinueUrl } from "#/hooks/useSafeContinueUrl";
import { getPathPrefix } from "#/utils/pathPrefix";
import { Divider } from "./_components/Divider";
import { OIDCButton } from "./_components/OIDCButton";
import type { ConnectPageMutation, SendMagicLinkInput } from "./__generated__/ConnectPageMutation.graphql";
import type { ConnectPageOIDCButtonFragment$key } from "./__generated__/ConnectPageOIDCButtonFragment.graphql";
import type { ConnectPageQuery } from "./__generated__/ConnectPageQuery.graphql";
export const connectPageQuery = graphql`
@@ -30,7 +25,7 @@ export const connectPageQuery = graphql`
}
}
oidcProviders {
...ConnectPageOIDCButtonFragment
...OIDCButtonFragment
}
}
`;
@@ -43,21 +38,6 @@ const sendMagicLinkMutation = graphql`
}
`;
const oidcButtonFragment = graphql`
fragment ConnectPageOIDCButtonFragment on OIDCProviderInfo {
name
loginURL
}
`;
const providerIcons: Record<
string,
(props: ComponentProps<"svg">) => React.ReactNode
> = {
google: Google,
microsoft: Microsoft,
};
const schema = z.object({
email: z.string().email(),
});
@@ -66,65 +46,6 @@ type FormData = z.infer<typeof schema>;
const timerDurationSeconds = 60;
function OIDCButtons({
providers,
safeContinueUrl,
}: {
providers: ReadonlyArray<ConnectPageOIDCButtonFragment$key>;
safeContinueUrl: URL;
}) {
const { __ } = useTranslate();
if (providers.length === 0) {
return null;
}
return (
<>
{providers.map((providerRef, index) => (
<OIDCButton
key={index}
providerRef={providerRef}
safeContinueUrl={safeContinueUrl}
__={__}
/>
))}
<Divider>{__("Or")}</Divider>
</>
);
}
function OIDCButton({
providerRef,
safeContinueUrl,
__,
}: {
providerRef: ConnectPageOIDCButtonFragment$key;
safeContinueUrl: URL;
__: (s: string) => string;
}) {
const provider = useFragment(oidcButtonFragment, providerRef);
const Icon = providerIcons[provider.name];
return (
<Button
variant="secondary"
className="w-full h-10"
onClick={() => {
window.location.href
= provider.loginURL
+ "?continue="
+ encodeURIComponent(safeContinueUrl.toString());
}}
>
<span className="flex items-center gap-2">
{Icon && <Icon width={18} height={18} />}
{__(`Sign in with ${provider.name.charAt(0).toUpperCase() + provider.name.slice(1)}`)}
</span>
</Button>
);
}
export function ConnectPage(props: {
queryRef: PreloadedQuery<ConnectPageQuery>;
}) {
@@ -234,12 +155,14 @@ export function ConnectPage(props: {
</p>
</div>
<div className="space-y-4">
<OIDCButtons
providers={oidcProviders}
safeContinueUrl={safeContinueUrl}
/>
</div>
{oidcProviders.length > 0 && (
<div className="space-y-4">
{oidcProviders.map((providerRef, index) => (
<OIDCButton key={index} providerRef={providerRef} />
))}
<Divider>{__("Or")}</Divider>
</div>
)}
<form onSubmit={e => void handleSubmit(e)} className="space-y-6">
<Field

View File

@@ -0,0 +1,52 @@
import { useTranslate } from "@probo/i18n";
import { Button, Google, Microsoft } from "@probo/ui";
import type { ComponentProps } from "react";
import { useFragment } from "react-relay";
import { graphql } from "relay-runtime";
import type { OIDCButtonFragment$key } from "./__generated__/OIDCButtonFragment.graphql";
import { useSafeContinueUrl } from "#/hooks/useSafeContinueUrl";
const fragment = graphql`
fragment OIDCButtonFragment on OIDCProviderInfo {
name
loginURL
}
`;
const providerIcons: Record<
string,
(props: ComponentProps<"svg">) => React.ReactNode
> = {
google: Google,
microsoft: Microsoft,
};
export function OIDCButton({
providerRef,
}: {
providerRef: OIDCButtonFragment$key;
}) {
const { __ } = useTranslate();
const safeContinueUrl = useSafeContinueUrl();
const provider = useFragment(fragment, providerRef);
const Icon = providerIcons[provider.name];
return (
<Button
variant="secondary"
className="w-full h-10"
onClick={() => {
window.location.href
= provider.loginURL
+ "?continue="
+ encodeURIComponent(safeContinueUrl.toString());
}}
>
<span className="flex items-center gap-2">
{Icon && <Icon width={18} height={18} />}
{__(`Sign in with ${provider.name.charAt(0).toUpperCase() + provider.name.slice(1)}`)}
</span>
</Button>
);
}