Skip to content

Commit

Permalink
better ui/ux for oauth
Browse files Browse the repository at this point in the history
  • Loading branch information
RohinBhargava committed Sep 10, 2024
1 parent 7a576d7 commit 155aa7e
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 43 deletions.
154 changes: 113 additions & 41 deletions packages/ui/app/src/playground/PlaygroundAuthorizationForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { APIV1Read } from "@fern-api/fdr-sdk/client/types";
import { FernButton, FernCard, FernCollapse, FernInput } from "@fern-ui/components";
import { FernButton, FernCard, FernCollapse, FernDropdown, FernInput, FernSegmentedControl } from "@fern-ui/components";
import { visitDiscriminatedUnion } from "@fern-ui/core-utils";
import { useBooleanState } from "@fern-ui/react-commons";
import { Key, User } from "iconoir-react";
Expand Down Expand Up @@ -160,8 +160,8 @@ function FoundOAuthReferencedEndpointForm({

const [displayFailedLogin, setDisplayFailedLogin] = useState(false);

// TODO: implement a loading state
const oAuthClientCredentialLogin = async () =>
const oAuthClientCredentialLogin = async () => {
setValue((prev) => ({ ...prev, isLoggingIn: true }));
await oAuthClientCredentialReferencedEndpointLoginFlow({
formState,
endpoint: oAuthEndpoint,
Expand All @@ -171,50 +171,114 @@ function FoundOAuthReferencedEndpointForm({
closeContainer,
setDisplayFailedLogin,
});
setValue((prev) => ({ ...prev, isLoggingIn: false }));
};

return (
const authenticationOptions: FernDropdown.Option[] = [
{ type: "value", value: "credentials", label: "Credentials", icon: <User /> },
{ type: "value", value: "token", label: "Bearer Token", icon: <Key /> },
];

return value.isLoggingIn ? (
<li className="-mx-4 space-y-2 p-4 pt-8 flex flex-1 items-center justify-center">Loading...</li>
) : (
<>
<li className="-mx-4 space-y-2 p-4 pb-2">
<label className="inline-flex flex-wrap items-baseline">
<span className="font-mono text-sm">{"OAuth Client Credentials login"}</span>
</label>
<PlaygroundEndpointForm
endpoint={oAuthEndpoint}
formState={formState}
setFormState={setFormState}
types={types}
ignoreHeaders={true}
<FernSegmentedControl
options={authenticationOptions}
onValueChange={(value: string) =>
setValue((prev) => ({ ...prev, selectedInputMethod: value as "credentials" | "token" }))
}
value={value.selectedInputMethod}
disabled={disabled}
/>
</li>
<li className="-mx-4 space-y-2 p-4 pt-0">
<label className="inline-flex flex-wrap items-baseline">
<span className="font-mono text-sm">Bearer token</span>
</label>

<div>
<PasswordInputGroup
onValueChange={(newValue: string) => setValue((prev) => ({ ...prev, accessToken: newValue }))}
value={value.accessToken}
autoComplete="off"
data-1p-ignore="true"
{value.selectedInputMethod === "credentials" && !value.isLoggedIn ? (
<>
<li className="-mx-4 space-y-2 p-4">
<label className="inline-flex flex-wrap items-baseline">
<span className="font-mono text-sm">{"OAuth Client Credentials login"}</span>
</label>
<PlaygroundEndpointForm
endpoint={oAuthEndpoint}
formState={formState}
setFormState={setFormState}
types={types}
ignoreHeaders={true}
/>
</li>
{displayFailedLogin && (
<li className="-mx-4 space-y-2 p-4 py-0">
<span className="font-mono text-sm text-red-600">
Failed to login with the provided credentials
</span>
</li>
)}
</>
) : (
<>
<li className="-mx-4 space-y-2 p-4">
<label className="inline-flex flex-wrap items-baseline">
<span className="font-mono text-sm">Bearer token</span>
</label>

<div>
<PasswordInputGroup
onValueChange={(newValue: string) =>
setValue((prev) => ({ ...prev, accessToken: newValue }))
}
value={value.accessToken}
autoComplete="off"
data-1p-ignore="true"
disabled={disabled}
/>
</div>
</li>
{value.isLoggedIn && (
<>
{value.accessToken !== value.loggedInStartingToken ? (
value.selectedInputMethod === "credentials" && (
<Callout intent="warning">
The bearer token has been changed since the last login. Please refresh the
token.
</Callout>
)
) : (
<Callout intent="info">
This bearer token was generated as a result of Client Credential login
</Callout>
)}
</>
)}
</>
)}
<li className="flex">
{value.isLoggedIn && value.selectedInputMethod === "credentials" && (
<FernButton
text="Logout"
intent="danger"
onClick={() => {
setValue((prev) => ({
...prev,
selectedInputMethod: "credentials",
accessToken: "",
isLoggedIn: false,
}));
}}
disabled={disabled}
/>
</div>
</li>
{displayFailedLogin && (
<li className="-mx-4 space-y-2 p-4 py-0">
<span className="font-mono text-sm text-red-600">
Failed to login with the provided credentials
</span>
</li>
)}
<li className="flex justify-end py-2">
<FernButton
text={value.accessToken.length > 0 ? "Refresh token" : "Login"}
intent="primary"
onClick={oAuthClientCredentialLogin}
disabled={disabled}
/>
)}

{value.selectedInputMethod !== "token" && (
<FernButton
text={value.isLoggedIn ? "Refresh token" : "Login"}
intent="primary"
onClick={oAuthClientCredentialLogin}
disabled={disabled}
className="ml-auto"
/>
)}
</li>
</>
);
Expand Down Expand Up @@ -308,6 +372,7 @@ export function PlaygroundAuthorizationFormCard({
}: PlaygroundAuthorizationFormCardProps): ReactElement | null {
const authState = useAtomValue(PLAYGROUND_AUTH_STATE_ATOM);
const setBearerAuth = useSetAtom(PLAYGROUND_AUTH_STATE_BEARER_TOKEN_ATOM);
const oAuth = useAtomValue(PLAYGROUND_AUTH_STATE_OAUTH_ATOM);
const isOpen = useBooleanState(false);
const apiKeyInjection = useApiKeyInjectionConfig();
const router = useRouter();
Expand Down Expand Up @@ -340,9 +405,16 @@ export function PlaygroundAuthorizationFormCard({
isOpen.toggleValue();
}
};

const authButtonCopy = apiKeyInjection.enabled
? "Login to send a real request"
: `Authenticate with your ${auth.type === "oAuth" ? "credentials" : "API key"} to send a real request`;
: `Authenticate with your ${
auth.type === "oAuth"
? oAuth.selectedInputMethod === "credentials"
? "credentials"
: "bearer token"
: "API key"
} to send a real request`;

useEffect(() => {
if (!router.isReady) {
Expand Down Expand Up @@ -453,7 +525,7 @@ export function PlaygroundAuthorizationFormCard({
size="large"
intent="success"
variant="outlined"
text="Authenticate with your API key to send a real request"
text={authButtonCopy}
icon={<Key />}
rightIcon={
<span className="flex items-center rounded-[4px] bg-tag-success p-1 font-mono text-xs uppercase leading-none text-intent-success">
Expand Down
9 changes: 9 additions & 0 deletions packages/ui/app/src/playground/types/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export const PlaygroundAuthStateOAuthSchema = z.strictObject({
tokenUrl: z.string(),
tokenPrefix: z.string(),
scopes: z.array(z.string()),
isLoggedIn: z.boolean(),
isLoggingIn: z.boolean(),
selectedInputMethod: z.enum(["credentials", "token"]),
loggedInStartingToken: z.string(),
});
export type PlaygroundAuthStateOAuth = z.infer<typeof PlaygroundAuthStateOAuthSchema>;
export const PLAYGROUND_AUTH_STATE_OAUTH_INITIAL: PlaygroundAuthStateOAuth = {
Expand All @@ -39,6 +43,11 @@ export const PLAYGROUND_AUTH_STATE_OAUTH_INITIAL: PlaygroundAuthStateOAuth = {
tokenUrl: "",
tokenPrefix: "",
scopes: [],

isLoggedIn: false,
isLoggingIn: false,
selectedInputMethod: "credentials",
loggedInStartingToken: "",
};

export const PlaygroundAuthStateSchema = z.strictObject({
Expand Down
10 changes: 8 additions & 2 deletions packages/ui/app/src/playground/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,14 @@ export const oAuthClientCredentialReferencedEndpointLoginFlow = async ({
jsonRes.response,
oAuthClientCredentialsReferencedEndpoint.accessTokenLocator,
)?.[0];
setValue((prev) => ({ ...prev, accessToken }));
closeContainer && closeContainer();
setValue((prev) => ({
...prev,
selectedInputMethod: "credentials",
accessToken,
isLoggedIn: true,
loggedInStartingToken: accessToken,
}));
setTimeout(() => closeContainer && closeContainer(), 500);
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/components/src/FernSegmentedControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface FernSegmentedControlProps {
onValueChange: (value: string) => void;
muted?: boolean;
container?: HTMLElement;
disabled?: boolean;
}

export const FernSegmentedControl: FC<FernSegmentedControlProps> = ({
Expand All @@ -23,13 +24,15 @@ export const FernSegmentedControl: FC<FernSegmentedControlProps> = ({
onValueChange,
muted,
container,
disabled,
}) => (
<FernTooltipProvider>
<ToggleGroup.Root
className={cn("fern-segmented-control", className)}
type="single"
value={value}
onValueChange={onValueChange}
disabled={disabled}
>
{options.map((option) =>
option.type === "value" ? (
Expand Down

0 comments on commit 155aa7e

Please sign in to comment.