From 0ebdca227e53a9cbfd3df1dbfc968cefbb19fc75 Mon Sep 17 00:00:00 2001 From: David Lopez Date: Tue, 21 Nov 2023 17:08:54 -0800 Subject: [PATCH] fix: migrate useAuth hook for v6 (#1137) * fix: migrate useAuth hook for v6 * fix: remove console log --------- Co-authored-by: David Lopez --- .../studio-ui-codegen-react.test.ts.snap | 55 +++++++++++++ .../__tests__/studio-ui-codegen-react.test.ts | 7 ++ .../render-util-functions.test.ts.snap | 56 +++++++++++++- .../lib/imports/import-mapping.ts | 2 + .../lib/react-studio-template-renderer.ts | 7 +- .../react-utils-studio-template-renderer.ts | 3 + .../lib/utils-file-functions/hooks/index.ts | 1 + .../lib/utils-file-functions/hooks/useAuth.ts | 77 +++++++++++++++++++ 8 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 packages/codegen-ui-react/lib/utils-file-functions/hooks/useAuth.ts diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap index be60e6b1..452c6059 100644 --- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap +++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react.test.ts.snap @@ -879,6 +879,61 @@ export default function Test(props: TestProps): React.ReactElement { " `; +exports[`amplify render tests bindings auth supports auth bindings in actions - amplify js v6 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { getOverrideProps, useAuth, useDataStoreCreateAction } from \\"./utils\\"; +import { Customer } from \\"../models\\"; +import { schema } from \\"../models/schema\\"; +import { Button, ButtonProps } from \\"@aws-amplify/ui-react\\"; + +export type EscapeHatchProps = { + [elementHierarchy: string]: Record; +} | null; + +export type VariantValues = { [key: string]: string }; +export type Variant = { + variantValues: VariantValues; + overrides: EscapeHatchProps; +}; +export declare type PrimitiveOverrideProps = Partial & + React.DOMAttributes; +export declare type ComponentWithAuthEventBindingOverridesProps = { + ComponentWithAuthEventBinding?: PrimitiveOverrideProps; +} & EscapeHatchProps; +export type ComponentWithAuthEventBindingProps = React.PropsWithChildren< + Partial & { + overrides?: ComponentWithAuthEventBindingOverridesProps | undefined | null; + } +>; +export default function ComponentWithAuthEventBinding( + props: ComponentWithAuthEventBindingProps +): React.ReactElement { + const { overrides, ...rest } = props; + const authAttributes = useAuth().user?.attributes ?? {}; + const componentWithAuthEventBindingOnClick = useDataStoreCreateAction({ + model: Customer, + fields: { + userName: authAttributes[\\"username\\"], + favoriteIceCream: authAttributes[\\"custom:favorite_icecream\\"], + }, + schema: schema, + }); + return ( + /* @ts-ignore: TS2322 */ + + ); +} +" +`; + exports[`amplify render tests bindings auth supports auth bindings in actions 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; diff --git a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts index b938546e..59bdfbac 100644 --- a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts +++ b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react.test.ts @@ -744,6 +744,13 @@ describe('amplify render tests', () => { generateWithAmplifyRenderer('bindings/auth/componentWithAuthActionBinding').componentText, ).toMatchSnapshot(); }); + it('supports auth bindings in actions - amplify js v6', () => { + expect( + generateWithAmplifyRenderer('bindings/auth/componentWithAuthActionBinding', { + dependencies: { 'aws-amplify': '^6.0.0' }, + }).componentText, + ).toMatchSnapshot(); + }); }); describe('data', () => { diff --git a/packages/codegen-ui-react/lib/__tests__/utils/__snapshots__/render-util-functions.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/utils/__snapshots__/render-util-functions.test.ts.snap index 87dac1e8..7a7e80ac 100644 --- a/packages/codegen-ui-react/lib/__tests__/utils/__snapshots__/render-util-functions.test.ts.snap +++ b/packages/codegen-ui-react/lib/__tests__/utils/__snapshots__/render-util-functions.test.ts.snap @@ -808,7 +808,7 @@ export const processFile = async ({ file }) => { exports[`render utils file render all utils - amplify v6 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; -import { signOut } from \\"aws-amplify/auth\\"; +import { fetchUserAttributes, signOut } from \\"aws-amplify/auth\\"; import { DataStore } from \\"aws-amplify/datastore\\"; import { Hub } from \\"aws-amplify/utils\\"; export const UI_CHANNEL = \\"ui\\"; @@ -1305,6 +1305,60 @@ export const useAuthSignOutAction = (options) => async () => { ); } }; +export const useAuth = () => { + const [result, setResult] = React.useState({ + error: undefined, + isLoading: true, + user: undefined, + }); + const fetchCurrentUserAttributes = React.useCallback(async () => { + setResult((prevResult) => ({ ...prevResult, isLoading: true })); + try { + const attributes = await fetchUserAttributes(); + setResult({ user: { attributes }, isLoading: false }); + } catch (error) { + setResult({ error, isLoading: false }); + } + }, []); + const handleAuth = React.useCallback( + ({ payload }) => { + switch (payload.event) { + case \\"signedIn\\": + case \\"signUp\\": + case \\"tokenRefresh\\": + case \\"autoSignIn\\": { + fetchCurrentUserAttributes(); + break; + } + case \\"signedOut\\": { + setResult({ user: undefined, isLoading: false }); + break; + } + case \\"tokenRefresh_failure\\": + case \\"signIn_failure\\": { + setResult({ error: payload.data, isLoading: false }); + break; + } + case \\"autoSignIn_failure\\": { + setResult({ error: new Error(payload.message), isLoading: false }); + break; + } + default: { + break; + } + } + }, + [fetchCurrentUserAttributes] + ); + React.useEffect(() => { + const unsubscribe = Hub.listen(\\"auth\\", handleAuth, \\"useAuth\\"); + fetchCurrentUserAttributes(); + return unsubscribe; + }, [handleAuth, fetchCurrentUserAttributes]); + return { + ...result, + }; +}; export const validateField = (value, validations) => { for (const validation of validations) { if (value === undefined || value === \\"\\" || value === null) { diff --git a/packages/codegen-ui-react/lib/imports/import-mapping.ts b/packages/codegen-ui-react/lib/imports/import-mapping.ts index 9e8cff67..bf47cb2e 100644 --- a/packages/codegen-ui-react/lib/imports/import-mapping.ts +++ b/packages/codegen-ui-react/lib/imports/import-mapping.ts @@ -34,6 +34,7 @@ export enum ImportValue { USE_AUTH = 'useAuth', AUTH = 'Auth', SIGN_OUT = 'signOut', + FETCH_USER_ATTRIBUTES = 'fetchUserAttributes', GET_OVERRIDES_FROM_VARIANTS = 'getOverridesFromVariants', USE_BREAKPOINT_VALUE = 'useBreakpointValue', VARIANT = 'Variant', @@ -98,4 +99,5 @@ export const ImportMapping: Record = { [ImportValue.PROCESS_FILE]: ImportSource.UTILS, [ImportValue.USE_EFFECT]: ImportSource.REACT, [ImportValue.USE_STATE]: ImportSource.REACT, + [ImportValue.FETCH_USER_ATTRIBUTES]: ImportSource.AMPLIFY_AUTH, }; diff --git a/packages/codegen-ui-react/lib/react-studio-template-renderer.ts b/packages/codegen-ui-react/lib/react-studio-template-renderer.ts index 1e00a5c5..4860a3fe 100644 --- a/packages/codegen-ui-react/lib/react-studio-template-renderer.ts +++ b/packages/codegen-ui-react/lib/react-studio-template-renderer.ts @@ -777,7 +777,12 @@ export abstract class ReactStudioTemplateRenderer extends StudioTemplateRenderer const authStatement = this.buildUseAuthenticatedUserStatement(); if (authStatement !== undefined) { - this.importCollection.addMappedImport(ImportValue.USE_AUTH); + if (getAmplifyJSVersionToRender(this.renderConfig.dependencies) === AMPLIFY_JS_V5) { + this.importCollection.addMappedImport(ImportValue.USE_AUTH); + } else { + // V6 useAuth is in the utils file + this.importCollection.addImport(ImportSource.UTILS, ImportValue.USE_AUTH); + } statements.push(authStatement); } diff --git a/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts b/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts index 54380017..bc9692f0 100644 --- a/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts +++ b/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts @@ -42,6 +42,7 @@ import { createDataStorePredicateString, useDataStoreBindingString, useAuthSignOutActionStringV6, + useAuthString, } from './utils-file-functions'; import { getAmplifyJSVersionToRender } from './helpers/amplify-js-versioning'; import { AMPLIFY_JS_V6 } from './utils/constants'; @@ -101,9 +102,11 @@ export class ReactUtilsStudioTemplateRenderer extends StudioTemplateRenderer< if (getAmplifyJSVersionToRender(this.renderConfig.dependencies) === AMPLIFY_JS_V6) { this.importCollection.addImport(ImportSource.AMPLIFY_AUTH, ImportValue.SIGN_OUT); + this.importCollection.addImport(ImportSource.AMPLIFY_AUTH, ImportValue.FETCH_USER_ATTRIBUTES); this.importCollection.addImport(ImportSource.AMPLIFY_DATASTORE_V6, ImportValue.DATASTORE); this.importCollection.addImport(ImportSource.AMPLIFY_UTILS, ImportValue.HUB); parsedUtils.push(useAuthSignOutActionStringV6); + parsedUtils.push(useAuthString); } else { this.importCollection.addMappedImport(ImportValue.HUB, ImportValue.DATASTORE, ImportValue.AUTH); parsedUtils.push(useAuthSignOutActionString); diff --git a/packages/codegen-ui-react/lib/utils-file-functions/hooks/index.ts b/packages/codegen-ui-react/lib/utils-file-functions/hooks/index.ts index 3977001a..e084d2db 100644 --- a/packages/codegen-ui-react/lib/utils-file-functions/hooks/index.ts +++ b/packages/codegen-ui-react/lib/utils-file-functions/hooks/index.ts @@ -20,6 +20,7 @@ export * from './useTypeCastFields'; export * from './useDataStoreCreateAction'; export * from './useDataStoreUpdateAction'; export * from './useDataStoreDeleteAction'; +export * from './useAuth'; export * from './createDataStorePredicate'; export * from './useDataStoreBinding'; export * from './constants'; diff --git a/packages/codegen-ui-react/lib/utils-file-functions/hooks/useAuth.ts b/packages/codegen-ui-react/lib/utils-file-functions/hooks/useAuth.ts new file mode 100644 index 00000000..6e7c0e95 --- /dev/null +++ b/packages/codegen-ui-react/lib/utils-file-functions/hooks/useAuth.ts @@ -0,0 +1,77 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +export const useAuthString = `export const useAuth = () => { + const [result, setResult] = React.useState({ + error: undefined, + isLoading: true, + user: undefined, + }); + + const fetchCurrentUserAttributes = React.useCallback(async () => { + setResult((prevResult) => ({ ...prevResult, isLoading: true })); + + try { + const attributes = await fetchUserAttributes(); + setResult({ user: {attributes}, isLoading: false }); + } catch (error) { + setResult({ error, isLoading: false }); + } + }, []); + + const handleAuth = React.useCallback( + ({ payload }) => { + switch (payload.event) { + case 'signedIn': + case 'signUp': + case 'tokenRefresh': + case 'autoSignIn': { + fetchCurrentUserAttributes(); + break; + } + case 'signedOut': { + setResult({ user: undefined, isLoading: false }); + break; + } + + case 'tokenRefresh_failure': + case 'signIn_failure': { + setResult({ error: payload.data, isLoading: false }); + break; + } + case 'autoSignIn_failure': { + setResult({ error: new Error(payload.message), isLoading: false }); + break; + } + + default: { + break; + } + } + }, + [fetchCurrentUserAttributes] + ); + + React.useEffect(() => { + const unsubscribe = Hub.listen('auth', handleAuth, 'useAuth'); + fetchCurrentUserAttributes(); + + return unsubscribe; + }, [handleAuth, fetchCurrentUserAttributes]); + + return { + ...result, + }; +};`;