import React from 'react' import { type AppBskyFeedDefs, type AppBskyGraphDefs, AppBskyGraphStarterpack, } from '@atproto/api' import {msg, plural} from '@lingui/macro' import {STARTER_PACK_MAX_SIZE} from '#/lib/constants' import * as Toast from '#/view/com/util/Toast' import * as bsky from '#/types/bsky' const steps = ['Details', 'Profiles', 'Feeds'] as const type Step = (typeof steps)[number] type Action = | {type: 'Next'} | {type: 'Back'} | {type: 'SetCanNext'; canNext: boolean} | {type: 'SetName'; name: string} | {type: 'SetDescription'; description: string} | {type: 'AddProfile'; profile: bsky.profile.AnyProfileView} | {type: 'RemoveProfile'; profileDid: string} | {type: 'AddFeed'; feed: AppBskyFeedDefs.GeneratorView} | {type: 'RemoveFeed'; feedUri: string} | {type: 'SetProcessing'; processing: boolean} | {type: 'SetError'; error: string} interface State { canNext: boolean currentStep: Step name?: string description?: string profiles: bsky.profile.AnyProfileView[] feeds: AppBskyFeedDefs.GeneratorView[] processing: boolean error?: string transitionDirection: 'Backward' | 'Forward' targetDid?: string } type TStateContext = [State, (action: Action) => void] const StateContext = React.createContext([ {} as State, (_: Action) => {}, ]) StateContext.displayName = 'StarterPackWizardStateContext' export const useWizardState = () => React.useContext(StateContext) function reducer(state: State, action: Action): State { let updatedState = state // -- Navigation const currentIndex = steps.indexOf(state.currentStep) if (action.type === 'Next' && state.currentStep !== 'Feeds') { updatedState = { ...state, currentStep: steps[currentIndex + 1], transitionDirection: 'Forward', } } else if (action.type === 'Back' && state.currentStep !== 'Details') { updatedState = { ...state, currentStep: steps[currentIndex - 1], transitionDirection: 'Backward', } } switch (action.type) { case 'SetName': updatedState = {...state, name: action.name.slice(0, 50)} break case 'SetDescription': updatedState = {...state, description: action.description} break case 'AddProfile': if (state.profiles.length > STARTER_PACK_MAX_SIZE) { Toast.show( msg`You may only add up to ${plural(STARTER_PACK_MAX_SIZE, { other: `${STARTER_PACK_MAX_SIZE} profiles`, })}`.message ?? '', 'info', ) } else { updatedState = {...state, profiles: [...state.profiles, action.profile]} } break case 'RemoveProfile': updatedState = { ...state, profiles: state.profiles.filter( profile => profile.did !== action.profileDid, ), } break case 'AddFeed': if (state.feeds.length >= 3) { Toast.show(msg`You may only add up to 3 feeds`.message ?? '', 'info') } else { updatedState = {...state, feeds: [...state.feeds, action.feed]} } break case 'RemoveFeed': updatedState = { ...state, feeds: state.feeds.filter(f => f.uri !== action.feedUri), } break case 'SetProcessing': updatedState = {...state, processing: action.processing} break } return updatedState } export function Provider({ starterPack, listItems, targetProfile, children, }: { starterPack?: AppBskyGraphDefs.StarterPackView listItems?: AppBskyGraphDefs.ListItemView[] targetProfile: bsky.profile.AnyProfileView children: React.ReactNode }) { const createInitialState = (): State => { const targetDid = targetProfile?.did if ( starterPack && bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord) ) { return { canNext: true, currentStep: 'Details', name: starterPack.record.name, description: starterPack.record.description, profiles: listItems?.map(i => i.subject) ?? [], feeds: starterPack.feeds ?? [], processing: false, transitionDirection: 'Forward', targetDid, } } return { canNext: true, currentStep: 'Details', profiles: [targetProfile], feeds: [], processing: false, transitionDirection: 'Forward', targetDid, } } const [state, dispatch] = React.useReducer(reducer, null, createInitialState) return ( {children} ) } export { type Action as WizardAction, type State as WizardState, type Step as WizardStep, }