import { getNodeId } from '@ory/integrations/ui' ; import { isUiNodeInputAttributes } from '@ory/integrations/ui' ; import { SelfServiceLoginFlow , SelfServiceRecoveryFlow , SelfServiceRegistrationFlow , SelfServiceSettingsFlow , SelfServiceVerificationFlow , SubmitSelfServiceLoginFlowBody , SubmitSelfServiceRecoveryFlowBody , SubmitSelfServiceRegistrationFlowBody , SubmitSelfServiceSettingsFlowBody , SubmitSelfServiceVerificationFlowBody , UiNode } from '@ory/kratos-client' ; import { Component , FormEvent } from 'react' ; import { Messages } from './Messages' ; import { Node } from './Node' ; export type Values = Partial < SubmitSelfServiceLoginFlowBody | SubmitSelfServiceRegistrationFlowBody | SubmitSelfServiceRecoveryFlowBody | SubmitSelfServiceSettingsFlowBody | SubmitSelfServiceVerificationFlowBody > ; export type Methods = 'oidc' | 'password' | 'profile' | 'totp' | 'webauthn' | 'link' | 'lookup_secret' ; export type Props < T > = { flow ? : SelfServiceLoginFlow | SelfServiceRegistrationFlow | SelfServiceSettingsFlow | SelfServiceVerificationFlow | SelfServiceRecoveryFlow ; only ? : Methods ; onSubmit : ( values : T ) => Promise < void > ; hideGlobalMessages ? : boolean ; } ; function emptyState < T > ( ) { return { } as T ; } type State < T > = { values : T ; isLoading : boolean ; } ; export class Flow < T extends Values > extends Component < Props < T > , State < T > > { constructor ( props ) { super ( props ) ; this . state = { values : emptyState ( ) , isLoading : false } ; } componentDidMount ( ) { this . initializeValues ( this . filterNodes ( ) ) ; } componentDidUpdate ( prevProps ) { if ( prevProps . flow !== this . props . flow ) { this . initializeValues ( this . filterNodes ( ) ) ; } } initializeValues = ( nodes = [ ] ) => { const values = emptyState < T > ( ) ; nodes . forEach ( ( node ) => { if ( isUiNodeInputAttributes ( node . attributes ) ) { if ( node . attributes . type === 'button' || node . attributes . type === 'submit' ) { return ; } values [ node . attributes . name as keyof Values ] = node . attributes . value ; } } ) ; this . setState ( ( state ) => ( { ... state , values } ) ) ; } ; filterNodes = ( ) : Array < UiNode > => { const { flow , only } = this . props ; if ( ! flow ) { return [ ] ; } return flow . ui . nodes . filter ( ( { group } ) => { if ( ! only ) { return true ; } return group === 'default' || group === only ; } ) ; } ; handleSubmit = ( e ) => { e . stopPropagation ( ) ; e . preventDefault ( ) ; if ( this . state . isLoading ) { return Promise . resolve ( ) ; } this . setState ( ( state ) => ( { ... state , isLoading : true } ) ) ; return this . props . onSubmit ( this . state . values ) . finally ( ( ) => { this . setState ( ( state ) => ( { ... state , isLoading : false } ) ) ; } ) ; } ; render ( ) { const { hideGlobalMessages , flow } = this . props ; const { values , isLoading } = this . state ; const nodes = this . filterNodes ( ) ; if ( ! flow ) { return null ; } return ( < form action = { flow . ui . action } method = { flow . ui . method } onSubmit = { this . handleSubmit } > { ! hideGlobalMessages ? < Messages messages = { flow . ui . messages } /> : null} { nodes . map ( ( node , k ) => { const id = getNodeId ( node ) as keyof Values ; return ( < Node key = { `${ id }-${ k }` } disabled = { isLoading } node = { node } value = { values [ id ] } dispatchSubmit = { this . handleSubmit } setValue = { ( value ) => new Promise ( ( resolve ) => { this . setState ( ( state ) => ( { ... state , values : { ... state . values , [ getNodeId ( node ) ] : value } } ) , resolve ) ; } ) } />); } ) } < /form>); } }