@@ -166,7 +166,7 @@ type PendingViewerRequestState = {
166166 action : "submit" | "remove" ;
167167 songId ?: string ;
168168 query ?: string ;
169- requestMode ?: "catalog" | "random" | "choice" ;
169+ requestMode ?: "catalog" | "random" | "favorite" | " choice";
170170 requestKind ?: "regular" | "vip" ;
171171 requestedPath ?: RequestPathOption | null ;
172172 itemId ?: string ;
@@ -1192,6 +1192,14 @@ function PublicChannelPage() {
11921192 replaceExisting : boolean ;
11931193 itemId ?: string ;
11941194 }
1195+ | {
1196+ action : "submit" ;
1197+ requestMode : "favorite" ;
1198+ requestKind : "regular" | "vip" ;
1199+ vipTokenCost ?: number ;
1200+ replaceExisting : boolean ;
1201+ itemId ?: string ;
1202+ }
11951203 ) => {
11961204 const response = await fetch ( `/api/channel/${ slug } /viewer-request` , {
11971205 method : "POST" ,
@@ -1206,10 +1214,14 @@ function PublicChannelPage() {
12061214 requestMode : "catalog" ,
12071215 requestedPath : input . requestedPath ,
12081216 }
1209- : {
1210- query : input . query ,
1211- requestMode : input . requestMode ,
1212- } ) ,
1217+ : "query" in input
1218+ ? {
1219+ query : input . query ,
1220+ requestMode : input . requestMode ,
1221+ }
1222+ : {
1223+ requestMode : input . requestMode ,
1224+ } ) ,
12131225 requestKind : input . requestKind ,
12141226 vipTokenCost : input . vipTokenCost ,
12151227 replaceExisting : input . replaceExisting ,
@@ -1715,15 +1727,25 @@ function PublicChannelPage() {
17151727 replaceExisting = { effectiveViewerReplaceExisting }
17161728 mutationIsPending = { viewerRequestMutation . isPending }
17171729 pendingViewerRequest = { pendingViewerRequest }
1718- onSubmit = { ( query , requestMode , requestKind ) =>
1719- viewerRequestMutation . mutate ( {
1720- action : "submit" ,
1721- query,
1722- requestMode,
1723- requestKind,
1724- replaceExisting : effectiveViewerReplaceExisting ,
1725- itemId : editingViewerRequest ?. id ,
1726- } )
1730+ onSubmit = { ( requestMode , requestKind , query ) =>
1731+ viewerRequestMutation . mutate (
1732+ requestMode === "favorite"
1733+ ? {
1734+ action : "submit" ,
1735+ requestMode,
1736+ requestKind,
1737+ replaceExisting : effectiveViewerReplaceExisting ,
1738+ itemId : editingViewerRequest ?. id ,
1739+ }
1740+ : {
1741+ action : "submit" ,
1742+ query : query ?? "" ,
1743+ requestMode,
1744+ requestKind,
1745+ replaceExisting : effectiveViewerReplaceExisting ,
1746+ itemId : editingViewerRequest ?. id ,
1747+ }
1748+ )
17271749 }
17281750 onCancelEdit = { handleCancelViewerRequestEdit }
17291751 />
@@ -2717,17 +2739,20 @@ function ViewerSpecialRequestControls(props: {
27172739 mutationIsPending : boolean ;
27182740 pendingViewerRequest : PendingViewerRequestState ;
27192741 onSubmit : (
2720- query : string ,
2721- requestMode : "random " | "choice " ,
2722- requestKind : "regular" | "vip"
2742+ requestMode : "random" | "favorite" | "choice" ,
2743+ requestKind : "regular " | "vip " ,
2744+ query ?: string
27232745 ) => void ;
27242746 onCancelEdit : ( ) => void ;
27252747} ) {
27262748 const { t } = useLocaleTranslation ( "playlist" ) ;
27272749 const [ artistQuery , setArtistQuery ] = useState ( "" ) ;
2728- const [ requestMode , setRequestMode ] = useState < "random" | "choice" > ( "random" ) ;
2750+ const [ requestMode , setRequestMode ] = useState <
2751+ "random" | "favorite" | "choice"
2752+ > ( "random" ) ;
27292753 const [ requestKind , setRequestKind ] = useState < "regular" | "vip" > ( "regular" ) ;
27302754 const normalizedQuery = artistQuery . trim ( ) ;
2755+ const favoriteModeSelected = requestMode === "favorite" ;
27312756 const isViewerReady =
27322757 props . viewerStateLoading ||
27332758 props . viewerState != null ||
@@ -2767,17 +2792,22 @@ function ViewerSpecialRequestControls(props: {
27672792 } ) ;
27682793 const helperText =
27692794 selectedDisabledReason ||
2770- ( normalizedQuery . length >= 2
2771- ? requestMode === "random"
2772- ? t ( "specialRequest.randomHelp" )
2773- : t ( "specialRequest.choiceHelp" )
2774- : null ) ;
2795+ ( favoriteModeSelected
2796+ ? t ( "specialRequest.favoriteHelp" )
2797+ : normalizedQuery . length >= 2
2798+ ? requestMode === "random"
2799+ ? t ( "specialRequest.randomHelp" )
2800+ : t ( "specialRequest.choiceHelp" )
2801+ : null ) ;
2802+ const pendingQuery = props . pendingViewerRequest ?. query ?. trim ( ) ?? "" ;
27752803 const submitPending =
27762804 props . mutationIsPending &&
27772805 props . pendingViewerRequest ?. action === "submit" &&
27782806 props . pendingViewerRequest . requestMode === requestMode &&
27792807 props . pendingViewerRequest . requestKind === requestKind &&
2780- props . pendingViewerRequest . query ?. trim ( ) === normalizedQuery ;
2808+ ( favoriteModeSelected
2809+ ? pendingQuery . length === 0
2810+ : pendingQuery === normalizedQuery ) ;
27812811 const compactToggleClass =
27822812 "h-8 min-w-[4.5rem] px-2.5 text-[11px] tracking-[0.05em] shadow-none" ;
27832813
@@ -2813,10 +2843,15 @@ function ViewerSpecialRequestControls(props: {
28132843 </ Label >
28142844 < Input
28152845 id = "viewer-special-request-artist"
2816- value = { artistQuery }
2846+ value = { favoriteModeSelected ? "" : artistQuery }
28172847 onChange = { ( event ) => setArtistQuery ( event . target . value ) }
2818- placeholder = { t ( "specialRequest.artistPlaceholder" ) }
2848+ placeholder = {
2849+ favoriteModeSelected
2850+ ? t ( "specialRequest.favoritePlaceholder" )
2851+ : t ( "specialRequest.artistPlaceholder" )
2852+ }
28192853 className = "h-9 px-3"
2854+ disabled = { favoriteModeSelected }
28202855 />
28212856 </ div >
28222857
@@ -2845,6 +2880,16 @@ function ViewerSpecialRequestControls(props: {
28452880 >
28462881 { t ( "specialRequest.choice" ) }
28472882 </ Button >
2883+ < Button
2884+ type = "button"
2885+ size = "sm"
2886+ variant = { requestMode === "favorite" ? "secondary" : "ghost" }
2887+ className = { cn ( compactToggleClass , "w-auto" ) }
2888+ aria-pressed = { requestMode === "favorite" }
2889+ onClick = { ( ) => setRequestMode ( "favorite" ) }
2890+ >
2891+ { t ( "specialRequest.favorite" ) }
2892+ </ Button >
28482893 </ div >
28492894 </ div >
28502895
@@ -2882,7 +2927,11 @@ function ViewerSpecialRequestControls(props: {
28822927 variant = "secondary"
28832928 className = "h-9 min-w-[6.5rem] px-3 shadow-none"
28842929 onClick = { ( ) =>
2885- props . onSubmit ( normalizedQuery , requestMode , requestKind )
2930+ props . onSubmit (
2931+ requestMode ,
2932+ requestKind ,
2933+ favoriteModeSelected ? undefined : normalizedQuery
2934+ )
28862935 }
28872936 disabled = { ! ! selectedDisabledReason || props . mutationIsPending }
28882937 >
@@ -3290,7 +3339,7 @@ function ManageSearchSongActions(props: {
32903339
32913340function getViewerSpecialActionDisabledReason ( input : {
32923341 query : string ;
3293- requestMode : "random" | "choice" ;
3342+ requestMode : "random" | "favorite" | " choice";
32943343 requestKind : "regular" | "vip" ;
32953344 requestsOpen ?: boolean ;
32963345 viewerState : ViewerRequestStateData [ "viewer" ] ;
@@ -3299,7 +3348,7 @@ function getViewerSpecialActionDisabledReason(input: {
32993348 editingRequest : EnrichedPublicPlaylistItem | null ;
33003349 t : ( key : string , options ?: Record < string , unknown > ) => string ;
33013350} ) {
3302- if ( input . query . length < 2 ) {
3351+ if ( input . requestMode !== "favorite" && input . query . length < 2 ) {
33033352 return input . t ( "specialRequest.artistMin" ) ;
33043353 }
33053354
0 commit comments