├── .yarnrc ├── .eslintignore ├── e2e-tests └── auth │ ├── src │ ├── react-app-env.d.ts │ ├── App.css │ ├── index.tsx │ ├── App.tsx │ └── logo.svg │ ├── cypress │ ├── fixtures │ │ ├── profile.json │ │ ├── example.json │ │ └── users.json │ ├── integration │ │ └── navigation.js │ ├── plugins │ │ └── index.js │ └── support │ │ ├── index.js │ │ └── commands.js │ ├── public │ ├── favicon.ico │ ├── manifest.json │ └── index.html │ ├── .env.example │ ├── cypress.json │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── packages ├── amplify-material-ui │ ├── src │ │ ├── i18n │ │ │ ├── index.tsx │ │ │ ├── types.tsx │ │ │ ├── merge-deep.tsx │ │ │ ├── flatten-messages.tsx │ │ │ └── intl-provider.tsx │ │ ├── index.tsx │ │ ├── auth │ │ │ ├── types.tsx │ │ │ ├── federated-sign-in.tsx │ │ │ ├── auth-provider.tsx │ │ │ ├── index.tsx │ │ │ ├── recaptcha.tsx │ │ │ ├── change-auth-state-link.tsx │ │ │ ├── loading.tsx │ │ │ ├── auth-route.tsx │ │ │ ├── authenticator.tsx │ │ │ ├── use-username-field.tsx │ │ │ ├── auth-router.tsx │ │ │ ├── totp-setup.tsx │ │ │ ├── confirm-sign-in.tsx │ │ │ ├── confirm-sign-up.tsx │ │ │ ├── greetings.tsx │ │ │ ├── require-new-password.tsx │ │ │ ├── sign-in.tsx │ │ │ ├── sign-up.tsx │ │ │ ├── forgot-password.tsx │ │ │ └── verify-contact.tsx │ │ ├── notification │ │ │ ├── index.tsx │ │ │ ├── notification.tsx │ │ │ ├── use-notification-context.tsx │ │ │ └── notification-provider.tsx │ │ └── ui │ │ │ ├── phone-field.tsx │ │ │ ├── index.tsx │ │ │ ├── form-container.tsx │ │ │ ├── section-body.tsx │ │ │ ├── section-footer.tsx │ │ │ ├── theme-provider.tsx │ │ │ ├── form-section.tsx │ │ │ ├── section-header.tsx │ │ │ ├── password-field.tsx │ │ │ └── toast.tsx │ ├── .gitignore │ ├── test │ │ ├── helpers │ │ │ └── withTheme.tsx │ │ ├── ui │ │ │ ├── __snapshots__ │ │ │ │ ├── section-body.test.tsx.snap │ │ │ │ ├── section-footer.test.tsx.snap │ │ │ │ ├── form-container.test.tsx.snap │ │ │ │ ├── form-section.test.tsx.snap │ │ │ │ ├── section-header.test.tsx.snap │ │ │ │ └── password-field.test.tsx.snap │ │ │ ├── form-section.test.tsx │ │ │ ├── section-body.test.tsx │ │ │ ├── form-container.test.tsx │ │ │ ├── section-footer.test.tsx │ │ │ ├── section-header.test.tsx │ │ │ ├── password-field.test.tsx │ │ │ └── toast.test.tsx │ │ ├── auth │ │ │ ├── loading.test.tsx │ │ │ ├── __snapshots__ │ │ │ │ ├── change-auth-state-link.test.tsx.snap │ │ │ │ ├── recaptcha.test.tsx.snap │ │ │ │ ├── loading.test.tsx.snap │ │ │ │ ├── forgot-password.test.tsx.snap │ │ │ │ ├── confirm-sign-in.test.tsx.snap │ │ │ │ ├── confirm-sign-up.test.tsx.snap │ │ │ │ └── sign-in.test.tsx.snap │ │ │ ├── confirm-sign-up.test.tsx │ │ │ ├── forgot-password.test.tsx │ │ │ ├── confirm-sign-in.test.tsx │ │ │ ├── change-auth-state-link.test.tsx │ │ │ ├── recaptcha.test.tsx │ │ │ ├── helper.tsx │ │ │ ├── sign-in.test.tsx │ │ │ └── sign-up.test.tsx │ │ └── setupTests.ts │ ├── tsconfig.json │ ├── tsconfig.dev.json │ ├── LICENSE │ ├── package.json │ ├── README.md │ └── CHANGELOG.md └── amplify-auth-hooks │ ├── src │ ├── utils.tsx │ ├── use-sign-out.tsx │ ├── index.tsx │ ├── use-auth-context.tsx │ ├── use-check-contact.tsx │ ├── use-confirm-sign-in.tsx │ ├── use-confirm-sign-up.tsx │ ├── use-totp-setup.tsx │ ├── use-forgot-password.tsx │ ├── use-verify-contact.tsx │ ├── use-require-new-password.tsx │ ├── use-sign-up.tsx │ ├── use-sign-in.tsx │ ├── use-google-federation.tsx │ ├── use-auth.tsx │ └── use-amazon-federation.tsx │ ├── .gitignore │ ├── README.md │ ├── tsconfig.json │ ├── tsconfig.dev.json │ ├── LICENSE │ ├── package.json │ └── CHANGELOG.md ├── .editorconfig ├── scripts ├── e2e-test.sh └── assert-changed-files.sh ├── .vscode ├── settings.json └── launch.json ├── README.md ├── lerna.json ├── jest.config.js ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── package.json ├── .gitignore ├── .eslintrc.js ├── CHANGELOG.md └── .circleci └── config.yml /.yarnrc: -------------------------------------------------------------------------------- 1 | --install.ignore-engines true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | e2e-tests -------------------------------------------------------------------------------- /e2e-tests/auth/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/i18n/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './intl-provider'; 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | charset = utf-8 4 | indent_style = space 5 | tab_width = 2 6 | 7 | -------------------------------------------------------------------------------- /e2e-tests/auth/cypress/fixtures/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 8739, 3 | "name": "Jane", 4 | "email": "jane@example.com" 5 | } -------------------------------------------------------------------------------- /e2e-tests/auth/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hupe1980/amplify-material-ui/HEAD/e2e-tests/auth/public/favicon.ico -------------------------------------------------------------------------------- /e2e-tests/auth/.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_IDENTITY_POOL_ID= 2 | REACT_APP_REGION= 3 | REACT_APP_USER_POOL_ID= 4 | REACT_APP_USER_POOL_WEB_CLIENT_ID= -------------------------------------------------------------------------------- /e2e-tests/auth/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "failOnStatusCode": false, 4 | "video": false 5 | } 6 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/utils.tsx: -------------------------------------------------------------------------------- 1 | export function isEmptyObject(obj: object): boolean { 2 | return Object.keys(obj).length === 0; 3 | } 4 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/i18n/types.tsx: -------------------------------------------------------------------------------- 1 | export type RawIntlMessages = Record; 2 | 3 | export type IntlMessages = Record; 4 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './auth'; 2 | export * from './ui'; 3 | export * from './i18n'; 4 | export * from './notification'; 5 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/types.tsx: -------------------------------------------------------------------------------- 1 | export enum UsernameAttribute { 2 | EMAIL = 'email', 3 | PHONE_NUMBER = 'phone_number', 4 | USERNAME = 'username', 5 | } 6 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/federated-sign-in.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const FederatedSignIn: React.FC = () => { 4 | return null; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/notification/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './notification-provider'; 2 | export * from './notification'; 3 | export * from './use-notification-context'; 4 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | .rts2_cache_cjs 6 | .rts2_cache_esm 7 | .rts2_cache_umd 8 | .rts2_cache_system 9 | dist 10 | lib -------------------------------------------------------------------------------- /packages/amplify-material-ui/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | .rts2_cache_cjs 6 | .rts2_cache_esm 7 | .rts2_cache_umd 8 | .rts2_cache_system 9 | dist 10 | lib -------------------------------------------------------------------------------- /e2e-tests/auth/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /scripts/e2e-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SRC_PATH=$1 3 | CUSTOM_COMMAND="${2:-yarn test}" 4 | 5 | # setting up child integration test link to gatsby packages 6 | cd $SRC_PATH && 7 | sh -c "$CUSTOM_COMMAND" && 8 | echo "e2e test run succeeded" -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": [ 3 | "javascript", 4 | "typescript" 5 | ], 6 | "editor.formatOnSave": true, 7 | "eslint.alwaysShowStatus": true, 8 | "editor.codeActionsOnSave": { 9 | "source.fixAll.eslint": true 10 | } 11 | } -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/README.md: -------------------------------------------------------------------------------- 1 | # amplify-auth-hooks 2 | 3 | ## Install 4 | 5 | ```sh 6 | // with npm 7 | npm install amplify-auth-hooks 8 | 9 | // with yarn 10 | yarn add amplify-auth-hooks 11 | ``` 12 | 13 | ## License 14 | 15 | [MIT](LICENSE) 16 | -------------------------------------------------------------------------------- /e2e-tests/auth/cypress/integration/navigation.js: -------------------------------------------------------------------------------- 1 | describe('navigation', () => { 2 | beforeEach(() => { 3 | cy.visit('/'); 4 | }); 5 | 6 | it(`displays content`, () => { 7 | cy.get('[data-testid=signInForm]').should('be.visible'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Chrome", 6 | "type": "chrome", 7 | "request": "launch", 8 | "url": "http://localhost:3000", 9 | "webRoot": "${workspaceRoot}/e2e-tests/auch/src" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/helpers/withTheme.tsx: -------------------------------------------------------------------------------- 1 | import { createTheme, ThemeProvider } from "@mui/material" 2 | import React from "react" 3 | 4 | export const withTheme = (Component: React.ReactElement): JSX.Element => ( 5 | 6 | {Component} 7 | 8 | ) -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/ui/phone-field.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface PhoneFieldProps { 4 | defaultDialCode?: string; 5 | label?: string; 6 | required?: boolean; 7 | } 8 | 9 | export const PhoneField: React.FC = () => { 10 | return null; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/ui/__snapshots__/section-body.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`section-body should be rendered correctly 1`] = ` 4 | 5 |
8 |
9 |
10 | 11 | `; 12 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/ui/__snapshots__/section-footer.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`section-footer should be rendered correctly 1`] = ` 4 | 5 |
8 |
9 |
10 | 11 | `; 12 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/ui/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './form-container'; 2 | export * from './form-section'; 3 | export * from './section-body'; 4 | export * from './section-footer'; 5 | export * from './section-header'; 6 | export * from './toast'; 7 | export * from './password-field'; 8 | export * from './phone-field'; 9 | export * from './theme-provider'; 10 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/ui/__snapshots__/form-container.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`form-container should be rendered correctly 1`] = ` 4 | 5 |
8 |
9 |
10 | 11 | `; 12 | -------------------------------------------------------------------------------- /e2e-tests/auth/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /scripts/assert-changed-files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | GREP_PATTERN=$1 3 | 4 | FILES_COUNT="$(git diff-tree --no-commit-id --name-only -r $CIRCLE_BRANCH origin/master | grep -E "$GREP_PATTERN" | wc -l)" 5 | 6 | if [ $FILES_COUNT -eq 0 ]; then 7 | echo "0 files matching '$GREP_PATTERN'; exiting and marking successful." 8 | circleci step halt || exit 1 9 | else 10 | echo "$FILES_COUNT file(s) matching '$GREP_PATTERN'; continuing." 11 | fi -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/ui/form-container.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactChild, ReactFragment, ReactPortal } from 'react'; 2 | import { Container } from '@mui/material'; 3 | 4 | export interface FormContainerProps { 5 | children: boolean | ReactChild | ReactFragment | ReactPortal; 6 | } 7 | 8 | export const FormContainer: React.FC = ({ children }) => { 9 | return {children}; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/loading.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { Loading } from '../../src'; 4 | import { withTheme } from '../helpers/withTheme'; 5 | 6 | describe('loading', () => { 7 | it('should be rendered correctly', () => { 8 | const { asFragment } = render(withTheme()); 9 | expect(asFragment()).toMatchSnapshot(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-sign-out.tsx: -------------------------------------------------------------------------------- 1 | import invariant from 'tiny-invariant'; 2 | import Auth from '@aws-amplify/auth'; 3 | 4 | export const useSignOut = (global = false): ((global?: boolean) => Promise) => { 5 | invariant( 6 | Auth && typeof Auth.signOut === 'function', 7 | 'No Auth module found, please ensure @aws-amplify/auth is imported', 8 | ); 9 | 10 | return async (): Promise => Auth.signOut({ global }); 11 | }; 12 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/__snapshots__/change-auth-state-link.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`change-auth-state-link should be rendered correctly 1`] = ` 4 | 5 | 9 | LinkLabel 10 | 11 | 12 | `; 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # amplify-material-ui 2 | 3 | [![CircleCI](https://circleci.com/gh/hupe1980/amplify-material-ui.svg?style=svg)](https://circleci.com/gh/hupe1980/amplify-material-ui) 4 | 5 | > A [Material-UI](https://github.com/mui-org/material-ui) based implementation of [aws amplify](https://github.com/aws-amplify/amplify-js) 6 | 7 | ## Documentation 8 | 9 | [Read the documentation](/packages/amplify-material-ui/README.md). 10 | 11 | ## License 12 | 13 | [MIT](LICENSE) 14 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/auth-provider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useAuth, AuthContext, AuthProps } from 'amplify-auth-hooks'; 3 | 4 | export const AuthProvider: React.FC = (props) => { 5 | const { children, ...authProviderProps } = props; 6 | 7 | const authContexProviderProps = useAuth(authProviderProps); 8 | 9 | return {children}; 10 | }; 11 | -------------------------------------------------------------------------------- /e2e-tests/auth/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # cypress 26 | screenshots 27 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/confirm-sign-up.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import { ConfirmSignUp } from '../../src'; 5 | import { withContext } from './helper'; 6 | 7 | describe('confirm-sign-up', () => { 8 | it('should be rendered correctly', () => { 9 | const { asFragment } = render(withContext()()); 10 | expect(asFragment()).toMatchSnapshot(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/forgot-password.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import { ForgotPassword } from '../../src'; 5 | import { withContext } from './helper'; 6 | 7 | describe('forgot-password', () => { 8 | it('should be rendered correctly', () => { 9 | const { asFragment } = render(withContext()()); 10 | expect(asFragment()).toMatchSnapshot(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": { 3 | "create": { 4 | "license": "MIT" 5 | }, 6 | "publish": { 7 | "allowBranch": "master", 8 | "bump": "patch", 9 | "conventionalCommits": true, 10 | "message": "chore(release): Publish" 11 | } 12 | }, 13 | "npmClient": "yarn", 14 | "useWorkspaces": true, 15 | "version": "1.0.2", 16 | "ignoreChanges": [ 17 | "**/__tests__/**", 18 | "**/__mocks__/**", 19 | "**/__stories__/**" 20 | ] 21 | } -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/confirm-sign-in.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import { withContext } from './helper'; 5 | import { ConfirmSignIn } from '../../src'; 6 | 7 | describe('confirm-sign-in', () => { 8 | it('should be rendered correctly in the confirmSignIn authState', () => { 9 | const { asFragment } = render(withContext()()); 10 | expect(asFragment()).toMatchSnapshot(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/i18n/merge-deep.tsx: -------------------------------------------------------------------------------- 1 | import { RawIntlMessages } from './types'; 2 | 3 | export function mergeDeep(target: RawIntlMessages, source: RawIntlMessages): RawIntlMessages { 4 | if (!source) return target; 5 | 6 | for (const key of Object.keys(source)) { 7 | if (source[key] instanceof Object) { 8 | source[key] = { 9 | ...target[key], 10 | ...source[key], 11 | }; 12 | } 13 | } 14 | 15 | return { 16 | ...target, 17 | ...source, 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/change-auth-state-link.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import { withContext } from './helper'; 5 | import { ChangeAuthStateLink } from '../../src'; 6 | 7 | describe('change-auth-state-link', () => { 8 | it('should be rendered correctly', () => { 9 | const { asFragment } = render(withContext()()); 10 | expect(asFragment()).toMatchSnapshot(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/ui/__snapshots__/form-section.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`form-section should be rendered correctly 1`] = ` 4 | 5 |
8 |
11 |
12 |
13 |
14 | 15 | `; 16 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/ui/form-section.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { FormSection } from '../../src'; 4 | import { withTheme } from '../helpers/withTheme'; 5 | 6 | describe('form-section', () => { 7 | it('should be rendered correctly', () => { 8 | const { asFragment } = render( 9 | withTheme( 10 | 11 |
12 | , 13 | ) 14 | ); 15 | expect(asFragment()).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/ui/section-body.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { SectionBody } from '../../src'; 4 | import { withTheme } from '../helpers/withTheme'; 5 | 6 | describe('section-body', () => { 7 | it('should be rendered correctly', () => { 8 | const { asFragment } = render( 9 | withTheme( 10 | 11 |
12 | , 13 | ) 14 | ); 15 | expect(asFragment()).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/ui/form-container.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { FormContainer } from '../../src'; 4 | import { withTheme } from '../helpers/withTheme'; 5 | 6 | describe('form-container', () => { 7 | it('should be rendered correctly', () => { 8 | const { asFragment } = render( 9 | withTheme( 10 | 11 |
12 | 13 | ), 14 | ); 15 | expect(asFragment()).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/ui/section-footer.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { SectionFooter } from '../../src'; 4 | import { withTheme } from '../helpers/withTheme'; 5 | 6 | describe('section-footer', () => { 7 | it('should be rendered correctly', () => { 8 | const { asFragment } = render( 9 | withTheme( 10 | 11 |
12 | , 13 | ) 14 | ); 15 | expect(asFragment()).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/ui/section-header.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { SectionHeader } from '../../src'; 4 | import { withTheme } from '../helpers/withTheme'; 5 | 6 | describe('section-header', () => { 7 | it('should be rendered correctly', () => { 8 | const { asFragment } = render( 9 | withTheme( 10 | 11 |
12 | , 13 | ) 14 | ); 15 | expect(asFragment()).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/ui/section-body.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Box } from '@mui/material'; 3 | import { Theme } from '@mui/material/styles'; 4 | 5 | import makeStyles from '@mui/styles/makeStyles'; 6 | import createStyles from '@mui/styles/createStyles'; 7 | 8 | const useStyles = makeStyles((_theme: Theme) => 9 | createStyles({ 10 | box: {}, 11 | }), 12 | ); 13 | 14 | export const SectionBody: React.FC = ({ children }) => { 15 | const classes = useStyles(); 16 | 17 | return {children}; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/ui/section-footer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Box } from '@mui/material'; 3 | import { Theme } from '@mui/material/styles'; 4 | 5 | import makeStyles from '@mui/styles/makeStyles'; 6 | import createStyles from '@mui/styles/createStyles'; 7 | 8 | const useStyles = makeStyles((_theme: Theme) => 9 | createStyles({ 10 | box: {}, 11 | }), 12 | ); 13 | 14 | export const SectionFooter: React.FC = ({ children }) => { 15 | const classes = useStyles(); 16 | 17 | return {children}; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './use-amazon-federation'; 2 | export * from './use-auth-context'; 3 | export * from './use-auth'; 4 | export * from './use-check-contact'; 5 | export * from './use-confirm-sign-in'; 6 | export * from './use-confirm-sign-up'; 7 | export * from './use-forgot-password'; 8 | export * from './use-google-federation'; 9 | export * from './use-require-new-password'; 10 | export * from './use-sign-in'; 11 | export * from './use-sign-out'; 12 | export * from './use-sign-up'; 13 | export * from './use-totp-setup'; 14 | export * from './use-verify-contact'; 15 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/i18n/flatten-messages.tsx: -------------------------------------------------------------------------------- 1 | import { RawIntlMessages, IntlMessages } from './types'; 2 | 3 | export const flattenMessages = (nestedMessages: RawIntlMessages, prefix = ''): IntlMessages => 4 | Object.keys(nestedMessages).reduce((messages: Record, key: string) => { 5 | const value = nestedMessages[key]; 6 | const prefixedKey = prefix ? `${prefix}.${key}` : key; 7 | 8 | if (typeof value === 'string') { 9 | messages[prefixedKey] = value; 10 | } else { 11 | Object.assign(messages, flattenMessages(value, prefixedKey)); 12 | } 13 | 14 | return messages; 15 | }, {}); 16 | -------------------------------------------------------------------------------- /e2e-tests/auth/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /e2e-tests/auth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react-jsx", 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | projects: [ 3 | { 4 | displayName: 'amplify-auth-hooks', 5 | rootDir: './packages/amplify-auth-hooks', 6 | preset: 'ts-jest', 7 | testEnvironment: 'jsdom', 8 | globals: { 9 | 'ts-jest': { 10 | tsconfig: '/tsconfig.dev.json', 11 | }, 12 | }, 13 | }, 14 | { 15 | displayName: 'amplify-material-ui', 16 | rootDir: './packages/amplify-material-ui', 17 | preset: 'ts-jest', 18 | testEnvironment: 'jsdom', 19 | globals: { 20 | 'ts-jest': { 21 | tsconfig: '/tsconfig.dev.json', 22 | }, 23 | }, 24 | }, 25 | ], 26 | }; 27 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/__snapshots__/recaptcha.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`recaptcha should be rendered correctly 1`] = ` 4 | 5 | 6 | This site is protected by reCAPTCHA and the Google 7 | 12 | Privacy Policy 13 | 14 | and 15 | 20 | Terms of Service 21 | 22 | apply. 23 | 24 | 25 | `; 26 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/recaptcha.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | jest.mock('react-recaptcha-hook', () => ({ 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | ...(jest.requireActual('react-recaptcha-hook') as any), 7 | useRecaptcha: () => () => jest.fn().mockResolvedValue('token'), 8 | })); 9 | 10 | import { Recaptcha } from '../../src'; 11 | 12 | describe('recaptcha', () => { 13 | it('should be rendered correctly', () => { 14 | const { asFragment } = render( { }} />); 15 | expect(asFragment()).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/ui/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { CssBaseline } from '@mui/material'; 3 | import { 4 | Theme, 5 | ThemeProvider as MUIThemeProvider, 6 | createTheme, 7 | responsiveFontSizes, 8 | } from '@mui/material/styles'; 9 | 10 | export interface ThemeProviderProps { 11 | theme?: Theme; 12 | } 13 | 14 | export const ThemeProvider: React.FC = (props) => { 15 | const { children, theme = createTheme() } = props; 16 | 17 | const enrichedTheme = responsiveFontSizes(theme); 18 | 19 | return ( 20 | 21 | 22 | {children} 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './auth-provider'; 2 | export * from './auth-route'; 3 | export * from './auth-router'; 4 | export * from './authenticator'; 5 | export * from './change-auth-state-link'; 6 | export * from './confirm-sign-in'; 7 | export * from './confirm-sign-up'; 8 | export * from './federated-sign-in'; 9 | export * from './forgot-password'; 10 | export * from './greetings'; 11 | export * from './loading'; 12 | export * from './recaptcha'; 13 | export * from './require-new-password'; 14 | export * from './sign-in'; 15 | export * from './sign-up'; 16 | export * from './totp-setup'; 17 | export * from './types'; 18 | export * from './use-username-field'; 19 | export * from './verify-contact'; 20 | -------------------------------------------------------------------------------- /e2e-tests/auth/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | module.exports = () => { 15 | // `on` is used to hook into various events Cypress emits 16 | // `config` is the resolved Cypress config 17 | }; 18 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "lib/esm", 4 | "module": "esnext", 5 | "target": "es5", 6 | "lib": ["es6", "dom", "es2016", "es2017"], 7 | "jsx": "react", 8 | "declaration": true, 9 | "moduleResolution": "node", 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "esModuleInterop": true, 13 | "noImplicitReturns": true, 14 | "noImplicitThis": true, 15 | "noImplicitAny": true, 16 | "strictNullChecks": true, 17 | "suppressImplicitAnyIndexErrors": true, 18 | "allowSyntheticDefaultImports": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "lib", "test"] 22 | } -------------------------------------------------------------------------------- /packages/amplify-material-ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "lib/esm", 4 | "module": "esnext", 5 | "target": "es5", 6 | "lib": ["es6", "dom", "es2016", "es2017"], 7 | "jsx": "react", 8 | "declaration": true, 9 | "moduleResolution": "node", 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "esModuleInterop": true, 13 | "noImplicitReturns": true, 14 | "noImplicitThis": true, 15 | "noImplicitAny": true, 16 | "strictNullChecks": true, 17 | "suppressImplicitAnyIndexErrors": true, 18 | "allowSyntheticDefaultImports": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "lib", "test"] 22 | } -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "lib/esm", 4 | "module": "esnext", 5 | "target": "es5", 6 | "lib": ["es6", "dom", "es2016", "es2017"], 7 | "jsx": "react", 8 | "declaration": true, 9 | "moduleResolution": "node", 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "esModuleInterop": true, 13 | "noImplicitReturns": true, 14 | "noImplicitThis": true, 15 | "noImplicitAny": true, 16 | "strictNullChecks": true, 17 | "suppressImplicitAnyIndexErrors": true, 18 | "allowSyntheticDefaultImports": true 19 | }, 20 | "include": ["src", "dev"], 21 | "exclude": ["node_modules", "lib"] 22 | } -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/recaptcha.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useRecaptcha, Badge } from 'react-recaptcha-hook'; 3 | 4 | export interface RecaptchaProps { 5 | sitekey: string; 6 | action: string; 7 | onToken: (token: string) => void; 8 | } 9 | 10 | export const Recaptcha: React.FC = (props) => { 11 | const { sitekey, action, onToken } = props; 12 | const execute = useRecaptcha({ sitekey, hideDefaultBadge: true }); 13 | 14 | React.useEffect(() => { 15 | const getToken = async (): Promise => { 16 | const token = await execute(action); 17 | onToken(token); 18 | }; 19 | 20 | getToken(); 21 | }, [action, execute, onToken]); 22 | 23 | return ; 24 | }; 25 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "lib/esm", 4 | "module": "esnext", 5 | "target": "es5", 6 | "lib": ["es6", "dom", "es2016", "es2017"], 7 | "jsx": "react", 8 | "declaration": true, 9 | "moduleResolution": "node", 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "esModuleInterop": true, 13 | "noImplicitReturns": true, 14 | "noImplicitThis": true, 15 | "noImplicitAny": true, 16 | "strictNullChecks": true, 17 | "suppressImplicitAnyIndexErrors": true, 18 | "allowSyntheticDefaultImports": true 19 | }, 20 | "include": ["src", "dev"], 21 | "exclude": ["node_modules", "lib"] 22 | } -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/change-auth-state-link.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from '@mui/material'; 3 | import { AuthData, useAuthContext } from 'amplify-auth-hooks'; 4 | 5 | export interface ChangeAuthStateLinkProps { 6 | label: string; 7 | newState: string; 8 | authData?: AuthData; 9 | [key: string]: any; 10 | } 11 | 12 | export const ChangeAuthStateLink: React.FC = (props) => { 13 | const { label, newState, authData, ...rest } = props; 14 | 15 | const { handleStateChange } = useAuthContext(); 16 | 17 | return ( 18 | handleStateChange(newState, authData)} variant="body2" {...rest}> 19 | {label} 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /e2e-tests/auth/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-auth-context.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export type AuthData = any; 4 | 5 | export interface AuthState { 6 | authState: string; 7 | authData?: AuthData; 8 | } 9 | 10 | export interface AuthContextProps extends AuthState { 11 | handleStateChange: (authState: string, authData: AuthData) => void; 12 | } 13 | 14 | function createNamedContext(name: string, defaultValue: T): React.Context { 15 | const context = React.createContext(defaultValue); 16 | context.displayName = name; 17 | 18 | return context; 19 | } 20 | 21 | export const AuthContext = createNamedContext('Auth', null); 22 | 23 | export const useAuthContext = (): AuthContextProps => React.useContext(AuthContext) as AuthContextProps; 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node: ["12", "14", "16"] 15 | name: Node ${{ matrix.node }} build 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node }} 21 | - name: Installing 22 | run: yarn install --frozen-lockfile 23 | - name: Linting 24 | run: yarn lint 25 | - name: Testing 26 | run: yarn test:unit 27 | - name: Build 28 | run: yarn build 29 | - name: Anti-tamper check 30 | run: git diff --exit-code -------------------------------------------------------------------------------- /e2e-tests/auth/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Amplify from 'aws-amplify'; 4 | 5 | import App from './App'; 6 | 7 | Amplify.configure({ 8 | Auth: { 9 | // REQUIRED only for Federated Authentication - Amazon Cognito Identity Pool ID 10 | identityPoolId: process.env.REACT_APP_IDENTITY_POOL_ID, 11 | 12 | // REQUIRED - Amazon Cognito Region 13 | region: process.env.REACT_APP_REGION, 14 | 15 | // OPTIONAL - Amazon Cognito User Pool ID 16 | userPoolId: process.env.REACT_APP_USER_POOL_ID, 17 | 18 | // OPTIONAL - Amazon Cognito Web Client ID (26-char alphanumeric string) 19 | userPoolWebClientId: process.env.REACT_APP_USER_POOL_WEB_CLIENT_ID, 20 | }, 21 | }); 22 | 23 | ReactDOM.render(, document.getElementById('root')); 24 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/setupTests.ts: -------------------------------------------------------------------------------- 1 | const consoleError = console.error; 2 | 3 | let consoleErrorLog: any[] = []; 4 | 5 | beforeEach(() => { 6 | consoleErrorLog = []; 7 | // Make sure we aren't triggering React console.error calls 8 | console.error = (...args: any[]) => { 9 | // NOTE: We can't throw in here directly as most console.error calls happen 10 | // inside promises and result in an unhandled promise rejection 11 | consoleErrorLog.push(`console.error called with args: ${args}`); 12 | consoleError.apply(console, args as any); 13 | }; 14 | }); 15 | 16 | afterEach(() => { 17 | if (consoleErrorLog.length > 0) { 18 | // Not using an Error object here because the stacktrace is misleading 19 | throw consoleErrorLog[0]; 20 | } 21 | 22 | console.error = consoleError; 23 | }); 24 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/ui/form-section.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Paper } from '@mui/material'; 3 | import { Theme } from '@mui/material/styles'; 4 | 5 | import makeStyles from '@mui/styles/makeStyles'; 6 | import createStyles from '@mui/styles/createStyles'; 7 | 8 | import { FormContainer } from './form-container'; 9 | 10 | const useStyles = makeStyles((theme: Theme) => 11 | createStyles({ 12 | paper: { 13 | marginTop: theme.spacing(12), 14 | display: 'flex', 15 | flexDirection: 'column', 16 | alignItems: 'center', 17 | minWidth: '300px', 18 | padding: theme.spacing(1), 19 | }, 20 | }), 21 | ); 22 | 23 | export const FormSection: React.FC = ({ children }) => { 24 | const classes = useStyles(); 25 | 26 | return ( 27 | 28 | {children} 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/notification/notification.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Toast } from '../ui'; 4 | import { useNotificationContext } from './use-notification-context'; 5 | 6 | export interface NotificationProps { 7 | autoHideDuration?: number; 8 | className?: string; 9 | } 10 | 11 | export const Notification: React.FC = ({ autoHideDuration, className }) => { 12 | const { notification, clearNotification } = useNotificationContext(); 13 | 14 | const handleClose = (_event?: React.SyntheticEvent, reason?: string): void => { 15 | if (reason === 'clickaway') { 16 | return; 17 | } 18 | clearNotification(); 19 | }; 20 | 21 | return ( 22 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/notification/use-notification-context.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface NotificationState { 4 | content: string; 5 | variant: 'success' | 'warning' | 'error' | 'info'; 6 | } 7 | 8 | export interface NotificationContextProps { 9 | showNotification: (notificationState: NotificationState) => void; 10 | clearNotification: () => void; 11 | notification: NotificationState | null; 12 | } 13 | 14 | function createNamedContext(name: string, defaultValue: T): React.Context { 15 | const context = React.createContext(defaultValue); 16 | context.displayName = name; 17 | 18 | return context; 19 | } 20 | 21 | export const NotificationContext = createNamedContext('Notification', null); 22 | 23 | export const useNotificationContext = (): NotificationContextProps => 24 | React.useContext(NotificationContext) as NotificationContextProps; 25 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-check-contact.tsx: -------------------------------------------------------------------------------- 1 | import invariant from 'tiny-invariant'; 2 | import Auth from '@aws-amplify/auth'; 3 | 4 | import { isEmptyObject } from './utils'; 5 | import { AuthData, useAuthContext } from './use-auth-context'; 6 | 7 | export const useCheckContact = (): ((authData: AuthData) => Promise) => { 8 | const { handleStateChange } = useAuthContext(); 9 | 10 | return async (authData: AuthData): Promise => { 11 | invariant( 12 | Auth && typeof Auth.verifiedContact === 'function', 13 | 'No Auth module found, please ensure @aws-amplify/auth is imported', 14 | ); 15 | 16 | const data = await Auth.verifiedContact(authData); 17 | 18 | if (!isEmptyObject(data.verified)) { 19 | handleStateChange('signedIn', authData); 20 | } else { 21 | const newUser = Object.assign(authData, data); 22 | handleStateChange('verifyContact', newUser); 23 | } 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/loading.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { CircularProgress } from '@mui/material'; 3 | import { Theme } from '@mui/material/styles'; 4 | 5 | import makeStyles from '@mui/styles/makeStyles'; 6 | import createStyles from '@mui/styles/createStyles'; 7 | 8 | import { FormSection } from '../ui'; 9 | 10 | const useStyles = makeStyles((theme: Theme) => 11 | createStyles({ 12 | progress: { 13 | margin: theme.spacing(2), 14 | }, 15 | }), 16 | ); 17 | 18 | export interface LoadingProps { 19 | color?: 'inherit' | 'primary' | 'secondary' | undefined; 20 | } 21 | 22 | export const Loading: React.FC = ({ color }) => { 23 | const classes = useStyles(); 24 | 25 | return ( 26 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | Loading.defaultProps = { 33 | color: 'secondary', 34 | }; 35 | -------------------------------------------------------------------------------- /e2e-tests/auth/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/notification/notification-provider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { NotificationState, NotificationContext } from './use-notification-context'; 4 | 5 | export interface NotificationProviderProps { 6 | onShowNotification?: (message: NotificationState) => NotificationState; 7 | } 8 | 9 | export const NotificationProvider: React.FC = ({ children, onShowNotification }) => { 10 | const [notification, setNotification] = React.useState(null); 11 | 12 | const showNotification = (message: NotificationState): void => { 13 | const msg = onShowNotification ? onShowNotification(message) : message; 14 | setNotification(msg); 15 | }; 16 | 17 | const clearNotification = (): void => setNotification(null); 18 | 19 | return ( 20 | 21 | {children} 22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/helper.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { AuthContext, AuthContextProps } from 'amplify-auth-hooks'; 3 | 4 | import { IntlProvider } from '../../src'; 5 | import { NotificationProvider } from '../../src'; 6 | import { ThemeProvider, createTheme } from '@mui/material'; 7 | 8 | export const withContext = 9 | (Component: React.ReactElement) => 10 | (props?: AuthContextProps): JSX.Element => { 11 | const handleStateChange = jest.fn(); 12 | const authState = 'signIn'; 13 | 14 | return ( 15 | 16 | 17 | 24 | 25 | {Component} 26 | 27 | 28 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/ui/password-field.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render, act, fireEvent } from '@testing-library/react'; 3 | import { PasswordField } from '../../src'; 4 | import { withTheme } from '../helpers/withTheme'; 5 | 6 | describe('password-field', () => { 7 | it('should be rendered correctly [type=password]', () => { 8 | const { asFragment } = render(withTheme()); 9 | expect(asFragment()).toMatchSnapshot(); 10 | }); 11 | 12 | it('should change the password visibility', () => { 13 | const { getByTestId } = render(withTheme()); 14 | 15 | const passwordInput = getByTestId('password-input') as HTMLInputElement; 16 | 17 | act(() => { 18 | fireEvent.click(getByTestId('togglePasswordVisibility')); 19 | }); 20 | 21 | expect(passwordInput.type).toEqual('text'); 22 | 23 | act(() => { 24 | fireEvent.click(getByTestId('togglePasswordVisibility')); 25 | }); 26 | 27 | expect(passwordInput.type).toEqual('password'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/ui/toast.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { Toast } from '../../src'; 4 | import { withTheme } from '../helpers/withTheme'; 5 | 6 | describe('loading', () => { 7 | it('should be rendered correctly (error)', () => { 8 | const { asFragment } = render(withTheme()); 9 | expect(asFragment()).toMatchSnapshot(); 10 | }); 11 | 12 | it('should be rendered correctly (info)', () => { 13 | const { asFragment } = render(withTheme()); 14 | expect(asFragment()).toMatchSnapshot(); 15 | }); 16 | 17 | it('should be rendered correctly (warning)', () => { 18 | const { asFragment } = render(withTheme()); 19 | expect(asFragment()).toMatchSnapshot(); 20 | }); 21 | 22 | it('should be rendered correctly (success)', () => { 23 | const { asFragment } = render(withTheme()); 24 | expect(asFragment()).toMatchSnapshot(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/ui/section-header.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Avatar, Box, Typography } from '@mui/material'; 3 | import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; 4 | import { Theme } from '@mui/material/styles'; 5 | 6 | import makeStyles from '@mui/styles/makeStyles'; 7 | import createStyles from '@mui/styles/createStyles'; 8 | 9 | const useStyles = makeStyles((theme: Theme) => 10 | createStyles({ 11 | box: { 12 | marginTop: theme.spacing(1), 13 | display: 'flex', 14 | flexDirection: 'column', 15 | alignItems: 'center', 16 | }, 17 | avatar: { 18 | margin: theme.spacing(1), 19 | backgroundColor: theme.palette.secondary.main, 20 | }, 21 | }), 22 | ); 23 | 24 | export const SectionHeader: React.FC = ({ children }) => { 25 | const classes = useStyles(); 26 | 27 | return ( 28 | 29 | 30 | 31 | 32 | 33 | {children} 34 | 35 | 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 hupe1980 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/amplify-material-ui/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 hupe1980 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/ui/__snapshots__/section-header.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`section-header should be rendered correctly 1`] = ` 4 | 5 |
8 |
11 | 22 |
23 |

26 |
27 |

28 |
29 |
30 | `; 31 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/auth-route.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useAuthContext } from 'amplify-auth-hooks'; 3 | 4 | import { UsernameAttribute } from './types'; 5 | 6 | export interface AuthConfig { 7 | hideSignUpLink?: boolean; 8 | hideForgotPasswordLink?: boolean; 9 | usernameAttribute?: UsernameAttribute; 10 | title?: string | React.ReactElement; 11 | } 12 | export interface AuthRouteProps extends AuthConfig { 13 | validAuthStates: string[]; 14 | component?: React.FC>; 15 | children?: (props: AuthConfig) => React.ReactElement; 16 | } 17 | 18 | export const AuthRoute: React.FC = (props) => { 19 | const { validAuthStates, component, children, ...authConfig } = props; 20 | 21 | const { authState } = useAuthContext(); 22 | 23 | const regExp = new RegExp(`(${authState}|\\*)`); 24 | 25 | const match = validAuthStates.some((validAuthStates) => regExp.test(validAuthStates)); 26 | 27 | return match 28 | ? component 29 | ? React.createElement(component, authConfig) 30 | : children 31 | ? children(authConfig) 32 | : null 33 | : null; 34 | }; 35 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-confirm-sign-in.tsx: -------------------------------------------------------------------------------- 1 | import invariant from 'tiny-invariant'; 2 | import Auth from '@aws-amplify/auth'; 3 | import { ConsoleLogger as Logger } from '@aws-amplify/core'; 4 | 5 | import { useAuthContext } from './use-auth-context'; 6 | import { useCheckContact } from './use-check-contact'; 7 | 8 | const logger = new Logger('useConfirmSignIn'); 9 | 10 | export const useConfirmSignIn = () => { 11 | invariant( 12 | Auth && typeof Auth.confirmSignIn === 'function', 13 | 'No Auth module found, please ensure @aws-amplify/auth is imported', 14 | ); 15 | 16 | const { authData } = useAuthContext(); 17 | const checkContact = useCheckContact(); 18 | 19 | const mfaType = authData && authData.challengeName === 'SOFTWARE_TOKEN_MFA' ? 'TOTP' : 'SMS'; 20 | 21 | const confirm = async (code: string): Promise => { 22 | try { 23 | await Auth.confirmSignIn(authData, code, mfaType === 'TOTP' ? 'SOFTWARE_TOKEN_MFA' : null); 24 | checkContact(authData); 25 | } catch (error) { 26 | logger.error(error); 27 | throw error; 28 | } 29 | }; 30 | 31 | return { 32 | confirm, 33 | mfaType, 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "scripts": { 5 | "outdated:all": "yarn outdated", 6 | "upgrade:all": "yarn upgrade-interactive --latest", 7 | "lint": "eslint '*/**/*.{ts,tsx}' --report-unused-disable-directives", 8 | "prepare": "yarn build", 9 | "build": "lerna run --scope amplify* build", 10 | "test:unit": "jest --runInBand", 11 | "test:e2e-auth": "yarn workspace e2e-test-auth test" 12 | }, 13 | "workspaces": [ 14 | "e2e-tests/*", 15 | "packages/*" 16 | ], 17 | "devDependencies": { 18 | "@types/jest": "^27.0.3", 19 | "@typescript-eslint/eslint-plugin": "^5.6.0", 20 | "@typescript-eslint/parser": "^5.6.0", 21 | "cross-env": "^7.0.2", 22 | "eslint": "8.4.1", 23 | "eslint-config-prettier": "^8.3.0", 24 | "eslint-plugin-import": "2.25.3", 25 | "eslint-plugin-jsx-a11y": "6.5.1", 26 | "eslint-plugin-prettier": "^4.0.0", 27 | "eslint-plugin-react": "7.27.1", 28 | "eslint-plugin-react-hooks": "4.3.0", 29 | "jest": "27.4.3", 30 | "lerna": "^4.0.0", 31 | "prettier": "^2.2.0", 32 | "rimraf": "^3.0.2", 33 | "ts-jest": "^27.1.0", 34 | "typescript": "^4.5.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-confirm-sign-up.tsx: -------------------------------------------------------------------------------- 1 | import invariant from 'tiny-invariant'; 2 | import Auth from '@aws-amplify/auth'; 3 | import { Logger } from '@aws-amplify/core'; 4 | 5 | import { useAuthContext } from './use-auth-context'; 6 | 7 | const logger = new Logger('useConfirmSignUp'); 8 | 9 | export const useConfirmSignUp = () => { 10 | invariant( 11 | (Auth && typeof Auth.confirmSignUp === 'function') || typeof Auth.resendSignUp === 'function', 12 | 'No Auth module found, please ensure @aws-amplify/auth is imported', 13 | ); 14 | 15 | const { handleStateChange, authData = {} } = useAuthContext(); 16 | 17 | const { username } = authData; 18 | 19 | const confirm = async (code: string): Promise => { 20 | try { 21 | await Auth.confirmSignUp(username, code); 22 | handleStateChange('signedUp', null); 23 | } catch (error) { 24 | logger.error(error); 25 | throw error; 26 | } 27 | }; 28 | 29 | const resend = async (): Promise => { 30 | try { 31 | await Auth.resendSignUp(username); 32 | logger.debug('code resent'); 33 | } catch (error) { 34 | logger.error(error); 35 | throw error; 36 | } 37 | }; 38 | 39 | return { 40 | confirm, 41 | resend, 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/__snapshots__/loading.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`loading should be rendered correctly 1`] = ` 4 | 5 |
8 |
11 | 16 | 20 | 28 | 29 | 30 |
31 |
32 |
33 | `; 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .env.local 60 | .env.development 61 | .env.production 62 | 63 | # next.js build output 64 | .next 65 | 66 | # build 67 | dist 68 | 69 | # apple 70 | .DS_Store 71 | 72 | # IDE 73 | .idea 74 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/ui/password-field.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { 3 | InputAdornment, 4 | IconButton, 5 | TextField, 6 | StandardTextFieldProps, 7 | FilledTextFieldProps, 8 | OutlinedTextFieldProps, 9 | } from '@mui/material'; 10 | import Visibility from '@mui/icons-material/Visibility'; 11 | import VisibilityOff from '@mui/icons-material/VisibilityOff'; 12 | 13 | export type PasswordFieldProps = StandardTextFieldProps | FilledTextFieldProps | OutlinedTextFieldProps; 14 | 15 | export const PasswordField: React.FC = (props) => { 16 | const [showPassword, setShowPassword] = React.useState(false); 17 | 18 | return ( 19 | 26 | setShowPassword((showPassword) => !showPassword)} 30 | size="large"> 31 | {showPassword ? : } 32 | 33 | 34 | ), 35 | }} 36 | /> 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /e2e-tests/auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "e2e-test-auth", 3 | "version": "1.0.2", 4 | "private": true, 5 | "scripts": { 6 | "start": "cross-env SKIP_PREFLIGHT_CHECK=true react-scripts start", 7 | "build": "cross-env SKIP_PREFLIGHT_CHECK=true react-scripts build", 8 | "cy:open": "cross-env SKIP_PREFLIGHT_CHECK=true cypress open", 9 | "cy:run": "cross-env SKIP_PREFLIGHT_CHECK=true cypress run --browser chrome", 10 | "test": "cross-env SKIP_PREFLIGHT_CHECK=true start-server-and-test 'yarn start' http://localhost:3000 'yarn cy:run'" 11 | }, 12 | "browserslist": { 13 | "production": [ 14 | ">0.2%", 15 | "not dead", 16 | "not op_mini all" 17 | ], 18 | "development": [ 19 | "last 1 chrome version", 20 | "last 1 firefox version", 21 | "last 1 safari version" 22 | ] 23 | }, 24 | "dependencies": { 25 | "@mui/icons-material": "^5.2.5", 26 | "@mui/material": "^5.2.7", 27 | "amplify-material-ui": "^1.0.2", 28 | "aws-amplify": "^4.3.10", 29 | "react": "^17.0.2", 30 | "react-dom": "^17.0.2", 31 | "react-scripts": "^4.0.3" 32 | }, 33 | "devDependencies": { 34 | "@testing-library/cypress": "^8.0.2", 35 | "@types/node": "^14.14.9", 36 | "@types/react": "^17.0.37", 37 | "@types/react-dom": "^17.0.11", 38 | "cypress": "9.1.1", 39 | "start-server-and-test": "^1.14.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-totp-setup.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import invariant from 'tiny-invariant'; 3 | import Auth from '@aws-amplify/auth'; 4 | import { ConsoleLogger as Logger } from '@aws-amplify/core'; 5 | 6 | import { useAuthContext } from './use-auth-context'; 7 | 8 | const logger = new Logger('useTOTPSetup'); 9 | 10 | export const useTOTPSetup = () => { 11 | invariant( 12 | Auth && 13 | typeof Auth.setupTOTP === 'function' && 14 | typeof Auth.verifyTotpToken === 'function' && 15 | typeof Auth.setPreferredMFA === 'function', 16 | 'No Auth module found, please ensure @aws-amplify/auth is imported', 17 | ); 18 | 19 | const [code, setCode] = useState(null); 20 | 21 | const { authData } = useAuthContext(); 22 | 23 | const verifyTotpToken = async (totpCode: string) => { 24 | try { 25 | await Auth.verifyTotpToken(authData, totpCode); 26 | Auth.setPreferredMFA(authData, 'TOTP'); 27 | } catch (error) { 28 | logger.error(error); 29 | throw error; 30 | } 31 | }; 32 | 33 | useEffect(() => { 34 | const setup = async (): Promise => { 35 | const data = await Auth.setupTOTP(authData); 36 | logger.debug('secret key', data); 37 | 38 | setCode(`otpauth://totp/AWSCognito:${authData.username}?secret=${data}&issuer=AWSCognito`); 39 | }; 40 | setup(); 41 | }, [authData]); 42 | 43 | return { code, verifyTotpToken }; 44 | }; 45 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amplify-auth-hooks", 3 | "version": "1.0.0", 4 | "description": "Hooks for aws amplify auth", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/hupe1980/amplify-material-ui", 8 | "directory": "packages/amplify-auth-hooks" 9 | }, 10 | "keywords": [ 11 | "react", 12 | "aws amplify", 13 | "amplify", 14 | "hooks", 15 | "auth" 16 | ], 17 | "license": "MIT", 18 | "main": "./lib/cjs/index.js", 19 | "module": "./lib/esm/index.js", 20 | "types": "./lib/esm/index.d.ts", 21 | "files": [ 22 | "lib" 23 | ], 24 | "scripts": { 25 | "build": "rimraf lib && yarn build:esm && yarn build:cjs", 26 | "build:esm": "tsc", 27 | "build:cjs": "tsc --module commonjs --outDir lib/cjs", 28 | "test": "jest" 29 | }, 30 | "dependencies": { 31 | "react-script-hook": "^1.5.0", 32 | "tiny-invariant": "^1.2.0" 33 | }, 34 | "peerDependencies": { 35 | "@aws-amplify/auth": ">=1.2.24", 36 | "@aws-amplify/core": ">=1.0.27", 37 | "amazon-cognito-identity-js": ">=3.2.0", 38 | "react": ">=16" 39 | }, 40 | "devDependencies": { 41 | "@aws-amplify/auth": "^4.3.18", 42 | "@aws-amplify/core": "^4.3.10", 43 | "@types/gapi.auth2": "^0.0.55", 44 | "@types/react": "^17.0.0", 45 | "@types/react-dom": "^17.0.0", 46 | "amazon-cognito-identity-js": "^5.2.3", 47 | "react": "^17.0.1", 48 | "react-dom": "^17.0.1" 49 | }, 50 | "sideEffects": false 51 | } 52 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | parserOptions: { 4 | ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features 5 | sourceType: 'module', // Allows for the use of imports 6 | ecmaFeatures: { 7 | jsx: true, // Allows for the parsing of JSX 8 | }, 9 | }, 10 | settings: { 11 | react: { 12 | version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use 13 | }, 14 | }, 15 | extends: [ 16 | 'plugin:react/recommended', 17 | 'plugin:react-hooks/recommended', 18 | 'plugin:@typescript-eslint/recommended', 19 | 'prettier', 20 | ], 21 | plugins: ['@typescript-eslint', 'import'], 22 | rules: { 23 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 24 | // e.g. "@typescript-eslint/explicit-function-return-type": "off", 25 | 'no-restricted-imports': [ 26 | 'error', 27 | { 28 | patterns: ['@material-ui/*/*/*', '!@material-ui/core/test-utils/*'], 29 | }, 30 | ], 31 | 'react/display-name': 'off', 32 | '@typescript-eslint/no-empty-function': 'off', 33 | '@typescript-eslint/no-require-imports': ['error'], 34 | '@typescript-eslint/no-unused-vars': [ 35 | 'error', 36 | { 37 | vars: 'all', 38 | args: 'after-used', 39 | ignoreRestSiblings: false, 40 | argsIgnorePattern: '^_', 41 | }, 42 | ], 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-forgot-password.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import invariant from 'tiny-invariant'; 3 | import Auth from '@aws-amplify/auth'; 4 | import { ConsoleLogger as Logger } from '@aws-amplify/core'; 5 | 6 | import { useAuthContext } from './use-auth-context'; 7 | 8 | const logger = new Logger('useForgotPassword'); 9 | 10 | export const useForgotPassword = () => { 11 | invariant( 12 | (Auth && typeof Auth.forgotPassword === 'function') || typeof Auth.forgotPasswordSubmit === 'function', 13 | 'No Auth module found, please ensure @aws-amplify/auth is imported', 14 | ); 15 | const [delivery, setDelivery] = React.useState(null); 16 | const [username, setUsername] = React.useState(''); 17 | 18 | const { handleStateChange } = useAuthContext(); 19 | 20 | const submit = async (code: string, password: string): Promise => { 21 | try { 22 | await Auth.forgotPasswordSubmit(username, code, password); 23 | handleStateChange('signIn', null); 24 | } catch (error) { 25 | logger.error(error); 26 | throw error; 27 | } 28 | }; 29 | 30 | const send = async (usernameValue: string): Promise => { 31 | try { 32 | const data = await Auth.forgotPassword(usernameValue); 33 | setDelivery(data.CodeDeliveryDetails); 34 | setUsername(usernameValue); 35 | } catch (error) { 36 | logger.error(error); 37 | throw error; 38 | } 39 | }; 40 | 41 | return { 42 | username, 43 | delivery, 44 | submit, 45 | send, 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-verify-contact.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import invariant from 'tiny-invariant'; 3 | import Auth from '@aws-amplify/auth'; 4 | import { ConsoleLogger as Logger } from '@aws-amplify/core'; 5 | 6 | import { useAuthContext } from './use-auth-context'; 7 | 8 | const logger = new Logger('useVerifyContact'); 9 | 10 | export const useVerifyContact = () => { 11 | invariant( 12 | (Auth && typeof Auth.verifyCurrentUserAttribute === 'function') || 13 | typeof Auth.verifyCurrentUserAttributeSubmit === 'function', 14 | 'No Auth module found, please ensure @aws-amplify/auth is imported', 15 | ); 16 | 17 | const { handleStateChange, authData } = useAuthContext(); 18 | const [verifyAttr, setVerifyAttr] = React.useState(null); 19 | 20 | const verify = async (contact: string): Promise => { 21 | try { 22 | const data = await Auth.verifyCurrentUserAttribute(contact); 23 | logger.debug(data); 24 | setVerifyAttr(contact); 25 | } catch (error) { 26 | logger.error(error); 27 | throw error; 28 | } 29 | }; 30 | 31 | const submit = async (code: string): Promise => { 32 | if (!verifyAttr) { 33 | return; 34 | } 35 | 36 | try { 37 | await Auth.verifyCurrentUserAttributeSubmit(verifyAttr, code); 38 | handleStateChange('signedIn', authData); 39 | } catch (error) { 40 | logger.error(error); 41 | throw error; 42 | } 43 | }; 44 | 45 | return { 46 | verifyAttr, 47 | verify, 48 | submit, 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-require-new-password.tsx: -------------------------------------------------------------------------------- 1 | import invariant from 'tiny-invariant'; 2 | import Auth from '@aws-amplify/auth'; 3 | import { ConsoleLogger as Logger } from '@aws-amplify/core'; 4 | 5 | import { useAuthContext } from './use-auth-context'; 6 | import { useCheckContact } from './use-check-contact'; 7 | 8 | const logger = new Logger('useRequireNewPassword'); 9 | 10 | export const useRequireNewPassword = (): ((password: string) => Promise) => { 11 | invariant( 12 | Auth && typeof Auth.completeNewPassword === 'function', 13 | 'No Auth module found, please ensure @aws-amplify/auth is imported', 14 | ); 15 | 16 | const { authData: user, handleStateChange } = useAuthContext(); 17 | const checkContact = useCheckContact(); 18 | 19 | return async (password: string): Promise => { 20 | //const { requiredAttributes } = user.challengeParam; 21 | //const attrs = objectWithProperties(this.inputs, requiredAttributes); 22 | 23 | try { 24 | const updatedUser = await Auth.completeNewPassword(user, password, undefined); 25 | 26 | logger.debug('complete new password', updatedUser); 27 | 28 | if (updatedUser.challengeName === 'SMS_MFA') { 29 | handleStateChange('confirmSignIn', updatedUser); 30 | } else if (updatedUser.challengeName === 'MFA_SETUP') { 31 | logger.debug('TOTP setup', updatedUser.challengeParam); 32 | handleStateChange('TOTPSetup', updatedUser); 33 | } else { 34 | checkContact(updatedUser); 35 | } 36 | } catch (error) { 37 | logger.error(error); 38 | throw error; 39 | } 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-sign-up.tsx: -------------------------------------------------------------------------------- 1 | import invariant from 'tiny-invariant'; 2 | import Auth from '@aws-amplify/auth'; 3 | import { ConsoleLogger as Logger } from '@aws-amplify/core'; 4 | 5 | import { useAuthContext } from './use-auth-context'; 6 | import { CognitoUserAttribute } from 'amazon-cognito-identity-js'; 7 | 8 | const logger = new Logger('useSignUp'); 9 | 10 | export const useSignUp = (): (( 11 | username: string, 12 | password: string, 13 | validationData?: Record, 14 | attributes?: Record, 15 | ) => Promise) => { 16 | invariant( 17 | Auth && typeof Auth.signUp === 'function', 18 | 'No Auth module found, please ensure @aws-amplify/auth is imported', 19 | ); 20 | 21 | const { handleStateChange } = useAuthContext(); 22 | 23 | return async ( 24 | username: string, 25 | password: string, 26 | validationData?: Record, 27 | attributes?: Record, 28 | ): Promise => { 29 | const validationDataArray: CognitoUserAttribute[] = []; 30 | 31 | if (validationData) { 32 | for (const [name, value] of Object.entries(validationData)) { 33 | validationDataArray.push( 34 | new CognitoUserAttribute({ 35 | Name: name, 36 | Value: value, 37 | }), 38 | ); 39 | } 40 | } 41 | 42 | const signupInfo = { 43 | username, 44 | password, 45 | attributes, 46 | validationData: validationDataArray, 47 | }; 48 | 49 | try { 50 | const data = await Auth.signUp(signupInfo); 51 | handleStateChange('confirmSignUp', { username: data.user.getUsername() }); 52 | } catch (error) { 53 | logger.error(error); 54 | throw error; 55 | } 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/authenticator.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { IntlProvider, IntlProviderProps } from '../i18n'; 4 | import { NotificationProvider, NotificationProviderProps } from '../notification'; 5 | import { ThemeProvider, ThemeProviderProps } from '../ui'; 6 | import { StyledEngineProvider } from '@mui/system'; 7 | import { AuthRoute } from './auth-route'; 8 | import { AuthRouter, AuthRouterProps } from './auth-router'; 9 | import { SignUpConfig } from './sign-up'; 10 | 11 | 12 | export interface AuthenticatorProps extends AuthRouterProps, ThemeProviderProps { 13 | intlProps?: IntlProviderProps; 14 | notificationProps?: NotificationProviderProps; 15 | signUpConfig?: SignUpConfig; 16 | } 17 | 18 | export const Authenticator: React.FC = (props) => { 19 | const { children, intlProps, notificationProps, theme, ...authConfig } = props; 20 | 21 | return ( 22 | 23 | 24 | 25 | 26 | {children} 27 | 28 | 29 | 30 | 31 | ); 32 | }; 33 | 34 | export const withAuthenticator = 35 | (Component: React.ComponentType, authenticatorProps: AuthenticatorProps = {}): React.ComponentType => 36 | (props): React.ReactElement => 37 | ( 38 | 39 | 40 | {(authConfigProps): React.ReactElement => } 41 | 42 | 43 | ); 44 | -------------------------------------------------------------------------------- /e2e-tests/auth/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withAuthenticator, Greetings } from 'amplify-material-ui'; 3 | import logo from './logo.svg'; 4 | import './App.css'; 5 | 6 | const App: React.FC = () => ( 7 | <> 8 | 9 | 10 |
11 |
12 | logo 13 |

14 | Edit src/App.tsx and save to reload. 15 |

16 | 22 | Learn React 23 | 24 |
25 | Test 26 |
27 |
28 | 29 | ); 30 | 31 | export default withAuthenticator(App, { 32 | hide: [Greetings], 33 | //hideSignUpLink: true, 34 | //hideForgotPasswordLink: true, 35 | //initialAuthState: 'signUp', 36 | //title: 'TEST_TITLE', 37 | onStateChange: (prevState, newState) => { 38 | console.log('STATE_CHANGE', prevState, newState); 39 | return newState; 40 | }, 41 | notificationProps: { 42 | onShowNotification: (notification) => { 43 | console.log(notification); 44 | return notification; 45 | }, 46 | }, 47 | intlProps: { 48 | supportedLocales: ['en, de'], 49 | // customMessages: { 50 | // en: { 51 | // signIn: { 52 | // header: 'testEN', 53 | // errors: { 54 | // UserNotFoundException: 'TEST_EN', 55 | // }, 56 | // }, 57 | // }, 58 | // de: { 59 | // signIn: { 60 | // header: 'testDE', 61 | // errors: { 62 | // UserNotFoundException: 'TEST_DE', 63 | // }, 64 | // }, 65 | // }, 66 | // }, 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /e2e-tests/auth/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 20 | 29 | React App 30 | 31 | 32 | 33 |
34 | 44 | 45 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amplify-material-ui", 3 | "version": "1.0.2", 4 | "description": "A Material-UI based implementation of aws amplify", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/hupe1980/amplify-material-ui", 9 | "directory": "packages/amplify-material-ui" 10 | }, 11 | "keywords": [ 12 | "material-ui", 13 | "react", 14 | "aws amplify", 15 | "amplify", 16 | "cognito", 17 | "aws", 18 | "auth" 19 | ], 20 | "main": "./lib/cjs/index.js", 21 | "module": "./lib/esm/index.js", 22 | "types": "./lib/esm/index.d.ts", 23 | "files": [ 24 | "lib" 25 | ], 26 | "scripts": { 27 | "build": "rimraf lib && yarn build:esm && yarn build:cjs", 28 | "build:esm": "tsc", 29 | "build:cjs": "tsc --module commonjs --outDir lib/cjs", 30 | "test": "jest" 31 | }, 32 | "dependencies": { 33 | "@mui/styles": "^5.4.1", 34 | "amplify-auth-hooks": "^1.0.0", 35 | "clsx": "^1.1.1", 36 | "formik": "^2.2.9", 37 | "formik-mui": "^4.0.0-alpha.3", 38 | "langtag-utils": "^2.0.2", 39 | "qrcode.react": "^1.0.0", 40 | "react-intl": "^5.23.0", 41 | "react-recaptcha-hook": "^1.2.2", 42 | "tiny-invariant": "^1.2.0", 43 | "yup": "^0.32.9" 44 | }, 45 | "peerDependencies": { 46 | "@aws-amplify/core": "^4.3.10", 47 | "@mui/icons-material": "^5.2.5", 48 | "@mui/material": "^5.2.7", 49 | "react": ">=17" 50 | }, 51 | "devDependencies": { 52 | "@aws-amplify/core": "^4.3.10", 53 | "@emotion/react": "^11.7.1", 54 | "@emotion/styled": "^11.6.0", 55 | "@mui/icons-material": "^5.2.5", 56 | "@mui/material": "^5.2.7", 57 | "@testing-library/jest-dom": "^5.16.1", 58 | "@testing-library/react": "^12.1.2", 59 | "@testing-library/react-hooks": "^7.0.2", 60 | "@types/qrcode.react": "^1.0.1", 61 | "@types/react": "^17.0.0", 62 | "@types/react-dom": "^17.0.0", 63 | "@types/yup": "^0.29.11", 64 | "react": "^17.0.1", 65 | "react-dom": "^17.0.1" 66 | }, 67 | "sideEffects": false 68 | } 69 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/use-username-field.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useIntl } from 'react-intl'; 3 | import { Field } from 'formik'; 4 | import { TextField } from 'formik-mui'; 5 | 6 | import { PhoneField } from '../ui'; 7 | import { UsernameAttribute } from './types'; 8 | 9 | export const useUsernameField = ( 10 | usernameAttribute?: UsernameAttribute, 11 | ): { usernamefieldName: string; usernameField: React.ReactElement } => { 12 | const { formatMessage } = useIntl(); 13 | 14 | switch (usernameAttribute) { 15 | case UsernameAttribute.EMAIL: 16 | return { 17 | usernamefieldName: 'email', 18 | usernameField: ( 19 | 33 | ), 34 | }; 35 | 36 | case UsernameAttribute.PHONE_NUMBER: 37 | return { 38 | usernamefieldName: 'phone', 39 | usernameField: ( 40 | 47 | ), 48 | }; 49 | 50 | default: 51 | return { 52 | usernamefieldName: 'username', 53 | usernameField: ( 54 | 68 | ), 69 | }; 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /e2e-tests/auth/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-sign-in.tsx: -------------------------------------------------------------------------------- 1 | import invariant from 'tiny-invariant'; 2 | 3 | import Auth from '@aws-amplify/auth'; 4 | import { ConsoleLogger as Logger } from '@aws-amplify/core'; 5 | 6 | import { useAuthContext } from './use-auth-context'; 7 | import { useCheckContact } from './use-check-contact'; 8 | 9 | const logger = new Logger('useSignIn'); 10 | 11 | export const useSignIn = (): (( 12 | username: string, 13 | password: string, 14 | validationData?: Record, 15 | ) => Promise) => { 16 | invariant( 17 | Auth && typeof Auth.signIn === 'function', 18 | 'No Auth module found, please ensure @aws-amplify/auth is imported', 19 | ); 20 | 21 | const { handleStateChange } = useAuthContext(); 22 | const checkContact = useCheckContact(); 23 | 24 | return async ( 25 | username: string, 26 | password: string, 27 | validationData?: { 28 | [key: string]: string; 29 | }, 30 | ): Promise => { 31 | try { 32 | const user = await Auth.signIn({ 33 | username, 34 | password, 35 | validationData, 36 | }); 37 | logger.debug(user); 38 | if (user.challengeName === 'SMS_MFA' || user.challengeName === 'SOFTWARE_TOKEN_MFA') { 39 | logger.debug('confirm user with ' + user.challengeName); 40 | handleStateChange('confirmSignIn', user); 41 | } else if (user.challengeName === 'NEW_PASSWORD_REQUIRED') { 42 | logger.debug('require new password', user.challengeParam); 43 | handleStateChange('requireNewPassword', user); 44 | } else if (user.challengeName === 'MFA_SETUP') { 45 | logger.debug('TOTP setup', user.challengeParam); 46 | handleStateChange('TOTPSetup', user); 47 | } else { 48 | checkContact(user); 49 | } 50 | } catch (error) { 51 | if (error.code === 'UserNotConfirmedException') { 52 | logger.debug('the user is not confirmed'); 53 | handleStateChange('confirmSignUp', { username }); 54 | } else if (error.code === 'PasswordResetRequiredException') { 55 | logger.debug('the user requires a new password'); 56 | handleStateChange('forgotPassword', { username }); 57 | } else { 58 | logger.error(error); 59 | throw error; 60 | } 61 | } 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/auth-router.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { AuthProps } from 'amplify-auth-hooks'; 3 | 4 | import { Notification } from '../notification'; 5 | import { AuthProvider } from './auth-provider'; 6 | import { ForgotPassword } from './forgot-password'; 7 | import { Greetings } from './greetings'; 8 | import { Loading } from './loading'; 9 | import { SignIn } from './sign-in'; 10 | import { SignUp } from './sign-up'; 11 | import { RequireNewPassword } from './require-new-password'; 12 | import { ConfirmSignIn } from './confirm-sign-in'; 13 | import { ConfirmSignUp } from './confirm-sign-up'; 14 | import { VerifyContact } from './verify-contact'; 15 | import { AuthRoute, AuthConfig } from './auth-route'; 16 | 17 | const defaultChildren = [ 18 | { 19 | validAuthStates: ['*'], 20 | component: Notification, 21 | }, 22 | { 23 | validAuthStates: ['loading'], 24 | component: Loading, 25 | }, 26 | { 27 | validAuthStates: ['forgotPassword'], 28 | component: ForgotPassword, 29 | }, 30 | { 31 | validAuthStates: ['signedIn'], 32 | component: Greetings, 33 | }, 34 | { 35 | validAuthStates: ['signIn', 'signedOut', 'signedUp'], 36 | component: SignIn, 37 | }, 38 | { 39 | validAuthStates: ['signUp'], 40 | component: SignUp, 41 | }, 42 | { 43 | validAuthStates: ['requireNewPassword'], 44 | component: RequireNewPassword, 45 | }, 46 | { 47 | validAuthStates: ['verifyContact'], 48 | component: VerifyContact, 49 | }, 50 | { 51 | validAuthStates: ['confirmSignIn'], 52 | component: ConfirmSignIn, 53 | }, 54 | { 55 | validAuthStates: ['confirmSignUp'], 56 | component: ConfirmSignUp, 57 | }, 58 | ]; 59 | 60 | export interface AuthRouterProps extends AuthProps, AuthConfig { 61 | hide?: React.FC[]; 62 | } 63 | 64 | export const AuthRouter: React.FC = (props) => { 65 | const { hide = [], children, initialAuthState, onStateChange, ...authConfig } = props; 66 | 67 | const renderChildren = defaultChildren 68 | .filter((item) => !hide.includes(item.component)) 69 | .map((item, index) => ( 70 | 71 | )); 72 | return ( 73 | 74 | {renderChildren} 75 | {children} 76 | 77 | ); 78 | }; 79 | -------------------------------------------------------------------------------- /e2e-tests/auth/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-google-federation.tsx: -------------------------------------------------------------------------------- 1 | import invariant from 'tiny-invariant'; 2 | import Auth from '@aws-amplify/auth'; 3 | import { ConsoleLogger as Logger } from '@aws-amplify/core'; 4 | import useScript from 'react-script-hook'; 5 | 6 | import { useAuthContext } from './use-auth-context'; 7 | 8 | const logger = new Logger('useAmazonFederation'); 9 | 10 | export interface GoogleFederationProps { 11 | clientId: string; 12 | scriptSrc?: string; 13 | } 14 | 15 | export const useGoogleFederation = (props: GoogleFederationProps) => { 16 | invariant( 17 | Auth && typeof Auth.federatedSignIn === 'function' && typeof Auth.currentAuthenticatedUser === 'function', 18 | 'No Auth module found, please ensure @aws-amplify/auth is imported', 19 | ); 20 | 21 | const { clientId, scriptSrc = 'https://apis.google.com/js/platform.js' } = props; 22 | 23 | const { handleStateChange } = useAuthContext(); 24 | 25 | const [loading, error] = useScript({ 26 | src: scriptSrc, 27 | onload: () => { 28 | logger.debug('init gapi'); 29 | 30 | const g = (window as any).gapi; 31 | 32 | g.load('auth2', () => { 33 | g.auth2.init({ 34 | client_id: clientId, 35 | scope: 'profile email openid', 36 | }); 37 | }); 38 | }, 39 | }); 40 | 41 | const federatedSignIn = async (googleUser: gapi.auth2.GoogleUser) => { 42 | const { id_token: idToken, expires_at: expiresAt } = googleUser.getAuthResponse(); 43 | 44 | const profile = googleUser.getBasicProfile(); 45 | 46 | const user = { 47 | email: profile.getEmail(), 48 | name: profile.getName(), 49 | picture: profile.getImageUrl(), 50 | }; 51 | 52 | await Auth.federatedSignIn('google', { token: idToken, expires_at: expiresAt }, user); 53 | 54 | const authUser = await Auth.currentAuthenticatedUser(); 55 | 56 | handleStateChange('signedIn', authUser); 57 | }; 58 | 59 | const signIn = async () => { 60 | const ga = (window as any).gapi.auth2.getAuthInstance(); 61 | 62 | const googleUser = await ga.signIn(); 63 | 64 | return federatedSignIn(googleUser); 65 | }; 66 | 67 | const signOut = async () => { 68 | const googleAuth = 69 | (window as any).gapi && (window as any).gapi.auth2 ? (window as any).gapi.auth2.getAuthInstance() : null; 70 | 71 | if (!googleAuth) { 72 | logger.debug('google Auth undefined'); 73 | return Promise.resolve(); 74 | } 75 | 76 | logger.debug('google signing out'); 77 | await googleAuth.signOut(); 78 | }; 79 | 80 | return { 81 | loading, 82 | error, 83 | signIn, 84 | signOut, 85 | }; 86 | }; 87 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/sign-in.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Auth from '@aws-amplify/auth'; 3 | import { render, fireEvent, act, waitFor } from '@testing-library/react'; 4 | import '@testing-library/jest-dom/extend-expect'; 5 | 6 | import { SignIn } from '../../src'; 7 | import { withContext } from './helper'; 8 | 9 | describe('sign-in', () => { 10 | const handleStateChange = jest.fn(); 11 | 12 | it('should be rendered correctly', () => { 13 | const { asFragment } = render(withContext()()); 14 | expect(asFragment()).toMatchSnapshot(); 15 | }); 16 | 17 | it('should hide the signup link', () => { 18 | const { getByTestId, queryByTestId } = render(withContext()()); 19 | 20 | const forgotPasswordLink = getByTestId('forgot-password-link'); 21 | expect(forgotPasswordLink).toBeInTheDocument(); 22 | 23 | const signUpLink = queryByTestId('sign-up-link'); 24 | expect(signUpLink).not.toBeInTheDocument(); 25 | }); 26 | 27 | it('should hide the forgot password link', () => { 28 | const { getByTestId, queryByTestId } = render(withContext()()); 29 | 30 | const signUpLink = getByTestId('sign-up-link'); 31 | expect(signUpLink).toBeInTheDocument(); 32 | 33 | const forgotPasswordLink = queryByTestId('forgot-password-link'); 34 | expect(forgotPasswordLink).not.toBeInTheDocument(); 35 | }); 36 | 37 | it('it should change state to requireNewPassword if challengeName equals NEW_PASSWORD_REQUIRED', async () => { 38 | jest.spyOn(Auth, 'signIn').mockImplementationOnce(() => { 39 | return new Promise((res) => 40 | res({ 41 | challengeName: 'NEW_PASSWORD_REQUIRED', 42 | }), 43 | ); 44 | }); 45 | 46 | const { getByTestId, getByLabelText } = render( 47 | withContext()({ 48 | authState: 'signIn', 49 | handleStateChange: handleStateChange, 50 | }), 51 | ); 52 | 53 | const usernameInput = getByLabelText('Username', { 54 | exact: false, 55 | selector: 'input', 56 | }); 57 | 58 | const passwordInput = getByLabelText('Password', { 59 | exact: false, 60 | selector: 'input', 61 | }); 62 | 63 | act(() => { 64 | fireEvent.change(usernameInput, { 65 | target: { value: 'test@test.de' }, 66 | }); 67 | 68 | fireEvent.change(passwordInput, { 69 | target: { value: 'Qwertz123!' }, 70 | }); 71 | 72 | fireEvent.click(getByTestId('signInSubmit')); 73 | }); 74 | 75 | await waitFor(() => { 76 | expect(handleStateChange).toHaveBeenCalledTimes(1); 77 | expect(handleStateChange).toHaveBeenCalledWith('requireNewPassword', { 78 | challengeName: 'NEW_PASSWORD_REQUIRED', 79 | }); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/ui/__snapshots__/password-field.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`password-field should be rendered correctly [type=password] 1`] = ` 4 | 5 |
8 |
11 | 19 |
22 | 44 |
45 | 59 |
60 |
61 |
62 | `; 63 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-auth.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import invariant from 'tiny-invariant'; 3 | import Auth from '@aws-amplify/auth'; 4 | import { Hub } from '@aws-amplify/core'; 5 | import { HubCapsule } from '@aws-amplify/core/lib/Hub'; 6 | 7 | import { AuthData, AuthState, AuthContextProps } from './use-auth-context'; 8 | 9 | export interface AuthProps { 10 | initialAuthState?: string; 11 | onStateChange?: (prevState: AuthState, newState: AuthState) => AuthState; 12 | } 13 | 14 | export const useAuth = (props: AuthProps): AuthContextProps => { 15 | invariant( 16 | Auth && typeof Auth.currentAuthenticatedUser === 'function', 17 | 'No Auth module found, please ensure @aws-amplify/auth is imported', 18 | ); 19 | 20 | const { initialAuthState = 'signIn', onStateChange } = props; 21 | 22 | const [state, setState] = React.useState({ 23 | authState: initialAuthState, 24 | authData: null, 25 | }); 26 | 27 | const handleStateChange = React.useCallback( 28 | (authState: string, authData: AuthData) => { 29 | if (authState === 'signedOut') { 30 | authState = 'signIn'; 31 | } 32 | 33 | setState((prev) => { 34 | const newState = onStateChange ? onStateChange(prev, { authState, authData }) : { authState, authData }; 35 | return { 36 | ...prev, 37 | ...newState, 38 | }; 39 | }); 40 | }, 41 | [onStateChange], 42 | ); 43 | 44 | React.useEffect(() => { 45 | const checkUser = async (): Promise => { 46 | try { 47 | const user = await Auth.currentAuthenticatedUser(); 48 | handleStateChange('signedIn', user); 49 | } catch (error) { 50 | handleStateChange(initialAuthState, null); 51 | } 52 | }; 53 | checkUser(); 54 | }, [handleStateChange, initialAuthState]); 55 | 56 | React.useEffect(() => { 57 | const handleAuthCapsule = (capsule: HubCapsule): void => { 58 | const { payload } = capsule; 59 | 60 | switch (payload.event) { 61 | case 'cognitoHostedUI': 62 | handleStateChange('signedIn', payload.data); 63 | break; 64 | case 'cognitoHostedUI_failure': 65 | handleStateChange('signIn', null); 66 | break; 67 | case 'parsingUrl_failure': 68 | handleStateChange('signIn', null); 69 | break; 70 | case 'signOut': 71 | handleStateChange('signIn', null); 72 | break; 73 | case 'customGreetingSignOut': 74 | handleStateChange('signIn', null); 75 | break; 76 | default: 77 | //TODO 78 | break; 79 | } 80 | }; 81 | Hub.listen('auth', handleAuthCapsule); 82 | 83 | return (): void => { 84 | Hub.remove('auth', handleAuthCapsule); 85 | }; 86 | }); 87 | 88 | return { 89 | ...state, 90 | handleStateChange, 91 | }; 92 | }; 93 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/ui/toast.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import clsx from 'clsx'; 3 | import CheckCircleIcon from '@mui/icons-material/CheckCircle'; 4 | import ErrorIcon from '@mui/icons-material/Error'; 5 | import InfoIcon from '@mui/icons-material/Info'; 6 | import CloseIcon from '@mui/icons-material/Close'; 7 | import WarningIcon from '@mui/icons-material/Warning'; 8 | import { colors, IconButton, Snackbar, SnackbarOrigin, SnackbarContent } from '@mui/material'; 9 | import { Theme } from '@mui/material/styles'; 10 | 11 | import makeStyles from '@mui/styles/makeStyles'; 12 | 13 | const variantIcon = { 14 | success: CheckCircleIcon, 15 | warning: WarningIcon, 16 | error: ErrorIcon, 17 | info: InfoIcon, 18 | }; 19 | 20 | const useStyles = makeStyles((theme: Theme) => ({ 21 | success: { 22 | backgroundColor: colors.green[600], 23 | }, 24 | error: { 25 | backgroundColor: theme.palette.error.dark, 26 | }, 27 | info: { 28 | backgroundColor: theme.palette.primary.dark, 29 | }, 30 | warning: { 31 | backgroundColor: colors.amber[700], 32 | }, 33 | icon: { 34 | fontSize: 20, 35 | }, 36 | iconVariant: { 37 | opacity: 0.9, 38 | marginRight: theme.spacing(1), 39 | }, 40 | message: { 41 | display: 'flex', 42 | alignItems: 'center', 43 | }, 44 | })); 45 | 46 | export interface ToastProps { 47 | className?: string; 48 | autoHideDuration?: number; 49 | anchorOrigin?: SnackbarOrigin; 50 | content?: string; 51 | variant?: keyof typeof variantIcon; 52 | open?: boolean; 53 | onClose?: (event: React.SyntheticEvent | React.MouseEvent, reason?: string) => void; 54 | } 55 | 56 | export const Toast: React.FC = (props) => { 57 | const { 58 | autoHideDuration = 6000, 59 | anchorOrigin = { 60 | vertical: 'top', 61 | horizontal: 'center', 62 | } as SnackbarOrigin, 63 | className, 64 | content, 65 | variant = 'info', 66 | open = false, 67 | onClose, 68 | } = props; 69 | 70 | const classes = useStyles(); 71 | 72 | const Icon = variantIcon[variant]; 73 | 74 | return ( 75 | 76 | 81 | 82 | {content} 83 | 84 | } 85 | action={[ 86 | 92 | 93 | , 94 | ]} 95 | /> 96 | 97 | ); 98 | }; 99 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/sign-up.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import { SignUp } from '../../src'; 5 | import { withContext } from './helper'; 6 | 7 | describe('sign-up', () => { 8 | it('should be rendered correctly', () => { 9 | const { asFragment } = render(withContext()()); 10 | expect(asFragment()).toMatchSnapshot(); 11 | }); 12 | 13 | it('should render custom sign-up fields', () => { 14 | const signUpConfig = { 15 | signUpFields: [ 16 | { 17 | label: 'First name', 18 | key: 'given_name', 19 | required: true, 20 | displayOrder: 1, 21 | type: 'text', 22 | }, 23 | { 24 | label: 'Surname', 25 | key: 'family_name', 26 | required: true, 27 | displayOrder: 2, 28 | type: 'text', 29 | }, 30 | { 31 | label: 'Email', 32 | key: 'email', 33 | required: true, 34 | displayOrder: 3, 35 | type: 'email', 36 | }, 37 | { 38 | label: 'Password', 39 | key: 'password', 40 | required: true, 41 | displayOrder: 4, 42 | type: 'password', 43 | }, 44 | ], 45 | }; 46 | 47 | const { asFragment } = render(withContext()()); 48 | expect(asFragment()).toMatchSnapshot(); 49 | }); 50 | 51 | it('should render custom sign-up fields in the correct order', () => { 52 | const signUpConfig = { 53 | signUpFields: [ 54 | { 55 | label: 'Password', 56 | key: 'password', 57 | required: true, 58 | displayOrder: 4, 59 | type: 'password', 60 | }, 61 | { 62 | label: 'Email', 63 | key: 'email', 64 | required: true, 65 | displayOrder: 3, 66 | type: 'email', 67 | }, 68 | { 69 | label: 'Surname', 70 | key: 'family_name', 71 | required: true, 72 | displayOrder: 2, 73 | type: 'text', 74 | }, 75 | { 76 | label: 'First name', 77 | key: 'given_name', 78 | required: true, 79 | displayOrder: 1, 80 | type: 'text', 81 | }, 82 | ], 83 | }; 84 | 85 | const { asFragment } = render(withContext()()); 86 | expect(asFragment()).toMatchSnapshot(); 87 | }); 88 | 89 | it('should allow custom initial values', () => { 90 | const signUpConfig = { 91 | initialValues: { 92 | username: 'username initial value', 93 | email: 'email@example.com', 94 | password: 'extremelysecurepassword1!', 95 | }, 96 | }; 97 | 98 | const { asFragment } = render(withContext()()); 99 | expect(asFragment()).toMatchSnapshot(); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/src/use-amazon-federation.tsx: -------------------------------------------------------------------------------- 1 | import invariant from 'tiny-invariant'; 2 | import Auth from '@aws-amplify/auth'; 3 | import { ConsoleLogger as Logger } from '@aws-amplify/core'; 4 | import useScript from 'react-script-hook'; 5 | 6 | import { useAuthContext } from './use-auth-context'; 7 | 8 | const logger = new Logger('useAmazonFederation'); 9 | 10 | export interface AmazonFederationProps { 11 | clientId: string; 12 | scriptSrc?: string; 13 | } 14 | 15 | interface UserInfo { 16 | success: boolean; 17 | profile: Record; 18 | } 19 | 20 | interface AuthorizeResponse { 21 | access_token?: string; 22 | expires_in: number; 23 | error?: Error; 24 | } 25 | 26 | export const useAmazonFederation = (props: AmazonFederationProps) => { 27 | invariant( 28 | Auth && typeof Auth.federatedSignIn === 'function' && typeof Auth.currentAuthenticatedUser === 'function', 29 | 'No Auth module found, please ensure @aws-amplify/auth is imported', 30 | ); 31 | 32 | const { clientId, scriptSrc = 'https://api-cdn.amazon.com/sdk/login1.js' } = props; 33 | 34 | const { handleStateChange } = useAuthContext(); 35 | 36 | const [loading, error] = useScript({ 37 | src: scriptSrc, 38 | onload: () => { 39 | logger.debug('init amazon'); 40 | const amz = (window as any).amazon; 41 | amz.Login.setClientId(clientId); 42 | }, 43 | }); 44 | 45 | const federatedSignIn = (response: AuthorizeResponse) => { 46 | const { access_token: accessToken, expires_in: expiresIn } = response; 47 | 48 | if (!accessToken) { 49 | return; 50 | } 51 | 52 | const expiresAt = expiresIn * 1000 + Date.now(); 53 | 54 | const amz = (window as any).amazon; 55 | 56 | amz.Login.retrieveProfile(async (userInfo: UserInfo) => { 57 | if (!userInfo.success) { 58 | logger.debug('Get user Info failed'); 59 | return; 60 | } 61 | 62 | const user = { 63 | name: userInfo.profile.Name, 64 | email: userInfo.profile.PrimaryEmail, 65 | }; 66 | 67 | await Auth.federatedSignIn('amazon', { token: accessToken, expires_at: expiresAt }, user); 68 | const authUser = await Auth.currentAuthenticatedUser(); 69 | 70 | handleStateChange('signedIn', authUser); 71 | }); 72 | }; 73 | 74 | const signIn = () => { 75 | const amz = (window as any).amazon; 76 | const options = { scope: 'profile' }; 77 | 78 | amz.Login.authorize(options, async (response: AuthorizeResponse) => { 79 | if (response.error) { 80 | logger.debug('Failed to login with amazon: ' + response.error); 81 | return; 82 | } 83 | 84 | federatedSignIn(response); 85 | }); 86 | }; 87 | 88 | const signOut = () => { 89 | const amz = (window as any).amazon; 90 | 91 | if (!amz) { 92 | logger.debug('Amazon Login sdk undefined'); 93 | return; 94 | } 95 | 96 | logger.debug('Amazon signing out'); 97 | amz.Login.logout(); 98 | }; 99 | 100 | return { 101 | loading, 102 | error, 103 | signIn, 104 | signOut, 105 | }; 106 | }; 107 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/totp-setup.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useIntl, FormattedMessage } from 'react-intl'; 3 | import { useTOTPSetup } from 'amplify-auth-hooks'; 4 | import { Button } from '@mui/material'; 5 | import { Theme } from '@mui/material/styles'; 6 | import makeStyles from '@mui/styles/makeStyles'; 7 | import createStyles from '@mui/styles/createStyles'; 8 | import { Formik, Field, Form } from 'formik'; 9 | import { TextField } from 'formik-mui'; 10 | import QRCode from 'qrcode.react'; 11 | 12 | import { FormSection, SectionHeader, SectionBody, SectionFooter } from '../ui'; 13 | import { useNotificationContext } from '../notification'; 14 | import { Loading } from './loading'; 15 | 16 | const useStyles = makeStyles((theme: Theme) => 17 | createStyles({ 18 | form: { 19 | width: '100%', // Fix IE 11 issue. 20 | marginTop: theme.spacing(1), 21 | }, 22 | submit: { 23 | margin: theme.spacing(3, 0, 2), 24 | }, 25 | }), 26 | ); 27 | 28 | export const TOTPSetup: React.FC = () => { 29 | const classes = useStyles(); 30 | const { formatMessage } = useIntl(); 31 | const { showNotification } = useNotificationContext(); 32 | const { code, verifyTotpToken } = useTOTPSetup(); 33 | 34 | if (!code) return ; 35 | 36 | return ( 37 | 38 | initialValues={{ 39 | totpCode: '', 40 | }} 41 | onSubmit={async ({ totpCode }): Promise => { 42 | try { 43 | await verifyTotpToken(totpCode); 44 | } catch (error) { 45 | const content = formatMessage({ 46 | id: `totpSetup.errors.${error.code}`, 47 | defaultMessage: error.message, 48 | }); 49 | showNotification({ content, variant: 'error' }); 50 | } 51 | }} 52 | > 53 | {({ handleSubmit, isValid }): React.ReactNode => ( 54 | 55 | 56 | 57 | 58 |
63 | 64 | 65 | 79 | 80 | 81 | 91 | 92 |
93 | )} 94 | 95 | ); 96 | }; 97 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [1.0.2](https://github.com/hupe1980/amplify-material-ui/compare/v1.0.1...v1.0.2) (2022-03-07) 7 | 8 | **Note:** Version bump only for package root 9 | 10 | 11 | 12 | 13 | 14 | ## [1.0.1](https://github.com/hupe1980/amplify-material-ui/compare/v1.0.0...v1.0.1) (2022-02-24) 15 | 16 | **Note:** Version bump only for package root 17 | 18 | 19 | 20 | 21 | 22 | # [1.0.0](https://github.com/hupe1980/amplify-material-ui/compare/v0.0.17...v1.0.0) (2022-02-15) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * Add convertLegacyDataProvider ([3785c4a](https://github.com/hupe1980/amplify-material-ui/commit/3785c4a1908a573dd699d34f38c9b736eb6a4025)) 28 | * ApiMapping ([44d17e6](https://github.com/hupe1980/amplify-material-ui/commit/44d17e6bc3b9f3dc32617b4bf9ad3493bdce2b92)) 29 | * Build error ([fadb556](https://github.com/hupe1980/amplify-material-ui/commit/fadb556ac46e21823273cb8373f64b0b4da6f432)) 30 | * CircleCi ([c3feef3](https://github.com/hupe1980/amplify-material-ui/commit/c3feef38006e7f28a3d52aedc1da6bec307f1b8d)) 31 | * CircleCi ([3e0eb09](https://github.com/hupe1980/amplify-material-ui/commit/3e0eb0983745991c44838b2ec0757287f5840288)) 32 | * CircleCi ([f0164dc](https://github.com/hupe1980/amplify-material-ui/commit/f0164dcd0d013ef48692949ae456813287756379)) 33 | * CircleCi ([f17dd4b](https://github.com/hupe1980/amplify-material-ui/commit/f17dd4b84c088281fb5365e670a72a0782b61dc4)) 34 | * CircleCI config ([309c78e](https://github.com/hupe1980/amplify-material-ui/commit/309c78e2a8a7364cecac487e45a2f23d0532d6fb)) 35 | * Customizable default messages ([e760eb8](https://github.com/hupe1980/amplify-material-ui/commit/e760eb819498272ea64e7a828aaf62c267086eee)) 36 | * e2e test script ([fd51b0a](https://github.com/hupe1980/amplify-material-ui/commit/fd51b0a80e9145a83e944b8e84126c99f5d1a6ee)) 37 | * Exports ([cbc99b4](https://github.com/hupe1980/amplify-material-ui/commit/cbc99b4d01cfecbb0687636bc1e3fee3c7f98ae5)) 38 | * Linter ([145dba7](https://github.com/hupe1980/amplify-material-ui/commit/145dba72882cb90284cb772e779f3faec2654e04)) 39 | * **material-ui:** ensure password type is set on the password field ([253d2ed](https://github.com/hupe1980/amplify-material-ui/commit/253d2ed9b149870c839a1ab9d1fe1828a7374f12)) 40 | * Misc ([bc1cb63](https://github.com/hupe1980/amplify-material-ui/commit/bc1cb634ef69e77f937dcd0ced6aa4672e07d6c5)) 41 | * Misc ([78603a5](https://github.com/hupe1980/amplify-material-ui/commit/78603a50e43c661628ce39db2a07222bdd32539e)) 42 | * Misc ([8850e23](https://github.com/hupe1980/amplify-material-ui/commit/8850e233dfee90f530362a677ad3f47b1b6307d0)) 43 | * **test:** MUI components need to be wrapped in ThemeProvider ([4609d7e](https://github.com/hupe1980/amplify-material-ui/commit/4609d7e8cae10c06acec05ea955f0bd977681017)) 44 | * Trim white spaces from username and password ([46943b0](https://github.com/hupe1980/amplify-material-ui/commit/46943b048786e484775f1e9e07a2817b8a9d104f)) 45 | 46 | 47 | ### Features 48 | 49 | * Add ra-data-amplify-rest ([9f7b024](https://github.com/hupe1980/amplify-material-ui/commit/9f7b024f6467356f736226c14ba47713b4af3dea)) 50 | * migrate to mui v5 ([18d7a2e](https://github.com/hupe1980/amplify-material-ui/commit/18d7a2ea1c5ed5dc52e2d5d9992f7329c8138ccc)) 51 | * **sign-up:** Allow custom sign up fields and attributes ([c1c78a8](https://github.com/hupe1980/amplify-material-ui/commit/c1c78a8e08b031d92123940d98209678b6555c30)) 52 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/confirm-sign-in.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useIntl, FormattedMessage } from 'react-intl'; 3 | import { useConfirmSignIn } from 'amplify-auth-hooks'; 4 | import { Button, Grid } from '@mui/material'; 5 | import { Theme } from '@mui/material/styles'; 6 | import makeStyles from '@mui/styles/makeStyles'; 7 | import createStyles from '@mui/styles/createStyles'; 8 | import { Formik, Field, Form } from 'formik'; 9 | import { TextField } from 'formik-mui'; 10 | 11 | import { FormSection, SectionHeader, SectionBody, SectionFooter } from '../ui'; 12 | import { useNotificationContext } from '../notification'; 13 | import { ChangeAuthStateLink } from './change-auth-state-link'; 14 | 15 | const useStyles = makeStyles((theme: Theme) => 16 | createStyles({ 17 | form: { 18 | width: '100%', // Fix IE 11 issue. 19 | marginTop: theme.spacing(1), 20 | }, 21 | submit: { 22 | margin: theme.spacing(3, 0, 2), 23 | }, 24 | }), 25 | ); 26 | 27 | export const ConfirmSignIn: React.FC = () => { 28 | const classes = useStyles(); 29 | const { formatMessage } = useIntl(); 30 | const { showNotification } = useNotificationContext(); 31 | const { confirm, mfaType } = useConfirmSignIn(); 32 | 33 | return ( 34 | 35 | initialValues={{ 36 | code: '', 37 | }} 38 | onSubmit={async ({ code }): Promise => { 39 | try { 40 | await confirm(code); 41 | } catch (error) { 42 | const content = formatMessage({ 43 | id: `confirmSignIn.errors.${error.code}`, 44 | defaultMessage: error.message, 45 | }); 46 | showNotification({ content, variant: 'error' }); 47 | } 48 | }} 49 | > 50 | {({ handleSubmit, isValid }): React.ReactNode => ( 51 | 52 | 53 | 54 | 55 |
56 | 57 | 72 | 73 | 74 | 84 | 85 | 86 | 93 | 94 | 95 | 96 |
97 |
98 | )} 99 | 100 | ); 101 | }; 102 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/confirm-sign-up.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useIntl, FormattedMessage } from 'react-intl'; 3 | import { useConfirmSignUp } from 'amplify-auth-hooks'; 4 | import { Button, Grid, Link } from '@mui/material'; 5 | import { Theme } from '@mui/material/styles'; 6 | import makeStyles from '@mui/styles/makeStyles'; 7 | import createStyles from '@mui/styles/createStyles'; 8 | import { Formik, Field, Form } from 'formik'; 9 | import { TextField } from 'formik-mui'; 10 | 11 | import { FormSection, SectionHeader, SectionBody, SectionFooter } from '../ui'; 12 | import { useNotificationContext } from '../notification'; 13 | import { ChangeAuthStateLink } from './change-auth-state-link'; 14 | 15 | const useStyles = makeStyles((theme: Theme) => 16 | createStyles({ 17 | form: { 18 | width: '100%', // Fix IE 11 issue. 19 | marginTop: theme.spacing(1), 20 | }, 21 | submit: { 22 | margin: theme.spacing(3, 0, 2), 23 | }, 24 | }), 25 | ); 26 | 27 | export const ConfirmSignUp: React.FC = () => { 28 | const classes = useStyles(); 29 | const { formatMessage } = useIntl(); 30 | const { showNotification } = useNotificationContext(); 31 | const { confirm, resend } = useConfirmSignUp(); 32 | 33 | return ( 34 | 35 | initialValues={{ 36 | code: '', 37 | }} 38 | onSubmit={async ({ code }): Promise => { 39 | try { 40 | await confirm(code); 41 | } catch (error) { 42 | const content = formatMessage({ 43 | id: `confirmSignUp.errors.${error.code}`, 44 | defaultMessage: error.message, 45 | }); 46 | showNotification({ content, variant: 'error' }); 47 | } 48 | }} 49 | > 50 | {({ handleSubmit, isValid }): React.ReactNode => ( 51 | 52 | 53 | 54 | 55 |
56 | 57 | 72 | 73 | 74 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 98 | 99 | 100 | 101 |
102 |
103 | )} 104 | 105 | ); 106 | }; 107 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/greetings.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | import { useAuthContext, useSignOut } from 'amplify-auth-hooks'; 4 | import clsx from 'clsx'; 5 | import { AppBar, Toolbar, IconButton, Typography, Menu, MenuItem, Divider } from '@mui/material'; 6 | import { Theme } from '@mui/material/styles'; 7 | import makeStyles from '@mui/styles/makeStyles'; 8 | import createStyles from '@mui/styles/createStyles'; 9 | import AccountCircle from '@mui/icons-material/AccountCircle'; 10 | 11 | import { UsernameAttribute } from './types'; 12 | 13 | const useStyles = makeStyles((theme: Theme) => 14 | createStyles({ 15 | toolbar: { 16 | paddingRight: 24, 17 | }, 18 | title: { 19 | flexGrow: 1, 20 | }, 21 | appBar: { 22 | zIndex: theme.zIndex.drawer + 1, 23 | transition: theme.transitions.create(['width', 'margin'], { 24 | easing: theme.transitions.easing.sharp, 25 | duration: theme.transitions.duration.leavingScreen, 26 | }), 27 | }, 28 | }), 29 | ); 30 | 31 | export interface GreetingsProps { 32 | renderUserMenu?: () => React.ReactElement; 33 | title?: string | React.ReactElement; 34 | className?: string; 35 | burgerMenu?: React.ReactElement; 36 | globalSignOut?: boolean; 37 | usernameAttribute?: UsernameAttribute; 38 | } 39 | 40 | export const Greetings: React.FC = (props) => { 41 | const { 42 | className, 43 | renderUserMenu, 44 | title = 'Greetings', 45 | burgerMenu, 46 | globalSignOut, 47 | usernameAttribute = UsernameAttribute.USERNAME, 48 | } = props; 49 | 50 | const { authData } = useAuthContext(); 51 | 52 | const signOut = useSignOut(globalSignOut); 53 | 54 | const [anchorEl, setAnchorEl] = React.useState(null); 55 | 56 | const classes = useStyles(); 57 | 58 | const handleClick = (event: React.MouseEvent): void => { 59 | setAnchorEl(event.currentTarget); 60 | }; 61 | 62 | const handleClose = (): void => { 63 | setAnchorEl(null); 64 | }; 65 | 66 | const logout = async (): Promise => { 67 | handleClose(); 68 | await signOut(); 69 | }; 70 | 71 | const getUserName = () => { 72 | switch (usernameAttribute) { 73 | case UsernameAttribute.EMAIL: 74 | return authData.attributes ? authData.attributes.email : authData.username; 75 | case UsernameAttribute.PHONE_NUMBER: 76 | return authData.attributes ? authData.attributes.phone_number : authData.username; 77 | //case UsernameAttribute.EMAIL: 78 | default: 79 | return authData.username; 80 | } 81 | }; 82 | 83 | return ( 84 | 85 | 86 | {burgerMenu} 87 | {typeof title === 'string' ? ( 88 | 89 | {title} 90 | 91 | ) : ( 92 |
{title}
93 | )} 94 | 100 | 101 | 102 |
103 | 104 | 105 | 110 | 111 | 112 | {renderUserMenu && renderUserMenu()} 113 | 114 | 115 | 116 | 117 |
118 | ); 119 | }; 120 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/i18n/intl-provider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { IntlProvider as Intl } from 'react-intl'; 3 | import { lookup, navigatorLanguages } from 'langtag-utils'; 4 | 5 | import { flattenMessages } from './flatten-messages'; 6 | import { mergeDeep } from './merge-deep'; 7 | import { RawIntlMessages, IntlMessages } from './types'; 8 | 9 | const DEFAULT_LOCAL = 'en'; 10 | 11 | const defaultMessages: RawIntlMessages = { 12 | de: { 13 | global: { 14 | labels: { 15 | password: 'Passwort', 16 | newPassword: 'Neues Passwort', 17 | username: 'Benutzername', 18 | email: 'Email', 19 | phoneNumber: 'Telefonnummer', 20 | code: 'Code', 21 | confirmationCode: 'Bestätigungs-Code', 22 | }, 23 | }, 24 | signUp: { 25 | header: 'Erstelle einen neuen Account', 26 | buttons: { 27 | create: 'Account erstellen', 28 | }, 29 | links: { 30 | signIn: 'Anmelden', 31 | }, 32 | text: { 33 | haveAccount: 'Schon registriert?', 34 | }, 35 | }, 36 | signIn: { 37 | header: 'Melden Sie sich mit Ihrem Account an', 38 | buttons: { 39 | signIn: 'Anmelden', 40 | }, 41 | links: { 42 | backToSignIn: 'Zurück zur Anmeldung', 43 | forgotPassword: 'Passwort zurücksetzen', 44 | signUp: 'Hier registrieren', 45 | }, 46 | }, 47 | verifyContact: { 48 | header: 'Zurücksetzen des Account benötigt einen verifizierten Account', 49 | buttons: { 50 | submit: 'Abschicken', 51 | verify: 'Verifizieren', 52 | }, 53 | links: { 54 | skip: 'Überspringen', 55 | }, 56 | }, 57 | requireNewPassword: { 58 | header: 'Passwort ändern', 59 | buttons: { 60 | change: 'Ändern', 61 | }, 62 | links: { 63 | backToSignIn: 'Zurück zur Anmeldung', 64 | }, 65 | }, 66 | confirmSignIn: { 67 | header: '{mfaType} Code bestätigen', 68 | buttons: { 69 | confirm: 'Bestätigen', 70 | }, 71 | links: { 72 | backToSignIn: 'Zurück zur Anmeldung', 73 | }, 74 | }, 75 | confirmSignUp: { 76 | header: 'Zurücksetzen des Passworts', 77 | buttons: { 78 | confirm: 'Bestätigen', 79 | }, 80 | links: { 81 | resendCode: 'Code erneut senden', 82 | backToSignIn: 'Zurück zur Anmeldung', 83 | }, 84 | }, 85 | forgotPassword: { 86 | header: 'Zurücksetzen des Passworts', 87 | buttons: { 88 | submit: 'Abschicken', 89 | sendCode: 'Code senden', 90 | }, 91 | links: { 92 | resendCode: 'Code erneut senden', 93 | backToSignIn: 'Zurück zur Anmeldung', 94 | }, 95 | }, 96 | totpSetup: { 97 | header: '', 98 | labels: { 99 | totpCode: '', 100 | }, 101 | buttons: { 102 | verifyTotpToken: '', 103 | }, 104 | }, 105 | greetings: { 106 | menu: { 107 | signedIn: 'Angemeldet als {username}', 108 | logout: 'Logout', 109 | }, 110 | }, 111 | }, 112 | }; 113 | 114 | export interface IntlProviderProps { 115 | supportedLocales?: string[]; 116 | customMessages?: RawIntlMessages; 117 | } 118 | 119 | export const IntlProvider: React.FC = ({ 120 | supportedLocales = [DEFAULT_LOCAL, 'de'], 121 | customMessages = {}, 122 | children, 123 | }) => { 124 | const detectedLocale = lookup(supportedLocales, navigatorLanguages(), DEFAULT_LOCAL); 125 | 126 | const createMessages = (): IntlMessages | undefined => { 127 | const mergedMessages = defaultMessages[detectedLocale] 128 | ? mergeDeep(defaultMessages[detectedLocale], customMessages[detectedLocale]) 129 | : customMessages[detectedLocale]; 130 | 131 | return mergedMessages ? flattenMessages(mergedMessages) : undefined; 132 | }; 133 | 134 | const messages = createMessages(); 135 | 136 | return ( 137 | 138 | {children} 139 | 140 | ); 141 | }; 142 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/README.md: -------------------------------------------------------------------------------- 1 | # amplify-material-ui 2 | 3 | > A [Material-UI](https://github.com/mui-org/material-ui) based implementation of [aws amplify](https://github.com/aws-amplify/amplify-js) 4 | 5 | ## Install 6 | 7 | ```sh 8 | // with npm 9 | npm install amplify-material-ui 10 | 11 | // with yarn 12 | yarn add amplify-material-ui 13 | ``` 14 | 15 | ## How to use 16 | 17 | ```typescript 18 | import React from 'react'; 19 | import Amplify from 'aws-amplify'; 20 | import { withAuthenticator } from 'amplify-material-ui'; 21 | 22 | import awsconfig from './aws-exports'; 23 | import logo from './logo.svg'; 24 | import './App.css'; 25 | 26 | Amplify.configure(awsconfig); 27 | 28 | const App: React.FC = () => ( 29 |
30 |
31 | logo 32 |

33 | Edit src/App.tsx and save to reload. 34 |

35 | 41 | Learn React 42 | 43 |
44 |
45 | ); 46 | 47 | export default withAuthenticator(App); 48 | ``` 49 | 50 | ## Hide default components 51 | 52 | You can hide default components of your Authenticator. 53 | 54 | ```typescript 55 | withAuthenticator(App, { 56 | hide: [Greetings], 57 | }); 58 | ``` 59 | 60 | ## Hide links in default components 61 | 62 | You can disable sign up if you do not allow users to sign up themselves. 63 | 64 | ```typescript 65 | withAuthenticator(App, { 66 | hide: [SignUp], 67 | hideSignUpLink: true, 68 | //hideForgotPasswordLink: true, 69 | }); 70 | ``` 71 | 72 | ## Customize initial authState 73 | 74 | You can change the initial auth state for your Authenticator. By default the initial state is signIn which will shows the signIn component. 75 | 76 | ```typescript 77 | withAuthenticator(App, { 78 | initialAuthState: 'signUp', 79 | }); 80 | ``` 81 | 82 | 83 | ## Localization 84 | 85 | amplify-material-ui is built with [react-intl](https://formatjs.io/docs/getting-started/installation/) support. You can add your own localized strings translations by passing the intlProps into the authenticator. 86 | 87 | ```typescript 88 | withAuthenticator(App, { 89 | intlProps: { 90 | customMessages: { 91 | de: { 92 | global: { 93 | labels: { 94 | username: 'Overwrite username label', 95 | }, 96 | }, 97 | }, 98 | }, 99 | }, 100 | }); 101 | ``` 102 | 103 | 104 | ## Customize sign-up form 105 | 106 | You can customize the sign-up fields as well as the initial values passed into the form: 107 | 108 | ```typescript 109 | const signUpConfig = { 110 | signUpFields: [ 111 | { 112 | label: 'First name', 113 | key: 'given_name', 114 | required: true, 115 | displayOrder: 1, 116 | type: 'text', 117 | intl: { 118 | label: 'signUp.labels.family_name', 119 | } 120 | }, 121 | { 122 | label: 'Surname', 123 | key: 'family_name', 124 | required: true, 125 | displayOrder: 2, 126 | type: 'text', 127 | intl: { 128 | label: 'signUp.labels.given_name', 129 | } 130 | }, 131 | { 132 | label: 'Email', 133 | key: 'email', 134 | required: true, 135 | displayOrder: 3, 136 | type: 'email', 137 | }, 138 | { 139 | label: 'Password', 140 | key: 'password', 141 | required: true, 142 | displayOrder: 4, 143 | type: 'password', 144 | }, 145 | ], 146 | initialValues: { 147 | given_name: 'John', 148 | family_name: 'Smith', 149 | }, 150 | }; 151 | 152 | withAuthenticator(App, { 153 | signUpConfig, 154 | intlProps: { 155 | customMessages: { 156 | de: { 157 | signUp: { 158 | labels: { 159 | given_name: 'Translated given name', 160 | family_name: 'Translated family name', 161 | }, 162 | }, 163 | }, 164 | }, 165 | }, 166 | }); 167 | 168 | ``` 169 | 170 | ## Customize sign-up fields 171 | 172 | ## License 173 | 174 | [MIT](LICENSE) 175 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | executors: 2 | node: 3 | parameters: 4 | image: 5 | type: string 6 | default: "14" 7 | docker: 8 | - image: circleci/node:<< parameters.image >> 9 | 10 | aliases: 11 | e2e-executor: &e2e-executor 12 | docker: 13 | - image: cypress/browsers:node14.15.0-chrome86-ff82 14 | 15 | restore_cache: &restore_cache 16 | restore_cache: 17 | name: Restore node_modules cache 18 | keys: 19 | - yarn-cache-{{ checksum "yarn.lock" }} 20 | 21 | install_node_modules: &install_node_modules 22 | run: 23 | name: Install node modules 24 | command: yarn --frozen-lockfile 25 | 26 | persist_cache: &persist_cache 27 | save_cache: 28 | name: Save node modules cache 29 | key: yarn-cache-{{ checksum "yarn.lock" }} 30 | paths: 31 | - ~/.cache 32 | 33 | attach_to_bootstrap: &attach_to_bootstrap 34 | attach_workspace: 35 | at: packages 36 | 37 | ignore_master: &ignore_master 38 | filters: 39 | branches: 40 | ignore: 41 | - master 42 | 43 | e2e-test-workflow: &e2e-test-workflow 44 | filters: 45 | branches: 46 | ignore: 47 | - master 48 | requires: 49 | - lint 50 | - unit_tests_node14 51 | - unit_tests_node16 52 | 53 | test_template: &test_template 54 | steps: 55 | - checkout 56 | - <<: *restore_cache 57 | - <<: *install_node_modules 58 | - <<: *persist_cache 59 | - <<: *attach_to_bootstrap 60 | - run: yarn test:unit 61 | 62 | commands: 63 | e2e-test: 64 | parameters: 65 | trigger_pattern: 66 | type: string 67 | default: "packages/*|.circleci/*" 68 | test_path: 69 | type: string 70 | test_command: 71 | type: string 72 | default: "" # if unset, e2e-test.sh specifies the command 73 | steps: 74 | - checkout 75 | - run: ./scripts/assert-changed-files.sh "<< parameters.trigger_pattern >>|<< parameters.test_path >>/*" 76 | - <<: *restore_cache 77 | - <<: *install_node_modules 78 | - <<: *persist_cache 79 | - <<: *attach_to_bootstrap 80 | - run: yarn build 81 | - run: ./scripts/e2e-test.sh "<< parameters.test_path >>" "<< parameters.test_command >>" 82 | 83 | version: 2.1 84 | 85 | jobs: 86 | bootstrap: 87 | executor: node 88 | steps: 89 | - checkout 90 | - run: ./scripts/assert-changed-files.sh "packages/*|(e2e-tests/*|.circleci/*" 91 | - <<: *restore_cache 92 | - <<: *install_node_modules 93 | - <<: *persist_cache 94 | - persist_to_workspace: 95 | root: packages 96 | paths: 97 | - "*" 98 | 99 | lint: 100 | executor: node 101 | steps: 102 | - checkout 103 | - <<: *restore_cache 104 | - <<: *install_node_modules 105 | - <<: *persist_cache 106 | - run: yarn lint 107 | 108 | unit_tests_node14: 109 | executor: node 110 | <<: *test_template 111 | 112 | unit_tests_node16: 113 | executor: 114 | name: node 115 | image: "16" 116 | <<: *test_template 117 | 118 | e2e_tests_auth: 119 | <<: *e2e-executor 120 | steps: 121 | - e2e-test: 122 | test_path: e2e-tests/auth 123 | trigger_pattern: "packages/amplify-material-ui|.circleci/*" 124 | 125 | workflows: 126 | version: 2 127 | build-test: 128 | jobs: 129 | - bootstrap 130 | - lint: 131 | requires: 132 | - bootstrap 133 | - unit_tests_node14: 134 | requires: 135 | - lint 136 | - unit_tests_node16: 137 | requires: 138 | - lint 139 | - e2e_tests_auth: 140 | <<: *e2e-test-workflow 141 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/require-new-password.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useIntl, FormattedMessage } from 'react-intl'; 3 | import { useRequireNewPassword } from 'amplify-auth-hooks'; 4 | import { Button, Grid } from '@mui/material'; 5 | import { Theme } from '@mui/material/styles'; 6 | import makeStyles from '@mui/styles/makeStyles'; 7 | import createStyles from '@mui/styles/createStyles'; 8 | import { Formik, Field, Form } from 'formik'; 9 | import { TextField } from 'formik-mui'; 10 | import * as Yup from 'yup'; 11 | 12 | import { FormSection, SectionHeader, SectionBody, SectionFooter } from '../ui'; 13 | import { useNotificationContext } from '../notification'; 14 | import { ChangeAuthStateLink } from './change-auth-state-link'; 15 | 16 | const useStyles = makeStyles((theme: Theme) => 17 | createStyles({ 18 | form: { 19 | width: '100%', // Fix IE 11 issue. 20 | marginTop: theme.spacing(1), 21 | }, 22 | submit: { 23 | margin: theme.spacing(3, 0, 2), 24 | }, 25 | }), 26 | ); 27 | 28 | export const RequireNewPassword: React.FC = () => { 29 | const classes = useStyles(); 30 | const { formatMessage } = useIntl(); 31 | const { showNotification } = useNotificationContext(); 32 | const completeNewPassword = useRequireNewPassword(); 33 | 34 | return ( 35 | 36 | initialValues={{ 37 | password: '', 38 | }} 39 | validationSchema={Yup.object().shape({ 40 | password: Yup.string().required('This field is required'), 41 | confirmPassword: Yup.string().when('password', { 42 | is: (val: string) => val && val.length, 43 | then: Yup.string().oneOf( 44 | [Yup.ref('password')], 45 | formatMessage({ 46 | id: 'requireNewPassword.confirmPassword.notEqual', 47 | defaultMessage: 'passwords do not match', 48 | }), 49 | ), 50 | }), 51 | })} 52 | onSubmit={async ({ password }): Promise => { 53 | try { 54 | await completeNewPassword(password); 55 | } catch (error) { 56 | const content = formatMessage({ 57 | id: `requireNewPassword.errors.${error.code}`, 58 | defaultMessage: error.message, 59 | }); 60 | showNotification({ content, variant: 'error' }); 61 | } 62 | }} 63 | > 64 | {({ handleSubmit, isValid }): React.ReactNode => ( 65 | 66 | 67 | 68 | 69 |
70 | 71 | 85 | 99 | 100 | 101 | 111 | 112 | 113 | 120 | 121 | 122 | 123 |
124 |
125 | )} 126 | 127 | ); 128 | }; 129 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/__snapshots__/forgot-password.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`forgot-password should be rendered correctly 1`] = ` 4 | 5 |
8 |
11 |
14 |
17 | 28 |
29 |

32 | Reset your password 33 |

34 |
35 |
39 |
42 |
45 | 59 |
62 | 71 | 83 |
84 |
85 |
86 |
89 | 99 |
102 | 112 |
113 |
114 |
115 |
116 |
117 |
118 | `; 119 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/__snapshots__/confirm-sign-in.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`confirm-sign-in should be rendered correctly in the confirmSignIn authState 1`] = ` 4 | 5 |
8 |
11 |
14 |
17 | 28 |
29 |

32 | Confirm SMS Code 33 |

34 |
35 |
39 |
42 |
45 | 59 |
62 | 72 | 84 |
85 |
86 |
87 |
90 | 100 |
103 | 113 |
114 |
115 |
116 |
117 |
118 |
119 | `; 120 | -------------------------------------------------------------------------------- /packages/amplify-auth-hooks/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [1.0.0](https://github.com/hupe1980/amplify-material-ui/compare/v0.0.17...v1.0.0) (2022-02-15) 7 | 8 | 9 | ### Features 10 | 11 | * **sign-up:** Allow custom sign up fields and attributes ([c1c78a8](https://github.com/hupe1980/amplify-material-ui/commit/c1c78a8e08b031d92123940d98209678b6555c30)) 12 | 13 | 14 | 15 | 16 | 17 | # [0.3.0](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.2.0...amplify-auth-hooks@0.3.0) (2021-12-19) 18 | 19 | **Note:** Version bump only for package amplify-auth-hooks 20 | 21 | 22 | 23 | 24 | 25 | # [0.2.0](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.1.0...amplify-auth-hooks@0.2.0) (2021-12-07) 26 | 27 | **Note:** Version bump only for package amplify-auth-hooks 28 | 29 | 30 | 31 | 32 | 33 | # [0.1.0](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.17...amplify-auth-hooks@0.1.0) (2021-04-12) 34 | 35 | **Note:** Version bump only for package amplify-auth-hooks 36 | 37 | 38 | 39 | 40 | 41 | ## [0.0.17](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.16...amplify-auth-hooks@0.0.17) (2020-11-22) 42 | 43 | **Note:** Version bump only for package amplify-auth-hooks 44 | 45 | 46 | 47 | 48 | 49 | ## [0.0.16](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.15...amplify-auth-hooks@0.0.16) (2020-10-24) 50 | 51 | 52 | ### Features 53 | 54 | * **sign-up:** Allow custom sign up fields and attributes ([c1c78a8](https://github.com/hupe1980/amplify-material-ui/commit/c1c78a8e08b031d92123940d98209678b6555c30)) 55 | 56 | 57 | 58 | 59 | 60 | ## [0.0.15](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.14...amplify-auth-hooks@0.0.15) (2020-10-21) 61 | 62 | **Note:** Version bump only for package amplify-auth-hooks 63 | 64 | 65 | 66 | 67 | 68 | ## [0.0.14](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.13...amplify-auth-hooks@0.0.14) (2020-10-01) 69 | 70 | **Note:** Version bump only for package amplify-auth-hooks 71 | 72 | 73 | 74 | 75 | 76 | ## [0.0.13](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.12...amplify-auth-hooks@0.0.13) (2020-07-06) 77 | 78 | **Note:** Version bump only for package amplify-auth-hooks 79 | 80 | 81 | 82 | 83 | 84 | ## [0.0.12](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.11...amplify-auth-hooks@0.0.12) (2020-01-16) 85 | 86 | **Note:** Version bump only for package amplify-auth-hooks 87 | 88 | 89 | 90 | 91 | 92 | ## [0.0.11](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.10...amplify-auth-hooks@0.0.11) (2020-01-05) 93 | 94 | **Note:** Version bump only for package amplify-auth-hooks 95 | 96 | 97 | 98 | 99 | 100 | ## [0.0.10](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.9...amplify-auth-hooks@0.0.10) (2019-12-14) 101 | 102 | **Note:** Version bump only for package amplify-auth-hooks 103 | 104 | 105 | 106 | 107 | 108 | ## [0.0.9](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.8...amplify-auth-hooks@0.0.9) (2019-12-06) 109 | 110 | **Note:** Version bump only for package amplify-auth-hooks 111 | 112 | 113 | 114 | 115 | 116 | ## [0.0.8](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.7...amplify-auth-hooks@0.0.8) (2019-11-28) 117 | 118 | **Note:** Version bump only for package amplify-auth-hooks 119 | 120 | 121 | 122 | 123 | 124 | ## [0.0.7](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.6...amplify-auth-hooks@0.0.7) (2019-11-27) 125 | 126 | **Note:** Version bump only for package amplify-auth-hooks 127 | 128 | 129 | 130 | 131 | 132 | ## [0.0.6](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.5...amplify-auth-hooks@0.0.6) (2019-11-23) 133 | 134 | **Note:** Version bump only for package amplify-auth-hooks 135 | 136 | 137 | 138 | 139 | 140 | ## [0.0.5](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.4...amplify-auth-hooks@0.0.5) (2019-11-23) 141 | 142 | **Note:** Version bump only for package amplify-auth-hooks 143 | 144 | 145 | 146 | 147 | 148 | ## [0.0.4](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.3...amplify-auth-hooks@0.0.4) (2019-11-20) 149 | 150 | **Note:** Version bump only for package amplify-auth-hooks 151 | 152 | 153 | 154 | 155 | 156 | ## [0.0.3](https://github.com/hupe1980/amplify-material-ui/compare/amplify-auth-hooks@0.0.2...amplify-auth-hooks@0.0.3) (2019-11-19) 157 | 158 | **Note:** Version bump only for package amplify-auth-hooks 159 | 160 | 161 | 162 | 163 | 164 | ## 0.0.2 (2019-11-18) 165 | 166 | **Note:** Version bump only for package amplify-auth-hooks 167 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/sign-in.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useIntl, FormattedMessage } from 'react-intl'; 3 | import { Button, Grid } from '@mui/material'; 4 | import { Theme } from '@mui/material/styles'; 5 | import makeStyles from '@mui/styles/makeStyles'; 6 | import createStyles from '@mui/styles/createStyles'; 7 | import { Formik, Field, Form } from 'formik'; 8 | import { TextField } from 'formik-mui'; 9 | import { useSignIn } from 'amplify-auth-hooks'; 10 | 11 | import { FormSection, SectionHeader, SectionBody, SectionFooter } from '../ui'; 12 | import { useNotificationContext } from '../notification'; 13 | import { useUsernameField } from './use-username-field'; 14 | import { ChangeAuthStateLink } from './change-auth-state-link'; 15 | import { UsernameAttribute } from './types'; 16 | 17 | const useStyles = makeStyles((theme: Theme) => 18 | createStyles({ 19 | form: { 20 | width: '100%', // Fix IE 11 issue. 21 | marginTop: theme.spacing(1), 22 | }, 23 | submit: { 24 | margin: theme.spacing(3, 0, 2), 25 | }, 26 | }), 27 | ); 28 | 29 | export interface SignInProps { 30 | validationData?: { [key: string]: string }; 31 | hideSignUpLink?: boolean; 32 | hideForgotPasswordLink?: boolean; 33 | usernameAttribute?: UsernameAttribute; 34 | } 35 | 36 | export const SignIn: React.FC = (props) => { 37 | const { validationData, hideSignUpLink = false, hideForgotPasswordLink = false, usernameAttribute } = props; 38 | 39 | const classes = useStyles(); 40 | const { formatMessage } = useIntl(); 41 | const { showNotification } = useNotificationContext(); 42 | const signIn = useSignIn(); 43 | const { usernamefieldName, usernameField } = useUsernameField(usernameAttribute); 44 | 45 | return ( 46 | 47 | initialValues={{ 48 | [usernamefieldName]: '', 49 | password: '', 50 | }} 51 | onSubmit={async (values): Promise => { 52 | try { 53 | await signIn(values[usernamefieldName].trim(), values['password'].trim(), validationData); 54 | } catch (error) { 55 | const content = formatMessage({ 56 | id: `signIn.errors.${error.code}`, 57 | defaultMessage: error.message, 58 | }); 59 | showNotification({ content, variant: 'error' }); 60 | } 61 | }} 62 | > 63 | {({ handleSubmit, isValid }): React.ReactNode => ( 64 | 65 | 66 | 67 | 68 |
74 | 75 | {usernameField} 76 | 91 | 92 | 93 | 104 | 105 | {!hideForgotPasswordLink && ( 106 | 107 | 115 | 116 | )} 117 | {!hideSignUpLink && ( 118 | 119 | 127 | 128 | )} 129 | 130 | 131 |
132 |
133 | )} 134 | 135 | ); 136 | }; 137 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/__snapshots__/confirm-sign-up.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`confirm-sign-up should be rendered correctly 1`] = ` 4 | 5 |
8 |
11 |
14 |
17 | 28 |
29 |

32 | Confirm Sign Up 33 |

34 |
35 |
39 |
42 |
45 | 59 |
62 | 72 | 84 |
85 |
86 |
87 |
90 | 100 |
103 | 113 | 123 |
124 |
125 |
126 |
127 |
128 |
129 | `; 130 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/sign-up.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useIntl, FormattedMessage } from 'react-intl'; 3 | import { useSignUp } from 'amplify-auth-hooks'; 4 | import { Button, Grid } from '@mui/material'; 5 | import { Theme } from '@mui/material/styles'; 6 | import makeStyles from '@mui/styles/makeStyles'; 7 | import createStyles from '@mui/styles/createStyles'; 8 | import { Formik, Field, Form } from 'formik'; 9 | import { TextField } from 'formik-mui'; 10 | 11 | import { FormSection, SectionHeader, SectionBody, SectionFooter } from '../ui'; 12 | import { useNotificationContext } from '../notification'; 13 | import { ChangeAuthStateLink } from './change-auth-state-link'; 14 | import { UsernameAttribute } from './types'; 15 | 16 | const useStyles = makeStyles((theme: Theme) => 17 | createStyles({ 18 | form: { 19 | width: '100%', // Fix IE 11 issue. 20 | marginTop: theme.spacing(1), 21 | }, 22 | submit: { 23 | margin: theme.spacing(3, 0, 2), 24 | }, 25 | }), 26 | ); 27 | 28 | export type SignUpValues = Record; 29 | 30 | export interface SignUpConfig { 31 | signUpFields?: SignUpField[]; 32 | initialValues?: SignUpValues; 33 | } 34 | 35 | export type SignUpField = { 36 | key: string; 37 | displayOrder?: number; 38 | label?: string; 39 | placeholder?: string; 40 | type?: string; 41 | required?: boolean; 42 | intl?: Record; 43 | } & Record>; 44 | 45 | export interface SignUpProps { 46 | validationData?: { [key: string]: string }; 47 | usernameAttribute?: UsernameAttribute; 48 | signUpConfig?: SignUpConfig; 49 | } 50 | 51 | const defaultSignUpFields: SignUpField[] = [ 52 | { 53 | label: 'Username', 54 | key: 'username', 55 | required: true, 56 | type: 'text', 57 | displayOrder: 1, 58 | intl: { 59 | label: 'global.labels.username', 60 | }, 61 | }, 62 | { 63 | label: 'Password', 64 | key: 'password', 65 | required: true, 66 | type: 'password', 67 | displayOrder: 2, 68 | intl: { 69 | label: 'global.labels.password', 70 | }, 71 | }, 72 | { 73 | label: 'Email', 74 | key: 'email', 75 | required: true, 76 | type: 'email', 77 | displayOrder: 3, 78 | intl: { 79 | label: 'global.labels.email', 80 | }, 81 | }, 82 | ]; 83 | 84 | const sortByDisplayOrder = (a: SignUpField, b?: SignUpField) => { 85 | if (a.displayOrder === undefined) { 86 | return -1; 87 | } 88 | 89 | if (b?.displayOrder === undefined) { 90 | return 1; 91 | } 92 | 93 | return Math.sign(a.displayOrder - (b.displayOrder ?? 0)); 94 | }; 95 | 96 | export const SignUp: React.FC = (props) => { 97 | const { validationData, signUpConfig } = props; 98 | 99 | const signUpFields = signUpConfig?.signUpFields ?? defaultSignUpFields; 100 | const initialValues = signUpConfig?.initialValues ?? {}; 101 | signUpFields.forEach((field) => (initialValues[field.key] = initialValues[field.key] ?? '')); 102 | 103 | const classes = useStyles(); 104 | const { formatMessage } = useIntl(); 105 | const { showNotification } = useNotificationContext(); 106 | const signUp = useSignUp(); 107 | 108 | return ( 109 | 110 | initialValues={initialValues} 111 | onSubmit={async ({ email, password, ...attributes }): Promise => { 112 | try { 113 | await signUp(email, password, validationData, attributes); 114 | } catch (error) { 115 | const content = formatMessage({ 116 | id: `signUp.errors.${error.code}`, 117 | defaultMessage: error.message, 118 | }); 119 | showNotification({ content, variant: 'error' }); 120 | } 121 | }} 122 | > 123 | {({ handleSubmit, isValid }): React.ReactNode => ( 124 | 125 | 126 | 127 | 128 |
129 | 130 | {signUpFields 131 | .sort(sortByDisplayOrder) 132 | .map(({ key, displayOrder: _displayOrder, label, intl, ...inputProps }) => { 133 | return ( 134 | 152 | ); 153 | })} 154 | 155 | 156 | 166 | 167 | 168 | {' '} 169 | 176 | 177 | 178 | 179 |
180 |
181 | )} 182 | 183 | ); 184 | }; 185 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/forgot-password.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useIntl, FormattedMessage } from 'react-intl'; 3 | import { useForgotPassword } from 'amplify-auth-hooks'; 4 | import { Button, Grid, Link } from '@mui/material'; 5 | import { Theme } from '@mui/material/styles'; 6 | import makeStyles from '@mui/styles/makeStyles'; 7 | import createStyles from '@mui/styles/createStyles'; 8 | import { Formik, Field, Form } from 'formik'; 9 | import { TextField } from 'formik-mui'; 10 | 11 | import { FormSection, SectionHeader, SectionBody, SectionFooter } from '../ui'; 12 | import { useNotificationContext } from '../notification'; 13 | import { useUsernameField } from './use-username-field'; 14 | import { ChangeAuthStateLink } from './change-auth-state-link'; 15 | 16 | const useStyles = makeStyles((theme: Theme) => 17 | createStyles({ 18 | form: { 19 | width: '100%', // Fix IE 11 issue. 20 | marginTop: theme.spacing(1), 21 | }, 22 | submit: { 23 | margin: theme.spacing(3, 0, 2), 24 | }, 25 | }), 26 | ); 27 | 28 | export const ForgotPassword: React.FC = () => { 29 | const classes = useStyles(); 30 | const { formatMessage } = useIntl(); 31 | const { showNotification } = useNotificationContext(); 32 | const { delivery, submit, send, username } = useForgotPassword(); 33 | const { usernamefieldName, usernameField } = useUsernameField(); 34 | 35 | const renderSectionHeader = (): React.ReactElement => ( 36 | 37 | 38 | 39 | ); 40 | 41 | const submitView = (): React.ReactElement => ( 42 | 43 | initialValues={{ 44 | code: '', 45 | password: '', 46 | }} 47 | key="forgot-password-submit-form" 48 | onSubmit={async ({ code, password }): Promise => { 49 | try { 50 | await submit(code, password); 51 | } catch (error) { 52 | const content = formatMessage({ 53 | id: `forgotPassword.errors.${error.code}`, 54 | defaultMessage: error.message, 55 | }); 56 | showNotification({ content, variant: 'error' }); 57 | } 58 | }} 59 | > 60 | {({ handleSubmit, isValid }): React.ReactNode => ( 61 | 62 | {renderSectionHeader()} 63 |
64 | 65 | 79 | 93 | 94 | 95 | 105 | 106 | 107 | => send(username)} variant="body2"> 108 | 109 | 110 | 111 | 112 | 113 |
114 |
115 | )} 116 | 117 | ); 118 | 119 | const sendView = (): React.ReactElement => ( 120 | 121 | initialValues={{ 122 | [usernamefieldName]: '', 123 | }} 124 | key="forgot-password-send-form" 125 | onSubmit={async (values): Promise => { 126 | try { 127 | await send(values[usernamefieldName]); 128 | } catch (error) { 129 | const content = formatMessage({ 130 | id: `forgotPassword.errors.${error.code}`, 131 | defaultMessage: error.message, 132 | }); 133 | showNotification({ content, variant: 'error' }); 134 | } 135 | }} 136 | > 137 | {({ handleSubmit, isValid }): React.ReactNode => ( 138 | 139 | {renderSectionHeader()} 140 |
141 | {usernameField} 142 | 143 | 153 | 154 | 155 | 162 | 163 | 164 | 165 |
166 |
167 | )} 168 | 169 | ); 170 | 171 | return delivery || username ? submitView() : sendView(); 172 | }; 173 | -------------------------------------------------------------------------------- /e2e-tests/auth/cypress/fixtures/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Leanne Graham", 5 | "username": "Bret", 6 | "email": "Sincere@april.biz", 7 | "address": { 8 | "street": "Kulas Light", 9 | "suite": "Apt. 556", 10 | "city": "Gwenborough", 11 | "zipcode": "92998-3874", 12 | "geo": { 13 | "lat": "-37.3159", 14 | "lng": "81.1496" 15 | } 16 | }, 17 | "phone": "1-770-736-8031 x56442", 18 | "website": "hildegard.org", 19 | "company": { 20 | "name": "Romaguera-Crona", 21 | "catchPhrase": "Multi-layered client-server neural-net", 22 | "bs": "harness real-time e-markets" 23 | } 24 | }, 25 | { 26 | "id": 2, 27 | "name": "Ervin Howell", 28 | "username": "Antonette", 29 | "email": "Shanna@melissa.tv", 30 | "address": { 31 | "street": "Victor Plains", 32 | "suite": "Suite 879", 33 | "city": "Wisokyburgh", 34 | "zipcode": "90566-7771", 35 | "geo": { 36 | "lat": "-43.9509", 37 | "lng": "-34.4618" 38 | } 39 | }, 40 | "phone": "010-692-6593 x09125", 41 | "website": "anastasia.net", 42 | "company": { 43 | "name": "Deckow-Crist", 44 | "catchPhrase": "Proactive didactic contingency", 45 | "bs": "synergize scalable supply-chains" 46 | } 47 | }, 48 | { 49 | "id": 3, 50 | "name": "Clementine Bauch", 51 | "username": "Samantha", 52 | "email": "Nathan@yesenia.net", 53 | "address": { 54 | "street": "Douglas Extension", 55 | "suite": "Suite 847", 56 | "city": "McKenziehaven", 57 | "zipcode": "59590-4157", 58 | "geo": { 59 | "lat": "-68.6102", 60 | "lng": "-47.0653" 61 | } 62 | }, 63 | "phone": "1-463-123-4447", 64 | "website": "ramiro.info", 65 | "company": { 66 | "name": "Romaguera-Jacobson", 67 | "catchPhrase": "Face to face bifurcated interface", 68 | "bs": "e-enable strategic applications" 69 | } 70 | }, 71 | { 72 | "id": 4, 73 | "name": "Patricia Lebsack", 74 | "username": "Karianne", 75 | "email": "Julianne.OConner@kory.org", 76 | "address": { 77 | "street": "Hoeger Mall", 78 | "suite": "Apt. 692", 79 | "city": "South Elvis", 80 | "zipcode": "53919-4257", 81 | "geo": { 82 | "lat": "29.4572", 83 | "lng": "-164.2990" 84 | } 85 | }, 86 | "phone": "493-170-9623 x156", 87 | "website": "kale.biz", 88 | "company": { 89 | "name": "Robel-Corkery", 90 | "catchPhrase": "Multi-tiered zero tolerance productivity", 91 | "bs": "transition cutting-edge web services" 92 | } 93 | }, 94 | { 95 | "id": 5, 96 | "name": "Chelsey Dietrich", 97 | "username": "Kamren", 98 | "email": "Lucio_Hettinger@annie.ca", 99 | "address": { 100 | "street": "Skiles Walks", 101 | "suite": "Suite 351", 102 | "city": "Roscoeview", 103 | "zipcode": "33263", 104 | "geo": { 105 | "lat": "-31.8129", 106 | "lng": "62.5342" 107 | } 108 | }, 109 | "phone": "(254)954-1289", 110 | "website": "demarco.info", 111 | "company": { 112 | "name": "Keebler LLC", 113 | "catchPhrase": "User-centric fault-tolerant solution", 114 | "bs": "revolutionize end-to-end systems" 115 | } 116 | }, 117 | { 118 | "id": 6, 119 | "name": "Mrs. Dennis Schulist", 120 | "username": "Leopoldo_Corkery", 121 | "email": "Karley_Dach@jasper.info", 122 | "address": { 123 | "street": "Norberto Crossing", 124 | "suite": "Apt. 950", 125 | "city": "South Christy", 126 | "zipcode": "23505-1337", 127 | "geo": { 128 | "lat": "-71.4197", 129 | "lng": "71.7478" 130 | } 131 | }, 132 | "phone": "1-477-935-8478 x6430", 133 | "website": "ola.org", 134 | "company": { 135 | "name": "Considine-Lockman", 136 | "catchPhrase": "Synchronised bottom-line interface", 137 | "bs": "e-enable innovative applications" 138 | } 139 | }, 140 | { 141 | "id": 7, 142 | "name": "Kurtis Weissnat", 143 | "username": "Elwyn.Skiles", 144 | "email": "Telly.Hoeger@billy.biz", 145 | "address": { 146 | "street": "Rex Trail", 147 | "suite": "Suite 280", 148 | "city": "Howemouth", 149 | "zipcode": "58804-1099", 150 | "geo": { 151 | "lat": "24.8918", 152 | "lng": "21.8984" 153 | } 154 | }, 155 | "phone": "210.067.6132", 156 | "website": "elvis.io", 157 | "company": { 158 | "name": "Johns Group", 159 | "catchPhrase": "Configurable multimedia task-force", 160 | "bs": "generate enterprise e-tailers" 161 | } 162 | }, 163 | { 164 | "id": 8, 165 | "name": "Nicholas Runolfsdottir V", 166 | "username": "Maxime_Nienow", 167 | "email": "Sherwood@rosamond.me", 168 | "address": { 169 | "street": "Ellsworth Summit", 170 | "suite": "Suite 729", 171 | "city": "Aliyaview", 172 | "zipcode": "45169", 173 | "geo": { 174 | "lat": "-14.3990", 175 | "lng": "-120.7677" 176 | } 177 | }, 178 | "phone": "586.493.6943 x140", 179 | "website": "jacynthe.com", 180 | "company": { 181 | "name": "Abernathy Group", 182 | "catchPhrase": "Implemented secondary concept", 183 | "bs": "e-enable extensible e-tailers" 184 | } 185 | }, 186 | { 187 | "id": 9, 188 | "name": "Glenna Reichert", 189 | "username": "Delphine", 190 | "email": "Chaim_McDermott@dana.io", 191 | "address": { 192 | "street": "Dayna Park", 193 | "suite": "Suite 449", 194 | "city": "Bartholomebury", 195 | "zipcode": "76495-3109", 196 | "geo": { 197 | "lat": "24.6463", 198 | "lng": "-168.8889" 199 | } 200 | }, 201 | "phone": "(775)976-6794 x41206", 202 | "website": "conrad.com", 203 | "company": { 204 | "name": "Yost and Sons", 205 | "catchPhrase": "Switchable contextually-based project", 206 | "bs": "aggregate real-time technologies" 207 | } 208 | }, 209 | { 210 | "id": 10, 211 | "name": "Clementina DuBuque", 212 | "username": "Moriah.Stanton", 213 | "email": "Rey.Padberg@karina.biz", 214 | "address": { 215 | "street": "Kattie Turnpike", 216 | "suite": "Suite 198", 217 | "city": "Lebsackbury", 218 | "zipcode": "31428-2261", 219 | "geo": { 220 | "lat": "-38.2386", 221 | "lng": "57.2232" 222 | } 223 | }, 224 | "phone": "024-648-3804", 225 | "website": "ambrose.net", 226 | "company": { 227 | "name": "Hoeger LLC", 228 | "catchPhrase": "Centralized empowering task-force", 229 | "bs": "target end-to-end models" 230 | } 231 | } 232 | ] -------------------------------------------------------------------------------- /packages/amplify-material-ui/src/auth/verify-contact.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useIntl, FormattedMessage } from 'react-intl'; 3 | import { useAuthContext, useVerifyContact } from 'amplify-auth-hooks'; 4 | import { ConsoleLogger as Logger } from '@aws-amplify/core'; 5 | import { Button, Grid, FormControlLabel, Radio } from '@mui/material'; 6 | import { Theme } from '@mui/material/styles'; 7 | import makeStyles from '@mui/styles/makeStyles'; 8 | import createStyles from '@mui/styles/createStyles'; 9 | import { Formik, Field, Form } from 'formik'; 10 | import { RadioGroup, TextField } from 'formik-mui'; 11 | 12 | import { FormSection, SectionHeader, SectionBody, SectionFooter } from '../ui'; 13 | import { useNotificationContext } from '../notification'; 14 | import { ChangeAuthStateLink } from './change-auth-state-link'; 15 | 16 | const logger = new Logger('VerifyContact'); 17 | 18 | const useStyles = makeStyles((theme: Theme) => 19 | createStyles({ 20 | form: { 21 | width: '100%', // Fix IE 11 issue. 22 | marginTop: theme.spacing(1), 23 | }, 24 | submit: { 25 | margin: theme.spacing(3, 0, 2), 26 | }, 27 | }), 28 | ); 29 | 30 | export const VerifyContact: React.FC = () => { 31 | const classes = useStyles(); 32 | const { formatMessage } = useIntl(); 33 | const { showNotification } = useNotificationContext(); 34 | const { authData } = useAuthContext(); 35 | const { verifyAttr, verify, submit } = useVerifyContact(); 36 | 37 | const renderSkipLinkPanel = (): React.ReactElement => ( 38 | 39 | 40 | 48 | 49 | 50 | ); 51 | 52 | const renderSectionHeader = (): React.ReactElement => ( 53 | 54 | 58 | 59 | ); 60 | 61 | const verifyView = (): React.ReactElement | null => { 62 | const { unverified } = authData; 63 | if (!unverified) { 64 | logger.debug('no unverified on user'); 65 | return null; 66 | } 67 | 68 | const { email, phone_number: phoneNumber } = unverified; 69 | 70 | return ( 71 | 72 | initialValues={{ 73 | contact: '', 74 | }} 75 | key="verify-contract-verify-form" 76 | onSubmit={async ({ contact }): Promise => { 77 | try { 78 | await verify(contact); 79 | } catch (error) { 80 | const content = formatMessage({ 81 | id: `verifyContact.errors.${error.code}`, 82 | defaultMessage: error.message, 83 | }); 84 | showNotification({ content, variant: 'error' }); 85 | } 86 | }} 87 | > 88 | {({ handleSubmit, isSubmitting, isValid }): React.ReactNode => ( 89 | 90 | {renderSectionHeader()} 91 |
92 | 93 | 94 | {email && ( 95 | } 98 | label={formatMessage({ 99 | id: 'global.labels.email', 100 | defaultMessage: 'Email', 101 | })} 102 | disabled={isSubmitting} 103 | /> 104 | )} 105 | {phoneNumber && ( 106 | } 109 | label={formatMessage({ 110 | id: 'global.labels.phoneNumber', 111 | defaultMessage: 'Phone Number', 112 | })} 113 | disabled={isSubmitting} 114 | /> 115 | )} 116 | 117 | 118 | 119 | 122 | {renderSkipLinkPanel()} 123 | 124 |
125 |
126 | )} 127 | 128 | ); 129 | }; 130 | 131 | const submitView = (): React.ReactElement => ( 132 | 133 | initialValues={{ 134 | code: '', 135 | }} 136 | key="verify-contract-submit-form" 137 | onSubmit={async ({ code }): Promise => { 138 | try { 139 | await submit(code); 140 | } catch (error) { 141 | const content = formatMessage({ 142 | id: `verifyContact.errors.${error.code}`, 143 | defaultMessage: error.message, 144 | }); 145 | showNotification({ content, variant: 'error' }); 146 | } 147 | }} 148 | > 149 | {({ handleSubmit, isValid }): React.ReactNode => ( 150 | 151 | {renderSectionHeader()} 152 |
153 | 154 | 169 | 170 | 171 | 174 | {renderSkipLinkPanel()} 175 | 176 |
177 |
178 | )} 179 | 180 | ); 181 | 182 | return verifyAttr ? submitView() : verifyView(); 183 | }; 184 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/test/auth/__snapshots__/sign-in.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`sign-in should be rendered correctly 1`] = ` 4 | 5 |
8 |
11 |
14 |
17 | 28 |
29 |

32 | Sign in to your account 33 |

34 |
35 |
40 |
43 |
46 | 60 |
63 | 72 | 84 |
85 |
86 |
89 | 103 |
106 | 116 | 128 |
129 |
130 |
131 |
134 | 145 |
148 | 159 | 170 |
171 |
172 |
173 |
174 |
175 |
176 | `; 177 | -------------------------------------------------------------------------------- /packages/amplify-material-ui/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [1.0.2](https://github.com/hupe1980/amplify-material-ui/compare/v1.0.1...v1.0.2) (2022-03-07) 7 | 8 | **Note:** Version bump only for package amplify-material-ui 9 | 10 | 11 | 12 | 13 | 14 | ## [1.0.1](https://github.com/hupe1980/amplify-material-ui/compare/v1.0.0...v1.0.1) (2022-02-24) 15 | 16 | **Note:** Version bump only for package amplify-material-ui 17 | 18 | 19 | 20 | 21 | 22 | # [1.0.0](https://github.com/hupe1980/amplify-material-ui/compare/v0.0.17...v1.0.0) (2022-02-15) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * Build error ([fadb556](https://github.com/hupe1980/amplify-material-ui/commit/fadb556ac46e21823273cb8373f64b0b4da6f432)) 28 | * Customizable default messages ([e760eb8](https://github.com/hupe1980/amplify-material-ui/commit/e760eb819498272ea64e7a828aaf62c267086eee)) 29 | * Exports ([cbc99b4](https://github.com/hupe1980/amplify-material-ui/commit/cbc99b4d01cfecbb0687636bc1e3fee3c7f98ae5)) 30 | * **material-ui:** ensure password type is set on the password field ([253d2ed](https://github.com/hupe1980/amplify-material-ui/commit/253d2ed9b149870c839a1ab9d1fe1828a7374f12)) 31 | * Misc ([bc1cb63](https://github.com/hupe1980/amplify-material-ui/commit/bc1cb634ef69e77f937dcd0ced6aa4672e07d6c5)) 32 | * Misc ([8850e23](https://github.com/hupe1980/amplify-material-ui/commit/8850e233dfee90f530362a677ad3f47b1b6307d0)) 33 | * **test:** MUI components need to be wrapped in ThemeProvider ([4609d7e](https://github.com/hupe1980/amplify-material-ui/commit/4609d7e8cae10c06acec05ea955f0bd977681017)) 34 | * Trim white spaces from username and password ([46943b0](https://github.com/hupe1980/amplify-material-ui/commit/46943b048786e484775f1e9e07a2817b8a9d104f)) 35 | 36 | 37 | ### Features 38 | 39 | * Add ra-data-amplify-rest ([9f7b024](https://github.com/hupe1980/amplify-material-ui/commit/9f7b024f6467356f736226c14ba47713b4af3dea)) 40 | * migrate to mui v5 ([18d7a2e](https://github.com/hupe1980/amplify-material-ui/commit/18d7a2ea1c5ed5dc52e2d5d9992f7329c8138ccc)) 41 | * **sign-up:** Allow custom sign up fields and attributes ([c1c78a8](https://github.com/hupe1980/amplify-material-ui/commit/c1c78a8e08b031d92123940d98209678b6555c30)) 42 | 43 | 44 | 45 | 46 | 47 | # [0.3.0](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.2.0...amplify-material-ui@0.3.0) (2021-12-19) 48 | 49 | **Note:** Version bump only for package amplify-material-ui 50 | 51 | 52 | 53 | 54 | 55 | # [0.2.0](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.1.0...amplify-material-ui@0.2.0) (2021-12-07) 56 | 57 | **Note:** Version bump only for package amplify-material-ui 58 | 59 | 60 | 61 | 62 | 63 | # [0.1.0](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.52...amplify-material-ui@0.1.0) (2021-04-12) 64 | 65 | **Note:** Version bump only for package amplify-material-ui 66 | 67 | 68 | 69 | 70 | 71 | ## [0.0.52](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.51...amplify-material-ui@0.0.52) (2020-11-22) 72 | 73 | 74 | ### Bug Fixes 75 | 76 | * Customizable default messages ([e760eb8](https://github.com/hupe1980/amplify-material-ui/commit/e760eb819498272ea64e7a828aaf62c267086eee)) 77 | 78 | 79 | 80 | 81 | 82 | ## [0.0.51](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.50...amplify-material-ui@0.0.51) (2020-10-24) 83 | 84 | 85 | ### Features 86 | 87 | * **sign-up:** Allow custom sign up fields and attributes ([c1c78a8](https://github.com/hupe1980/amplify-material-ui/commit/c1c78a8e08b031d92123940d98209678b6555c30)) 88 | 89 | 90 | 91 | 92 | 93 | ## [0.0.50](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.49...amplify-material-ui@0.0.50) (2020-10-21) 94 | 95 | **Note:** Version bump only for package amplify-material-ui 96 | 97 | 98 | 99 | 100 | 101 | ## [0.0.49](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.48...amplify-material-ui@0.0.49) (2020-10-21) 102 | 103 | 104 | ### Bug Fixes 105 | 106 | * **material-ui:** ensure password type is set on the password field ([253d2ed](https://github.com/hupe1980/amplify-material-ui/commit/253d2ed9b149870c839a1ab9d1fe1828a7374f12)) 107 | 108 | 109 | 110 | 111 | 112 | ## [0.0.48](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.47...amplify-material-ui@0.0.48) (2020-10-01) 113 | 114 | **Note:** Version bump only for package amplify-material-ui 115 | 116 | 117 | 118 | 119 | 120 | ## [0.0.47](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.46...amplify-material-ui@0.0.47) (2020-10-01) 121 | 122 | 123 | ### Bug Fixes 124 | 125 | * Trim white spaces from username and password ([46943b0](https://github.com/hupe1980/amplify-material-ui/commit/46943b048786e484775f1e9e07a2817b8a9d104f)) 126 | 127 | 128 | 129 | 130 | 131 | ## [0.0.46](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.45...amplify-material-ui@0.0.46) (2020-07-06) 132 | 133 | **Note:** Version bump only for package amplify-material-ui 134 | 135 | 136 | 137 | 138 | 139 | ## [0.0.45](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.44...amplify-material-ui@0.0.45) (2020-01-16) 140 | 141 | **Note:** Version bump only for package amplify-material-ui 142 | 143 | 144 | 145 | 146 | 147 | ## [0.0.44](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.43...amplify-material-ui@0.0.44) (2020-01-05) 148 | 149 | **Note:** Version bump only for package amplify-material-ui 150 | 151 | 152 | 153 | 154 | 155 | ## [0.0.43](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.42...amplify-material-ui@0.0.43) (2019-12-14) 156 | 157 | **Note:** Version bump only for package amplify-material-ui 158 | 159 | 160 | 161 | 162 | 163 | ## [0.0.42](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.41...amplify-material-ui@0.0.42) (2019-12-06) 164 | 165 | **Note:** Version bump only for package amplify-material-ui 166 | 167 | 168 | 169 | 170 | 171 | ## [0.0.41](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.40...amplify-material-ui@0.0.41) (2019-12-02) 172 | 173 | **Note:** Version bump only for package amplify-material-ui 174 | 175 | 176 | 177 | 178 | 179 | ## [0.0.40](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.39...amplify-material-ui@0.0.40) (2019-12-01) 180 | 181 | **Note:** Version bump only for package amplify-material-ui 182 | 183 | 184 | 185 | 186 | 187 | ## [0.0.39](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.38...amplify-material-ui@0.0.39) (2019-11-28) 188 | 189 | **Note:** Version bump only for package amplify-material-ui 190 | 191 | 192 | 193 | 194 | 195 | ## [0.0.38](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.37...amplify-material-ui@0.0.38) (2019-11-27) 196 | 197 | **Note:** Version bump only for package amplify-material-ui 198 | 199 | 200 | 201 | 202 | 203 | ## [0.0.37](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.36...amplify-material-ui@0.0.37) (2019-11-23) 204 | 205 | **Note:** Version bump only for package amplify-material-ui 206 | 207 | 208 | 209 | 210 | 211 | ## [0.0.36](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.35...amplify-material-ui@0.0.36) (2019-11-23) 212 | 213 | **Note:** Version bump only for package amplify-material-ui 214 | 215 | 216 | 217 | 218 | 219 | ## [0.0.35](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.34...amplify-material-ui@0.0.35) (2019-11-20) 220 | 221 | **Note:** Version bump only for package amplify-material-ui 222 | 223 | 224 | 225 | 226 | 227 | ## [0.0.34](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.33...amplify-material-ui@0.0.34) (2019-11-19) 228 | 229 | **Note:** Version bump only for package amplify-material-ui 230 | 231 | 232 | 233 | 234 | 235 | ## [0.0.33](https://github.com/hupe1980/amplify-material-ui/compare/amplify-material-ui@0.0.32...amplify-material-ui@0.0.33) (2019-11-18) 236 | 237 | **Note:** Version bump only for package amplify-material-ui 238 | --------------------------------------------------------------------------------