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

View File

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

View File

@@ -1,14 +1,9 @@
import type { GraphQLError } from "@probo/helpers"; import type { GraphQLError } from "@probo/helpers";
import { usePageTitle } from "@probo/hooks"; import { usePageTitle } from "@probo/hooks";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
import { Button, Field, Google, Microsoft, useToast } from "@probo/ui"; import { Button, Field, useToast } from "@probo/ui";
import { type ComponentProps, useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { import { type PreloadedQuery, useMutation, usePreloadedQuery } from "react-relay";
type PreloadedQuery,
useFragment,
useMutation,
usePreloadedQuery,
} from "react-relay";
import { graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
import { z } from "zod"; import { z } from "zod";
@@ -17,9 +12,9 @@ import { useSafeContinueUrl } from "#/hooks/useSafeContinueUrl";
import { getPathPrefix } from "#/utils/pathPrefix"; import { getPathPrefix } from "#/utils/pathPrefix";
import { Divider } from "./_components/Divider"; import { Divider } from "./_components/Divider";
import { OIDCButton } from "./_components/OIDCButton";
import type { ConnectPageMutation, SendMagicLinkInput } from "./__generated__/ConnectPageMutation.graphql"; import type { ConnectPageMutation, SendMagicLinkInput } from "./__generated__/ConnectPageMutation.graphql";
import type { ConnectPageOIDCButtonFragment$key } from "./__generated__/ConnectPageOIDCButtonFragment.graphql";
import type { ConnectPageQuery } from "./__generated__/ConnectPageQuery.graphql"; import type { ConnectPageQuery } from "./__generated__/ConnectPageQuery.graphql";
export const connectPageQuery = graphql` export const connectPageQuery = graphql`
@@ -30,7 +25,7 @@ export const connectPageQuery = graphql`
} }
} }
oidcProviders { 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({ const schema = z.object({
email: z.string().email(), email: z.string().email(),
}); });
@@ -66,65 +46,6 @@ type FormData = z.infer<typeof schema>;
const timerDurationSeconds = 60; 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: { export function ConnectPage(props: {
queryRef: PreloadedQuery<ConnectPageQuery>; queryRef: PreloadedQuery<ConnectPageQuery>;
}) { }) {
@@ -234,12 +155,14 @@ export function ConnectPage(props: {
</p> </p>
</div> </div>
<div className="space-y-4"> {oidcProviders.length > 0 && (
<OIDCButtons <div className="space-y-4">
providers={oidcProviders} {oidcProviders.map((providerRef, index) => (
safeContinueUrl={safeContinueUrl} <OIDCButton key={index} providerRef={providerRef} />
/> ))}
</div> <Divider>{__("Or")}</Divider>
</div>
)}
<form onSubmit={e => void handleSubmit(e)} className="space-y-6"> <form onSubmit={e => void handleSubmit(e)} className="space-y-6">
<Field <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>
);
}