@@ -9,17 +9,30 @@ import {
99 Option ,
1010 FormLabel ,
1111 Note ,
12- Flex ,
13- Box ,
14- Subheading ,
15- Stack ,
1612} from '@contentful/f36-components' ;
17- import ContentTypeMultiSelect , { ContentType } from '../components/ContentTypeMultiSelect' ;
18- import { OAuthConnector } from '../components/OAuthConnector' ;
19- import { InstallationParameters , WorkspaceOption , WorkspacesResponse } from '../typings' ;
20- import { validateParameters , getToken , resetLocalStorage } from '../utils' ;
13+ import FieldSelector from './FieldSelector' ;
14+ import {
15+ CompatibleFields ,
16+ ContentType ,
17+ Hash ,
18+ EditorInterface ,
19+ InstallationParameters ,
20+ SelectedFields ,
21+ WorkspaceOption ,
22+ WorkspacesResponse ,
23+ } from '../typings' ;
24+ import {
25+ getCompatibleFields ,
26+ editorInterfacesToSelectedFields ,
27+ selectedFieldsToTargetState ,
28+ validateParameters ,
29+ getToken ,
30+ resetLocalStorage ,
31+ } from '../utils' ;
2132import { styles } from './styles' ;
22- import { BASE_URL } from '../constants' ;
33+
34+ // @ts -ignore 2307
35+ import logo from './config-screen-logo.svg' ;
2336
2437const AUTH_ERROR_CODES = [ 401 , 403 ] ;
2538
@@ -30,18 +43,22 @@ interface Props {
3043
3144interface State {
3245 selectedWorkspaceId : string ;
33- baseUrl : string ;
3446 accessToken : string ;
3547 workspaces : WorkspaceOption [ ] ;
36- selectedContentTypes : ContentType [ ] ;
48+ contentTypes : ContentType [ ] ;
49+ selectedContentTypes : string [ ] ;
50+ selectedFields : SelectedFields ;
51+ compatibleFields : CompatibleFields ;
3752}
3853
3954export class AppConfig extends React . Component < Props , State > {
4055 state : State = {
56+ contentTypes : [ ] ,
57+ compatibleFields : { } ,
4158 workspaces : [ ] ,
4259 selectedContentTypes : [ ] ,
60+ selectedFields : { } ,
4361 selectedWorkspaceId : '' ,
44- baseUrl : BASE_URL ,
4562 accessToken : getToken ( ) ,
4663 } ;
4764
@@ -50,61 +67,51 @@ export class AppConfig extends React.Component<Props, State> {
5067
5168 sdk . app . onConfigure ( this . onAppConfigure ) ;
5269
53- const paramsResponse = await sdk . app . getParameters ( ) ;
70+ const [ contentTypesResponse , eisResponse , paramsResponse ] = await Promise . all ( [
71+ sdk . space . getContentTypes ( ) ,
72+ sdk . space . getEditorInterfaces ( ) ,
73+ sdk . app . getParameters ( ) ,
74+ this . fetchWorkspaces ( ) ,
75+ ] ) ;
76+
77+ const contentTypes = ( contentTypesResponse as Hash ) . items as ContentType [ ] ;
78+ const editorInterfaces = ( eisResponse as Hash ) . items as EditorInterface [ ] ;
79+ const compatibleFields = getCompatibleFields ( contentTypes ) ;
80+ const filteredContentTypes = contentTypes . filter ( ( ct ) => {
81+ const fields = compatibleFields [ ct . sys . id ] ;
82+ return fields && fields . length > 0 ;
83+ } ) ;
84+
5485 const parameters : InstallationParameters = paramsResponse as InstallationParameters ;
55- const effectiveBaseUrl = get ( parameters , [ 'baseUrl' ] , BASE_URL ) ;
56- const effectiveAccessToken = getToken ( effectiveBaseUrl ) ;
5786
5887 this . setState (
5988 {
6089 selectedWorkspaceId : get ( parameters , [ 'selectedWorkspaceId' ] , '' ) ,
61- baseUrl : effectiveBaseUrl ,
62- accessToken : effectiveAccessToken ,
90+ compatibleFields,
91+ contentTypes : filteredContentTypes ,
92+ selectedFields : editorInterfacesToSelectedFields ( editorInterfaces , sdk . ids . app ) ,
6393 } ,
64- ( ) => {
65- sdk . app . setReady ( ) ;
66- // Fetch workspaces after state is set
67- if ( effectiveAccessToken ) {
68- this . fetchWorkspaces ( ) ;
69- }
70- }
94+ ( ) => sdk . app . setReady ( )
7195 ) ;
7296 }
7397
7498 fetchWorkspaces = async ( ) => {
7599 try {
76- const { baseUrl, accessToken } = this . state ;
77-
78- if ( ! accessToken ) {
79- return ;
80- }
81-
82- const apiUrl = `${ window . location . origin } /workspaces?baseUrl=${ encodeURIComponent ( baseUrl ) } ` ;
83- const response = await fetch ( apiUrl , {
100+ const response = await fetch ( `/workspaces` , {
84101 headers : {
85- Authorization : `Bearer ${ accessToken } ` ,
102+ Authorization : `Bearer ${ this . state . accessToken } ` ,
86103 } ,
87104 } ) ;
88105
89106 if ( AUTH_ERROR_CODES . includes ( response . status ) ) {
90- resetLocalStorage ( baseUrl ) ;
107+ resetLocalStorage ( ) ;
91108 this . setState ( { accessToken : '' } ) ;
92109 return ;
93110 }
94111
95- if ( ! response . ok ) {
96- const errorText = await response . text ( ) ;
97- if ( AUTH_ERROR_CODES . includes ( response . status ) ) {
98- resetLocalStorage ( baseUrl ) ;
99- this . setState ( { accessToken : '' } ) ;
100- return ;
101- }
102- throw new Error ( `Failed to fetch workspaces: ${ response . status } ${ errorText } ` ) ;
103- }
104-
105- const result : WorkspacesResponse = ( await response . json ( ) ) as WorkspacesResponse ;
112+ const result : WorkspacesResponse = await response . json ( ) ;
106113 this . setState ( { workspaces : this . normalizeWorkspaceResponse ( result ) } ) ;
107- } catch ( error ) {
114+ } catch ( _error ) {
108115 this . props . sdk . notifier . error (
109116 'There was a problem fetching your Typeform workspaces. Please try again.'
110117 ) ;
@@ -119,7 +126,7 @@ export class AppConfig extends React.Component<Props, State> {
119126 } ;
120127
121128 onAppConfigure = ( ) => {
122- const { accessToken, selectedWorkspaceId, baseUrl , selectedContentTypes } = this . state ;
129+ const { accessToken, selectedWorkspaceId, contentTypes , selectedFields } = this . state ;
123130 const parameters = { selectedWorkspaceId, accessToken } ;
124131 const error = validateParameters ( parameters ) ;
125132 const hasStaleWorkspaceIdSelected = ! this . selectedWorkspaceIdIsValid ( ) ;
@@ -134,19 +141,9 @@ export class AppConfig extends React.Component<Props, State> {
134141 return false ;
135142 }
136143
137- // Convert selectedContentTypes to targetState format
138- const editorInterface = selectedContentTypes . reduce ( ( acc , contentType ) => {
139- return {
140- ...acc ,
141- [ contentType . id ] : {
142- sidebar : { position : 0 } ,
143- } ,
144- } ;
145- } , { } ) ;
146-
147144 return {
148- parameters : { selectedWorkspaceId, baseUrl } ,
149- targetState : { EditorInterface : editorInterface } ,
145+ parameters : { selectedWorkspaceId } ,
146+ targetState : selectedFieldsToTargetState ( contentTypes , selectedFields ) ,
150147 } ;
151148 } ;
152149
@@ -160,26 +157,16 @@ export class AppConfig extends React.Component<Props, State> {
160157 this . setState ( { selectedWorkspaceId : id . trim ( ) } ) ;
161158 } ;
162159
163- setBaseUrl = ( baseUrl : string ) => {
164- const trimmedBaseUrl = baseUrl . trim ( ) ;
165- const tokenForNewBaseUrl = getToken ( trimmedBaseUrl ) ;
166- this . setState ( { baseUrl : trimmedBaseUrl , accessToken : tokenForNewBaseUrl } , ( ) => {
167- if ( tokenForNewBaseUrl ) {
168- this . fetchWorkspaces ( ) ;
169- }
170- } ) ;
160+ setAccessToken = ( token : string ) => {
161+ this . setState ( { accessToken : token . trim ( ) } ) ;
171162 } ;
172163
173- setAccessToken = ( token : string ) => {
174- this . setState ( { accessToken : token . trim ( ) } , ( ) => {
175- if ( token ) {
176- this . fetchWorkspaces ( ) ;
177- }
178- } ) ;
164+ onSelectedFieldsChange = ( selectedFields : SelectedFields ) => {
165+ this . setState ( { selectedFields } ) ;
179166 } ;
180167
181168 render ( ) {
182- const { selectedWorkspaceId , baseUrl , workspaces , selectedContentTypes , accessToken } =
169+ const { contentTypes , compatibleFields , selectedFields , selectedWorkspaceId , workspaces } =
183170 this . state ;
184171
185172 const { sdk } = this . props ;
@@ -188,50 +175,29 @@ export class AppConfig extends React.Component<Props, State> {
188175 } = sdk ;
189176
190177 return (
191- < Flex justifyContent = "center" alignItems = "center" >
192- < Box marginBottom = "spacing2Xl" marginTop = "spacing2Xl" className = { styles . body } >
193- < Heading marginBottom = "spacingS" > Set up Typeform</ Heading >
194- < Paragraph marginBottom = "spacingXl" >
195- The{ ' ' }
196- < TextLink href = "https://www.typeform.com/" target = "_blank" rel = "noopener noreferrer" >
197- Typeform
198- </ TextLink > { ' ' }
199- app allows you to reference your forms from Typeform without leaving Contentful.
200- </ Paragraph >
201-
202- < Subheading marginTop = "spacingXl" marginBottom = "spacing2Xs" >
203- Configure access
204- </ Subheading >
205- < Paragraph marginBottom = "spacingM" > Section subtitle with basic instructions</ Paragraph >
206-
207- < Box marginBottom = "spacingL" >
208- < OAuthConnector
209- sdk = { sdk }
210- baseUrl = { baseUrl }
211- onBaseUrlChange = { this . setBaseUrl }
212- onTokenChange = { this . setAccessToken }
213- expireSoon = { this . props . expireSoon }
214- />
215- </ Box >
216-
217- < Box marginBottom = "spacingL" >
218- < FormLabel htmlFor = "baseUrl" marginBottom = "spacingXs" >
219- Typeform region
220- </ FormLabel >
221- < Select
222- id = "baseUrl"
223- name = "baseUrl"
224- onChange = { ( event : any ) => this . setBaseUrl ( event . currentTarget . value ) }
225- value = { baseUrl }
226- data-test-id = "typeform-base-url-select" >
227- < Option value = "https://api.typeform.com" > US (typeform.com)</ Option >
228- < Option value = "https://api.typeform.eu" > EU (typeform.eu)</ Option >
229- </ Select >
230- </ Box >
231-
232- { accessToken && (
233- < >
234- < Box marginBottom = "spacingL" >
178+ < div >
179+ < div className = { styles . background ( '#262627' ) } />
180+ < div className = { styles . body } >
181+ < div >
182+ < div >
183+ < >
184+ < Heading > About Typeform</ Heading >
185+ < Paragraph className = { styles . aboutP } >
186+ The{ ' ' }
187+ < TextLink
188+ href = "https://www.typeform.com/"
189+ target = "_blank"
190+ rel = "noopener noreferrer" >
191+ Typeform
192+ </ TextLink > { ' ' }
193+ app allows you to reference your forms from Typeform without leaving Contentful.
194+ </ Paragraph >
195+ </ >
196+ < hr className = { styles . splitter } />
197+ </ div >
198+ < div >
199+ < >
200+ < Heading > Configuration</ Heading >
235201 < FormLabel htmlFor = "workspaceId" isRequired >
236202 Typeform workspace
237203 </ FormLabel >
@@ -251,32 +217,49 @@ export class AppConfig extends React.Component<Props, State> {
251217 </ Option >
252218 ) ) }
253219 </ Select >
254- </ Box >
255- </ >
256- ) }
257-
258- < hr className = { styles . splitter } />
259-
260- < Subheading marginTop = "spacingXl" marginBottom = "spacing2Xs" >
261- Assign content types
262- </ Subheading >
263- < Paragraph marginBottom = "spacingM" >
264- Select the content type(s) you want to use with Typeform. You can change this anytime by
265- navigating to the 'Sidebar' tab in your content model.
266- </ Paragraph >
267- < Paragraph marginBottom = "spacingXs" style = { { fontWeight : '600' } } >
268- Content types
269- </ Paragraph >
270- < ContentTypeMultiSelect
271- selectedContentTypes = { selectedContentTypes }
272- setSelectedContentTypes = { ( contentTypes ) =>
273- this . setState ( { selectedContentTypes : contentTypes } )
274- }
275- sdk = { sdk }
276- cma = { sdk . cma }
277- />
278- </ Box >
279- </ Flex >
220+ </ >
221+ < hr className = { styles . splitter } />
222+ < >
223+ < Heading > Assign to content types</ Heading >
224+ { contentTypes . length > 0 ? (
225+ < >
226+ < Paragraph >
227+ This app can only be used with < strong > Short text</ strong > fields. Select
228+ which content types to use with the Typeform App.
229+ </ Paragraph >
230+ < FieldSelector
231+ contentTypes = { contentTypes }
232+ compatibleFields = { compatibleFields }
233+ selectedFields = { selectedFields }
234+ onSelectedFieldsChange = { this . onSelectedFieldsChange }
235+ />
236+ </ >
237+ ) : (
238+ < Note variant = "warning" >
239+ There are < strong > no content types with fields of type Short Text</ strong > { ' ' }
240+ fields in this environment. You can add one in your{ ' ' }
241+ < TextLink
242+ variant = "primary"
243+ target = "_blank"
244+ rel = "noopener noreferrer"
245+ href = {
246+ environment === 'master'
247+ ? `https://${ sdk . hostnames . webapp } /spaces/${ space } /content_types`
248+ : `https://${ sdk . hostnames . webapp } /spaces/${ space } /environments/${ environment } /content_types`
249+ } >
250+ content model
251+ </ TextLink > { ' ' }
252+ and assign it to the app from this screen.
253+ </ Note >
254+ ) }
255+ </ >
256+ </ div >
257+ </ div >
258+ </ div >
259+ < div className = { styles . icon } >
260+ < img src = { logo } alt = "typeform logo" />
261+ </ div >
262+ </ div >
280263 ) ;
281264 }
282265}
0 commit comments