11import { useCallback , useEffect , useRef , useState } from "react" ;
22import { Link , useParams } from "react-router" ;
33import { type Draft , getDraft } from "../../api/drafts.ts" ;
4- import { getStack , type Stack } from "../../api/stacks.ts" ;
4+ import { getStack , listStacks , type Stack } from "../../api/stacks.ts" ;
55import { listStates , type State } from "../../api/states.ts" ;
66import { isApiError } from "../../lib/api/api-error.ts" ;
77import { isAbortError } from "../../lib/async/abort-error.ts" ;
88import { readErrorMessage } from "../../lib/async/loadable.ts" ;
9- import { StateBadge } from "../stack/state-badge .tsx" ;
9+ import { DraftEditForm } from "./draft-edit-form .tsx" ;
1010import { sortStatesByPosition } from "./sort-states-by-position.ts" ;
1111
1212type DraftLoadable =
@@ -25,6 +25,16 @@ type StatesEnrichmentLoadable =
2525 readonly states ?: State [ ] ;
2626 } ;
2727
28+ type StacksEnrichmentLoadable =
29+ | { readonly kind : "none" }
30+ | { readonly kind : "loading" }
31+ | { readonly kind : "ready" ; readonly stacks : Stack [ ] }
32+ | {
33+ readonly kind : "error" ;
34+ readonly message : string ;
35+ readonly stacks ?: Stack [ ] ;
36+ } ;
37+
2838type StackEnrichmentLoadable =
2939 | { readonly kind : "none" }
3040 | { readonly kind : "loading" }
@@ -39,11 +49,15 @@ export function DraftDetailScreen() {
3949 const [ statesState , setStatesState ] = useState < StatesEnrichmentLoadable > ( {
4050 kind : "none" ,
4151 } ) ;
52+ const [ stacksState , setStacksState ] = useState < StacksEnrichmentLoadable > ( {
53+ kind : "none" ,
54+ } ) ;
4255 const [ stackState , setStackState ] = useState < StackEnrichmentLoadable > ( {
4356 kind : "none" ,
4457 } ) ;
4558 const [ reloadToken , setReloadToken ] = useState ( 0 ) ;
4659 const [ statesReloadToken , setStatesReloadToken ] = useState ( 0 ) ;
60+ const [ stacksReloadToken , setStacksReloadToken ] = useState ( 0 ) ;
4761 const [ stackReloadToken , setStackReloadToken ] = useState ( 0 ) ;
4862 const headingRef = useRef < HTMLHeadingElement > ( null ) ;
4963 const focusHeadingOnReadyRef = useRef ( false ) ;
@@ -56,16 +70,35 @@ export function DraftDetailScreen() {
5670 setStatesReloadToken ( ( current ) => current + 1 ) ;
5771 } , [ ] ) ;
5872
73+ const reloadStacks = useCallback ( ( ) => {
74+ setStacksReloadToken ( ( current ) => current + 1 ) ;
75+ } , [ ] ) ;
76+
5977 const reloadStack = useCallback ( ( ) => {
6078 setStackReloadToken ( ( current ) => current + 1 ) ;
6179 } , [ ] ) ;
6280
81+ const handleUpdated = useCallback ( ( draft : Draft ) => {
82+ setDraftState ( {
83+ kind : "ready" ,
84+ draft,
85+ } ) ;
86+ } , [ ] ) ;
87+
88+ const loadedDraftId = draftState . kind === "ready"
89+ ? draftState . draft . id
90+ : null ;
91+ const assignedStackId = draftState . kind === "ready"
92+ ? draftState . draft . stackId
93+ : null ;
94+
6395 useEffect ( ( ) => {
6496 const abortController = new AbortController ( ) ;
6597 const { signal } = abortController ;
6698
6799 setDraftState ( { kind : "loading" } ) ;
68100 setStatesState ( { kind : "none" } ) ;
101+ setStacksState ( { kind : "none" } ) ;
69102 setStackState ( { kind : "none" } ) ;
70103
71104 void ( async ( ) => {
@@ -102,7 +135,7 @@ export function DraftDetailScreen() {
102135 } , [ draftId , reloadToken ] ) ;
103136
104137 useEffect ( ( ) => {
105- if ( draftState . kind !== "ready" ) {
138+ if ( loadedDraftId === null ) {
106139 setStatesState ( { kind : "none" } ) ;
107140 return ;
108141 }
@@ -148,17 +181,66 @@ export function DraftDetailScreen() {
148181 } ) ( ) ;
149182
150183 return ( ) => abortController . abort ( ) ;
151- } , [ draftState , statesReloadToken ] ) ;
184+ } , [ loadedDraftId , statesReloadToken ] ) ;
152185
153186 useEffect ( ( ) => {
154- if ( draftState . kind !== "ready" || draftState . draft . stackId === null ) {
187+ if ( loadedDraftId === null ) {
188+ setStacksState ( { kind : "none" } ) ;
189+ return ;
190+ }
191+
192+ const abortController = new AbortController ( ) ;
193+ const { signal } = abortController ;
194+
195+ setStacksState ( ( current ) => {
196+ if ( current . kind === "error" ) {
197+ return current ;
198+ }
199+
200+ return { kind : "loading" } ;
201+ } ) ;
202+
203+ void ( async ( ) => {
204+ try {
205+ const stacks = await listStacks ( undefined , signal ) ;
206+
207+ if ( signal . aborted ) {
208+ return ;
209+ }
210+
211+ setStacksState ( {
212+ kind : "ready" ,
213+ stacks,
214+ } ) ;
215+ } catch ( error : unknown ) {
216+ if ( signal . aborted || isAbortError ( error ) ) {
217+ return ;
218+ }
219+
220+ setStacksState ( ( current ) => ( {
221+ kind : "error" ,
222+ message : readErrorMessage ( error , "Could not load Stacks." ) ,
223+ ...( current . kind === "ready"
224+ ? { stacks : current . stacks }
225+ : current . kind === "error" && current . stacks !== undefined
226+ ? { stacks : current . stacks }
227+ : { } ) ,
228+ } ) ) ;
229+ }
230+ } ) ( ) ;
231+
232+ return ( ) => abortController . abort ( ) ;
233+ } , [ loadedDraftId , stacksReloadToken ] ) ;
234+
235+ useEffect ( ( ) => {
236+ if ( assignedStackId === null ) {
155237 setStackState ( { kind : "none" } ) ;
156238 return ;
157239 }
158240
159241 const abortController = new AbortController ( ) ;
160242 const { signal } = abortController ;
161- const { stackId } = draftState . draft ;
243+ const stackId = assignedStackId ;
162244
163245 setStackState ( ( current ) => {
164246 if ( current . kind === "ready" && current . stack . id === stackId ) {
@@ -197,7 +279,7 @@ export function DraftDetailScreen() {
197279 } ) ( ) ;
198280
199281 return ( ) => abortController . abort ( ) ;
200- } , [ draftState , stackReloadToken ] ) ;
282+ } , [ assignedStackId , stackReloadToken ] ) ;
201283
202284 useEffect ( ( ) => {
203285 if ( draftState . kind !== "ready" || ! focusHeadingOnReadyRef . current ) {
@@ -276,13 +358,40 @@ export function DraftDetailScreen() {
276358 : statesState . kind === "error" && statesState . states !== undefined
277359 ? statesState . states
278360 : [ ] ;
279- const state = states . find ( ( entry ) => entry . id === draft . stateId ) ;
361+ const stacks = stacksState . kind === "ready"
362+ ? stacksState . stacks
363+ : stacksState . kind === "error" && stacksState . stacks !== undefined
364+ ? stacksState . stacks
365+ : [ ] ;
280366
281367 return (
282368 < section
283369 className = "page draft-detail"
284370 aria-labelledby = "draft-detail-heading"
285371 >
372+ < nav className = "breadcrumb" aria-label = "Breadcrumb" >
373+ < ol className = "breadcrumb__list" >
374+ < li className = "breadcrumb__item" >
375+ < Link className = "breadcrumb__link" to = "/" >
376+ Drafts
377+ </ Link >
378+ </ li >
379+ { stackState . kind === "ready" && (
380+ < li className = "breadcrumb__item" >
381+ < Link
382+ className = "breadcrumb__link"
383+ to = { `/stacks/${ stackState . stack . id } ` }
384+ >
385+ { stackState . stack . title }
386+ </ Link >
387+ </ li >
388+ ) }
389+ < li className = "breadcrumb__item breadcrumb__item--current" >
390+ < span aria-current = "page" > { draft . title } </ span >
391+ </ li >
392+ </ ol >
393+ </ nav >
394+
286395 < p className = "page__eyebrow" > Draft</ p >
287396 < h1
288397 className = "page__title"
@@ -306,10 +415,17 @@ export function DraftDetailScreen() {
306415 </ div >
307416 ) }
308417
309- { state && (
310- < p className = "draft-detail__state" >
311- < StateBadge state = { state } />
312- </ p >
418+ { stacksState . kind === "error" && (
419+ < div className = "draft-detail__enrichment-error" role = "alert" >
420+ < p className = "draft-detail__status" > { stacksState . message } </ p >
421+ < button
422+ className = "draft-detail__retry"
423+ type = "button"
424+ onClick = { reloadStacks }
425+ >
426+ Retry loading Stacks
427+ </ button >
428+ </ div >
313429 ) }
314430
315431 { stackState . kind === "error" && (
@@ -325,31 +441,12 @@ export function DraftDetailScreen() {
325441 </ div >
326442 ) }
327443
328- { stackState . kind === "ready" && (
329- < p className = "draft-detail__stack" >
330- < span className = "draft-detail__stack-label" > Stack</ span >
331- < Link
332- className = "draft-detail__stack-link"
333- to = { `/stacks/${ stackState . stack . id } ` }
334- >
335- { stackState . stack . title }
336- </ Link >
337- </ p >
338- ) }
339-
340- { draft . description . trim ( ) . length > 0
341- ? < p className = "draft-detail__description" > { draft . description } </ p >
342- : (
343- < p className = "draft-detail__description draft-detail__description--empty" >
344- No description yet.
345- </ p >
346- ) }
347-
348- < p >
349- < Link className = "page__action-link" to = "/" >
350- Back to Drafts
351- </ Link >
352- </ p >
444+ < DraftEditForm
445+ draft = { draft }
446+ states = { states }
447+ stacks = { stacks }
448+ onUpdated = { handleUpdated }
449+ />
353450 </ section >
354451 ) ;
355452}
0 commit comments