Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"react-dom": "^17.0.2",
"react-scripts": "5.0.0",
"react-select": "3.1.0",
"rxjs": "^7.5.4",
"rxjs": "^7.5.5",
"theme-ui": "^0.13.1",
"typescript": "^4.5.4",
"web-vitals": "^2.1.2"
Expand Down
30 changes: 10 additions & 20 deletions src/flow/Flow.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
import { useState, FC } from 'react'
import { Observable } from 'rxjs'
import { useObservable } from '../stateMachineExperiment/helpers/useObservable'

export type GenericStepProps<S> = {
next?: () => void
previous?: () => void
updateState: (s: Partial<S>) => void
skip?: () => void
state$: Observable<S>
} & S

type Direction = 'forwards' | 'backwards'

export function Flow<S>(
props: { steps: Array<IStep<GenericStepProps<S>>> } & {
props: { steps: Array<IStep<S>> } & {
updateState: (state: Partial<S>) => void
next?: () => void
previous?: () => void
name: string
state$: Observable<S>
} & S,
) {
const parentPrevious = props.previous
const parentNext = props.next

let nextPreviousCalledThisRender = false
const [currentStepIndex, setCurrentStepIndex] = useState(0)
const currentStep = props.steps[currentStepIndex]
const canSkip = useObservable(currentStep.canSkip$(props.state$))

const [direction, setDirection] = useState<Direction>('forwards')

const isNestedFlow = parentPrevious || parentNext
Expand All @@ -30,7 +36,6 @@ export function Flow<S>(

const next = canProgress
? () => {
nextPreviousCalledThisRender = true
setDirection('forwards')
setCurrentStepIndex((currentStep) => currentStep + 1)
}
Expand All @@ -40,7 +45,6 @@ export function Flow<S>(

const previous = canRegress
? () => {
nextPreviousCalledThisRender = true
setDirection('backwards')
setCurrentStepIndex((currentStep) => currentStep - 1)
}
Expand All @@ -50,29 +54,15 @@ export function Flow<S>(

const skip = direction === 'forwards' ? next : previous

const currentStep = props.steps[currentStepIndex]

if (
nextPreviousCalledThisRender &&
isSkippable(currentStep) &&
skip &&
currentStep.canSkip(props)
) {
if (skip && canSkip) {
console.log(`skipping step with index ${currentStepIndex} in flow ${props.name}`)
skip()
}

return <currentStep.Component {...props} next={next} previous={previous} skip={skip} />
}

function isSkippable<T>(step: IStep<T> | ISkippableStep<T>): step is ISkippableStep<T> {
return (step as ISkippableStep<T>).canSkip !== undefined
}

export interface IStep<StepSpecificProps> {
Component: FC<GenericStepProps<StepSpecificProps>>
}

export interface ISkippableStep<StepSpecificProps> extends IStep<StepSpecificProps> {
canSkip: (s: StepSpecificProps) => boolean
canSkip$: (state$: Observable<StepSpecificProps>) => Observable<boolean>
}
10 changes: 9 additions & 1 deletion src/flow/OpenBorrowVault.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CreateProxy, CreateProxyProps } from './create-proxy/CreateProxy'
import { Confirmation, ConfirmationProps } from './steps/Confirmation'
import { Complete, CompleteProps } from './steps/Complete'
import { Allowance } from './allowance/Allowance'
import { Subject } from 'rxjs'

type OpenBorrowVaultType = SimulateStepProps & CreateProxyProps & ConfirmationProps & CompleteProps

Expand All @@ -20,11 +21,17 @@ export function OpenBorrowVault() {
ethPrice: 2000,
walletAddress: '0xWalletAddress',
})
const state$ = new Subject<OpenBorrowVaultType>()

useEffect(() => {
const i = setInterval(() => {
setViewState((oldState) => {
return calculateViewModal({ ...oldState, ethPrice: Math.floor(Math.random() * 10000) })
const nextState = calculateViewModal({
...oldState,
ethPrice: Math.floor(Math.random() * 10000),
})
state$.next(nextState)
return nextState
})
}, 1000)
return () => clearInterval(i)
Expand All @@ -38,6 +45,7 @@ export function OpenBorrowVault() {
updateState={(newState) =>
setViewState((oldState) => calculateViewModal({ ...oldState, ...newState }))
}
state$={state$.asObservable()}
/>
)
}
17 changes: 13 additions & 4 deletions src/flow/allowance/Allowance.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Flow, GenericStepProps, ISkippableStep } from '../Flow'
import { Flow, GenericStepProps, IStep } from '../Flow'
import {
ConfigureAllowanceAmount,
ConfigureAllowanceAmountProps,
} from './steps/ConfigureAllowanceAmount'
import { Done, DoneProps } from './steps/Done'
import { useEffect, useState } from 'react'
import { Observable, of, Subject, switchMap } from 'rxjs'

export type AllowanceProps = ConfigureAllowanceAmountProps & DoneProps

export const Allowance: ISkippableStep<AllowanceProps> = {
export const Allowance: IStep<AllowanceProps> = {
Component: (props: GenericStepProps<AllowanceProps>) => {
const [viewState, setViewState] = useState<AllowanceProps>(props)
const state$ = new Subject<AllowanceProps>()

useEffect(() => {
if (props.configuredAllowance) {
Expand All @@ -24,11 +26,18 @@ export const Allowance: ISkippableStep<AllowanceProps> = {
name="allowance"
steps={[ConfigureAllowanceAmount, Done]}
updateState={(newState) => {
setViewState((oldState) => ({ ...oldState, ...newState }))
setViewState((oldState) => {
const nextState = { ...oldState, ...newState }
state$.next(nextState)
return nextState
})
props.updateState(newState)
}}
state$={state$.asObservable()}
/>
)
},
canSkip: (props) => !!props.configuredAllowance,
canSkip$: (state$: Observable<AllowanceProps>): Observable<boolean> => {
return state$.pipe(switchMap((state) => of(!!state.configuredAllowance)))
},
}
4 changes: 4 additions & 0 deletions src/flow/allowance/steps/ConfigureAllowanceAmount.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { GenericStepProps, IStep } from '../../Flow'
import { SyntheticEvent, useState } from 'react'
import { useLoadingDots } from '../../hooks/useLoadingDots'
import { Observable, of } from 'rxjs'

export type ConfigureAllowanceAmountProps = {
depositAmount?: number
Expand Down Expand Up @@ -92,4 +93,7 @@ export const ConfigureAllowanceAmount: IStep<ConfigureAllowanceAmountProps> = {
</>
)
},
canSkip$: (): Observable<boolean> => {
return of(false)
},
}
4 changes: 4 additions & 0 deletions src/flow/allowance/steps/Done.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Observable, of } from 'rxjs'
import { GenericStepProps, IStep } from '../../Flow'

export type DoneProps = {}
Expand All @@ -12,4 +13,7 @@ export const Done: IStep<DoneProps> = {
</>
)
},
canSkip$: (): Observable<boolean> => {
return of(false)
},
}
60 changes: 60 additions & 0 deletions src/flow/create-proxy/CreateProxy.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
Subject,
from,
combineLatest,
map,
switchMap,
share,
startWith,
Observable,
merge,
of,
} from 'rxjs'

type ConnectedContext = {
account: string
}

type ProxyAddress = (account: string) => Observable<string>

function createDsProxy(account: string) {
return '0xProxyAdress'
}

// TODO: Think about how AppContext could be tidied up
function createProxyPipe$(
context$: Observable<ConnectedContext>,
proxyAddress$: Observable<ProxyAddress>,
): Observable<{ createProxy: any; proxy: any }> {
const createProxyClick$ = new Subject<void>()

const newProxy$ = combineLatest([createProxyClick$, context$]).pipe(
switchMap(([_, context]) => from(createDsProxy(context.account))),
share(),
startWith(undefined),
)

const existingProxy$ = combineLatest([context$, proxyAddress$]).pipe(
switchMap(([context, proxyAddress]) => proxyAddress(context.account)),
share(),
)

const proxy$ = merge(newProxy$, existingProxy$)

function createProxy() {
createProxyClick$.next()
}

// Return viewModel
return combineLatest([of(createProxy), proxy$]).pipe(
map(([createProxy, proxy]) => ({
createProxy,
proxy,
})),
)
}

export const proxy$ = createProxyPipe$(
of({ account: '0xUserAdress' }),
of((account: string) => of('OxProxyAddress')),
)
32 changes: 25 additions & 7 deletions src/flow/create-proxy/CreateProxy.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
import { Flow, GenericStepProps, ISkippableStep } from '../Flow'
import { FC, useState } from 'react'
import { Flow, IStep } from '../Flow'
import { useEffect, useState } from 'react'
import { Explanation, ExplanationProps } from './steps/Explanation'
import { Creation, CreationProps } from './steps/Creation'
import { Done, DoneProps } from './steps/Done'
import { useObservable } from '../../stateMachineExperiment/helpers/useObservable'
import { proxy$ } from './CreateProxy.pipe'
import { Observable, Subject, switchMap, of } from 'rxjs'

export type CreateProxyProps = ExplanationProps & CreationProps & DoneProps

export const CreateProxy: ISkippableStep<CreateProxyProps> = {
export const CreateProxy: IStep<CreateProxyProps> = {
Component: (props) => {
const [viewState, setViewState] = useState<CreateProxyProps>(props)
const proxyViewModel = useObservable(proxy$)
const state$ = new Subject<CreateProxyProps>()

useEffect(() => {
setViewState((oldState) => {
return {
...oldState,
proxyAddress: proxyViewModel?.proxy,
}
})
}, [proxyViewModel])

return (
<Flow<CreateProxyProps>
{...viewState}
name="proxy"
steps={[Explanation, Creation, Done]}
updateState={(newState) => {
setViewState((oldState) => ({ ...oldState, ...newState }))
setViewState((oldState) => {
const nextState = { ...oldState, ...newState }
state$.next(nextState)
return nextState
})
props.updateState(newState)
}}
state$={state$.asObservable()}
/>
)
},
canSkip: (props: CreateProxyProps) => {
console.log('here')
return !!props.proxyAddress
canSkip$: (state$: Observable<CreateProxyProps>): Observable<boolean> => {
return state$.pipe(switchMap((state) => of(!!state.proxyAddress)))
},
}
5 changes: 4 additions & 1 deletion src/flow/create-proxy/steps/Creation.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { GenericStepProps, IStep } from '../../Flow'
import { useEffect } from 'react'
import { useLoadingDots } from '../../hooks/useLoadingDots'
import { clear } from '@testing-library/user-event/dist/clear'
import { Observable, of } from 'rxjs'

export type CreationProps = {
walletAddress: string
Expand Down Expand Up @@ -30,4 +30,7 @@ export const Creation: IStep<CreationProps> = {
</>
)
},
canSkip$: (): Observable<boolean> => {
return of(false)
},
}
5 changes: 5 additions & 0 deletions src/flow/create-proxy/steps/Done.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Observable, of } from 'rxjs'
import { GenericStepProps, IStep } from '../../Flow'

export type DoneProps = {
walletAddress: string
proxyAddress?: string
}

Expand All @@ -15,4 +17,7 @@ export const Done: IStep<DoneProps> = {
</>
)
},
canSkip$: (): Observable<boolean> => {
return of(false)
},
}
4 changes: 4 additions & 0 deletions src/flow/create-proxy/steps/Explanation.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Observable, of } from 'rxjs'
import { GenericStepProps, IStep } from '../../Flow'

export type ExplanationProps = {
Expand All @@ -24,4 +25,7 @@ export const Explanation: IStep<ExplanationProps> = {
</>
)
},
canSkip$: (): Observable<boolean> => {
return of(false)
},
}
4 changes: 4 additions & 0 deletions src/flow/steps/Complete.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Observable, of } from 'rxjs'
import { GenericStepProps, IStep } from '../Flow'

export type CompleteProps = {
Expand All @@ -17,4 +18,7 @@ export const Complete: IStep<CompleteProps> = {
</>
)
},
canSkip$: (): Observable<boolean> => {
return of(false)
},
}
4 changes: 4 additions & 0 deletions src/flow/steps/Confirmation.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { GenericStepProps, IStep } from '../Flow'
import { useState } from 'react'
import { useLoadingDots } from '../hooks/useLoadingDots'
import { Observable, of } from 'rxjs'

export type ConfirmationProps = {
depositAmount?: number
Expand Down Expand Up @@ -43,4 +44,7 @@ export const Confirmation: IStep<ConfirmationProps> = {
</>
)
},
canSkip$: (): Observable<boolean> => {
return of(false)
},
}
Loading