Refactor code structure and remove redundant sections for improved readability and maintainability

This commit is contained in:
byntangxyz committed 2026-05-26 20:21:02 +07:00
1 parent 8df58d74de
commit c0be5d76d6
31 files changed
+80 -35

No files matched your search

@@ -1 +1 @@
.range-player[data-v-ed9db691]::-webkit-slider-thumb{-webkit-appearance:none;cursor:pointer;background:#fff;border-radius:50%;width:12px;height:12px}.range-vol[data-v-ed9db691]::-webkit-slider-thumb{-webkit-appearance:none;cursor:pointer;background:#fff;border-radius:50%;width:12px;height:12px}.animate-slide-in{animation:.3s ease-out slideIn}@keyframes slideIn{0%{opacity:0;transform:translate(100%)}to{opacity:1;transform:translate(0)}}*,:before,:after,::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border:0 solid #e5e7eb}:before,:after{--tw-content:""}html,:host{-webkit-text-size-adjust:100%;tab-size:4;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent;font-family:Inter,system-ui,sans-serif;line-height:1.5}body{line-height:inherit;margin:0}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-feature-settings:normal;font-variation-settings:normal;font-family:Space Mono,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-feature-settings:inherit;font-variation-settings:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:#0000;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{margin:0;padding:0;list-style:none}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder{opacity:1;color:#9ca3af}textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.-right-2{right:-.5rem}.-top-2{top:-.5rem}.bottom-0{bottom:0}.bottom-12{bottom:3rem}.bottom-2{bottom:.5rem}.bottom-24{bottom:6rem}.left-0{left:0}.left-12{left:3rem}.left-20{left:5rem}.left-3{left:.75rem}.right-0{right:0}.right-12{right:3rem}.right-2{right:.5rem}.right-20{right:5rem}.right-4{right:1rem}.right-8{right:2rem}.top-0{top:0}.top-16{top:4rem}.top-20{top:5rem}.top-4{top:1rem}.top-8{top:2rem}.top-\[57px\]{top:57px}.top-full{top:100%}.z-20{z-index:20}.z-30{z-index:30}.z-50{z-index:50}.z-\[100\]{z-index:100}.m-4{margin:1rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-8{margin-left:2rem;margin-right:2rem}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-6{margin-top:1.5rem;margin-bottom:1.5rem}.mb-1{margin-bottom:.25rem}.mb-12{margin-bottom:3rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottomLine truncated .range-player[data-v-bb2272cc]::-webkit-slider-thumb{-webkit-appearance:none;cursor:pointer;background:#fff;border-radius:50%;width:12px;height:12px}.range-vol[data-v-bb2272cc]::-webkit-slider-thumb{-webkit-appearance:none;cursor:pointer;background:#fff;border-radius:50%;width:12px;height:12px}.animate-slide-in{animation:.3s ease-out slideIn}@keyframes slideIn{0%{opacity:0;transform:translate(100%)}to{opacity:1;transform:translate(0)}}*,:before,:after,::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border:0 solid #e5e7eb}:before,:after{--tw-content:""}html,:host{-webkit-text-size-adjust:100%;tab-size:4;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent;font-family:Inter,system-ui,sans-serif;line-height:1.5}body{line-height:inherit;margin:0}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-feature-settings:normal;font-variation-settings:normal;font-family:Space Mono,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-feature-settings:inherit;font-variation-settings:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:#0000;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{margin:0;padding:0;list-style:none}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder{opacity:1;color:#9ca3af}textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.-right-2{right:-.5rem}.-top-2{top:-.5rem}.bottom-0{bottom:0}.bottom-12{bottom:3rem}.bottom-2{bottom:.5rem}.bottom-24{bottom:6rem}.left-0{left:0}.left-12{left:3rem}.left-20{left:5rem}.left-3{left:.75rem}.right-0{right:0}.right-12{right:3rem}.right-2{right:.5rem}.right-20{right:5rem}.right-4{right:1rem}.right-8{right:2rem}.top-0{top:0}.top-16{top:4rem}.top-20{top:5rem}.top-4{top:1rem}.top-8{top:2rem}.top-\[57px\]{top:57px}.top-full{top:100%}.z-20{z-index:20}.z-30{z-index:30}.z-50{z-index:50}.z-\[100\]{z-index:100}.m-4{margin:1rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-8{margin-left:2rem;margin-right:2rem}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-6{margin-top:1.5rem;margin-bottom:1.5rem}.mb-1{margin-bottom:.25rem}.mb-12{margin-bottom:3rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottomLine truncated
@@ -6,4 +6,4 @@ var e=Object.defineProperty,t=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=nu
`);return e===-1?``:t.stack.slice(e+1)})();try{if(!e.stack)e.stack=n;else if(n){let t=n.indexOf(` `);return e===-1?``:t.stack.slice(e+1)})();try{if(!e.stack)e.stack=n;else if(n){let t=n.indexOf(`
`),r=t===-1?-1:n.indexOf(` `),r=t===-1?-1:n.indexOf(`
`,t+1),i=r===-1?``:n.slice(r+1);String(e.stack).endsWith(i)||(e.stack+=` `,t+1),i=r===-1?``:n.slice(r+1);String(e.stack).endsWith(i)||(e.stack+=`
`+n)}}catch{}}throw e}}_request(e,t){typeof e==`string`?(t||={},t.url=e):t=e||{},t=Qd(this.defaults,t);let{transitional:n,paramsSerializer:r,headers:i}=t;n!==void 0&&Of.assertOptions(n,{silentJSONParsing:kf.transitional(kf.boolean),forcedJSONParsing:kf.transitional(kf.boolean),clarifyTimeoutError:kf.transitional(kf.boolean),legacyInterceptorReqResOrdering:kf.transitional(kf.boolean)},!1),r!=null&&(Y.isFunction(r)?t.paramsSerializer={serialize:r}:Of.assertOptions(r,{encode:kf.function,serialize:kf.function},!0)),t.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls===void 0?t.allowAbsoluteUrls=!0:t.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls),Of.assertOptions(t,{baseUrl:kf.spelling(`baseURL`),withXsrfToken:kf.spelling(`withXSRFToken`)},!0),t.method=(t.method||this.defaults.method||`get`).toLowerCase();let a=i&&Y.merge(i.common,i[t.method]);i&&Y.forEach([`delete`,`get`,`head`,`post`,`put`,`patch`,`query`,`common`],e=>{delete i[e]}),t.headers=id.concat(a,i);let o=[],s=!0;this.interceptors.request.forEach(function(e){if(typeof e.runWhen==`function`&&e.runWhen(t)===!1)return;s&&=e.synchronous;let n=t.transitional||bd;n&&n.legacyInterceptorReqResOrdering?o.unshift(e.fulfilled,e.rejected):o.push(e.fulfilled,e.rejected)});let c=[];this.interceptors.response.forEach(function(e){c.push(e.fulfilled,e.rejected)});let l,u=0,d;if(!s){let e=[wf.bind(this),void 0];for(e.unshift(...o),e.push(...c),d=e.length,l=Promise.resolve(t);u<d;)l=l.then(e[u++],e[u++]);return l}d=o.length;let f=t;for(;u<d;){let e=o[u++],t=o[u++];try{f=e(f)}catch(e){t.call(this,e);break}}try{l=wf.call(this,f)}catch(e){return Promise.reject(e)}for(u=0,d=c.length;u<d;)l=l.then(c[u++],c[u++]);return l}getUri(e){return e=Qd(this.defaults,e),vd(Xd(e.baseURL,e.url,e.allowAbsoluteUrls),e.params,e.paramsSerializer)}};Y.forEach([`delete`,`get`,`head`,`options`],function(e){Af.prototype[e]=function(t,n){return this.request(Qd(n||{},{method:e,url:t,data:(n||{}).data}))}}),Y.forEach([`post`,`put`,`patch`,`query`],function(e){function t(t){return function(n,r,i){return this.request(Qd(i||{},{method:e,headers:t?{"Content-Type":`multipart/form-data`}:{},url:n,data:r}))}}Af.prototype[e]=t(),e!==`query`&&(Af.prototype[e+`Form`]=t(!0))});var jf=class e{constructor(e){if(typeof e!=`function`)throw TypeError(`executor must be a function.`);let t;this.promise=new Promise(function(e){t=e});let n=this;this.promise.then(e=>{if(!n._listeners)return;let t=n._listeners.length;for(;t-- >0;)n._listeners[t](e);n._listeners=null}),this.promise.then=e=>{let t,r=new Promise(e=>{n.subscribe(e),t=e}).then(e);return r.cancel=function(){n.unsubscribe(t)},r},e(function(e,r,i){n.reason||(n.reason=new Rd(e,r,i),t(n.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(e){if(this.reason){e(this.reason);return}this._listeners?this._listeners.push(e):this._listeners=[e]}unsubscribe(e){if(!this._listeners)return;let t=this._listeners.indexOf(e);t!==-1&&this._listeners.splice(t,1)}toAbortSignal(){let e=new AbortController,t=t=>{e.abort(t)};return this.subscribe(t),e.signal.unsubscribe=()=>this.unsubscribe(t),e.signal}static source(){let t;return{token:new e(function(e){t=e}),cancel:t}}};function Mf(e){return function(t){return e.apply(null,t)}}function Nf(e){return Y.isObject(e)&&e.isAxiosError===!0}var Pf={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(Pf).forEach(([e,t])=>{Pf[t]=e});function Ff(e){let t=new Af(e),n=Al(Af.prototype.request,t);return Y.extend(n,Af.prototype,t,{allOwnKeys:!0}),Y.extend(n,t,null,{allOwnKeys:!0}),n.create=function(t){return Ff(Qd(e,t))},n}var Z=Ff(Fd);Z.Axios=Af,Z.CanceledError=Rd,Z.CancelToken=jf,Z.isCancel=Ld,Z.VERSION=df,Z.toFormData=pd,Z.AxiosError=X,Z.Can Line truncated `+n)}}catch{}}throw e}}_request(e,t){typeof e==`string`?(t||={},t.url=e):t=e||{},t=Qd(this.defaults,t);let{transitional:n,paramsSerializer:r,headers:i}=t;n!==void 0&&Of.assertOptions(n,{silentJSONParsing:kf.transitional(kf.boolean),forcedJSONParsing:kf.transitional(kf.boolean),clarifyTimeoutError:kf.transitional(kf.boolean),legacyInterceptorReqResOrdering:kf.transitional(kf.boolean)},!1),r!=null&&(Y.isFunction(r)?t.paramsSerializer={serialize:r}:Of.assertOptions(r,{encode:kf.function,serialize:kf.function},!0)),t.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls===void 0?t.allowAbsoluteUrls=!0:t.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls),Of.assertOptions(t,{baseUrl:kf.spelling(`baseURL`),withXsrfToken:kf.spelling(`withXSRFToken`)},!0),t.method=(t.method||this.defaults.method||`get`).toLowerCase();let a=i&&Y.merge(i.common,i[t.method]);i&&Y.forEach([`delete`,`get`,`head`,`post`,`put`,`patch`,`query`,`common`],e=>{delete i[e]}),t.headers=id.concat(a,i);let o=[],s=!0;this.interceptors.request.forEach(function(e){if(typeof e.runWhen==`function`&&e.runWhen(t)===!1)return;s&&=e.synchronous;let n=t.transitional||bd;n&&n.legacyInterceptorReqResOrdering?o.unshift(e.fulfilled,e.rejected):o.push(e.fulfilled,e.rejected)});let c=[];this.interceptors.response.forEach(function(e){c.push(e.fulfilled,e.rejected)});let l,u=0,d;if(!s){let e=[wf.bind(this),void 0];for(e.unshift(...o),e.push(...c),d=e.length,l=Promise.resolve(t);u<d;)l=l.then(e[u++],e[u++]);return l}d=o.length;let f=t;for(;u<d;){let e=o[u++],t=o[u++];try{f=e(f)}catch(e){t.call(this,e);break}}try{l=wf.call(this,f)}catch(e){return Promise.reject(e)}for(u=0,d=c.length;u<d;)l=l.then(c[u++],c[u++]);return l}getUri(e){return e=Qd(this.defaults,e),vd(Xd(e.baseURL,e.url,e.allowAbsoluteUrls),e.params,e.paramsSerializer)}};Y.forEach([`delete`,`get`,`head`,`options`],function(e){Af.prototype[e]=function(t,n){return this.request(Qd(n||{},{method:e,url:t,data:(n||{}).data}))}}),Y.forEach([`post`,`put`,`patch`,`query`],function(e){function t(t){return function(n,r,i){return this.request(Qd(i||{},{method:e,headers:t?{"Content-Type":`multipart/form-data`}:{},url:n,data:r}))}}Af.prototype[e]=t(),e!==`query`&&(Af.prototype[e+`Form`]=t(!0))});var jf=class e{constructor(e){if(typeof e!=`function`)throw TypeError(`executor must be a function.`);let t;this.promise=new Promise(function(e){t=e});let n=this;this.promise.then(e=>{if(!n._listeners)return;let t=n._listeners.length;for(;t-- >0;)n._listeners[t](e);n._listeners=null}),this.promise.then=e=>{let t,r=new Promise(e=>{n.subscribe(e),t=e}).then(e);return r.cancel=function(){n.unsubscribe(t)},r},e(function(e,r,i){n.reason||(n.reason=new Rd(e,r,i),t(n.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(e){if(this.reason){e(this.reason);return}this._listeners?this._listeners.push(e):this._listeners=[e]}unsubscribe(e){if(!this._listeners)return;let t=this._listeners.indexOf(e);t!==-1&&this._listeners.splice(t,1)}toAbortSignal(){let e=new AbortController,t=t=>{e.abort(t)};return this.subscribe(t),e.signal.unsubscribe=()=>this.unsubscribe(t),e.signal}static source(){let t;return{token:new e(function(e){t=e}),cancel:t}}};function Mf(e){return function(t){return e.apply(null,t)}}function Nf(e){return Y.isObject(e)&&e.isAxiosError===!0}var Pf={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(Pf).forEach(([e,t])=>{Pf[t]=e});function Ff(e){let t=new Af(e),n=Al(Af.prototype.request,t);return Y.extend(n,Af.prototype,t,{allOwnKeys:!0}),Y.extend(n,t,null,{allOwnKeys:!0}),n.create=function(t){return Ff(Qd(e,t))},n}var Z=Ff(Fd);Z.Axios=Af,Z.CanceledError=Rd,Z.CancelToken=jf,Z.isCancel=Ld,Z.VERSION=df,Z.toFormData=pd,Z.AxiosError=X,Z.Can Line truncated
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

+2 -2
View File
@@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Noctune - Music That Hits Different</title> <title>Noctune - Music That Hits Different</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-C2ZrghzO.js"></script> <script type="module" crossorigin src="/assets/index-Ulrd01cQ.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-eqvJxEw1.css"> <link rel="stylesheet" crossorigin href="/assets/index-B-WaF5DI.css">
</head> </head>
<body class="bg-noctune-cream"> <body class="bg-noctune-cream">
<div id="app"></div> <div id="app"></div>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+1 -3
View File
@@ -52,9 +52,7 @@ const authStore = useAuthStore();
const uiStore = useUiStore(); const uiStore = useUiStore();
const playerStore = usePlayerStore(); const playerStore = usePlayerStore();
const isAuthPage = computed(() => { const isAuthPage = computed(() => route.meta?.guest === true)
return route.meta?.guest === true || route.path === '/register';
});
const hasPlayer = computed(() => !!playerStore.currentTrack); const hasPlayer = computed(() => !!playerStore.currentTrack);
+15 -3
View File
@@ -41,14 +41,14 @@
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/> <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</svg> </svg>
</button> </button>
<router-link to="/profile" class="text-black hover:opacity-80"> <button @click="goToProfile" class="text-black hover:opacity-80">
<div class="w-8 h-8 rounded-full bg-noctune-dark overflow-hidden border-2 border-black"> <div class="w-8 h-8 rounded-full bg-noctune-dark overflow-hidden border-2 border-black">
<img v-if="authStore.user?.avatarUrl" :src="formatUrl(authStore.user.avatarUrl)" class="w-full h-full object-cover" /> <img v-if="authStore.user?.avatarUrl" :src="formatUrl(authStore.user.avatarUrl)" class="w-full h-full object-cover" />
<div v-else class="w-full h-full flex items-center justify-center"> <div v-else class="w-full h-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg> <svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
</div> </div>
</div> </div>
</router-link> </button>
</div> </div>
</header> </header>
</template> </template>
@@ -61,11 +61,13 @@ import { usePlayerStore } from '../stores/playerStore.js'
import { useQueueStore } from '../stores/queueStore.js' import { useQueueStore } from '../stores/queueStore.js'
import api from '../lib/api.js' import api from '../lib/api.js'
import { formatUrl } from '../lib/utils.js' import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const router = useRouter() const router = useRouter()
const authStore = useAuthStore() const authStore = useAuthStore()
const playerStore = usePlayerStore() const playerStore = usePlayerStore()
const queueStore = useQueueStore() const queueStore = useQueueStore()
const { requireAuth } = useAuthGate()
const query = ref('') const query = ref('')
const searchResults = ref(null) const searchResults = ref(null)
@@ -100,6 +102,16 @@ function playSearchTrack(track) {
} }
function toggleLikeRedirect() { function toggleLikeRedirect() {
router.push('/library') requireAuth({
redirectTo: '/login',
onAuthenticated: () => router.push('/library')
})
}
function goToProfile() {
requireAuth({
redirectTo: '/login',
onAuthenticated: () => router.push('/profile')
})
} }
</script> </script>
+7 -2
View File
@@ -60,10 +60,12 @@ import { usePlayerStore } from '../stores/playerStore.js'
import { useQueueStore } from '../stores/queueStore.js' import { useQueueStore } from '../stores/queueStore.js'
import { useAuthStore } from '../stores/authStore.js' import { useAuthStore } from '../stores/authStore.js'
import { formatUrl } from '../lib/utils.js' import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const playerStore = usePlayerStore() const playerStore = usePlayerStore()
const queueStore = useQueueStore() const queueStore = useQueueStore()
const authStore = useAuthStore() const authStore = useAuthStore()
const { requireAuth } = useAuthGate()
const isLiked = ref(false) const isLiked = ref(false)
@@ -105,10 +107,13 @@ function playPrev() {
} }
} }
async function toggleLike() { function toggleLike() {
if (!playerStore.currentTrack) return if (!playerStore.currentTrack) return
const id = playerStore.currentTrack._id || playerStore.currentTrack.id const id = playerStore.currentTrack._id || playerStore.currentTrack.id
await authStore.toggleLike(id) requireAuth({
redirectTo: '/login',
onAuthenticated: () => authStore.toggleLike(id)
})
} }
// Sync isLiked ref reactively from authStore // Sync isLiked ref reactively from authStore
+9 -1
View File
@@ -70,6 +70,7 @@ import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '../stores/authStore.js' import { useAuthStore } from '../stores/authStore.js'
import api from '../lib/api.js' import api from '../lib/api.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@@ -77,6 +78,8 @@ const authStore = useAuthStore()
const playlists = ref([]) const playlists = ref([])
const { requireAuth } = useAuthGate()
const isActive = (path) => route.path === path const isActive = (path) => route.path === path
const isActivePrefix = (prefix) => route.path.startsWith(prefix) const isActivePrefix = (prefix) => route.path.startsWith(prefix)
@@ -88,7 +91,10 @@ async function fetchPlaylists() {
} catch {} } catch {}
} }
async function openCreatePlaylist() { function openCreatePlaylist() {
requireAuth({
redirectTo: '/login',
onAuthenticated: async () => {
const name = prompt('Playlist name:') const name = prompt('Playlist name:')
if (!name?.trim()) return if (!name?.trim()) return
try { try {
@@ -98,6 +104,8 @@ async function openCreatePlaylist() {
} }
} catch {} } catch {}
} }
})
}
onMounted(fetchPlaylists) onMounted(fetchPlaylists)
</script> </script>
+7 -4
View File
@@ -1,16 +1,19 @@
import { useAuthStore } from '../stores/authStore.js' import { useAuthStore } from '../stores/authStore.js'
import router from '../router/index.js'
let pendingAction = null let pendingAction = null
export function useAuthGate() { export function useAuthGate() {
const authStore = useAuthStore() const authStore = useAuthStore()
async function requireAuth({ message, onAuthenticated }) { async function requireAuth({ message, onAuthenticated, redirectTo }) {
if (authStore.isLoggedIn) { if (authStore.isLoggedIn) {
onAuthenticated() onAuthenticated?.()
} else { } else {
pendingAction = onAuthenticated pendingAction = onAuthenticated || null
if (message) { if (redirectTo) {
router.push(redirectTo)
} else if (message) {
const { useUiStore } = await import('../stores/uiStore.js') const { useUiStore } = await import('../stores/uiStore.js')
const uiStore = useUiStore() const uiStore = useUiStore()
uiStore.showToast(message, 'info') uiStore.showToast(message, 'info')
+8 -11
View File
@@ -10,14 +10,15 @@ import ProfilePage from '../views/ProfilePage.vue'
import AdminPage from '../views/AdminPage.vue' import AdminPage from '../views/AdminPage.vue'
const routes = [ const routes = [
{ path: '/', name: 'Login', component: LoginPage, meta: { guest: true } }, { path: '/', redirect: '/home' },
{ path: '/login', name: 'Login', component: LoginPage, meta: { guest: true } },
{ path: '/register', name: 'Register', component: RegisterPage, meta: { guest: true } }, { path: '/register', name: 'Register', component: RegisterPage, meta: { guest: true } },
{ path: '/home', name: 'Home', component: LandingPage, meta: { auth: true } }, { path: '/home', name: 'Home', component: LandingPage },
{ path: '/library', name: 'Library', component: LibraryPage, meta: { auth: true } }, { path: '/library', name: 'Library', component: LibraryPage },
{ path: '/playlist', name: 'Playlist', component: PlaylistPage, meta: { auth: true } }, { path: '/playlist', name: 'Playlist', component: PlaylistPage },
{ path: '/playlist/:id', name: 'PlaylistDetail', component: PlaylistPage, meta: { auth: true } }, { path: '/playlist/:id', name: 'PlaylistDetail', component: PlaylistPage },
{ path: '/profile', name: 'Profile', component: ProfilePage, meta: { auth: true } }, { path: '/profile', name: 'Profile', component: ProfilePage },
{ path: '/admin/tracks', name: 'AdminTracks', component: AdminPage, meta: { auth: true, admin: true } }, { path: '/admin/tracks', name: 'AdminTracks', component: AdminPage, meta: { admin: true } },
] ]
const router = createRouter({ const router = createRouter({
@@ -32,10 +33,6 @@ router.beforeEach((to, from, next) => {
return next('/home') return next('/home')
} }
if (to.meta.auth && !authStore.isLoggedIn) {
return next('/')
}
if (to.meta.guest && authStore.isLoggedIn) { if (to.meta.guest && authStore.isLoggedIn) {
return next('/home') return next('/home')
} }
+7 -2
View File
@@ -62,10 +62,12 @@ import { useQueueStore } from '../stores/queueStore.js'
import { useAuthStore } from '../stores/authStore.js' import { useAuthStore } from '../stores/authStore.js'
import api from '../lib/api.js' import api from '../lib/api.js'
import { formatUrl } from '../lib/utils.js' import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const playerStore = usePlayerStore() const playerStore = usePlayerStore()
const queueStore = useQueueStore() const queueStore = useQueueStore()
const authStore = useAuthStore() const authStore = useAuthStore()
const { requireAuth } = useAuthGate()
const tracks = ref([]) const tracks = ref([])
const loading = ref(true) const loading = ref(true)
@@ -93,8 +95,11 @@ function playFirstTrack() {
if (tracks.value.length) playTrack(tracks.value[0]) if (tracks.value.length) playTrack(tracks.value[0])
} }
async function toggleLike(track) { function toggleLike(track) {
await authStore.toggleLike(track._id) requireAuth({
redirectTo: '/login',
onAuthenticated: () => authStore.toggleLike(track._id)
})
} }
onMounted(async () => { onMounted(async () => {
+15 -3
View File
@@ -3,6 +3,10 @@
<section class="mb-12"> <section class="mb-12">
<h2 class="text-3xl font-black text-black mb-6">Liked Tracks</h2> <h2 class="text-3xl font-black text-black mb-6">Liked Tracks</h2>
<div v-if="loading" class="text-center py-12 text-gray-500">Loading...</div> <div v-if="loading" class="text-center py-12 text-gray-500">Loading...</div>
<div v-else-if="!authStore.isLoggedIn" class="text-center py-12">
<p class="text-gray-500 mb-4">Login to see your liked tracks</p>
<router-link to="/login" class="bg-noctune-yellow border-2 border-black px-6 py-3 font-bold inline-block hover:bg-yellow-400 transition-colors rounded-lg">Login</router-link>
</div>
<div v-else-if="likedTracks.length === 0" class="text-center py-12"> <div v-else-if="likedTracks.length === 0" class="text-center py-12">
<p class="text-gray-500 mb-4">No liked tracks yet</p> <p class="text-gray-500 mb-4">No liked tracks yet</p>
<router-link to="/home" class="bg-noctune-yellow border-2 border-black px-6 py-3 font-bold inline-block hover:bg-yellow-400 transition-colors rounded-lg">Discover Music</router-link> <router-link to="/home" class="bg-noctune-yellow border-2 border-black px-6 py-3 font-bold inline-block hover:bg-yellow-400 transition-colors rounded-lg">Discover Music</router-link>
@@ -75,10 +79,12 @@ import { useQueueStore } from '../stores/queueStore.js'
import { useAuthStore } from '../stores/authStore.js' import { useAuthStore } from '../stores/authStore.js'
import api from '../lib/api.js' import api from '../lib/api.js'
import { formatUrl } from '../lib/utils.js' import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const playerStore = usePlayerStore() const playerStore = usePlayerStore()
const queueStore = useQueueStore() const queueStore = useQueueStore()
const authStore = useAuthStore() const authStore = useAuthStore()
const { requireAuth } = useAuthGate()
const likedTracks = ref([]) const likedTracks = ref([])
const allTracks = ref([]) const allTracks = ref([])
@@ -105,15 +111,21 @@ function playTrack(track, fromLiked = false, idx) {
playerStore.play() playerStore.play()
} }
async function toggleLike(track) { function toggleLike(track) {
const wasLiked = isLiked(track) const wasLiked = isLiked(track)
requireAuth({
redirectTo: '/login',
onAuthenticated: async () => {
await authStore.toggleLike(track._id) await authStore.toggleLike(track._id)
if (wasLiked) { if (wasLiked) {
likedTracks.value = likedTracks.value.filter((t) => t._id !== track._id) likedTracks.value = likedTracks.value.filter((t) => t._id !== track._id)
} }
} }
})
}
onMounted(async () => { onMounted(async () => {
if (authStore.isLoggedIn) {
try { try {
const { data: liked } = await api.get('/tracks/liked') const { data: liked } = await api.get('/tracks/liked')
if (liked.success) { if (liked.success) {
@@ -121,9 +133,9 @@ onMounted(async () => {
} }
} catch { } catch {
// ignore // ignore
} finally {
loading.value = false
} }
}
loading.value = false
try { try {
const { data: all } = await api.get('/tracks') const { data: all } = await api.get('/tracks')
+6 -1
View File
@@ -77,6 +77,7 @@ import { useRouter } from 'vue-router'
import { useAuthStore } from '../stores/authStore.js' import { useAuthStore } from '../stores/authStore.js'
import api from '../lib/api.js' import api from '../lib/api.js'
import { formatUrl } from '../lib/utils.js' import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const router = useRouter() const router = useRouter()
const authStore = useAuthStore() const authStore = useAuthStore()
@@ -138,10 +139,14 @@ async function handleUpdate() {
async function handleLogout() { async function handleLogout() {
await authStore.logout() await authStore.logout()
router.push('/') router.push('/login')
} }
onMounted(async () => { onMounted(async () => {
if (!authStore.isLoggedIn) {
router.push('/login')
return
}
try { try {
const { data } = await api.get('/tracks/liked') const { data } = await api.get('/tracks/liked')
if (data.success) { if (data.success) {
+1 -1
View File
@@ -47,7 +47,7 @@
</button> </button>
<p class="text-center mt-6 text-gray-600"> <p class="text-center mt-6 text-gray-600">
Already have an account? Already have an account?
<router-link to="/" class="text-noctune-orange font-bold hover:underline">Login</router-link> <router-link to="/login" class="text-noctune-orange font-bold hover:underline">Login</router-link>
</p> </p>
</div> </div>
</div> </div>