Skip to content

Commit

Permalink
chore: code-split api playground auth form (#1794)
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity authored Nov 7, 2024
1 parent 5686077 commit 2277c31
Show file tree
Hide file tree
Showing 22 changed files with 733 additions and 622 deletions.
594 changes: 0 additions & 594 deletions packages/ui/app/src/playground/PlaygroundAuthorizationForm.tsx

This file was deleted.

8 changes: 3 additions & 5 deletions packages/ui/app/src/playground/PlaygroundContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import type * as FernNavigation from "@fern-api/fdr-sdk/navigation";
import { ArrowLeft } from "iconoir-react";
import { ReactElement } from "react";
import { usePlaygroundNode } from "../atoms";
import { PlaygroundWebSocket } from "./PlaygroundWebSocket";
import { PlaygroundEndpoint } from "./endpoint/PlaygroundEndpoint";
import { PlaygroundEndpointSkeleton } from "./endpoint/PlaygroundEndpointSkeleton";
import { useEndpointContext } from "./hooks/useEndpointContext";
import { useWebSocketContext } from "./hooks/useWebSocketContext";
import { PlaygroundEndpoint, PlaygroundEndpointSkeleton } from "./endpoint";
import { useEndpointContext, useWebSocketContext } from "./hooks";
import { PlaygroundWebSocket } from "./websocket";

const PlaygroundContentForEndpoint = ({ node }: { node: FernNavigation.EndpointNode }) => {
const { context, isLoading } = useEndpointContext(node);
Expand Down
11 changes: 7 additions & 4 deletions packages/ui/app/src/playground/PlaygroundDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@ import { useAtomValue, useSetAtom } from "jotai";
import { useAtomCallback } from "jotai/utils";
import { ReactElement, memo, useEffect } from "react";
import { useCallbackOne } from "use-memo-one";
import { HEADER_HEIGHT_ATOM, useAtomEffect } from "../atoms";
import {
HEADER_HEIGHT_ATOM,
IS_MOBILE_SCREEN_ATOM,
MAX_PLAYGROUND_HEIGHT_ATOM,
MOBILE_SIDEBAR_ENABLED_ATOM,
PLAYGROUND_NODE_ID,
VIEWPORT_HEIGHT_ATOM,
useAtomEffect,
useIsPlaygroundOpen,
usePlaygroundFormStateAtom,
usePlaygroundNode,
useTogglePlayground,
} from "../atoms/playground";
import { IS_MOBILE_SCREEN_ATOM, MOBILE_SIDEBAR_ENABLED_ATOM, VIEWPORT_HEIGHT_ATOM } from "../atoms/viewport";
} from "../atoms";
import { FernErrorBoundary } from "../components/FernErrorBoundary";
import { PlaygroundContent } from "./PlaygroundContent";
import { HorizontalSplitPane } from "./VerticalSplitPane";
import { PlaygroundEndpointSelectorContent } from "./endpoint/PlaygroundEndpointSelectorContent";
import { PlaygroundEndpointSelectorContent } from "./endpoint";
import { useResizeY } from "./useSplitPlane";
import { PLAYGROUND_API_GROUPS_ATOM } from "./utils/flatten-apis";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { APIV1Read } from "@fern-api/fdr-sdk/client/types";
import { visitDiscriminatedUnion } from "@fern-api/ui-core-utils";
import { FC, ReactElement } from "react";
import { PlaygroundBasicAuthForm } from "./PlaygroundBasicAuthForm";
import { PlaygroundBearerAuthForm } from "./PlaygroundBearerAuthForm";
import { PlaygroundHeaderAuthForm } from "./PlaygroundHeaderAuthForm";
import { PlaygroundOAuthForm } from "./PlaygroundOAuthForm";

interface PlaygroundAuthorizationFormProps {
auth: APIV1Read.ApiAuth;
closeContainer: () => void;
disabled: boolean;
}

export const PlaygroundAuthorizationForm: FC<PlaygroundAuthorizationFormProps> = ({
auth,
closeContainer,
disabled,
}) => {
return (
<ul className="list-none px-4">
{visitDiscriminatedUnion(auth, "type")._visit<ReactElement | false>({
bearerAuth: (bearerAuth) => <PlaygroundBearerAuthForm bearerAuth={bearerAuth} disabled={disabled} />,
basicAuth: (basicAuth) => <PlaygroundBasicAuthForm basicAuth={basicAuth} disabled={disabled} />,
header: (header) => <PlaygroundHeaderAuthForm header={header} disabled={disabled} />,
oAuth: (oAuth) => (
<PlaygroundOAuthForm oAuth={oAuth} closeContainer={closeContainer} disabled={disabled} />
),
_other: () => false,
})}
</ul>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { APIV1Read } from "@fern-api/fdr-sdk/client/types";
import { FernButton, FernCollapse } from "@fern-ui/components";
import { useBooleanState } from "@fern-ui/react-commons";
import { useSetAtom } from "jotai/react";
import { ReactElement } from "react";
import { PLAYGROUND_AUTH_STATE_BEARER_TOKEN_ATOM } from "../../atoms";
import { useApiKeyInjectionConfig } from "../../services/useApiKeyInjectionConfig";
import { PlaygroundAuthorizationForm } from "./PlaygroundAuthorizationForm";
import { PlaygroundCardTriggerApiKeyInjected } from "./PlaygroundCardTriggerApiKeyInjected";
import { PlaygroundCardTriggerManual } from "./PlaygroundCardTriggerManual";

interface PlaygroundAuthorizationFormCardProps {
auth: APIV1Read.ApiAuth;
disabled: boolean;
}
export function PlaygroundAuthorizationFormCard({
auth,
disabled,
}: PlaygroundAuthorizationFormCardProps): ReactElement | null {
const setBearerAuth = useSetAtom(PLAYGROUND_AUTH_STATE_BEARER_TOKEN_ATOM);
const isOpen = useBooleanState(false);
const apiKeyInjection = useApiKeyInjectionConfig();
const apiKey = apiKeyInjection.enabled && apiKeyInjection.authenticated ? apiKeyInjection.access_token : null;

const handleResetBearerAuth = () => {
setBearerAuth({ token: apiKey ?? "" });
};

return (
<div>
{apiKeyInjection.enabled ? (
<PlaygroundCardTriggerApiKeyInjected
auth={auth}
config={apiKeyInjection}
disabled={disabled}
toggleOpen={isOpen.toggleValue}
onClose={isOpen.setFalse}
/>
) : (
<PlaygroundCardTriggerManual
auth={auth}
disabled={disabled}
isOpen={isOpen.value}
toggleOpen={isOpen.toggleValue}
/>
)}

<FernCollapse open={isOpen.value}>
<div className="pt-4">
<div className="fern-dropdown max-h-full">
<PlaygroundAuthorizationForm auth={auth} closeContainer={isOpen.setFalse} disabled={disabled} />

<div className="flex justify-end p-4 pt-2 gap-2">
{auth.type !== "oAuth" && (
<FernButton text="Done" intent="primary" onClick={isOpen.setFalse} />
)}
{apiKey != null && (
<FernButton
text="Reset token to default"
intent="none"
onClick={handleResetBearerAuth}
size="normal"
variant="outlined"
/>
)}
</div>
</div>
</div>
</FernCollapse>
</div>
);
}
57 changes: 57 additions & 0 deletions packages/ui/app/src/playground/auth/PlaygroundBasicAuthForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { APIV1Read } from "@fern-api/fdr-sdk/client/types";
import { FernInput } from "@fern-ui/components";
import { User } from "iconoir-react";
import { useAtom } from "jotai/react";
import { ReactElement, useCallback } from "react";
import { PLAYGROUND_AUTH_STATE_BASIC_AUTH_ATOM } from "../../atoms";
import { PasswordInputGroup } from "../PasswordInputGroup";

export function PlaygroundBasicAuthForm({
basicAuth,
disabled,
}: {
basicAuth: APIV1Read.BasicAuth;
disabled?: boolean;
}): ReactElement {
const [value, setValue] = useAtom(PLAYGROUND_AUTH_STATE_BASIC_AUTH_ATOM);
const handleChangeUsername = useCallback(
(newValue: string) => setValue((prev) => ({ ...prev, username: newValue })),
[setValue],
);
const handleChangePassword = useCallback(
(newValue: string) => setValue((prev) => ({ ...prev, password: newValue })),
[setValue],
);
return (
<>
<li className="-mx-4 space-y-2 p-4">
<label className="inline-flex flex-wrap items-baseline">
<span className="font-mono text-sm">{basicAuth.usernameName ?? "Username"}</span>
</label>
<div>
<FernInput
onValueChange={handleChangeUsername}
value={value.username}
leftIcon={<User className="size-icon" />}
rightElement={<span className="t-muted text-xs">{"string"}</span>}
disabled={disabled}
/>
</div>
</li>

<li className="-mx-4 space-y-2 p-4">
<label className="inline-flex flex-wrap items-baseline">
<span className="font-mono text-sm">{basicAuth.passwordName ?? "Password"}</span>
</label>

<div>
<PasswordInputGroup
onValueChange={handleChangePassword}
value={value.password}
disabled={disabled}
/>
</div>
</li>
</>
);
}
34 changes: 34 additions & 0 deletions packages/ui/app/src/playground/auth/PlaygroundBearerAuthForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { APIV1Read } from "@fern-api/fdr-sdk/client/types";
import { useAtom } from "jotai/react";
import { ReactElement, useCallback } from "react";
import { PLAYGROUND_AUTH_STATE_BEARER_TOKEN_ATOM } from "../../atoms";
import { PasswordInputGroup } from "../PasswordInputGroup";

export function PlaygroundBearerAuthForm({
bearerAuth,
disabled,
}: {
bearerAuth: APIV1Read.BearerAuth;
disabled?: boolean;
}): ReactElement {
const [value, setValue] = useAtom(PLAYGROUND_AUTH_STATE_BEARER_TOKEN_ATOM);
const handleChange = useCallback((newValue: string) => setValue({ token: newValue }), [setValue]);

return (
<li className="-mx-4 space-y-2 p-4">
<label className="inline-flex flex-wrap items-baseline">
<span className="font-mono text-sm">{bearerAuth.tokenName ?? "Bearer token"}</span>
</label>

<div>
<PasswordInputGroup
onValueChange={handleChange}
value={value.token}
autoComplete="off"
data-1p-ignore="true"
disabled={disabled}
/>
</div>
</li>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import type { APIV1Read } from "@fern-api/fdr-sdk/client/types";
import { FernButton, FernCard } from "@fern-ui/components";
import { APIKeyInjectionConfigEnabled } from "@fern-ui/fern-docs-auth";
import { Key, User } from "iconoir-react";
import { useAtomValue, useSetAtom } from "jotai";
import { useSearchParams } from "next/navigation";
import { ReactElement, useEffect } from "react";
import urlJoin from "url-join";
import { PLAYGROUND_AUTH_STATE_ATOM, PLAYGROUND_AUTH_STATE_BEARER_TOKEN_ATOM } from "../../atoms";
import { useApiRoute } from "../../hooks/useApiRoute";
import { Callout } from "../../mdx/components/callout";
import { PlaygroundAuthorizationForm } from "./PlaygroundAuthorizationForm";

interface PlaygroundCardTriggerApiKeyInjectedProps {
auth: APIV1Read.ApiAuth;
config: APIKeyInjectionConfigEnabled;
disabled: boolean;
toggleOpen: () => void;
onClose: () => void;
}

export function PlaygroundCardTriggerApiKeyInjected({
auth,
config,
disabled,
toggleOpen,
onClose,
}: PlaygroundCardTriggerApiKeyInjectedProps): ReactElement | false {
const searchParams = useSearchParams();
const error = searchParams.get("error");
const errorDescription = searchParams.get("error_description");
const authState = useAtomValue(PLAYGROUND_AUTH_STATE_ATOM);
const logoutApiRoute = useApiRoute("/api/fern-docs/auth/logout");

const apiKey = config.authenticated ? config.access_token : null;
const setBearerAuth = useSetAtom(PLAYGROUND_AUTH_STATE_BEARER_TOKEN_ATOM);

// TODO change this to on-login
useEffect(() => {
if (apiKey != null) {
setBearerAuth({ token: apiKey });
}
}, [apiKey, setBearerAuth]);

const handleResetBearerAuth = () => {
setBearerAuth({ token: apiKey ?? "" });
};

const redirectOrOpenAuthForm = () => {
if (!config.authenticated) {
const url = new URL(config.authorizationUrl);
const state = new URL(window.location.href);
if (state.searchParams.has("error")) {
state.searchParams.delete("error");
}
if (state.searchParams.has("error_description")) {
state.searchParams.delete("error_description");
}
url.searchParams.set(config.returnToQueryParam, state.toString());
window.location.replace(url);
} else {
toggleOpen();
}
};

if (apiKey != null) {
return (
<FernCard className="rounded-xl p-4 shadow-sm mb-3" title="Login to send a real request">
<FernButton
className="w-full text-left pointer-events-none"
size="large"
intent="success"
variant="outlined"
text="Successfully logged in"
icon={<Key />}
active={true}
/>
<div className="-mx-4">
<PlaygroundAuthorizationForm auth={auth} closeContainer={onClose} disabled={disabled} />
</div>
{
<div className="flex justify-end gap-2">
{apiKey !== authState?.bearerAuth?.token && apiKey && (
<FernButton
text="Reset token to default"
intent="none"
icon={<Key />}
onClick={handleResetBearerAuth}
size="normal"
variant="outlined"
/>
)}
<FernButton
text="Logout"
intent="none"
onClick={() => {
if (!config.authenticated) {
return;
}
const url = new URL(urlJoin(window.location.origin, logoutApiRoute));
const returnTo = new URL(window.location.href);
url.searchParams.set(config.returnToQueryParam, returnTo.toString());
fetch(url)
.then(() => {
window.location.reload();
})
.catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
});
}}
size="normal"
variant="outlined"
/>
</div>
}
</FernCard>
);
}

return (
<FernCard className="rounded-xl p-4 shadow-sm mb-2">
{error && <Callout intent="error">{errorDescription ?? error}</Callout>}

<h5 className="t-muted m-0">Login to send a real request</h5>
<div className="flex justify-center my-5 gap-2">
<FernButton
size="normal"
intent="primary"
text="Login"
icon={<User />}
onClick={redirectOrOpenAuthForm}
/>
<FernButton
size="normal"
intent="none"
variant="outlined"
icon={<Key />}
text="Provide token manually"
onClick={toggleOpen}
/>
</div>
</FernCard>
);
}
Loading

0 comments on commit 2277c31

Please sign in to comment.