github.com/pluralsh/plural-cli@v0.9.5/pkg/ui/web/src/routes/installer/Application.tsx (about) 1 import { useQuery } from '@apollo/client' 2 import { Chip, WizardStep, useActive } from '@pluralsh/design-system' 3 import { Div, Span } from 'honorable' 4 import { 5 ReactElement, 6 useContext, 7 useEffect, 8 useMemo, 9 useState, 10 } from 'react' 11 12 import Loader from '../../components/loader/Loader' 13 import { WailsContext } from '../../context/wails' 14 import { 15 GetRecipeDocument, 16 ListRecipesDocument, 17 Recipe, 18 RecipeEdge, 19 } from '../../graphql/generated/graphql' 20 21 import { Configuration } from './Configuration' 22 23 interface StepData { 24 id: string | undefined, 25 context: Record<string, unknown> 26 oidc: boolean 27 skipped?: boolean, 28 } 29 30 const toConfig = config => (config ? Object.keys(config) 31 .map(key => ({ [key]: { value: config[key], valid: true } })) 32 .reduce((acc, entry) => ({ ...acc, ...entry }), {}) : undefined) 33 34 export function Application({ provider, ...props }: any): ReactElement { 35 const { active, setData } = useActive<StepData>() 36 const { context: { configuration } } = useContext(WailsContext) 37 const [context, setContext] = useState<Record<string, unknown>>(active.data?.context || {}) 38 const [valid, setValid] = useState(true) 39 const [oidc, setOIDC] = useState(active.data?.oidc ?? true) 40 const { data: { recipes: { edges: recipeEdges } = { edges: undefined } } = {} } = useQuery(ListRecipesDocument, { 41 variables: { repositoryId: active.key }, 42 }) 43 44 const { node: recipeBase } = recipeEdges?.find((recipe: RecipeEdge) => recipe!.node!.provider === provider) || { node: undefined } 45 const { data: recipe } = useQuery<{recipe: Recipe}>(GetRecipeDocument, { 46 variables: { id: recipeBase?.id }, 47 skip: !recipeBase, 48 }) 49 50 const recipeContext = useMemo(() => toConfig(configuration?.[active.label!]), [active.label, configuration]) 51 const mergedContext = useMemo<Record<string, unknown>>(() => ({ ...recipeContext, ...context }), [recipeContext, context]) 52 const stepData = useMemo(() => ({ 53 ...active.data, ...{ id: recipe?.recipe.id }, ...{ context: mergedContext }, ...{ oidc }, 54 }), [active.data, recipe?.recipe.id, mergedContext, oidc]) 55 56 useEffect(() => { 57 const valid = Object.values<any>(context).every(({ valid }) => valid) 58 59 setValid(valid) 60 }, [context, setValid]) 61 62 // Update step data on change 63 useEffect(() => setData(stepData), [stepData, setData]) 64 65 if (!recipe) { 66 return ( 67 <WizardStep {...props}> 68 <Loader /> 69 </WizardStep> 70 ) 71 } 72 73 if (recipe.recipe?.restricted) { 74 return ( 75 <WizardStep 76 valid={false} 77 {...props} 78 > 79 <Div 80 marginTop="xxsmall" 81 marginBottom="medium" 82 display="flex" 83 gap="medium" 84 flexDirection="column" 85 > 86 <Span 87 color="text-xlight" 88 overline 89 >Cannot install app 90 </Span> 91 <Span 92 color="text-light" 93 body2 94 >This application has been marked restricted because it requires configuration, like ssh keys, that are only able to be securely configured locally. 95 </Span> 96 </Div> 97 </WizardStep> 98 ) 99 } 100 101 return ( 102 <WizardStep 103 valid={valid} 104 data={stepData} 105 {...props} 106 > 107 <Div 108 marginBottom="medium" 109 display="flex" 110 lineHeight="24px" 111 alignItems="center" 112 height="24px" 113 > 114 <Span 115 overline 116 color="text-xlight" 117 > 118 configure {active.label} 119 </Span> 120 {active.isDependency && ( 121 <Chip 122 size="small" 123 hue="lighter" 124 marginLeft="xsmall" 125 >Dependency 126 </Chip> 127 )} 128 </Div> 129 <Configuration 130 recipe={recipe.recipe} 131 context={mergedContext} 132 setContext={setContext} 133 oidc={oidc} 134 setOIDC={setOIDC} 135 /> 136 </WizardStep> 137 ) 138 }