11'use client' ;
22
33import { zodResolver } from '@hookform/resolvers/zod' ;
4- import { createContext , useContext , useEffect , useState } from 'react' ;
4+ import { createContext , useContext , useEffect } from 'react' ;
5+ import { create } from 'zustand' ;
56import {
67 FieldErrors ,
78 UseFormRegister ,
@@ -11,7 +12,7 @@ import {
1112import { toast } from 'react-hot-toast' ;
1213
1314import { BG_COLORS , THUMBNAIL_CONSTANTS } from '@nbw/config' ;
14- import { parseSongFromBuffer , SongFileType } from '@nbw/song' ;
15+ import { parseSongFromBuffer , type SongFileType } from '@nbw/song' ;
1516import axiosInstance from '@web/lib/axios' ;
1617import { InvalidTokenError , getTokenLocal } from '@web/lib/axios/token.utils' ;
1718import {
@@ -21,6 +22,72 @@ import {
2122
2223import UploadCompleteModal from '../UploadCompleteModal' ;
2324
25+ interface UploadSongState {
26+ song : SongFileType | null ;
27+ filename : string | null ;
28+ instrumentSounds : string [ ] ;
29+ isSubmitting : boolean ;
30+ sendError : string | null ;
31+ isUploadComplete : boolean ;
32+ uploadedSongId : string | null ;
33+ }
34+
35+ interface UploadSongActions {
36+ setSong : ( song : SongFileType | null ) => void ;
37+ setFilename : ( filename : string | null ) => void ;
38+ setInstrumentSounds : ( sounds : string [ ] ) => void ;
39+ setInstrumentSound : ( index : number , value : string ) => void ;
40+ setIsSubmitting : ( isSubmitting : boolean ) => void ;
41+ setSendError : ( error : string | null ) => void ;
42+ setIsUploadComplete : ( isComplete : boolean ) => void ;
43+ setUploadedSongId : ( id : string | null ) => void ;
44+ reset : ( ) => void ;
45+ }
46+
47+ type UploadSongStore = UploadSongState & UploadSongActions ;
48+
49+ const initialState : UploadSongState = {
50+ song : null ,
51+ filename : null ,
52+ instrumentSounds : [ ] ,
53+ isSubmitting : false ,
54+ sendError : null ,
55+ isUploadComplete : false ,
56+ uploadedSongId : null ,
57+ } ;
58+
59+ export const useUploadSongStore = create < UploadSongStore > ( ( set , get ) => ( {
60+ ...initialState ,
61+
62+ setSong : ( song ) => set ( { song } ) ,
63+ setFilename : ( filename ) => set ( { filename } ) ,
64+ setInstrumentSounds : ( sounds ) => set ( { instrumentSounds : sounds } ) ,
65+ setInstrumentSound : ( index , value ) => {
66+ const newValues = [ ...get ( ) . instrumentSounds ] ;
67+ newValues [ index ] = value ;
68+ set ( { instrumentSounds : newValues } ) ;
69+ } ,
70+ setIsSubmitting : ( isSubmitting ) => set ( { isSubmitting } ) ,
71+ setSendError : ( error ) => set ( { sendError : error } ) ,
72+ setIsUploadComplete : ( isComplete ) => set ( { isUploadComplete : isComplete } ) ,
73+ setUploadedSongId : ( id ) => set ( { uploadedSongId : id } ) ,
74+ reset : ( ) => set ( initialState ) ,
75+ } ) ) ;
76+
77+ // Context for form methods (React Hook Form needs to be initialized in a component)
78+ interface UploadSongFormContextType {
79+ formMethods : UseFormReturn < UploadSongForm > ;
80+ register : UseFormRegister < UploadSongForm > ;
81+ errors : FieldErrors < UploadSongForm > ;
82+ setFile : ( file : File | null ) => Promise < void > ;
83+ setInstrumentSound : ( index : number , value : string ) => void ;
84+ submitSong : ( ) => Promise < void > ;
85+ }
86+
87+ const UploadSongFormContext = createContext < UploadSongFormContextType | null > (
88+ null ,
89+ ) ;
90+
2491export type useUploadSongProviderType = {
2592 song : SongFileType | null ;
2693 filename : string | null ;
@@ -37,27 +104,29 @@ export type useUploadSongProviderType = {
37104 uploadedSongId : string | null ;
38105} ;
39106
40- export const UploadSongContext = createContext < useUploadSongProviderType > (
41- null as unknown as useUploadSongProviderType ,
42- ) ;
43-
44107export const UploadSongProvider = ( {
45108 children,
46109} : {
47110 children : React . ReactNode ;
48111} ) => {
49- const [ song , setSong ] = useState < SongFileType | null > ( null ) ;
50- const [ filename , setFilename ] = useState < string | null > ( null ) ;
51- const [ instrumentSounds , setInstrumentSounds ] = useState < string [ ] > ( [ ] ) ;
52- const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
53-
54- {
55- /* TODO: React Hook Form has an isSubmitting attribute. Can we leverage it? https://react-hook-form.com/docs/useformstate */
56- }
57-
58- const [ sendError , setSendError ] = useState < string | null > ( null ) ;
59- const [ isUploadComplete , setIsUploadComplete ] = useState ( false ) ;
60- const [ uploadedSongId , setUploadedSongId ] = useState < string | null > ( null ) ;
112+ const store = useUploadSongStore ( ) ;
113+ const {
114+ song,
115+ filename,
116+ instrumentSounds,
117+ isSubmitting,
118+ sendError,
119+ isUploadComplete,
120+ uploadedSongId,
121+ setSong,
122+ setFilename,
123+ setInstrumentSounds,
124+ setInstrumentSound : setInstrumentSoundStore ,
125+ setIsSubmitting,
126+ setSendError,
127+ setIsUploadComplete,
128+ setUploadedSongId,
129+ } = store ;
61130
62131 const formMethods = useForm < UploadSongForm > ( {
63132 resolver : zodResolver ( uploadSongFormSchema ) ,
@@ -164,30 +233,29 @@ export const UploadSongProvider = ({
164233 const setFileHandler = async ( file : File | null ) => {
165234 if ( ! file ) return ;
166235
167- let song : SongFileType ;
236+ let parsedSong : SongFileType ;
168237
169238 try {
170- song = ( await parseSongFromBuffer (
239+ parsedSong = ( await parseSongFromBuffer (
171240 await file . arrayBuffer ( ) ,
172241 ) ) as unknown as SongFileType ; // TODO: Investigate this weird type error
173242 } catch ( e ) {
174243 console . error ( 'Error parsing song file' , e ) ;
175244 toast . error ( 'Invalid song file! Please try again with a different song.' ) ;
176245 setSong ( null ) ;
177-
178246 return ;
179247 }
180248
181- setSong ( song ) ;
249+ setSong ( parsedSong ) ;
182250 setFilename ( file . name ) ;
183251
184- const { title, description, originalAuthor } = song ;
252+ const { title, description, originalAuthor } = parsedSong ;
185253 const formTitle = title || file . name . replace ( '.nbs' , '' ) ;
186254 formMethods . setValue ( 'title' , formTitle ) ;
187255 formMethods . setValue ( 'description' , description ) ;
188256 formMethods . setValue ( 'originalAuthor' , originalAuthor ) ;
189257
190- const instrumentList = song . instruments . map (
258+ const instrumentList = parsedSong . instruments . map (
191259 ( instrument ) => instrument . file ,
192260 ) ;
193261
@@ -196,9 +264,9 @@ export const UploadSongProvider = ({
196264 } ;
197265
198266 const setInstrumentSound = ( index : number , value : string ) => {
267+ setInstrumentSoundStore ( index , value ) ;
199268 const newValues = [ ...instrumentSounds ] ;
200269 newValues [ index ] = value ;
201- setInstrumentSounds ( newValues ) ;
202270 formMethods . setValue ( 'customInstruments' , newValues ) ;
203271 } ;
204272
@@ -252,35 +320,42 @@ export const UploadSongProvider = ({
252320 } ;
253321 } , [ formMethods . formState . isDirty , isUploadComplete ] ) ;
254322
323+ const formContextValue : UploadSongFormContextType = {
324+ formMethods,
325+ register,
326+ errors,
327+ setFile : setFileHandler ,
328+ setInstrumentSound,
329+ submitSong,
330+ } ;
331+
255332 return (
256- < UploadSongContext . Provider
257- value = { {
258- sendError,
259- formMethods,
260- register,
261- errors,
262- submitSong,
263- song,
264- filename,
265- instrumentSounds,
266- setInstrumentSound,
267- setFile : setFileHandler ,
268- isSubmitting,
269- isUploadComplete,
270- uploadedSongId,
271- } }
272- >
333+ < UploadSongFormContext . Provider value = { formContextValue } >
273334 { uploadedSongId && (
274335 < UploadCompleteModal
275336 isOpen = { isUploadComplete }
276337 songId = { uploadedSongId }
277338 />
278339 ) }
279340 { children }
280- </ UploadSongContext . Provider >
341+ </ UploadSongFormContext . Provider >
281342 ) ;
282343} ;
283344
345+ // Hook that combines Zustand store with React Hook Form from context
346+ // This maintains backward compatibility with the old Context API
284347export const useUploadSongProvider = ( ) : useUploadSongProviderType => {
285- return useContext ( UploadSongContext ) ;
348+ const store = useUploadSongStore ( ) ;
349+ const formContext = useContext ( UploadSongFormContext ) ;
350+
351+ if ( ! formContext ) {
352+ throw new Error (
353+ 'useUploadSongProvider must be used within UploadSongProvider' ,
354+ ) ;
355+ }
356+
357+ return {
358+ ...store ,
359+ ...formContext ,
360+ } ;
286361} ;
0 commit comments