// App.js import React, {ChangeEvent, Component, FormEvent, Fragment} from 'react'; import './App.css'; import {Auth, Hub} from 'aws-amplify'; import {CognitoUser} from '@aws-amplify/auth'; import {Pet} from "../model/pet"; import {User} from "../model/user"; import {APIService} from "../service/APIService"; const numberFormat = new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD', minimumFractionDigits: 0, maximumFractionDigits: 0 }); interface AppProps { apiService: APIService } export interface State { authState?: 'signedIn' | 'signedOut' | 'loading'; user?: User; pets?: Pet[]; error?: any; message?: string; selectedPet?: Pet; loading?: boolean; } class App extends Component { private apiService: APIService; constructor(props: AppProps) { super(props); this.apiService = props.apiService; this.state = { authState: 'loading', } } async componentDidMount() { console.log("componentDidMount"); Hub.listen('auth', async ({payload: {event, data}}) => { switch (event) { case 'cognitoHostedUI': let user = await this.getUser(); // workaround for FF bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1422334 // eslint-disable-next-line // noinspection SillyAssignmentJS window.location.hash = window.location.hash; this.setState({authState: 'signedIn', user: user}); break; case 'cognitoHostedUI_failure': this.setState({authState: 'signedOut', user: null, error: data}); break; default: break; } }); // if the URL contains ?identity_provider=x, and the user is signed out, we redirect to the IdP on load const urlParams = new URLSearchParams(window.location.search); const idpParamName = 'identity_provider'; const idp = urlParams.get(idpParamName); try { let user = await this.getUser(); // remove identity_provider query param (not needed if signed in successfully) if (idp) { urlParams.delete(idpParamName); const params = urlParams.toString(); window.history.replaceState(null, null, window.location.pathname + (params ? '?' + params : '')); } this.setState({authState: 'signedIn', user: user}); } catch (e) { // user is not authenticated, and we have an IdP in the request if (e === 'not authenticated' && idp) { await Auth.federatedSignIn({customProvider: idp}); } else { console.warn(e); this.setState({authState: 'signedOut', user: null}); } } } private async getUser() { let cognitoUser: CognitoUser = await Auth.currentAuthenticatedUser(); return new User(cognitoUser); } async componentDidUpdate(prevProps: Readonly, prevState: Readonly) { if (prevState.authState !== this.state.authState && this.state.authState === "signedIn") { await this.getAllPets(); } } render() { const {authState, pets, user, error, selectedPet, message, loading}: Readonly = this.state; let username: string; let groups: string[] = []; if(user) { // using first name for display username = user.name || user.email; groups = user.groups; } return (
{error &&
this.setState({error: null})}>{error.toString()}
} {message &&
this.setState({message: null})}>{message.toString()}
} {authState === 'signedOut' &&
Please sign in
} {authState === 'signedIn' &&
{pets && {pets.map(pet => this.setState({selectedPet: pet})} className={selectedPet && pet.id === selectedPet.id ? "table-active" : ""} > ) }
owner type price
{pet.ownerDisplayName} {pet.type} {numberFormat.format(pet.price || 0)}
} {selectedPet && selectedPet.id && } {} {} {selectedPet &&
this.savePet(e)}> this.handleChange(e, (state, value) => state.selectedPet.id = value)}/> this.handleChange(e, (state, value) => state.selectedPet.type = value)}/> this.handleChange(e, (state, value) => state.selectedPet.price = this.getAsNumber(value))}/>
} {loading &&
Loading...
}
}
); } handleChange(event: ChangeEvent, mapper: (state: State, value: any) => void) { const value = event.target.value; this.setState(state => { mapper(state, value); return state; }); } newOnClick() { // we explicitly set to null, undefined causes react to assume there was no change this.setState({selectedPet: new Pet()}); } async getAllPets() { try { this.setState({loading: true, selectedPet: undefined}); let pets: Pet[] = await this.apiService.getAllPets(); this.setState({pets, loading: false}); } catch (e) { console.log(e); this.setState({error: `Failed to load pets: ${e}`, pets: [], loading: false}); } } async savePet(event: FormEvent) { event.preventDefault(); const pet = this.state.selectedPet; if (!pet) { this.setState({error: "Pet is needed"}); return; } try { this.setState({loading: true}); await this.apiService.savePet(pet); await this.getAllPets(); } catch (e) { this.setState({error: "Failed to save pet. " + e, loading: false}); } } async deletePet() { if (!window.confirm("Are you sure?")) { return; } const pet = this.state.selectedPet; if (!pet) { this.setState({error: "Pet is needed"}); return; } try { this.setState({loading: true}); await this.apiService.deletePet(pet); return this.getAllPets(); } catch (e) { this.setState({error: "Failed to save pet. " + e, loading: false}); } } async signOut() { try { this.setState({authState: 'signedOut', pets: null, user: null}); await this.apiService.forceSignOut(); } catch (e) { console.log(e); } } private getAsNumber(value: any): number | undefined { if (value) { try { return parseInt(value) } catch (ignored) { } } return undefined; } } export default App;