diff --git a/.changeset/cool-chairs-swim.md b/.changeset/cool-chairs-swim.md new file mode 100644 index 00000000000..73d3feb4c42 --- /dev/null +++ b/.changeset/cool-chairs-swim.md @@ -0,0 +1,5 @@ +--- +"@clerk/astro": patch +--- + +Add an Astro component and a React UI Component for Google One Tap. diff --git a/packages/astro/src/astro-components/interactive/GoogleOneTap.astro b/packages/astro/src/astro-components/interactive/GoogleOneTap.astro new file mode 100644 index 00000000000..e161a232ca5 --- /dev/null +++ b/packages/astro/src/astro-components/interactive/GoogleOneTap.astro @@ -0,0 +1,8 @@ +--- +import type { GoogleOneTapProps } from "@clerk/types"; +type Props = GoogleOneTapProps + +import InternalUIComponentRenderer from './InternalUIComponentRenderer.astro' +--- + + diff --git a/packages/astro/src/astro-components/interactive/InternalUIComponentRenderer.astro b/packages/astro/src/astro-components/interactive/InternalUIComponentRenderer.astro index d0e89ccc541..0f217aa156e 100644 --- a/packages/astro/src/astro-components/interactive/InternalUIComponentRenderer.astro +++ b/packages/astro/src/astro-components/interactive/InternalUIComponentRenderer.astro @@ -1,7 +1,7 @@ --- interface Props { [key: string]: unknown - component: 'sign-in' | 'sign-up' | 'organization-list' | 'organization-profile' | 'organization-switcher' | 'user-button' | 'user-profile' + component: 'sign-in' | 'sign-up' | 'organization-list' | 'organization-profile' | 'organization-switcher' | 'user-button' | 'user-profile' | 'google-one-tap' } import { customAlphabet, urlAlphabet } from "nanoid"; diff --git a/packages/astro/src/astro-components/interactive/index.ts b/packages/astro/src/astro-components/interactive/index.ts index 09f98a26afc..d73a17d01a7 100644 --- a/packages/astro/src/astro-components/interactive/index.ts +++ b/packages/astro/src/astro-components/interactive/index.ts @@ -14,3 +14,5 @@ export { default as OrganizationProfile } from './OrganizationProfile.astro'; export { default as OrganizationSwitcher } from './OrganizationSwitcher.astro'; // @ts-ignore export { default as OrganizationList } from './OrganizationList.astro'; +// @ts-ignore +export { default as GoogleOneTap } from './GoogleOneTap.astro'; diff --git a/packages/astro/src/client/mount-clerk-astro-js-components.ts b/packages/astro/src/client/mount-clerk-astro-js-components.ts index ba050907734..af8fb87ea78 100644 --- a/packages/astro/src/client/mount-clerk-astro-js-components.ts +++ b/packages/astro/src/client/mount-clerk-astro-js-components.ts @@ -12,6 +12,7 @@ const mountAllClerkAstroJSComponents = () => { 'user-profile': 'mountUserProfile', 'sign-in': 'mountSignIn', 'sign-up': 'mountSignUp', + 'google-one-tap': 'openGoogleOneTap', } as const; Object.entries(mountFns).forEach(([category, mountFn]) => { diff --git a/packages/astro/src/client/react/uiComponents.tsx b/packages/astro/src/client/react/uiComponents.tsx index 9f58a8dcfb1..3bedd626e42 100644 --- a/packages/astro/src/client/react/uiComponents.tsx +++ b/packages/astro/src/client/react/uiComponents.tsx @@ -1,4 +1,5 @@ import type { + GoogleOneTapProps, OrganizationListProps, OrganizationProfileProps, OrganizationSwitcherProps, @@ -7,22 +8,40 @@ import type { UserButtonProps, UserProfileProps, } from '@clerk/types'; -import React, { createElement } from 'react'; +import React from 'react'; import { withClerk, type WithClerkProp } from './utils'; +export interface OpenProps { + open: ((props: any) => void) | undefined; + close: (() => void) | undefined; + props?: any; +} + export interface MountProps { - mount?: (node: HTMLDivElement, props: any) => void; - unmount?: (node: HTMLDivElement) => void; + mount: ((node: HTMLDivElement, props: any) => void) | undefined; + unmount: ((node: HTMLDivElement) => void) | undefined; updateProps?: (props: any) => void; props?: any; - customPagesPortals?: any[]; + // TODO: Support custom pages + // customPagesPortals?: any[]; } -class Portal extends React.PureComponent { +const isMountProps = (props: any): props is MountProps => { + return 'mount' in props; +}; + +const isOpenProps = (props: any): props is OpenProps => { + return 'open' in props; +}; + +class Portal extends React.PureComponent { private portalRef = React.createRef(); componentDidUpdate(prevProps: Readonly) { + if (!isMountProps(prevProps) || !isMountProps(this.props)) { + return; + } if ( prevProps.props.appearance !== this.props.props.appearance || prevProps.props?.customPages?.length !== this.props.props?.customPages?.length @@ -36,13 +55,24 @@ class Portal extends React.PureComponent { componentDidMount() { if (this.portalRef.current) { - this.props.mount?.(this.portalRef.current, this.props.props); + if (isMountProps(this.props)) { + this.props.mount?.(this.portalRef.current, this.props.props); + } + + if (isOpenProps(this.props)) { + this.props.open?.(this.props.props); + } } } componentWillUnmount() { if (this.portalRef.current) { - this.props.unmount?.(this.portalRef.current); + if (isMountProps(this.props)) { + this.props.unmount?.(this.portalRef.current); + } + if (isOpenProps(this.props)) { + this.props.close?.(); + } } } @@ -50,7 +80,9 @@ class Portal extends React.PureComponent { return ( <>
- {this.props?.customPagesPortals?.map((portal, index) => createElement(portal, { key: index }))} + {/*TODO: Support custom pages*/} + {/*{isMountProps(this.props) &&*/} + {/* this.props?.customPagesPortals?.map((portal, index) => createElement(portal, { key: index }))}*/} ); } @@ -132,3 +164,13 @@ export const OrganizationList = withClerk(({ clerk, ...props }: WithClerkProp ); }, 'OrganizationList'); + +export const GoogleOneTap = withClerk(({ clerk, ...props }: WithClerkProp) => { + return ( + + ); +}, 'GoogleOneTap');