Reauth
This commit is contained in:
1 parent
7723dd0722
commit
0b661c7ccc
33 files changed
+620
-372
No files matched your search
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
import{j as e,M as Pe,a as z,T as w,u as Y,b as ee,D as N,c as U,G as v,d as We,e as te,B as P,f as ne,g as A,C as Q,h as E,L as M,A as Qe,i as we,I as Z,k as He,l as B,P as _,m as Ke,n as Ge,o as Fe,S as W,p as Ee,q as Ae,r as Ve,s as Te,t as De,v as Je,w as pe,x as Ze,y as Xe,z as Ye,E as et,F as tt,H as nt,J as rt}from"./mui-53GMXZgr.js";import{u as H,a as K,b as je,c as ae,d as st,e as at,L as R,f as ot,O as it,g as ct,h as G,q as oe,i as fe,j as X,r as me,k as lt,R as ut,Q as dt,l as ht}from"./tanstack-ZGp-Rrdw.js";import{c as mt,r as j}from"./react-C_FdcE2X.js";import{i as J,B as xt,a as gt,b as pt,u as $,t as C,T as jt}from"./i18n-DyW0LrNj.js";(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))c(r);new MutationObserver(r=>{for(const d of r)if(d.type==="childList")for(const h of d.addedNodes)h.tagName==="LINK"&&h.rel==="modulepreload"&&c(h)}).observe(document,{childList:!0,subtree:!0});function i(r){const d={};return r.integrity&&(d.integrity=r.integrity),r.referrerPolicy&&(d.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?d.credentials="include":r.crossOrigin==="anonymous"?d.credentials="omit":d.credentials="same-origin",d}function c(r){if(r.ep)return;r.ep=!0;const d=i(r);fetch(r.href,d)}})();var xe={},be=mt;xe.createRoot=be.createRoot,xe.hydrateRoot=be.hydrateRoot;const k=new Map;function ft(t,n){const i=n==null?void 0:n.serializer,[c]=j.useState(n==null?void 0:n.defaultValue);return yt(t,c,n==null?void 0:n.storageSync,i==null?void 0:i.parse,i==null?void 0:i.stringify)}function yt(t,n,i=!0,c=vt,r=JSON.stringify){const d=j.useRef({string:null,parsed:void 0}),h=j.useSyncExternalStore(j.useCallback(o=>{const g=u=>{t===u&&o()};return ge.add(g),()=>{ge.delete(g)}},[t]),()=>{var o;const g=(o=se(()=>localStorage.getItem(t)))!==null&&o!==void 0?o:null;if(k.has(t))d.current.parsed=k.get(t);else if(g!==d.current.string){let u;try{u=g===null?n:c(g)}catch{u=n}d.current.parsed=u}return d.current.string=g,g===null&&n!==void 0&&se(()=>{const u=r(n);localStorage.setItem(t,u),d.current={string:u,parsed:n}}),d.current.parsed},()=>n),s=j.useCallback(o=>{const g=o instanceof Function?o(d.current.parsed):o;try{localStorage.setItem(t,r(g)),k.delete(t)}catch{k.set(t,g)}de(t)},[t,r]);return j.useEffect(()=>{if(!i)return;const o=g=>{g.storageArea===se(()=>localStorage)&&g.key===t&&de(t)};return window.addEventListener("storage",o),()=>window.removeEventListener("storage",o)},[t,i]),j.useMemo(()=>[h,s,{isPersistent:h===n||!k.has(t),removeItem(){se(()=>localStorage.removeItem(t)),k.delete(t),de(t)}}],[t,s,h,n])}const ge=new Set;function de(t){for(const n of[...ge])n(t)}function vt(t){return t==="undefined"?void 0:JSON.parse(t)}function se(t){try{return t()}catch{}}const he=15,Ct=3,O="https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api/",Ue=j.createContext({hasAuth:!1,currentSession:[void 0,void 0]}),F=()=>{const{hasAuth:t,authenticatedUser:n,currentSession:i,logIn:c,logOut:r,posts:d,newPost:h,updatePost:s,deletePost:o,user:g,createUser:u,updateUser:a,updateUserImage:l,userPosts:m}=j.useContext(Ue);if(c&&r&&d&&h&&s&&o&&g&&u&&a&&l&&m)return{hasAuth:t,authenticatedUser:n,currentSession:i,logIn:c,logOut:r,posts:d,newPost:h,updatePost:s,deletePost:o,user:g,createUser:u,updateUser:a,updateUserImage:l,userPosts:m};throw new Error("Couldn't find context. Is your component inside an ApiProvider?")},wt=({children:t})=>{const[n,i]=j.useState(!1),[c,r]=j.useState(),[d,h]=ft("egb_session",{defaultValue:[void 0,void 0]}),s=j.useRef();j.useEffect(()=>{d[0]&&!s.current&&(s.current=d[0],Ce())},[d]);const o=async(p,f)=>{const{user:y,token:S,refreshToken:Ne}=await(await ye("login",{email:p,password:f})).json();r(y),h([S,Ne]),i(!0),s.current=S},g=async()=>{try{return s.current?await(await T(()=>le("logout"))).json():!0}catch{return!1}finally{r(void 0),h([void 0,void 0]),i(!1),s.current=void 0}},u=async p=>{const f=`posts?p=${p??0}&l=${he}`;return n?await(await T(()=>ue(f))).json():await(await _e(f)).json()},a=async p=>await(await T(()=>le(`posts?l=${he}`,p))).json(),l=async(p,f)=>await(await T(()=>ve(`posts/${f}`,p))).json(),m=async p=>await(await T(()=>ze(`posts/${p}?l=${he}`))).json(),x=async p=>await(await T(()=>ue(`users/${p??(c==null?void 0:c.id)}`))).json(),b=async p=>await(await ye("register",p)).json(),L=async(p,f)=>{const y=await(await T(()=>ve(`users/${f??"self"}`,p))).json();return r(y),y},re=async(p,f)=>{const y=new FormData;p.image&&y.append("image",p.image),!p.image&&p.predefined&&y.append("predefined",p.predefined);const S=await(await T(()=>Be(`users/${f??"self"}/image`,y))).json();return r(S),S},V=async p=>await(await T(()=>ue(`users/${p}/posts?l=${Ct}&s=desc`))).json(),ye=async(p,f,y)=>{const S=await fetch(`${O}${p}`,{mode:"cors",method:"post",headers:y,body:JSON.stringify(f)});if(S.ok)return S;throw await S.json()},le=async(p,f,y)=>{const S=await fetch(`${O}${p}`,{mode:"corLine truncated
|
||||||
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
import{j as e,M as ce,a as U,T as y,u as K,b as _,D as q,c as P,G as g,d as we,e as V,B as v,f as G,g as w,C as O,h as b,L as k,A as Pe,i as ae,I as W,k as Se,l as L,P as I,m as Fe,n as Ae,o as ue,p as de,q as Ee,r as he,s as me,t as Te,S as N,v as De,w as ne,x as ke,y as $e,z as Le,E as Ie,F as Ue,H as qe,J as Oe}from"./mui-C4H8cxTH.js";import{u as M,a as B,b as se,c as H,d as Me,e as Be,L as $,f as ze,O as Re,g as z,q as J,h as re,i as Q,r as ee,Q as We,j as Ne,k as Qe,R as Ke,l as _e}from"./tanstack-C0csOcmc.js";import{c as Ve,r as f}from"./react-C_FdcE2X.js";import{i as R,B as Ge,a as He,b as Je,u as F,t as j,T as Xe}from"./i18n-DyW0LrNj.js";(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))s(r);new MutationObserver(r=>{for(const l of r)if(l.type==="childList")for(const a of l.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&s(a)}).observe(document,{childList:!0,subtree:!0});function o(r){const l={};return r.integrity&&(l.integrity=r.integrity),r.referrerPolicy&&(l.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?l.credentials="include":r.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function s(r){if(r.ep)return;r.ep=!0;const l=o(r);fetch(r.href,l)}})();var te={},ie=Ve;te.createRoot=ie.createRoot,te.hydrateRoot=ie.hydrateRoot;const Y=15,Ze=3,A="https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api/";let oe;class Ye{constructor(){if(this.userListeners=[],this.hasAuth=()=>this.token!==void 0,this.isAdmin=()=>{var n;return this.hasAuth()&&((n=this.self)==null?void 0:n.isAdmin)},this.getAuthenticatedUser=()=>this.self,this.getCurrentSession=()=>[this.token,this.refreshToken],this.subscribeToAuthenticatedUser=n=>(this.userListeners.push(n),()=>{this.userListeners=this.userListeners.filter(o=>o!==n)}),this.logIn=async(n,o)=>{const{user:s,token:r}=await(await this.post("login",{email:n,password:o})).json();this.self=s,this.token=r,this.userListeners.forEach(l=>l(s))},this.logOut=async()=>{try{return this.token?await(await this.postAuth("logout")).json():!0}catch{return!1}finally{this.self=void 0,this.token=void 0,this.userListeners.forEach(n=>n())}},this.posts=async n=>{const o=`posts?p=${n??0}&l=${Y}`;return this.token?await(await this.getAuth(o)).json():await(await this.get(o)).json()},this.deletePost=async n=>await(await this.delete(`posts/${n}?l=${Y}`)).json(),this.user=async n=>{var o;return await(await this.getAuth(`users/${n??((o=this.self)==null?void 0:o.id)}`)).json()},this.updateUser=async(n,o)=>{const s=await(await this.patch(`users/${o??"self"}`,n)).json();return this.self=s,this.userListeners.forEach(r=>r(s)),s},this.updateUserImage=async(n,o)=>{const s=new FormData;n.image&&s.append("image",n.image),!n.image&&n.predefined&&s.append("predefined",n.predefined);const r=await(await this.postAuthRaw(`users/${o??"self"}/image`,s)).json();return this.self=r,this.userListeners.forEach(l=>l(r)),r},this.newPost=async n=>await(await this.postAuth(`posts?l=${Y}`,n)).json(),this.updatePost=async(n,o)=>await(await this.patch(`posts/${o}`,n)).json(),this.userPosts=async n=>await(await this.getAuth(`users/${n}/posts?l=${Ze}&s=desc`)).json(),this.createUser=async n=>await(await this.post("register",n)).json(),this.post=async(n,o,s)=>{const r=await fetch(`${A}${n}`,{mode:"cors",method:"post",headers:s,body:JSON.stringify(o)});if(r.ok)return r;throw await r.json()},this.postAuth=async(n,o,s)=>{const r=await fetch(`${A}${n}`,{mode:"cors",method:"post",headers:{token:this.token??"",...s},body:JSON.stringify(o)});if(r.ok)return r;throw await r.json()},this.postAuthRaw=async(n,o,s)=>{const r=await fetch(`${A}${n}`,{mode:"cors",method:"post",headers:{token:this.token??"",...s},body:o});if(r.ok)return r;throw await r.json()},this.get=async(n,o)=>{const s=await fetch(`${A}${n}`,{mode:"cors",method:"get",headers:o});if(s.ok)return s;throw await s.json()},this.getAuth=async(n,o)=>{const s=await fetch(`${A}${n}`,{mode:"cors",method:"get",headers:{token:this.token??"",...o}});if(s.ok)return s;throw await s.json()},this.delete=async(n,o)=>{const s=await fetch(`${A}${n}`,{mode:"cors",method:"delete",headers:{token:this.token??"",...o}});if(s.ok)return s;throw await s.json()},this.patch=async(n,o,s)=>{const r=await fetch(`${A}${n}`,{mode:"cors",method:"patch",headers:{token:this.token??"",...s},body:JSON.stringify(o)});if(r.ok)return r;throw await r.json()},oe)throw new Error("New instance cannot be created!!");oe=this}}const p=new Ye;var E=(t=>(t.NOT_FOUND="NotFound",t.UNAUTHORIZED="Unauthorized",t.NOT_ALLOWED="NotAllowed",t.FAILED_UPDATE="FailedUpdate",t.MISSING_FIELD="MissingField",t.DUPLICATE="Duplicate",t))(E||{});R.use(Ge).use(He).use(Je).init({fallbackLng:"en",debug:!1,interpolation:{escapeValue:!1},backend:{loadPath:"/phpCourse/exam/dist/locales/{{lng}}/{{ns}}.json"}});const et=({anchorEl:t,handleClose:n})=>e.jsxs(ce,{anchorEl:t,anLine truncated
|
|
||||||
@@ -175,4 +175,4 @@ import{r as p,R as jo,g as gt,a as st,b as yn,c as ga}from"./react-C_FdcE2X.js";
|
|||||||
`),kg)),Og=M("span",{name:"MuiLinearProgress",slot:"Bar2",overridesResolver:(e,t)=>{const{ownerState:n}=e;return[t.bar,t[`barColor${I(n.color)}`],(n.variant==="indeterminate"||n.variant==="query")&&t.bar2Indeterminate,n.variant==="buffer"&&t.bar2Buffer]}})(({ownerState:e,theme:t})=>u({width:"100%",position:"absolute",left:0,bottom:0,top:0,transition:"transform 0.2s linear",transformOrigin:"left"},e.variant!=="buffer"&&{backgroundColor:e.color==="inherit"?"currentColor":(t.vars||t).palette[e.color].main},e.color==="inherit"&&{opacity:.3},e.variant==="buffer"&&{backgroundColor:Or(t,e.color),transition:`transform .${tr}s linear`}),({ownerState:e})=>(e.variant==="indeterminate"||e.variant==="query")&&St(Fi||(Fi=Vt`
|
`),kg)),Og=M("span",{name:"MuiLinearProgress",slot:"Bar2",overridesResolver:(e,t)=>{const{ownerState:n}=e;return[t.bar,t[`barColor${I(n.color)}`],(n.variant==="indeterminate"||n.variant==="query")&&t.bar2Indeterminate,n.variant==="buffer"&&t.bar2Buffer]}})(({ownerState:e,theme:t})=>u({width:"100%",position:"absolute",left:0,bottom:0,top:0,transition:"transform 0.2s linear",transformOrigin:"left"},e.variant!=="buffer"&&{backgroundColor:e.color==="inherit"?"currentColor":(t.vars||t).palette[e.color].main},e.color==="inherit"&&{opacity:.3},e.variant==="buffer"&&{backgroundColor:Or(t,e.color),transition:`transform .${tr}s linear`}),({ownerState:e})=>(e.variant==="indeterminate"||e.variant==="query")&&St(Fi||(Fi=Vt`
|
||||||
width: auto;
|
width: auto;
|
||||||
animation: ${0} 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) 1.15s infinite;
|
animation: ${0} 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) 1.15s infinite;
|
||||||
`),Pg)),Gb=p.forwardRef(function(t,n){const o=G({props:t,name:"MuiLinearProgress"}),{className:r,color:i="primary",value:s,valueBuffer:a,variant:l="indeterminate"}=o,c=O(o,Sg),d=u({},o,{color:i,variant:l}),f=Mg(d),h=xr(),b={},m={bar1:{},bar2:{}};if((l==="determinate"||l==="buffer")&&s!==void 0){b["aria-valuenow"]=Math.round(s),b["aria-valuemin"]=0,b["aria-valuemax"]=100;let g=s-100;h&&(g=-g),m.bar1.transform=`translateX(${g}%)`}if(l==="buffer"&&a!==void 0){let g=(a||0)-100;h&&(g=-g),m.bar2.transform=`translateX(${g}%)`}return C.jsxs(wg,u({className:F(f.root,r),ownerState:d,role:"progressbar"},b,{ref:n},c,{children:[l==="buffer"?C.jsx(Tg,{className:f.dashed,ownerState:d}):null,C.jsx(Ig,{className:f.bar1,ownerState:d,style:m.bar1}),l==="determinate"?null:C.jsx(Og,{className:f.bar2,ownerState:d,style:m.bar2})]}))});function Ag(e){return V("MuiLink",e)}const Ng=U("MuiLink",["root","underlineNone","underlineHover","underlineAlways","button","focusVisible"]),la={primary:"primary.main",textPrimary:"text.primary",secondary:"secondary.main",textSecondary:"text.secondary",error:"error.main"},_g=e=>la[e]||e,Bg=({theme:e,ownerState:t})=>{const n=_g(t.color),o=zt(e,`palette.${n}`,!1)||t.color,r=zt(e,`palette.${n}Channel`);return"vars"in e&&r?`rgba(${r} / 0.4)`:Ce(o,.4)},Lg=["className","color","component","onBlur","onFocus","TypographyClasses","underline","variant","sx"],zg=e=>{const{classes:t,component:n,focusVisible:o,underline:r}=e,i={root:["root",`underline${I(r)}`,n==="button"&&"button",o&&"focusVisible"]};return K(i,Ag,t)},Fg=M(xt,{name:"MuiLink",slot:"Root",overridesResolver:(e,t)=>{const{ownerState:n}=e;return[t.root,t[`underline${I(n.underline)}`],n.component==="button"&&t.button]}})(({theme:e,ownerState:t})=>u({},t.underline==="none"&&{textDecoration:"none"},t.underline==="hover"&&{textDecoration:"none","&:hover":{textDecoration:"underline"}},t.underline==="always"&&u({textDecoration:"underline"},t.color!=="inherit"&&{textDecorationColor:Bg({theme:e,ownerState:t})},{"&:hover":{textDecorationColor:"inherit"}}),t.component==="button"&&{position:"relative",WebkitTapHighlightColor:"transparent",backgroundColor:"transparent",outline:0,border:0,margin:0,borderRadius:0,padding:0,cursor:"pointer",userSelect:"none",verticalAlign:"middle",MozAppearance:"none",WebkitAppearance:"none","&::-moz-focus-inner":{borderStyle:"none"},[`&.${Ng.focusVisible}`]:{outline:"auto"}})),qb=p.forwardRef(function(t,n){const o=G({props:t,name:"MuiLink"}),{className:r,color:i="primary",component:s="a",onBlur:a,onFocus:l,TypographyClasses:c,underline:d="always",variant:f="inherit",sx:h}=o,b=O(o,Lg),{isFocusVisibleRef:m,onBlur:g,onFocus:$,ref:v}=zs(),[R,y]=p.useState(!1),x=$e(n,v),S=E=>{g(E),m.current===!1&&y(!1),a&&a(E)},P=E=>{$(E),m.current===!0&&y(!0),l&&l(E)},k=u({},o,{color:i,component:s,focusVisible:R,underline:d,variant:f}),w=zg(k);return C.jsx(Fg,u({color:i,className:F(w.root,r),classes:c,component:s,onBlur:S,onFocus:P,ref:x,ownerState:k,variant:f,sx:[...Object.keys(la).includes(i)?[]:[{color:i}],...Array.isArray(h)?h:[h]]},b))}),nr=p.createContext({});function jg(e){return V("MuiList",e)}U("MuiList",["root","padding","dense","subheader"]);const Dg=["children","className","component","dense","disablePadding","subheader"],Wg=e=>{const{classes:t,disablePadding:n,dense:o,subheader:r}=e;return K({root:["root",!n&&"padding",o&&"dense",r&&"subheader"]},jg,t)},Ug=M("ul",{name:"MuiList",slot:"Root",overridesResolver:(e,t)=>{const{ownerState:n}=e;return[t.root,!n.disablePadding&&t.padding,n.dense&&t.dense,n.subheader&&t.subheader]}})(({ownerState:e})=>u({listStyle:"none",margin:0,padding:0,position:"relative"},!e.disablePadding&&{paddingTop:8,paddingBottom:8},e.subheader&&{paddingTop:0})),Hg=p.forwardRef(function(t,n){const o=G({props:t,name:"MuiList"}),{children:r,className:i,component:s="ul",dense:a=!1,disablePadding:l=!1,subheader:c}=o,d=O(o,Dg),f=p.useMemo(()=>({dense:a}),[a]),h=u({},o,{component:s,dense:a,disablePadding:l}),b=Wg(h);return C.jsx(nr.Provider,{value:f,children:C.jsxs(Ug,u({as:s,className:F(b.root,i),ref:n,ownerState:h},d,{children:[c,r]}))})}),ji=U("MuiListItemIcon",["root","alignItemsFlexStart"]),Di=U("MuiListItemText",["root","multiline","dense","inset","primary","secondary"]),Vg=["actions","autoFocus","autoFocusItem","children","className","disabledItemsFocusable","disableListWrap","onKeyDown","variant"];function zo(e,t,n){return e===t?e.firstChild:t&&t.nextElementSibling?t.nextElementSibling:n?null:e.firstChild}function Wi(e,t,n){return e===t?n?e.firstChild:e.lastChild:t&&t.previousElementSibling?t.previousElementSibling:n?null:e.lastChild}function ca(e,t){if(t===void 0)return!0;let n=e.innerText;return n===void 0&&(n=e.textContent),n=n.trim().toLowerCase(),n.length===0?!1:t.repeating?n[0]===t.keys[0]:n.indexOf(t.keys.join(""))===0}function Qt(e,t,n,o,r,i){let s=!1,a=r(e,t,t?n:!1);for(;a;){if(a===e.firstChild){if(s)return!1;s=!0}const l=o?!1:a.disabled||a.getAttribute("aria-disabled")==="true";if(!a.hasAttribute("tab
Line truncated
|
`),Pg)),Gb=p.forwardRef(function(t,n){const o=G({props:t,name:"MuiLinearProgress"}),{className:r,color:i="primary",value:s,valueBuffer:a,variant:l="indeterminate"}=o,c=O(o,Sg),d=u({},o,{color:i,variant:l}),f=Mg(d),h=xr(),b={},m={bar1:{},bar2:{}};if((l==="determinate"||l==="buffer")&&s!==void 0){b["aria-valuenow"]=Math.round(s),b["aria-valuemin"]=0,b["aria-valuemax"]=100;let g=s-100;h&&(g=-g),m.bar1.transform=`translateX(${g}%)`}if(l==="buffer"&&a!==void 0){let g=(a||0)-100;h&&(g=-g),m.bar2.transform=`translateX(${g}%)`}return C.jsxs(wg,u({className:F(f.root,r),ownerState:d,role:"progressbar"},b,{ref:n},c,{children:[l==="buffer"?C.jsx(Tg,{className:f.dashed,ownerState:d}):null,C.jsx(Ig,{className:f.bar1,ownerState:d,style:m.bar1}),l==="determinate"?null:C.jsx(Og,{className:f.bar2,ownerState:d,style:m.bar2})]}))});function Ag(e){return V("MuiLink",e)}const Ng=U("MuiLink",["root","underlineNone","underlineHover","underlineAlways","button","focusVisible"]),la={primary:"primary.main",textPrimary:"text.primary",secondary:"secondary.main",textSecondary:"text.secondary",error:"error.main"},_g=e=>la[e]||e,Bg=({theme:e,ownerState:t})=>{const n=_g(t.color),o=zt(e,`palette.${n}`,!1)||t.color,r=zt(e,`palette.${n}Channel`);return"vars"in e&&r?`rgba(${r} / 0.4)`:Ce(o,.4)},Lg=["className","color","component","onBlur","onFocus","TypographyClasses","underline","variant","sx"],zg=e=>{const{classes:t,component:n,focusVisible:o,underline:r}=e,i={root:["root",`underline${I(r)}`,n==="button"&&"button",o&&"focusVisible"]};return K(i,Ag,t)},Fg=M(xt,{name:"MuiLink",slot:"Root",overridesResolver:(e,t)=>{const{ownerState:n}=e;return[t.root,t[`underline${I(n.underline)}`],n.component==="button"&&t.button]}})(({theme:e,ownerState:t})=>u({},t.underline==="none"&&{textDecoration:"none"},t.underline==="hover"&&{textDecoration:"none","&:hover":{textDecoration:"underline"}},t.underline==="always"&&u({textDecoration:"underline"},t.color!=="inherit"&&{textDecorationColor:Bg({theme:e,ownerState:t})},{"&:hover":{textDecorationColor:"inherit"}}),t.component==="button"&&{position:"relative",WebkitTapHighlightColor:"transparent",backgroundColor:"transparent",outline:0,border:0,margin:0,borderRadius:0,padding:0,cursor:"pointer",userSelect:"none",verticalAlign:"middle",MozAppearance:"none",WebkitAppearance:"none","&::-moz-focus-inner":{borderStyle:"none"},[`&.${Ng.focusVisible}`]:{outline:"auto"}})),qb=p.forwardRef(function(t,n){const o=G({props:t,name:"MuiLink"}),{className:r,color:i="primary",component:s="a",onBlur:a,onFocus:l,TypographyClasses:c,underline:d="always",variant:f="inherit",sx:h}=o,b=O(o,Lg),{isFocusVisibleRef:m,onBlur:g,onFocus:$,ref:v}=zs(),[R,y]=p.useState(!1),x=$e(n,v),S=E=>{g(E),m.current===!1&&y(!1),a&&a(E)},P=E=>{$(E),m.current===!0&&y(!0),l&&l(E)},k=u({},o,{color:i,component:s,focusVisible:R,underline:d,variant:f}),w=zg(k);return C.jsx(Fg,u({color:i,className:F(w.root,r),classes:c,component:s,onBlur:S,onFocus:P,ref:x,ownerState:k,variant:f,sx:[...Object.keys(la).includes(i)?[]:[{color:i}],...Array.isArray(h)?h:[h]]},b))}),nr=p.createContext({});function jg(e){return V("MuiList",e)}U("MuiList",["root","padding","dense","subheader"]);const Dg=["children","className","component","dense","disablePadding","subheader"],Wg=e=>{const{classes:t,disablePadding:n,dense:o,subheader:r}=e;return K({root:["root",!n&&"padding",o&&"dense",r&&"subheader"]},jg,t)},Ug=M("ul",{name:"MuiList",slot:"Root",overridesResolver:(e,t)=>{const{ownerState:n}=e;return[t.root,!n.disablePadding&&t.padding,n.dense&&t.dense,n.subheader&&t.subheader]}})(({ownerState:e})=>u({listStyle:"none",margin:0,padding:0,position:"relative"},!e.disablePadding&&{paddingTop:8,paddingBottom:8},e.subheader&&{paddingTop:0})),Hg=p.forwardRef(function(t,n){const o=G({props:t,name:"MuiList"}),{children:r,className:i,component:s="ul",dense:a=!1,disablePadding:l=!1,subheader:c}=o,d=O(o,Dg),f=p.useMemo(()=>({dense:a}),[a]),h=u({},o,{component:s,dense:a,disablePadding:l}),b=Wg(h);return C.jsx(nr.Provider,{value:f,children:C.jsxs(Ug,u({as:s,className:F(b.root,i),ref:n,ownerState:h},d,{children:[c,r]}))})}),ji=U("MuiListItemIcon",["root","alignItemsFlexStart"]),Di=U("MuiListItemText",["root","multiline","dense","inset","primary","secondary"]),Vg=["actions","autoFocus","autoFocusItem","children","className","disabledItemsFocusable","disableListWrap","onKeyDown","variant"];function zo(e,t,n){return e===t?e.firstChild:t&&t.nextElementSibling?t.nextElementSibling:n?null:e.firstChild}function Wi(e,t,n){return e===t?n?e.firstChild:e.lastChild:t&&t.previousElementSibling?t.previousElementSibling:n?null:e.lastChild}function ca(e,t){if(t===void 0)return!0;let n=e.innerText;return n===void 0&&(n=e.textContent),n=n.trim().toLowerCase(),n.length===0?!1:t.repeating?n[0]===t.keys[0]:n.indexOf(t.keys.join(""))===0}function Qt(e,t,n,o,r,i){let s=!1,a=r(e,t,t?n:!1);for(;a;){if(a===e.firstChild){if(s)return!1;s=!0}const l=o?!1:a.disabled||a.getAttribute("aria-disabled")==="true";if(!a.hasAttribute("tab
Line truncated
|
||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
var ws=e=>{throw TypeError(e)};var ze=(e,t,s)=>t.has(e)||ws("Cannot "+s);var l=(e,t,s)=>(ze(e,t,"read from private field"),s?s.call(e):t.get(e)),F=(e,t,s)=>t.has(e)?ws("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,s),P=(e,t,s,i)=>(ze(e,t,"write to private field"),i?i.call(e,s):t.set(e,s),s),E=(e,t,s)=>(ze(e,t,"access private method"),s);var Ce=(e,t,s,i)=>({set _(r){P(e,t,r,s)},get _(){return l(e,t,i)}});import{r as C,c as Ii}from"./react-C_FdcE2X.js";import{j as _}from"./mui-C4H8cxTH.js";var ve=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(e){return this.listeners.add(e),this.onSubscribe(),()=>{this.listeners.delete(e),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}},de=typeof window>"u"||"Deno"in globalThis;function ct(){}function Vi(e,t){return typeof e=="function"?e(t):e}function ts(e){return typeof e=="number"&&e>=0&&e!==1/0}function Zs(e,t){return Math.max(e+(t||0)-Date.now(),0)}function Xt(e,t){return typeof e=="function"?e(t):e}function dt(e,t){return typeof e=="function"?e(t):e}function Fs(e,t){const{type:s="all",exact:i,fetchStatus:r,predicate:n,queryKey:o,stale:a}=e;if(o){if(i){if(t.queryHash!==vs(o,t.options))return!1}else if(!Se(t.queryKey,o))return!1}if(s!=="all"){const h=t.isActive();if(s==="active"&&!h||s==="inactive"&&h)return!1}return!(typeof a=="boolean"&&t.isStale()!==a||r&&r!==t.state.fetchStatus||n&&!n(t))}function _s(e,t){const{exact:s,status:i,predicate:r,mutationKey:n}=e;if(n){if(!t.options.mutationKey)return!1;if(s){if(Ht(t.options.mutationKey)!==Ht(n))return!1}else if(!Se(t.options.mutationKey,n))return!1}return!(i&&t.state.status!==i||r&&!r(t))}function vs(e,t){return((t==null?void 0:t.queryKeyHashFn)||Ht)(e)}function Ht(e){return JSON.stringify(e,(t,s)=>es(s)?Object.keys(s).sort().reduce((i,r)=>(i[r]=s[r],i),{}):s)}function Se(e,t){return e===t?!0:typeof e!=typeof t?!1:e&&t&&typeof e=="object"&&typeof t=="object"?!Object.keys(t).some(s=>!Se(e[s],t[s])):!1}function ti(e,t){if(e===t)return e;const s=Cs(e)&&Cs(t);if(s||es(e)&&es(t)){const i=s?e:Object.keys(e),r=i.length,n=s?t:Object.keys(t),o=n.length,a=s?[]:{};let h=0;for(let u=0;u<o;u++){const c=s?u:n[u];(!s&&i.includes(c)||s)&&e[c]===void 0&&t[c]===void 0?(a[c]=void 0,h++):(a[c]=ti(e[c],t[c]),a[c]===e[c]&&e[c]!==void 0&&h++)}return r===o&&h===r?e:a}return t}function Te(e,t){if(!t||Object.keys(e).length!==Object.keys(t).length)return!1;for(const s in e)if(e[s]!==t[s])return!1;return!0}function Cs(e){return Array.isArray(e)&&e.length===Object.keys(e).length}function es(e){if(!Rs(e))return!1;const t=e.constructor;if(t===void 0)return!0;const s=t.prototype;return!(!Rs(s)||!s.hasOwnProperty("isPrototypeOf")||Object.getPrototypeOf(e)!==Object.prototype)}function Rs(e){return Object.prototype.toString.call(e)==="[object Object]"}function ji(e){return new Promise(t=>{setTimeout(t,e)})}function ss(e,t,s){return typeof s.structuralSharing=="function"?s.structuralSharing(e,t):s.structuralSharing!==!1?ti(e,t):t}function Di(e,t,s=0){const i=[...e,t];return s&&i.length>s?i.slice(1):i}function ki(e,t,s=0){const i=[t,...e];return s&&i.length>s?i.slice(0,-1):i}var ei=Symbol();function si(e,t){return!e.queryFn&&(t!=null&&t.initialPromise)?()=>t.initialPromise:!e.queryFn||e.queryFn===ei?()=>Promise.reject(new Error(`Missing queryFn: '${e.queryHash}'`)):e.queryFn}var $t,Rt,Zt,Ks,Bi=(Ks=class extends ve{constructor(){super();F(this,$t);F(this,Rt);F(this,Zt);P(this,Zt,t=>{if(!de&&window.addEventListener){const s=()=>t();return window.addEventListener("visibilitychange",s,!1),()=>{window.removeEventListener("visibilitychange",s)}}})}onSubscribe(){l(this,Rt)||this.setEventListener(l(this,Zt))}onUnsubscribe(){var t;this.hasListeners()||((t=l(this,Rt))==null||t.call(this),P(this,Rt,void 0))}setEventListener(t){var s;P(this,Zt,t),(s=l(this,Rt))==null||s.call(this),P(this,Rt,t(i=>{typeof i=="boolean"?this.setFocused(i):this.onFocus()}))}setFocused(t){l(this,$t)!==t&&(P(this,$t,t),this.onFocus())}onFocus(){const t=this.isFocused();this.listeners.forEach(s=>{s(t)})}isFocused(){var t;return typeof l(this,$t)=="boolean"?l(this,$t):((t=globalThis.document)==null?void 0:t.visibilityState)!=="hidden"}},$t=new WeakMap,Rt=new WeakMap,Zt=new WeakMap,Ks),ys=new Bi,te,Et,ee,Ns,$i=(Ns=class extends ve{constructor(){super();F(this,te,!0);F(this,Et);F(this,ee);P(this,ee,t=>{if(!de&&window.addEventListener){const s=()=>t(!0),i=()=>t(!1);return window.addEventListener("online",s,!1),window.addEventListener("offline",i,!1),()=>{window.removeEventListener("online",s),window.removeEventListener("offline",i)}}})}onSubscribe(){l(this,Et)||this.setEventListener(l(this,ee))}onUnsubscribe(){var t;this.hasListeners()||((t=l(this,Et))==null||t.call(this),P(this,Et,void 0))}setEventListener(t){var s;P(this,ee,t),(s=l(this,Et))==null||s.call(this),P(this,Et,t(this.setOnline.bind(this)))}setOnline(t){l(this,te)!==t&&(P(this,te,t),this.listenersLine truncated
|
var ws=e=>{throw TypeError(e)};var ze=(e,t,s)=>t.has(e)||ws("Cannot "+s);var l=(e,t,s)=>(ze(e,t,"read from private field"),s?s.call(e):t.get(e)),F=(e,t,s)=>t.has(e)?ws("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,s),P=(e,t,s,i)=>(ze(e,t,"write to private field"),i?i.call(e,s):t.set(e,s),s),E=(e,t,s)=>(ze(e,t,"access private method"),s);var Ce=(e,t,s,i)=>({set _(r){P(e,t,r,s)},get _(){return l(e,t,i)}});import{r as C,c as Ii}from"./react-C_FdcE2X.js";import{j as _}from"./mui-53GMXZgr.js";var ve=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(e){return this.listeners.add(e),this.onSubscribe(),()=>{this.listeners.delete(e),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}},de=typeof window>"u"||"Deno"in globalThis;function ct(){}function Vi(e,t){return typeof e=="function"?e(t):e}function ts(e){return typeof e=="number"&&e>=0&&e!==1/0}function Zs(e,t){return Math.max(e+(t||0)-Date.now(),0)}function Xt(e,t){return typeof e=="function"?e(t):e}function dt(e,t){return typeof e=="function"?e(t):e}function Fs(e,t){const{type:s="all",exact:i,fetchStatus:r,predicate:n,queryKey:o,stale:a}=e;if(o){if(i){if(t.queryHash!==vs(o,t.options))return!1}else if(!Se(t.queryKey,o))return!1}if(s!=="all"){const h=t.isActive();if(s==="active"&&!h||s==="inactive"&&h)return!1}return!(typeof a=="boolean"&&t.isStale()!==a||r&&r!==t.state.fetchStatus||n&&!n(t))}function _s(e,t){const{exact:s,status:i,predicate:r,mutationKey:n}=e;if(n){if(!t.options.mutationKey)return!1;if(s){if(Ht(t.options.mutationKey)!==Ht(n))return!1}else if(!Se(t.options.mutationKey,n))return!1}return!(i&&t.state.status!==i||r&&!r(t))}function vs(e,t){return((t==null?void 0:t.queryKeyHashFn)||Ht)(e)}function Ht(e){return JSON.stringify(e,(t,s)=>es(s)?Object.keys(s).sort().reduce((i,r)=>(i[r]=s[r],i),{}):s)}function Se(e,t){return e===t?!0:typeof e!=typeof t?!1:e&&t&&typeof e=="object"&&typeof t=="object"?!Object.keys(t).some(s=>!Se(e[s],t[s])):!1}function ti(e,t){if(e===t)return e;const s=Cs(e)&&Cs(t);if(s||es(e)&&es(t)){const i=s?e:Object.keys(e),r=i.length,n=s?t:Object.keys(t),o=n.length,a=s?[]:{};let h=0;for(let u=0;u<o;u++){const c=s?u:n[u];(!s&&i.includes(c)||s)&&e[c]===void 0&&t[c]===void 0?(a[c]=void 0,h++):(a[c]=ti(e[c],t[c]),a[c]===e[c]&&e[c]!==void 0&&h++)}return r===o&&h===r?e:a}return t}function Te(e,t){if(!t||Object.keys(e).length!==Object.keys(t).length)return!1;for(const s in e)if(e[s]!==t[s])return!1;return!0}function Cs(e){return Array.isArray(e)&&e.length===Object.keys(e).length}function es(e){if(!Rs(e))return!1;const t=e.constructor;if(t===void 0)return!0;const s=t.prototype;return!(!Rs(s)||!s.hasOwnProperty("isPrototypeOf")||Object.getPrototypeOf(e)!==Object.prototype)}function Rs(e){return Object.prototype.toString.call(e)==="[object Object]"}function ji(e){return new Promise(t=>{setTimeout(t,e)})}function ss(e,t,s){return typeof s.structuralSharing=="function"?s.structuralSharing(e,t):s.structuralSharing!==!1?ti(e,t):t}function Di(e,t,s=0){const i=[...e,t];return s&&i.length>s?i.slice(1):i}function ki(e,t,s=0){const i=[t,...e];return s&&i.length>s?i.slice(0,-1):i}var ei=Symbol();function si(e,t){return!e.queryFn&&(t!=null&&t.initialPromise)?()=>t.initialPromise:!e.queryFn||e.queryFn===ei?()=>Promise.reject(new Error(`Missing queryFn: '${e.queryHash}'`)):e.queryFn}var $t,Rt,Zt,Ks,Bi=(Ks=class extends ve{constructor(){super();F(this,$t);F(this,Rt);F(this,Zt);P(this,Zt,t=>{if(!de&&window.addEventListener){const s=()=>t();return window.addEventListener("visibilitychange",s,!1),()=>{window.removeEventListener("visibilitychange",s)}}})}onSubscribe(){l(this,Rt)||this.setEventListener(l(this,Zt))}onUnsubscribe(){var t;this.hasListeners()||((t=l(this,Rt))==null||t.call(this),P(this,Rt,void 0))}setEventListener(t){var s;P(this,Zt,t),(s=l(this,Rt))==null||s.call(this),P(this,Rt,t(i=>{typeof i=="boolean"?this.setFocused(i):this.onFocus()}))}setFocused(t){l(this,$t)!==t&&(P(this,$t,t),this.onFocus())}onFocus(){const t=this.isFocused();this.listeners.forEach(s=>{s(t)})}isFocused(){var t;return typeof l(this,$t)=="boolean"?l(this,$t):((t=globalThis.document)==null?void 0:t.visibilityState)!=="hidden"}},$t=new WeakMap,Rt=new WeakMap,Zt=new WeakMap,Ks),ys=new Bi,te,Et,ee,Ns,$i=(Ns=class extends ve{constructor(){super();F(this,te,!0);F(this,Et);F(this,ee);P(this,ee,t=>{if(!de&&window.addEventListener){const s=()=>t(!0),i=()=>t(!1);return window.addEventListener("online",s,!1),window.addEventListener("offline",i,!1),()=>{window.removeEventListener("online",s),window.removeEventListener("offline",i)}}})}onSubscribe(){l(this,Et)||this.setEventListener(l(this,ee))}onUnsubscribe(){var t;this.hasListeners()||((t=l(this,Et))==null||t.call(this),P(this,Et,void 0))}setEventListener(t){var s;P(this,ee,t),(s=l(this,Et))==null||s.call(this),P(this,Et,t(this.setOnline.bind(this)))}setOnline(t){l(this,te)!==t&&(P(this,te,t),this.listenersLine truncated
|
||||||
* @license React
|
* @license React
|
||||||
* use-sync-external-store-shim.production.min.js
|
* use-sync-external-store-shim.production.min.js
|
||||||
*
|
*
|
||||||
@@ -14,4 +14,4 @@ var ws=e=>{throw TypeError(e)};var ze=(e,t,s)=>t.has(e)||ws("Cannot "+s);var l=(
|
|||||||
*
|
*
|
||||||
* This source code is licensed under the MIT license found in the
|
* This source code is licensed under the MIT license found in the
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/var $e=C,Rr=Cr;function Er(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var xr=typeof Object.is=="function"?Object.is:Er,Or=Rr.useSyncExternalStore,Lr=$e.useRef,Ar=$e.useEffect,Tr=$e.useMemo,Ir=$e.useDebugValue;Si.useSyncExternalStoreWithSelector=function(e,t,s,i,r){var n=Lr(null);if(n.current===null){var o={hasValue:!1,value:null};n.current=o}else o=n.current;n=Tr(function(){function h(p){if(!u){if(u=!0,c=p,p=i(p),r!==void 0&&o.hasValue){var v=o.value;if(r(v,p))return d=v}return d=p}if(v=d,xr(c,p))return v;var m=i(p);return r!==void 0&&r(v,m)?v:(c=p,d=m)}var u=!1,c,d,f=s===void 0?null:s;return[function(){return h(t())},f===null?void 0:function(){return h(f())}]},[t,s,i,r]);var a=Or(e,n[0],n[1]);return Ar(function(){o.hasValue=!0,o.value=a},[a]),Ir(a),a};bi.exports=Si;var Vr=bi.exports;class bs{constructor(t,s){this.listeners=new Set,this._batching=!1,this._flushing=0,this.subscribe=i=>{var r,n;this.listeners.add(i);const o=(n=(r=this.options)==null?void 0:r.onSubscribe)==null?void 0:n.call(r,i,this);return()=>{this.listeners.delete(i),o==null||o()}},this.setState=i=>{var r,n,o;const a=this.state;this.state=(r=this.options)!=null&&r.updateFn?this.options.updateFn(a)(i):i(a),(o=(n=this.options)==null?void 0:n.onUpdate)==null||o.call(n),this._flush()},this._flush=()=>{if(this._batching)return;const i=++this._flushing;this.listeners.forEach(r=>{this._flushing===i&&r()})},this.batch=i=>{if(this._batching)return i();this._batching=!0,i(),this._batching=!1,this._flush()},this.state=t,this.options=s}}function Ve(e,t=s=>s){return Vr.useSyncExternalStoreWithSelector(e.subscribe,()=>e.state,()=>e.state,t,jr)}function jr(e,t){if(Object.is(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;const s=Object.keys(e);if(s.length!==Object.keys(t).length)return!1;for(let i=0;i<s.length;i++)if(!Object.prototype.hasOwnProperty.call(t,s[i])||!Object.is(e[s[i]],t[s[i]]))return!1;return!0}const rt="__root__";function Dr(e,t){let s,i,r,n="";for(s in e)if((r=e[s])!==void 0)if(Array.isArray(r))for(i=0;i<r.length;i++)n&&(n+="&"),n+=encodeURIComponent(s)+"="+encodeURIComponent(r[i]);else n&&(n+="&"),n+=encodeURIComponent(s)+"="+encodeURIComponent(r);return""+n}function Ts(e){if(!e)return"";const t=decodeURIComponent(e);return t==="false"?!1:t==="true"?!0:+t*0===0&&+t+""===t?+t:t}function kr(e,t){let s,i;const r={},n=e.split("&");for(;s=n.shift();){const o=s.indexOf("=");if(o!==-1){i=s.slice(0,o);const a=s.slice(o+1);r[i]!==void 0?r[i]=[].concat(r[i],Ts(a)):r[i]=Ts(a)}else i=s,r[i]=""}return r}const Br=Ur(JSON.parse),$r=Qr(JSON.stringify,JSON.parse);function Ur(e){return t=>{t.substring(0,1)==="?"&&(t=t.substring(1));const s=kr(t);for(const i in s){const r=s[i];if(typeof r=="string")try{s[i]=e(r)}catch{}}return s}}function Qr(e,t){function s(i){if(typeof i=="object"&&i!==null)try{return e(i)}catch{}else if(typeof i=="string"&&typeof t=="function")try{return t(i),e(i)}catch{}return i}return i=>{i={...i},Object.keys(i).forEach(n=>{const o=i[n];typeof o>"u"||o===void 0?delete i[n]:i[n]=s(o)});const r=Dr(i).toString();return r?`?${r}`:""}}function je(e){return e[e.length-1]}function Kr(e){return typeof e=="function"}function Jt(e,t){return Kr(e)?e(t):e}function be(e,t){return t.reduce((s,i)=>(s[i]=e[i],s),{})}function pt(e,t){if(e===t)return e;const s=t,i=Vs(e)&&Vs(s);if(i||De(e)&&De(s)){const r=i?e:Object.keys(e),n=r.length,o=i?s:Object.keys(s),a=o.length,h=i?[]:{};let u=0;for(let c=0;c<a;c++){const d=i?c:o[c];(!i&&r.includes(d)||i)&&e[d]===void 0&&s[d]===void 0?(h[d]=void 0,u++):(h[d]=pt(e[d],s[d]),h[d]===e[d]&&e[d]!==void 0&&u++)}return n===a&&u===n?e:h}return s}function De(e){if(!Is(e))return!1;const t=e.constructor;if(typeof t>"u")return!0;const s=t.prototype;return!(!Is(s)||!s.hasOwnProperty("isPrototypeOf"))}function Is(e){return Object.prototype.toString.call(e)==="[object Object]"}function Vs(e){return Array.isArray(e)&&e.length===Object.keys(e).length}function Yt(e,t,s=!1){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(De(e)&&De(t)){const i=Object.keys(e).filter(n=>e[n]!==void 0),r=Object.keys(t).filter(n=>t[n]!==void 0);return!s&&i.length!==r.length?!1:!r.some(n=>!(n in e)||!Yt(e[n],t[n],s))}return Array.isArray(e)&&Array.isArray(t)?e.length!==t.length?!1:!e.some((i,r)=>!Yt(i,t[r],s)):!1}const Je=typeof window<"u"?C.useLayoutEffect:C.useEffect;function Gt(e){let t,s;const i=new Promise((r,n)=>{t=r,s=n});return i.status="pending",i.resolve=r=>{i.status="resolved",i.value=r,t(r),e==null||e(r)},i.reject=r=>{i.status="rejected",s(r)},i}function js(e){const t=C.useRef({value:e,prev:null}),s=t.current.value;return e!==s&&(t.current={value:e,prev:s}),t.current.prev}function Vt(e){return Ue(e.filter(Boolean).join("/"))}function Ue(e){return e.replace(/\/{2,}/g,"/")}function Ss(e){return e==="/"?e:e.replace(/^\/{1,}/,"")}function Bt(e){return e==="/"?e:e.replace(/\/{1,}$/,"")}function Nr(e){return Bt(Ss(e))}function ke(e,t){return e.endsWith("/")&&e!=="/"&&e!==`${t}/`?e.slice(0,-1):e}functLine truncated
|
*/var $e=C,Rr=Cr;function Er(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var xr=typeof Object.is=="function"?Object.is:Er,Or=Rr.useSyncExternalStore,Lr=$e.useRef,Ar=$e.useEffect,Tr=$e.useMemo,Ir=$e.useDebugValue;Si.useSyncExternalStoreWithSelector=function(e,t,s,i,r){var n=Lr(null);if(n.current===null){var o={hasValue:!1,value:null};n.current=o}else o=n.current;n=Tr(function(){function h(p){if(!u){if(u=!0,c=p,p=i(p),r!==void 0&&o.hasValue){var v=o.value;if(r(v,p))return d=v}return d=p}if(v=d,xr(c,p))return v;var m=i(p);return r!==void 0&&r(v,m)?v:(c=p,d=m)}var u=!1,c,d,f=s===void 0?null:s;return[function(){return h(t())},f===null?void 0:function(){return h(f())}]},[t,s,i,r]);var a=Or(e,n[0],n[1]);return Ar(function(){o.hasValue=!0,o.value=a},[a]),Ir(a),a};bi.exports=Si;var Vr=bi.exports;class bs{constructor(t,s){this.listeners=new Set,this._batching=!1,this._flushing=0,this.subscribe=i=>{var r,n;this.listeners.add(i);const o=(n=(r=this.options)==null?void 0:r.onSubscribe)==null?void 0:n.call(r,i,this);return()=>{this.listeners.delete(i),o==null||o()}},this.setState=i=>{var r,n,o;const a=this.state;this.state=(r=this.options)!=null&&r.updateFn?this.options.updateFn(a)(i):i(a),(o=(n=this.options)==null?void 0:n.onUpdate)==null||o.call(n),this._flush()},this._flush=()=>{if(this._batching)return;const i=++this._flushing;this.listeners.forEach(r=>{this._flushing===i&&r()})},this.batch=i=>{if(this._batching)return i();this._batching=!0,i(),this._batching=!1,this._flush()},this.state=t,this.options=s}}function Ve(e,t=s=>s){return Vr.useSyncExternalStoreWithSelector(e.subscribe,()=>e.state,()=>e.state,t,jr)}function jr(e,t){if(Object.is(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;const s=Object.keys(e);if(s.length!==Object.keys(t).length)return!1;for(let i=0;i<s.length;i++)if(!Object.prototype.hasOwnProperty.call(t,s[i])||!Object.is(e[s[i]],t[s[i]]))return!1;return!0}const rt="__root__";function Dr(e,t){let s,i,r,n="";for(s in e)if((r=e[s])!==void 0)if(Array.isArray(r))for(i=0;i<r.length;i++)n&&(n+="&"),n+=encodeURIComponent(s)+"="+encodeURIComponent(r[i]);else n&&(n+="&"),n+=encodeURIComponent(s)+"="+encodeURIComponent(r);return""+n}function Ts(e){if(!e)return"";const t=decodeURIComponent(e);return t==="false"?!1:t==="true"?!0:+t*0===0&&+t+""===t?+t:t}function kr(e,t){let s,i;const r={},n=e.split("&");for(;s=n.shift();){const o=s.indexOf("=");if(o!==-1){i=s.slice(0,o);const a=s.slice(o+1);r[i]!==void 0?r[i]=[].concat(r[i],Ts(a)):r[i]=Ts(a)}else i=s,r[i]=""}return r}const Br=Ur(JSON.parse),$r=Qr(JSON.stringify,JSON.parse);function Ur(e){return t=>{t.substring(0,1)==="?"&&(t=t.substring(1));const s=kr(t);for(const i in s){const r=s[i];if(typeof r=="string")try{s[i]=e(r)}catch{}}return s}}function Qr(e,t){function s(i){if(typeof i=="object"&&i!==null)try{return e(i)}catch{}else if(typeof i=="string"&&typeof t=="function")try{return t(i),e(i)}catch{}return i}return i=>{i={...i},Object.keys(i).forEach(n=>{const o=i[n];typeof o>"u"||o===void 0?delete i[n]:i[n]=s(o)});const r=Dr(i).toString();return r?`?${r}`:""}}function je(e){return e[e.length-1]}function Kr(e){return typeof e=="function"}function Jt(e,t){return Kr(e)?e(t):e}function be(e,t){return t.reduce((s,i)=>(s[i]=e[i],s),{})}function pt(e,t){if(e===t)return e;const s=t,i=Vs(e)&&Vs(s);if(i||De(e)&&De(s)){const r=i?e:Object.keys(e),n=r.length,o=i?s:Object.keys(s),a=o.length,h=i?[]:{};let u=0;for(let c=0;c<a;c++){const d=i?c:o[c];(!i&&r.includes(d)||i)&&e[d]===void 0&&s[d]===void 0?(h[d]=void 0,u++):(h[d]=pt(e[d],s[d]),h[d]===e[d]&&e[d]!==void 0&&u++)}return n===a&&u===n?e:h}return s}function De(e){if(!Is(e))return!1;const t=e.constructor;if(typeof t>"u")return!0;const s=t.prototype;return!(!Is(s)||!s.hasOwnProperty("isPrototypeOf"))}function Is(e){return Object.prototype.toString.call(e)==="[object Object]"}function Vs(e){return Array.isArray(e)&&e.length===Object.keys(e).length}function Yt(e,t,s=!1){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(De(e)&&De(t)){const i=Object.keys(e).filter(n=>e[n]!==void 0),r=Object.keys(t).filter(n=>t[n]!==void 0);return!s&&i.length!==r.length?!1:!r.some(n=>!(n in e)||!Yt(e[n],t[n],s))}return Array.isArray(e)&&Array.isArray(t)?e.length!==t.length?!1:!e.some((i,r)=>!Yt(i,t[r],s)):!1}const Je=typeof window<"u"?C.useLayoutEffect:C.useEffect;function Gt(e){let t,s;const i=new Promise((r,n)=>{t=r,s=n});return i.status="pending",i.resolve=r=>{i.status="resolved",i.value=r,t(r),e==null||e(r)},i.reject=r=>{i.status="rejected",s(r)},i}function js(e){const t=C.useRef({value:e,prev:null}),s=t.current.value;return e!==s&&(t.current={value:e,prev:s}),t.current.prev}function Vt(e){return Ue(e.filter(Boolean).join("/"))}function Ue(e){return e.replace(/\/{2,}/g,"/")}function Ss(e){return e==="/"?e:e.replace(/^\/{1,}/,"")}function Bt(e){return e==="/"?e:e.replace(/\/{1,}$/,"")}function Nr(e){return Bt(Ss(e))}function ke(e,t){return e.endsWith("/")&&e!=="/"&&e!==`${t}/`?e.slice(0,-1):e}functLine truncated
|
||||||
Vendored
+3
-3
@@ -5,10 +5,10 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/phpCourse/exam/dist/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/phpCourse/exam/dist/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>Vite + React + TS</title>
|
||||||
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-CfSUaRjT.js"></script>
|
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-BliQ0crd.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/react-C_FdcE2X.js">
|
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/react-C_FdcE2X.js">
|
||||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/mui-C4H8cxTH.js">
|
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/mui-53GMXZgr.js">
|
||||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-C0csOcmc.js">
|
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-ZGp-Rrdw.js">
|
||||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/i18n-DyW0LrNj.js">
|
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/i18n-DyW0LrNj.js">
|
||||||
<link rel="stylesheet" crossorigin href="/phpCourse/exam/dist/assets/mui-CKDNpdid.css">
|
<link rel="stylesheet" crossorigin href="/phpCourse/exam/dist/assets/mui-CKDNpdid.css">
|
||||||
<link rel="stylesheet" crossorigin href="/phpCourse/exam/dist/assets/index-D83Ey19k.css">
|
<link rel="stylesheet" crossorigin href="/phpCourse/exam/dist/assets/index-D83Ey19k.css">
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
"FailedUpdate_Duplicate:username:userUpdate": "Ein Benutzer mit diesem Benutzernamen existiert schon",
|
"FailedUpdate_Duplicate:username:userUpdate": "Ein Benutzer mit diesem Benutzernamen existiert schon",
|
||||||
"FailedUpdate_Duplicate:email:userUpdate": "Ein Benutzer mit dieser E-Mail existiert schon",
|
"FailedUpdate_Duplicate:email:userUpdate": "Ein Benutzer mit dieser E-Mail existiert schon",
|
||||||
|
|
||||||
"NotAllowed_newPost": "Keine Berechtigung",
|
"Unauthorized_newPost": "Keine Berechtigung",
|
||||||
"MissingField_content:newPost": "Beitrag darf nicht leer sein",
|
"MissingField_content:newPost": "Beitrag darf nicht leer sein",
|
||||||
|
|
||||||
"NotAllowed_postUpdate": "Keine Berechtigung",
|
"NotAllowed_postUpdate": "Keine Berechtigung",
|
||||||
|
|||||||
+5
-5
@@ -1,23 +1,23 @@
|
|||||||
{
|
{
|
||||||
"Unauthorized": "NotAllowed",
|
"Unauthorized": "Not allowed",
|
||||||
|
|
||||||
"NotAllowed_login": "Invalid email or password",
|
"NotAllowed_login": "Invalid email or password",
|
||||||
"NotFound_user:login": "User does not exist",
|
"NotFound_user:login": "User does not exist",
|
||||||
"MissingField_email:login": "E-Mail required",
|
"MissingField_email:login": "E-Mail required",
|
||||||
"MissingField_password:login": "Password required",
|
"MissingField_password:login": "Password required",
|
||||||
|
|
||||||
"NotAllowed_deletPost": "NotAllowed",
|
"NotAllowed_deletPost": "Not allowed",
|
||||||
"NotFound_post:deletePost": "Post not found",
|
"NotFound_post:deletePost": "Post not found",
|
||||||
|
|
||||||
"NotAllowed_userUpdate": "NotAllowed",
|
"NotAllowed_userUpdate": "Not allowed",
|
||||||
"NotFound_user:userUpdate": "User not found",
|
"NotFound_user:userUpdate": "User not found",
|
||||||
"FailedUpdate_Duplicate:userUpdate": "A user with this username already exists",
|
"FailedUpdate_Duplicate:userUpdate": "A user with this username already exists",
|
||||||
"FailedUpdate_Duplicate:email:userUpdate": "A user with this email already exists",
|
"FailedUpdate_Duplicate:email:userUpdate": "A user with this email already exists",
|
||||||
|
|
||||||
"NotAllowed_newPost": "NotAllowed",
|
"Unauthorized_newPost": "Not allowed",
|
||||||
"MissingField_content:newPost": "Content required",
|
"MissingField_content:newPost": "Content required",
|
||||||
|
|
||||||
"NotAllowed_postUpdate": "NotAllowed",
|
"NotAllowed_postUpdate": "Not allowed",
|
||||||
"NotFound_post:postUpdate": "Post not found",
|
"NotFound_post:postUpdate": "Post not found",
|
||||||
|
|
||||||
"Duplicate_user:register": "A user with this username or email already exists",
|
"Duplicate_user:register": "A user with this username or email already exists",
|
||||||
|
|||||||
Vendored
+1
-1
@@ -4822,7 +4822,7 @@ var drawChart = (function (exports) {
|
|||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
/*<!--*/
|
/*<!--*/
|
||||||
const data = {"version":2,"tree":{"name":"root","children":[{"name":"assets/zustand-DA8f69qW.js","children":[{"name":"\u0000/node_modules/.pnpm/use-sync-external-store@1.2.0_react@18.3.1/node_modules/use-sync-external-store","children":[{"name":"cjs","children":[{"uid":"71c0081e-1","name":"use-sync-external-store-shim.production.min.js?commonjs-exports"},{"uid":"71c0081e-5","name":"use-sync-external-store-shim.production.min.js?commonjs-proxy"},{"name":"use-sync-external-store-shim","children":[{"uid":"71c0081e-13","name":"with-selector.production.min.js?commonjs-exports"},{"uid":"71c0081e-17","name":"with-selector.production.min.js?commonjs-proxy"}]}]},{"name":"shim","children":[{"uid":"71c0081e-7","name":"index.js?commonjs-module"},{"uid":"71c0081e-11","name":"index.js?commonjs-proxy"},{"uid":"71c0081e-19","name":"with-selector.js?commonjs-module"}]}]},{"name":"node_modules/.pnpm","children":[{"name":"use-sync-external-store@1.2.0_react@18.3.1/node_modules/use-sync-external-store","children":[{"name":"cjs","children":[{"uid":"71c0081e-3","name":"use-sync-external-store-shim.production.min.js"},{"name":"use-sync-external-store-shim/with-selector.production.min.js","uid":"71c0081e-15"}]},{"name":"shim","children":[{"uid":"71c0081e-9","name":"index.js"},{"uid":"71c0081e-21","name":"with-selector.js"}]}]},{"name":"zustand@4.5.4_@types+react@18.3.3_react@18.3.1/node_modules/zustand/esm","children":[{"uid":"71c0081e-23","name":"vanilla.mjs"},{"uid":"71c0081e-25","name":"index.mjs"}]}]}]},{"name":"assets/index-CfSUaRjT.js","children":[{"name":"\u0000vite/modulepreload-polyfill.js","uid":"71c0081e-27"},{"name":"\u0000/node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/client.js?commonjs-exports","uid":"71c0081e-29"},{"name":"node_modules/.pnpm","children":[{"name":"react-dom@18.3.1_react@18.3.1/node_modules/react-dom/client.js","uid":"71c0081e-31"},{"name":"@fontsource+roboto@5.0.13/node_modules/@fontsource/roboto","children":[{"uid":"71c0081e-83","name":"300.css"},{"uid":"71c0081e-85","name":"400.css"},{"uid":"71c0081e-87","name":"500.css"},{"uid":"71c0081e-89","name":"700.css"}]}]},{"name":"src","children":[{"uid":"71c0081e-33","name":"constanst.ts"},{"name":"api/Api.ts","uid":"71c0081e-35"},{"name":"components","children":[{"name":"Error","children":[{"uid":"71c0081e-37","name":"Errors.ts"},{"uid":"71c0081e-45","name":"ErrorComponent.tsx"}]},{"name":"Menus","children":[{"name":"Language/LanguageMenu.tsx","uid":"71c0081e-41"},{"name":"User/UserMenu.tsx","uid":"71c0081e-51"}]},{"name":"Dialogs","children":[{"name":"Register/RegisterDialog.tsx","uid":"71c0081e-47"},{"name":"PostEdit/PostEditDialog.tsx","uid":"71c0081e-61"},{"name":"UserEdit/UserEditDialog.tsx","uid":"71c0081e-69"},{"name":"UserImage/UserImageDialog.tsx","uid":"71c0081e-71"}]},{"name":"Forms","children":[{"name":"Login/LoginForm.tsx","uid":"71c0081e-49"},{"name":"Post/PostForm.tsx","uid":"71c0081e-57"}]},{"name":"Header/Header.tsx","uid":"71c0081e-53"},{"name":"Post/Post.tsx","uid":"71c0081e-63"},{"name":"Profile/Profile.tsx","uid":"71c0081e-73"}]},{"uid":"71c0081e-39","name":"i18n.ts"},{"name":"types/Routes.ts","uid":"71c0081e-43"},{"name":"routes","children":[{"uid":"71c0081e-55","name":"__root.tsx"},{"uid":"71c0081e-67","name":"index.tsx"},{"name":"profile","children":[{"uid":"71c0081e-77","name":"index.tsx"},{"uid":"71c0081e-79","name":"$id.tsx"}]}]},{"name":"utils/date.ts","uid":"71c0081e-59"},{"name":"queries","children":[{"uid":"71c0081e-65","name":"postsQuery.ts"},{"uid":"71c0081e-75","name":"profileQuery.ts"}]},{"uid":"71c0081e-81","name":"routeTree.gen.ts"},{"uid":"71c0081e-91","name":"main.tsx"}]},{"uid":"71c0081e-93","name":"index.html"}]},{"name":"assets/i18n-DyW0LrNj.js","children":[{"name":"node_modules/.pnpm","children":[{"name":"void-elements@3.1.0/node_modules/void-elements/index.js","uid":"71c0081e-95"},{"name":"html-parse-stringify@3.0.1/node_modules/html-parse-stringify/dist/html-parse-stringify.module.js","uid":"71c0081e-97"},{"name":"react-i18next@15.0.0_i18next@23.12.2_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/react-i18next/dist/es","children":[{"uid":"71c0081e-99","name":"utils.js"},{"uid":"71c0081e-101","name":"unescape.js"},{"uid":"71c0081e-103","name":"defaults.js"},{"uid":"71c0081e-105","name":"i18nInstance.js"},{"uid":"71c0081e-107","name":"TransWithoutContext.js"},{"uid":"71c0081e-109","name":"initReactI18next.js"},{"uid":"71c0081e-111","name":"context.js"},{"uid":"71c0081e-113","name":"Trans.js"},{"uid":"71c0081e-115","name":"useTranslation.js"},{"uid":"71c0081e-117","name":"withTranslation.js"},{"uid":"71c0081e-119","name":"Translation.js"},{"uid":"71c0081e-121","name":"I18nextProvider.js"},{"uid":"71c0081e-123","name":"useSSR.js"},{"uid":"71c0081e-125","name":"withSSR.js"},{"uid":"71c0081e-127","name":"index.js"}]},{"name":"i18next@23.12.2/node_modules/i18next/dist/esm/i18next.js","uid":"71c0081e-129"},{"name":"i18next-browser-languagedetector@8.0.0/node_modules/i18next-broLine truncated
|
const data = {"version":2,"tree":{"name":"root","children":[{"name":"assets/zustand-DA8f69qW.js","children":[{"name":"\u0000/node_modules/.pnpm/use-sync-external-store@1.2.0_react@18.3.1/node_modules/use-sync-external-store","children":[{"name":"cjs","children":[{"uid":"c0581fdf-1","name":"use-sync-external-store-shim.production.min.js?commonjs-exports"},{"uid":"c0581fdf-5","name":"use-sync-external-store-shim.production.min.js?commonjs-proxy"},{"name":"use-sync-external-store-shim","children":[{"uid":"c0581fdf-13","name":"with-selector.production.min.js?commonjs-exports"},{"uid":"c0581fdf-17","name":"with-selector.production.min.js?commonjs-proxy"}]}]},{"name":"shim","children":[{"uid":"c0581fdf-7","name":"index.js?commonjs-module"},{"uid":"c0581fdf-11","name":"index.js?commonjs-proxy"},{"uid":"c0581fdf-19","name":"with-selector.js?commonjs-module"}]}]},{"name":"node_modules/.pnpm","children":[{"name":"use-sync-external-store@1.2.0_react@18.3.1/node_modules/use-sync-external-store","children":[{"name":"cjs","children":[{"uid":"c0581fdf-3","name":"use-sync-external-store-shim.production.min.js"},{"name":"use-sync-external-store-shim/with-selector.production.min.js","uid":"c0581fdf-15"}]},{"name":"shim","children":[{"uid":"c0581fdf-9","name":"index.js"},{"uid":"c0581fdf-21","name":"with-selector.js"}]}]},{"name":"zustand@4.5.4_@types+react@18.3.3_react@18.3.1/node_modules/zustand/esm","children":[{"uid":"c0581fdf-23","name":"vanilla.mjs"},{"uid":"c0581fdf-25","name":"index.mjs"}]}]}]},{"name":"assets/index-BliQ0crd.js","children":[{"name":"\u0000vite/modulepreload-polyfill.js","uid":"c0581fdf-27"},{"name":"\u0000/node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/client.js?commonjs-exports","uid":"c0581fdf-29"},{"name":"node_modules/.pnpm","children":[{"name":"react-dom@18.3.1_react@18.3.1/node_modules/react-dom/client.js","uid":"c0581fdf-31"},{"name":"use-local-storage-state@19.3.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/use-local-storage-state/src/useLocalStorageState.js","uid":"c0581fdf-33"},{"name":"@fontsource+roboto@5.0.13/node_modules/@fontsource/roboto","children":[{"uid":"c0581fdf-87","name":"300.css"},{"uid":"c0581fdf-89","name":"400.css"},{"uid":"c0581fdf-91","name":"500.css"},{"uid":"c0581fdf-93","name":"700.css"}]}]},{"name":"src","children":[{"uid":"c0581fdf-35","name":"constanst.ts"},{"name":"api/Api.tsx","uid":"c0581fdf-37"},{"uid":"c0581fdf-39","name":"i18n.ts"},{"name":"components","children":[{"name":"Menus","children":[{"name":"Language/LanguageMenu.tsx","uid":"c0581fdf-41"},{"name":"User/UserMenu.tsx","uid":"c0581fdf-53"}]},{"name":"Error","children":[{"uid":"c0581fdf-45","name":"Errors.ts"},{"uid":"c0581fdf-47","name":"ErrorComponent.tsx"}]},{"name":"Dialogs","children":[{"name":"Register/RegisterDialog.tsx","uid":"c0581fdf-49"},{"name":"PostEdit/PostEditDialog.tsx","uid":"c0581fdf-63"},{"name":"UserEdit/UserEditDialog.tsx","uid":"c0581fdf-71"},{"name":"UserImage/UserImageDialog.tsx","uid":"c0581fdf-73"}]},{"name":"Forms","children":[{"name":"Login/LoginForm.tsx","uid":"c0581fdf-51"},{"name":"Post/PostForm.tsx","uid":"c0581fdf-59"}]},{"name":"Header/Header.tsx","uid":"c0581fdf-55"},{"name":"Post/Post.tsx","uid":"c0581fdf-65"},{"name":"Profile/Profile.tsx","uid":"c0581fdf-75"}]},{"name":"types/Routes.ts","uid":"c0581fdf-43"},{"name":"routes","children":[{"uid":"c0581fdf-57","name":"__root.tsx"},{"uid":"c0581fdf-69","name":"index.tsx"},{"name":"profile","children":[{"uid":"c0581fdf-79","name":"index.tsx"},{"uid":"c0581fdf-81","name":"$id.tsx"}]}]},{"name":"utils/date.ts","uid":"c0581fdf-61"},{"name":"queries","children":[{"uid":"c0581fdf-67","name":"postsQuery.ts"},{"uid":"c0581fdf-77","name":"profileQuery.ts"}]},{"uid":"c0581fdf-83","name":"routeTree.gen.ts"},{"uid":"c0581fdf-85","name":"router.tsx"},{"uid":"c0581fdf-95","name":"main.tsx"}]},{"uid":"c0581fdf-97","name":"index.html"}]},{"name":"assets/i18n-DyW0LrNj.js","children":[{"name":"node_modules/.pnpm","children":[{"name":"void-elements@3.1.0/node_modules/void-elements/index.js","uid":"c0581fdf-99"},{"name":"html-parse-stringify@3.0.1/node_modules/html-parse-stringify/dist/html-parse-stringify.module.js","uid":"c0581fdf-101"},{"name":"react-i18next@15.0.0_i18next@23.12.2_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/react-i18next/dist/es","children":[{"uid":"c0581fdf-103","name":"utils.js"},{"uid":"c0581fdf-105","name":"unescape.js"},{"uid":"c0581fdf-107","name":"defaults.js"},{"uid":"c0581fdf-109","name":"i18nInstance.js"},{"uid":"c0581fdf-111","name":"TransWithoutContext.js"},{"uid":"c0581fdf-113","name":"initReactI18next.js"},{"uid":"c0581fdf-115","name":"context.js"},{"uid":"c0581fdf-117","name":"Trans.js"},{"uid":"c0581fdf-119","name":"useTranslation.js"},{"uid":"c0581fdf-121","name":"withTranslation.js"},{"uid":"c0581fdf-123","name":"Translation.js"},{"uid":"c0581fdf-125","name":"I18nextProvider.js"},{"uid":"c0581fdf-127","name":"useSSR.js"},{"uid":"c0581fdf-129","name":"wLine truncated
|
||||||
|
|
||||||
const run = () => {
|
const run = () => {
|
||||||
const width = window.innerWidth;
|
const width = window.innerWidth;
|
||||||
|
|||||||
@@ -12,5 +12,7 @@ module.exports = {
|
|||||||
plugins: ['react-refresh'],
|
plugins: ['react-refresh'],
|
||||||
rules: {
|
rules: {
|
||||||
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
||||||
|
'no-shadow': 'off',
|
||||||
|
'@typescript-eslint/no-shadow': 'error',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-i18next": "^15.0.0",
|
"react-i18next": "^15.0.0",
|
||||||
|
"use-local-storage-state": "^19.3.1",
|
||||||
"zustand": "^4.5.4"
|
"zustand": "^4.5.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
Generated
+18
-3
@@ -32,9 +32,6 @@ importers:
|
|||||||
'@tanstack/react-router':
|
'@tanstack/react-router':
|
||||||
specifier: ^1.45.8
|
specifier: ^1.45.8
|
||||||
version: 1.45.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 1.45.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@types/node':
|
|
||||||
specifier: ^20.14.12
|
|
||||||
version: 20.14.12
|
|
||||||
i18next:
|
i18next:
|
||||||
specifier: ^23.12.2
|
specifier: ^23.12.2
|
||||||
version: 23.12.2
|
version: 23.12.2
|
||||||
@@ -53,6 +50,9 @@ importers:
|
|||||||
react-i18next:
|
react-i18next:
|
||||||
specifier: ^15.0.0
|
specifier: ^15.0.0
|
||||||
version: 15.0.0(i18next@23.12.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 15.0.0(i18next@23.12.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
use-local-storage-state:
|
||||||
|
specifier: ^19.3.1
|
||||||
|
version: 19.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
zustand:
|
zustand:
|
||||||
specifier: ^4.5.4
|
specifier: ^4.5.4
|
||||||
version: 4.5.4(@types/react@18.3.3)(react@18.3.1)
|
version: 4.5.4(@types/react@18.3.3)(react@18.3.1)
|
||||||
@@ -72,6 +72,9 @@ importers:
|
|||||||
'@tanstack/router-plugin':
|
'@tanstack/router-plugin':
|
||||||
specifier: ^1.45.8
|
specifier: ^1.45.8
|
||||||
version: 1.45.8(vite@5.3.4(@types/node@20.14.12))
|
version: 1.45.8(vite@5.3.4(@types/node@20.14.12))
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^20.14.12
|
||||||
|
version: 20.14.12
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^18.3.3
|
specifier: ^18.3.3
|
||||||
version: 18.3.3
|
version: 18.3.3
|
||||||
@@ -1842,6 +1845,13 @@ packages:
|
|||||||
uri-js@4.4.1:
|
uri-js@4.4.1:
|
||||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||||
|
|
||||||
|
use-local-storage-state@19.3.1:
|
||||||
|
resolution: {integrity: sha512-y3Z1dODXvZXZB4qtLDNN8iuXbsYD6TAxz61K58GWB9/yKwrNG9ynI0GzCTHi/Je1rMiyOwMimz0oyFsZn+Kj7Q==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=18'
|
||||||
|
react-dom: '>=18'
|
||||||
|
|
||||||
use-sync-external-store@1.2.0:
|
use-sync-external-store@1.2.0:
|
||||||
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3721,6 +3731,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode: 2.3.1
|
punycode: 2.3.1
|
||||||
|
|
||||||
|
use-local-storage-state@19.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
|
||||||
use-sync-external-store@1.2.0(react@18.3.1):
|
use-sync-external-store@1.2.0(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"FailedUpdate_Duplicate:username:userUpdate": "Ein Benutzer mit diesem Benutzernamen existiert schon",
|
"FailedUpdate_Duplicate:username:userUpdate": "Ein Benutzer mit diesem Benutzernamen existiert schon",
|
||||||
"FailedUpdate_Duplicate:email:userUpdate": "Ein Benutzer mit dieser E-Mail existiert schon",
|
"FailedUpdate_Duplicate:email:userUpdate": "Ein Benutzer mit dieser E-Mail existiert schon",
|
||||||
|
|
||||||
"NotAllowed_newPost": "Keine Berechtigung",
|
"Unauthorized_newPost": "Keine Berechtigung",
|
||||||
"MissingField_content:newPost": "Beitrag darf nicht leer sein",
|
"MissingField_content:newPost": "Beitrag darf nicht leer sein",
|
||||||
|
|
||||||
"NotAllowed_postUpdate": "Keine Berechtigung",
|
"NotAllowed_postUpdate": "Keine Berechtigung",
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
{
|
{
|
||||||
"Unauthorized": "NotAllowed",
|
"Unauthorized": "Not allowed",
|
||||||
|
|
||||||
"NotAllowed_login": "Invalid email or password",
|
"NotAllowed_login": "Invalid email or password",
|
||||||
"NotFound_user:login": "User does not exist",
|
"NotFound_user:login": "User does not exist",
|
||||||
"MissingField_email:login": "E-Mail required",
|
"MissingField_email:login": "E-Mail required",
|
||||||
"MissingField_password:login": "Password required",
|
"MissingField_password:login": "Password required",
|
||||||
|
|
||||||
"NotAllowed_deletPost": "NotAllowed",
|
"NotAllowed_deletPost": "Not allowed",
|
||||||
"NotFound_post:deletePost": "Post not found",
|
"NotFound_post:deletePost": "Post not found",
|
||||||
|
|
||||||
"NotAllowed_userUpdate": "NotAllowed",
|
"NotAllowed_userUpdate": "Not allowed",
|
||||||
"NotFound_user:userUpdate": "User not found",
|
"NotFound_user:userUpdate": "User not found",
|
||||||
"FailedUpdate_Duplicate:userUpdate": "A user with this username already exists",
|
"FailedUpdate_Duplicate:userUpdate": "A user with this username already exists",
|
||||||
"FailedUpdate_Duplicate:email:userUpdate": "A user with this email already exists",
|
"FailedUpdate_Duplicate:email:userUpdate": "A user with this email already exists",
|
||||||
|
|
||||||
"NotAllowed_newPost": "NotAllowed",
|
"Unauthorized_newPost": "Not allowed",
|
||||||
"MissingField_content:newPost": "Content required",
|
"MissingField_content:newPost": "Content required",
|
||||||
|
|
||||||
"NotAllowed_postUpdate": "NotAllowed",
|
"NotAllowed_postUpdate": "Not allowed",
|
||||||
"NotFound_post:postUpdate": "Post not found",
|
"NotFound_post:postUpdate": "Post not found",
|
||||||
|
|
||||||
"Duplicate_user:register": "A user with this username or email already exists",
|
"Duplicate_user:register": "A user with this username or email already exists",
|
||||||
|
|||||||
@@ -1,186 +0,0 @@
|
|||||||
import { POST_LIMIT, PROFILE_POST_LIMIT } from '../constanst';
|
|
||||||
import { PostAuth, PostCreate, PostDelete, PostListAuth, PostListNonAuth, PostNew, PostUpdate } from '../types/Post';
|
|
||||||
import { User, UserCreate, UserImageUpdate, UserUpdate } from '../types/User';
|
|
||||||
|
|
||||||
const BASE = 'https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api/';
|
|
||||||
|
|
||||||
let instance: ApiImpl;
|
|
||||||
|
|
||||||
class ApiImpl {
|
|
||||||
private token?: string;
|
|
||||||
private refreshToken?: string;
|
|
||||||
private self?: User;
|
|
||||||
private userListeners: ((user?: User) => void)[] = [];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
if (instance) {
|
|
||||||
throw new Error('New instance cannot be created!!');
|
|
||||||
}
|
|
||||||
|
|
||||||
//eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public hasAuth = () => this.token !== undefined;
|
|
||||||
//FIXME: TESTING
|
|
||||||
public isAdmin = () => this.hasAuth() && this.self?.isAdmin;
|
|
||||||
public getAuthenticatedUser = () => this.self;
|
|
||||||
public getCurrentSession = () => [this.token, this.refreshToken];
|
|
||||||
|
|
||||||
public subscribeToAuthenticatedUser = (callback: (user?: User) => void) => {
|
|
||||||
this.userListeners.push(callback);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
this.userListeners = this.userListeners.filter((item) => item !== callback);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
public logIn = async (email: string, password: string): Promise<void> => {
|
|
||||||
const { user, token } = await (await this.post('login', { email, password })).json();
|
|
||||||
this.self = user;
|
|
||||||
this.token = token;
|
|
||||||
this.userListeners.forEach((listener) => listener(user));
|
|
||||||
};
|
|
||||||
|
|
||||||
public logOut = async (): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
if (this.token) return await (await this.postAuth('logout')).json();
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
this.self = undefined;
|
|
||||||
this.token = undefined;
|
|
||||||
this.userListeners.forEach((listener) => listener());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public posts = async (page?: number): Promise<PostListNonAuth | PostListAuth> => {
|
|
||||||
const url = `posts?p=${page ?? 0}&l=${POST_LIMIT}`;
|
|
||||||
|
|
||||||
if (this.token) return await (await this.getAuth(url)).json();
|
|
||||||
|
|
||||||
return await (await this.get(url)).json();
|
|
||||||
};
|
|
||||||
|
|
||||||
public deletePost = async (id: number): Promise<PostDelete> => {
|
|
||||||
return await (await this.delete(`posts/${id}?l=${POST_LIMIT}`)).json();
|
|
||||||
};
|
|
||||||
|
|
||||||
public user = async (id?: number): Promise<User> => {
|
|
||||||
return await (await this.getAuth(`users/${id ?? this.self?.id}`)).json();
|
|
||||||
};
|
|
||||||
|
|
||||||
public updateUser = async (data: UserUpdate, id?: number): Promise<User> => {
|
|
||||||
const user = await (await this.patch(`users/${id ?? 'self'}`, data as Record<string, unknown>)).json();
|
|
||||||
this.self = user;
|
|
||||||
this.userListeners.forEach((listener) => listener(user));
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
public updateUserImage = async (data: UserImageUpdate, id?: number): Promise<User> => {
|
|
||||||
const formData = new FormData();
|
|
||||||
if (data.image) formData.append('image', data.image);
|
|
||||||
if (!data.image && data.predefined) formData.append('predefined', data.predefined);
|
|
||||||
|
|
||||||
const user = await (await this.postAuthRaw(`users/${id ?? 'self'}/image`, formData)).json();
|
|
||||||
this.self = user;
|
|
||||||
this.userListeners.forEach((listener) => listener(user));
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
public newPost = async (data: PostCreate): Promise<PostNew> => {
|
|
||||||
return await (await this.postAuth(`posts?l=${POST_LIMIT}`, data as unknown as Record<string, unknown>)).json();
|
|
||||||
};
|
|
||||||
|
|
||||||
public updatePost = async (data: PostUpdate, id: number): Promise<PostAuth> => {
|
|
||||||
return await (await this.patch(`posts/${id}`, data as Record<string, unknown>)).json();
|
|
||||||
};
|
|
||||||
|
|
||||||
public userPosts = async (id?: number): Promise<PostListAuth> => {
|
|
||||||
return await (await this.getAuth(`users/${id}/posts?l=${PROFILE_POST_LIMIT}&s=desc`)).json();
|
|
||||||
};
|
|
||||||
|
|
||||||
public createUser = async (data: UserCreate): Promise<User> => {
|
|
||||||
return await (await this.post(`register`, data as unknown as Record<string, unknown>)).json();
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Internal */
|
|
||||||
|
|
||||||
private post = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
|
|
||||||
const response = await fetch(`${BASE}${endpoint}`, {
|
|
||||||
mode: 'cors',
|
|
||||||
method: 'post',
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
if (response.ok) return response;
|
|
||||||
throw await response.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
private postAuth = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
|
|
||||||
const response = await fetch(`${BASE}${endpoint}`, {
|
|
||||||
mode: 'cors',
|
|
||||||
method: 'post',
|
|
||||||
headers: { token: this.token ?? '', ...headers },
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
if (response.ok) return response;
|
|
||||||
throw await response.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
private postAuthRaw = async (endpoint: string, body?: FormData, headers?: HeadersInit) => {
|
|
||||||
const response = await fetch(`${BASE}${endpoint}`, {
|
|
||||||
mode: 'cors',
|
|
||||||
method: 'post',
|
|
||||||
headers: { token: this.token ?? '', ...headers },
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
if (response.ok) return response;
|
|
||||||
throw await response.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
private get = async (endpoint: string, headers?: HeadersInit) => {
|
|
||||||
const response = await fetch(`${BASE}${endpoint}`, {
|
|
||||||
mode: 'cors',
|
|
||||||
method: 'get',
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
if (response.ok) return response;
|
|
||||||
throw await response.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
private getAuth = async (endpoint: string, headers?: HeadersInit) => {
|
|
||||||
const response = await fetch(`${BASE}${endpoint}`, {
|
|
||||||
mode: 'cors',
|
|
||||||
method: 'get',
|
|
||||||
headers: { token: this.token ?? '', ...headers },
|
|
||||||
});
|
|
||||||
if (response.ok) return response;
|
|
||||||
throw await response.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
private delete = async (endpoint: string, headers?: HeadersInit) => {
|
|
||||||
const response = await fetch(`${BASE}${endpoint}`, {
|
|
||||||
mode: 'cors',
|
|
||||||
method: 'delete',
|
|
||||||
headers: { token: this.token ?? '', ...headers },
|
|
||||||
});
|
|
||||||
if (response.ok) return response;
|
|
||||||
throw await response.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
private patch = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
|
|
||||||
const response = await fetch(`${BASE}${endpoint}`, {
|
|
||||||
mode: 'cors',
|
|
||||||
method: 'patch',
|
|
||||||
headers: { token: this.token ?? '', ...headers },
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
if (response.ok) return response;
|
|
||||||
throw await response.json();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const Api = new ApiImpl();
|
|
||||||
export default Api;
|
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
import { createContext, FC, PropsWithChildren, useContext, useEffect, useRef, useState } from 'react';
|
||||||
|
import useLocalStorageState from 'use-local-storage-state';
|
||||||
|
import { POST_LIMIT, PROFILE_POST_LIMIT } from '../constanst';
|
||||||
|
import { PostAuth, PostCreate, PostDelete, PostListAuth, PostListNonAuth, PostNew, PostUpdate } from '../types/Post';
|
||||||
|
import { User, UserCreate, UserImageUpdate, UserUpdate } from '../types/User';
|
||||||
|
|
||||||
|
const BASE = 'https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api/';
|
||||||
|
|
||||||
|
interface ApiContext {
|
||||||
|
hasAuth: boolean;
|
||||||
|
authenticatedUser?: User;
|
||||||
|
currentSession: [string?, string?];
|
||||||
|
|
||||||
|
logIn?: (email: string, password: string) => Promise<void>;
|
||||||
|
logOut?: () => Promise<boolean>;
|
||||||
|
|
||||||
|
posts?: (page?: number) => Promise<PostListNonAuth | PostListAuth>;
|
||||||
|
newPost?: (data: PostCreate) => Promise<PostNew>;
|
||||||
|
updatePost?: (data: PostUpdate, id: number) => Promise<PostAuth>;
|
||||||
|
deletePost?: (id: number) => Promise<PostDelete>;
|
||||||
|
|
||||||
|
user?: (id?: number) => Promise<User>;
|
||||||
|
createUser?: (data: UserCreate) => Promise<User>;
|
||||||
|
updateUser?: (data: UserUpdate, id?: number) => Promise<User>;
|
||||||
|
updateUserImage?: (data: UserImageUpdate, id?: number) => Promise<User>;
|
||||||
|
|
||||||
|
userPosts?: (id?: number) => Promise<PostListAuth>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ApiContext = createContext<ApiContext>({
|
||||||
|
hasAuth: false,
|
||||||
|
currentSession: [undefined, undefined],
|
||||||
|
});
|
||||||
|
|
||||||
|
//eslint-disable-next-line react-refresh/only-export-components
|
||||||
|
export const useApi = () => {
|
||||||
|
const {
|
||||||
|
hasAuth,
|
||||||
|
authenticatedUser,
|
||||||
|
currentSession,
|
||||||
|
|
||||||
|
logIn,
|
||||||
|
logOut,
|
||||||
|
posts,
|
||||||
|
newPost,
|
||||||
|
updatePost,
|
||||||
|
deletePost,
|
||||||
|
|
||||||
|
user,
|
||||||
|
createUser,
|
||||||
|
updateUser,
|
||||||
|
updateUserImage,
|
||||||
|
|
||||||
|
userPosts,
|
||||||
|
} = useContext(ApiContext);
|
||||||
|
|
||||||
|
if (
|
||||||
|
logIn &&
|
||||||
|
logOut &&
|
||||||
|
posts &&
|
||||||
|
newPost &&
|
||||||
|
updatePost &&
|
||||||
|
deletePost &&
|
||||||
|
user &&
|
||||||
|
createUser &&
|
||||||
|
updateUser &&
|
||||||
|
updateUserImage &&
|
||||||
|
userPosts
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
hasAuth,
|
||||||
|
authenticatedUser,
|
||||||
|
currentSession,
|
||||||
|
|
||||||
|
logIn,
|
||||||
|
logOut,
|
||||||
|
posts,
|
||||||
|
newPost,
|
||||||
|
updatePost,
|
||||||
|
deletePost,
|
||||||
|
|
||||||
|
user,
|
||||||
|
createUser,
|
||||||
|
updateUser,
|
||||||
|
updateUserImage,
|
||||||
|
|
||||||
|
userPosts,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Couldn't find context. Is your component inside an ApiProvider?");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ApiProvider: FC<PropsWithChildren<Record<string, unknown>>> = ({ children }) => {
|
||||||
|
const [hasAuth, setHasAuth] = useState(false);
|
||||||
|
const [authenticatedUser, setAuthenticatedUser] = useState<User>();
|
||||||
|
const [currentSession, setCurrentSession] = useLocalStorageState<[string | undefined, string | undefined]>(
|
||||||
|
'egb_session',
|
||||||
|
{ defaultValue: [undefined, undefined] }
|
||||||
|
);
|
||||||
|
|
||||||
|
const token = useRef<string | undefined>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentSession[0] && !token.current) {
|
||||||
|
token.current = currentSession[0];
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}, [currentSession]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
const logIn = async (email: string, password: string): Promise<void> => {
|
||||||
|
const { user, token: _token, refreshToken } = await (await post('login', { email, password })).json();
|
||||||
|
setAuthenticatedUser(user);
|
||||||
|
setCurrentSession([_token, refreshToken]);
|
||||||
|
setHasAuth(true);
|
||||||
|
token.current = _token;
|
||||||
|
};
|
||||||
|
|
||||||
|
const logOut = async (): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
if (token.current) return await (await reAuth(() => postAuth('logout'))).json();
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
setAuthenticatedUser(undefined);
|
||||||
|
setCurrentSession([undefined, undefined]);
|
||||||
|
setHasAuth(false);
|
||||||
|
token.current = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const posts = async (page?: number): Promise<PostListNonAuth | PostListAuth> => {
|
||||||
|
const url = `posts?p=${page ?? 0}&l=${POST_LIMIT}`;
|
||||||
|
|
||||||
|
if (hasAuth) return await (await reAuth(() => getAuth(url))).json();
|
||||||
|
|
||||||
|
return await (await get(url)).json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const newPost = async (data: PostCreate): Promise<PostNew> => {
|
||||||
|
return await (
|
||||||
|
await reAuth(() => postAuth(`posts?l=${POST_LIMIT}`, data as unknown as Record<string, unknown>))
|
||||||
|
).json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePost = async (data: PostUpdate, id: number): Promise<PostAuth> => {
|
||||||
|
return await (await reAuth(() => patch(`posts/${id}`, data as Record<string, unknown>))).json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const deletePost = async (id: number): Promise<PostDelete> => {
|
||||||
|
return await (await reAuth(() => _delete(`posts/${id}?l=${POST_LIMIT}`))).json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const user = async (id?: number): Promise<User> => {
|
||||||
|
return await (await reAuth(() => getAuth(`users/${id ?? authenticatedUser?.id}`))).json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const createUser = async (data: UserCreate): Promise<User> => {
|
||||||
|
return await (await post(`register`, data as unknown as Record<string, unknown>)).json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUser = async (data: UserUpdate, id?: number): Promise<User> => {
|
||||||
|
const _user = await (await reAuth(() => patch(`users/${id ?? 'self'}`, data as Record<string, unknown>))).json();
|
||||||
|
setAuthenticatedUser(_user);
|
||||||
|
return _user;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUserImage = async (data: UserImageUpdate, id?: number): Promise<User> => {
|
||||||
|
const formData = new FormData();
|
||||||
|
if (data.image) formData.append('image', data.image);
|
||||||
|
if (!data.image && data.predefined) formData.append('predefined', data.predefined);
|
||||||
|
|
||||||
|
const _user = await (await reAuth(() => postAuthRaw(`users/${id ?? 'self'}/image`, formData))).json();
|
||||||
|
setAuthenticatedUser(_user);
|
||||||
|
return _user;
|
||||||
|
};
|
||||||
|
|
||||||
|
const userPosts = async (id?: number): Promise<PostListAuth> => {
|
||||||
|
return await (await reAuth(() => getAuth(`users/${id}/posts?l=${PROFILE_POST_LIMIT}&s=desc`))).json();
|
||||||
|
};
|
||||||
|
|
||||||
|
/* IMPL */
|
||||||
|
|
||||||
|
const post = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
|
||||||
|
const response = await fetch(`${BASE}${endpoint}`, {
|
||||||
|
mode: 'cors',
|
||||||
|
method: 'post',
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
if (response.ok) return response;
|
||||||
|
throw await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const postAuth = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
|
||||||
|
const response = await fetch(`${BASE}${endpoint}`, {
|
||||||
|
mode: 'cors',
|
||||||
|
method: 'post',
|
||||||
|
headers: { token: token.current ?? '', ...headers },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
if (response.ok) return response;
|
||||||
|
throw await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const postAuthRaw = async (endpoint: string, body?: FormData, headers?: HeadersInit) => {
|
||||||
|
const response = await fetch(`${BASE}${endpoint}`, {
|
||||||
|
mode: 'cors',
|
||||||
|
method: 'post',
|
||||||
|
headers: { token: token.current ?? '', ...headers },
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
if (response.ok) return response;
|
||||||
|
throw await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const get = async (endpoint: string, headers?: HeadersInit) => {
|
||||||
|
const response = await fetch(`${BASE}${endpoint}`, {
|
||||||
|
mode: 'cors',
|
||||||
|
method: 'get',
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
if (response.ok) return response;
|
||||||
|
throw await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAuth = async (endpoint: string, headers?: HeadersInit) => {
|
||||||
|
const response = await fetch(`${BASE}${endpoint}`, {
|
||||||
|
mode: 'cors',
|
||||||
|
method: 'get',
|
||||||
|
headers: { token: token.current ?? '', ...headers },
|
||||||
|
});
|
||||||
|
if (response.ok) return response;
|
||||||
|
throw await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const _delete = async (endpoint: string, headers?: HeadersInit) => {
|
||||||
|
const response = await fetch(`${BASE}${endpoint}`, {
|
||||||
|
mode: 'cors',
|
||||||
|
method: 'delete',
|
||||||
|
headers: { token: token.current ?? '', ...headers },
|
||||||
|
});
|
||||||
|
if (response.ok) return response;
|
||||||
|
throw await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const patch = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
|
||||||
|
const response = await fetch(`${BASE}${endpoint}`, {
|
||||||
|
mode: 'cors',
|
||||||
|
method: 'patch',
|
||||||
|
headers: { token: token.current ?? '', ...headers },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
if (response.ok) return response;
|
||||||
|
throw await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const reAuth = async <T,>(callback: () => Promise<T>): Promise<T> => {
|
||||||
|
try {
|
||||||
|
console.log('[REAUTH] fetching');
|
||||||
|
const ret = await callback();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
console.log('[REAUTH] fail, refreshing');
|
||||||
|
// REAUTH
|
||||||
|
await refresh();
|
||||||
|
// DO AGAIN
|
||||||
|
console.log('[REAUTH] refreshed, fetching again');
|
||||||
|
const ret = await callback();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
} catch (error) {
|
||||||
|
console.log('[REAUTH] terminating session', error);
|
||||||
|
setAuthenticatedUser(undefined);
|
||||||
|
setHasAuth(false);
|
||||||
|
setCurrentSession([undefined, undefined]);
|
||||||
|
token.current = undefined;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
const {
|
||||||
|
user: _user,
|
||||||
|
token: _token,
|
||||||
|
refreshToken,
|
||||||
|
} = await (await postAuth('refresh', { refreshToken: currentSession[1] ?? 'INVALID DEFAULT' })).json();
|
||||||
|
setAuthenticatedUser(_user);
|
||||||
|
setCurrentSession([_token, refreshToken]);
|
||||||
|
setHasAuth(true);
|
||||||
|
token.current = _token;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ApiContext.Provider
|
||||||
|
value={{
|
||||||
|
hasAuth,
|
||||||
|
authenticatedUser,
|
||||||
|
currentSession,
|
||||||
|
|
||||||
|
logIn,
|
||||||
|
logOut,
|
||||||
|
|
||||||
|
posts,
|
||||||
|
newPost,
|
||||||
|
updatePost,
|
||||||
|
deletePost,
|
||||||
|
|
||||||
|
user,
|
||||||
|
createUser,
|
||||||
|
updateUser,
|
||||||
|
updateUserImage,
|
||||||
|
|
||||||
|
userPosts,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ApiContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApiProvider;
|
||||||
@@ -14,7 +14,7 @@ import { useForm } from '@tanstack/react-form';
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { FC, FormEvent, useState } from 'react';
|
import { FC, FormEvent, useState } from 'react';
|
||||||
import Api from '../../../api/Api';
|
import { useApi } from '../../../api/Api';
|
||||||
import { PostAuth, PostUpdate } from '../../../types/Post';
|
import { PostAuth, PostUpdate } from '../../../types/Post';
|
||||||
import ErrorComponent from '../../Error/ErrorComponent';
|
import ErrorComponent from '../../Error/ErrorComponent';
|
||||||
|
|
||||||
@@ -29,6 +29,11 @@ const PostEditDialog: FC<Props> = ({ post, open, onClose }) => {
|
|||||||
const [error, setError] = useState<any>();
|
const [error, setError] = useState<any>();
|
||||||
const [characterCount, setCharacterCount] = useState(post.content.length);
|
const [characterCount, setCharacterCount] = useState(post.content.length);
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.only('xs'), { noSsr: true });
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const Api = useApi();
|
||||||
|
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
mutationFn: ({ data, id }: { data: PostUpdate; id: number }) => {
|
mutationFn: ({ data, id }: { data: PostUpdate; id: number }) => {
|
||||||
return Api.updatePost(data, id);
|
return Api.updatePost(data, id);
|
||||||
@@ -54,16 +59,12 @@ const PostEditDialog: FC<Props> = ({ post, open, onClose }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (_error: any) {
|
||||||
setError(error);
|
setError(_error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
const fullScreen = useMediaQuery(theme.breakpoints.only('xs'), { noSsr: true });
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
form.reset();
|
form.reset();
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { useForm } from '@tanstack/react-form';
|
|||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { FC, FormEvent, useState } from 'react';
|
import { FC, FormEvent, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Api from '../../../api/Api';
|
import { useApi } from '../../../api/Api';
|
||||||
import { UserCreate } from '../../../types/User';
|
import { UserCreate } from '../../../types/User';
|
||||||
import ErrorComponent from '../../Error/ErrorComponent';
|
import ErrorComponent from '../../Error/ErrorComponent';
|
||||||
|
|
||||||
@@ -29,16 +29,17 @@ const RegisterDialog: FC<Props> = ({ open, onClose }) => {
|
|||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const [error, setError] = useState<any>();
|
const [error, setError] = useState<any>();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.only('xs'), { noSsr: true });
|
||||||
|
const Api = useApi();
|
||||||
|
|
||||||
const createMutation = useMutation({
|
const createMutation = useMutation({
|
||||||
mutationFn: ({ data }: { data: UserCreate }) => {
|
mutationFn: ({ data }: { data: UserCreate }) => {
|
||||||
return Api.createUser(data);
|
return Api.createUser(data);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const theme = useTheme();
|
|
||||||
const fullScreen = useMediaQuery(theme.breakpoints.only('xs'), { noSsr: true });
|
|
||||||
|
|
||||||
const form = useForm<UserCreate>({
|
const form = useForm<UserCreate>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
username: '',
|
username: '',
|
||||||
@@ -55,8 +56,8 @@ const RegisterDialog: FC<Props> = ({ open, onClose }) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (_error: any) {
|
||||||
setError(error);
|
setError(_error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useForm } from '@tanstack/react-form';
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { FC, FormEvent, useState } from 'react';
|
import { FC, FormEvent, useState } from 'react';
|
||||||
import Api from '../../../api/Api';
|
import { useApi } from '../../../api/Api';
|
||||||
import { User, UserUpdate } from '../../../types/User';
|
import { User, UserUpdate } from '../../../types/User';
|
||||||
import ErrorComponent from '../../Error/ErrorComponent';
|
import ErrorComponent from '../../Error/ErrorComponent';
|
||||||
|
|
||||||
@@ -27,6 +27,11 @@ const UserEditDialog: FC<Props> = ({ user, open, onClose }) => {
|
|||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const [error, setError] = useState<any>();
|
const [error, setError] = useState<any>();
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.only('xs'), { noSsr: true });
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const Api = useApi();
|
||||||
|
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
mutationFn: ({ data, id }: { data: UserUpdate; id?: number }) => {
|
mutationFn: ({ data, id }: { data: UserUpdate; id?: number }) => {
|
||||||
return Api.updateUser(data, id);
|
return Api.updateUser(data, id);
|
||||||
@@ -41,12 +46,12 @@ const UserEditDialog: FC<Props> = ({ user, open, onClose }) => {
|
|||||||
onSubmit: async ({ value }) => {
|
onSubmit: async ({ value }) => {
|
||||||
try {
|
try {
|
||||||
updateMutation.mutate(
|
updateMutation.mutate(
|
||||||
{ data: value, id: Api.getAuthenticatedUser()?.id === user.id ? undefined : user.id },
|
{ data: value, id: Api.authenticatedUser?.id === user.id ? undefined : user.id },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
||||||
const queryKey = Api.getAuthenticatedUser()?.id === user.id ? ['profile'] : ['profile', { id: user.id }];
|
const queryKey = Api.authenticatedUser?.id === user.id ? ['profile'] : ['profile', { id: user.id }];
|
||||||
queryClient.invalidateQueries({ queryKey });
|
queryClient.invalidateQueries({ queryKey });
|
||||||
},
|
},
|
||||||
onError: setError,
|
onError: setError,
|
||||||
@@ -54,16 +59,12 @@ const UserEditDialog: FC<Props> = ({ user, open, onClose }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (_error: any) {
|
||||||
setError(error);
|
setError(_error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
const fullScreen = useMediaQuery(theme.breakpoints.only('xs'), { noSsr: true });
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
form.reset();
|
form.reset();
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { useForm } from '@tanstack/react-form';
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { FC, FormEvent, useState } from 'react';
|
import { FC, FormEvent, useState } from 'react';
|
||||||
import Api from '../../../api/Api';
|
import { useApi } from '../../../api/Api';
|
||||||
import { User, UserImageUpdate } from '../../../types/User';
|
import { User, UserImageUpdate } from '../../../types/User';
|
||||||
import ErrorComponent from '../../Error/ErrorComponent';
|
import ErrorComponent from '../../Error/ErrorComponent';
|
||||||
|
|
||||||
@@ -38,6 +38,11 @@ const UserImageDialog: FC<Props> = ({ user, open, onClose }) => {
|
|||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const [error, setError] = useState<any>();
|
const [error, setError] = useState<any>();
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const fullScreen = useMediaQuery(theme.breakpoints.only('xs'), { noSsr: true });
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const Api = useApi();
|
||||||
|
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
mutationFn: ({ data, id }: { data: UserImageUpdate; id?: number }) => {
|
mutationFn: ({ data, id }: { data: UserImageUpdate; id?: number }) => {
|
||||||
return Api.updateUserImage(data, id);
|
return Api.updateUserImage(data, id);
|
||||||
@@ -48,27 +53,24 @@ const UserImageDialog: FC<Props> = ({ user, open, onClose }) => {
|
|||||||
onSubmit: async ({ value }) => {
|
onSubmit: async ({ value }) => {
|
||||||
try {
|
try {
|
||||||
updateMutation.mutate(
|
updateMutation.mutate(
|
||||||
{ data: value, id: Api.getAuthenticatedUser()?.id === user.id ? undefined : user.id },
|
{ data: value, id: Api.authenticatedUser?.id === user.id ? undefined : user.id },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
||||||
const queryKey = Api.getAuthenticatedUser()?.id === user.id ? ['profile'] : ['profile', { id: user.id }];
|
const queryKey = Api.authenticatedUser?.id === user.id ? ['profile'] : ['profile', { id: user.id }];
|
||||||
queryClient.invalidateQueries({ queryKey });
|
queryClient.invalidateQueries({ queryKey });
|
||||||
},
|
},
|
||||||
onError: setError,
|
onError: setError,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (_error: any) {
|
||||||
setError(error);
|
setError(_error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
const fullScreen = useMediaQuery(theme.breakpoints.only('xs'), { noSsr: true });
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const formState = form.useStore((state) => ({ image: state.values.image, predefined: state.values.predefined }));
|
const formState = form.useStore((state) => ({ image: state.values.image, predefined: state.values.predefined }));
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const ErrorComponent: FC<Props> = ({ error, context, color = 'error.main' }) =>
|
|||||||
case ERRORS.NOT_FOUND:
|
case ERRORS.NOT_FOUND:
|
||||||
return <Typography color={color}>{t(error.code, { context: `${error.entity}:${context}` })}</Typography>;
|
return <Typography color={color}>{t(error.code, { context: `${error.entity}:${context}` })}</Typography>;
|
||||||
case ERRORS.NOT_ALLOWED:
|
case ERRORS.NOT_ALLOWED:
|
||||||
|
case ERRORS.UNAUTHORIZED:
|
||||||
|
console.log(error, context);
|
||||||
return <Typography color={color}>{t(error.code, { context })}</Typography>;
|
return <Typography color={color}>{t(error.code, { context })}</Typography>;
|
||||||
case ERRORS.FAILED_UPDATE:
|
case ERRORS.FAILED_UPDATE:
|
||||||
return error.fields.map((field: string, index: number) => (
|
return error.fields.map((field: string, index: number) => (
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useForm } from '@tanstack/react-form';
|
|||||||
import { useRouter } from '@tanstack/react-router';
|
import { useRouter } from '@tanstack/react-router';
|
||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Api from '../../../api/Api';
|
import { useApi } from '../../../api/Api';
|
||||||
import { Login } from '../../../types/User';
|
import { Login } from '../../../types/User';
|
||||||
import ErrorComponent from '../../Error/ErrorComponent';
|
import ErrorComponent from '../../Error/ErrorComponent';
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ const LoginForm: FC<Props> = ({ handleClose }) => {
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const Api = useApi();
|
||||||
|
|
||||||
const form = useForm<Login>({
|
const form = useForm<Login>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -28,8 +29,8 @@ const LoginForm: FC<Props> = ({ handleClose }) => {
|
|||||||
router.invalidate();
|
router.invalidate();
|
||||||
handleClose();
|
handleClose();
|
||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (_error: any) {
|
||||||
setError(error);
|
setError(_error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Box, Button, CircularProgress, LinearProgress, TextField } from '@mui/material';
|
import { Alert, Box, Button, CircularProgress, LinearProgress, Snackbar, TextField } from '@mui/material';
|
||||||
import { useForm } from '@tanstack/react-form';
|
import { useForm } from '@tanstack/react-form';
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useNavigate } from '@tanstack/react-router';
|
import { useNavigate } from '@tanstack/react-router';
|
||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Api from '../../../api/Api';
|
import { useApi } from '../../../api/Api';
|
||||||
import { PostCreate } from '../../../types/Post';
|
import { PostCreate } from '../../../types/Post';
|
||||||
import ErrorComponent from '../../Error/ErrorComponent';
|
import ErrorComponent from '../../Error/ErrorComponent';
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ const PostForm: FC = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const Api = useApi();
|
||||||
|
|
||||||
const newMutation = useMutation({
|
const newMutation = useMutation({
|
||||||
mutationFn: ({ data }: { data: PostCreate }) => {
|
mutationFn: ({ data }: { data: PostCreate }) => {
|
||||||
@@ -42,8 +43,8 @@ const PostForm: FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (_error: any) {
|
||||||
setError(error);
|
setError(_error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -120,7 +121,21 @@ const PostForm: FC = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{error && <ErrorComponent error={error} context="newPost" />}
|
|
||||||
|
<Snackbar
|
||||||
|
open={newMutation.isError}
|
||||||
|
autoHideDuration={2000}
|
||||||
|
onClose={() => {
|
||||||
|
newMutation.reset();
|
||||||
|
}}
|
||||||
|
TransitionProps={{
|
||||||
|
onExited: () => setError(undefined),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Alert severity="error" variant="filled" sx={{ width: '100%' }}>
|
||||||
|
{error && <ErrorComponent error={error} context="newPost" color="white" />}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
</Box>
|
</Box>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ import {
|
|||||||
useScrollTrigger,
|
useScrollTrigger,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Link, useRouterState } from '@tanstack/react-router';
|
import { Link, useRouterState } from '@tanstack/react-router';
|
||||||
import { cloneElement, FC, ReactElement, useEffect, useState } from 'react';
|
import { cloneElement, FC, ReactElement, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Api from '../../api/Api';
|
import { useApi } from '../../api/Api';
|
||||||
import { User } from '../../types/User';
|
|
||||||
import LanguageMenu from '../Menus/Language/LanguageMenu';
|
import LanguageMenu from '../Menus/Language/LanguageMenu';
|
||||||
import UserMenu from '../Menus/User/UserMenu';
|
import UserMenu from '../Menus/User/UserMenu';
|
||||||
|
|
||||||
@@ -31,12 +30,10 @@ const ElevationScroll = ({ children }: { children: ReactElement }) => {
|
|||||||
const Header: FC = () => {
|
const Header: FC = () => {
|
||||||
const [anchorUserMenu, setAnchorUserMenu] = useState<null | HTMLElement>(null);
|
const [anchorUserMenu, setAnchorUserMenu] = useState<null | HTMLElement>(null);
|
||||||
const [anchorLanguageMenu, setAnchorLanguageMenu] = useState<null | HTMLElement>(null);
|
const [anchorLanguageMenu, setAnchorLanguageMenu] = useState<null | HTMLElement>(null);
|
||||||
const [user, setUser] = useState<User>();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isLoading = useRouterState({ select: (s) => s.status === 'pending' });
|
const isLoading = useRouterState({ select: (s) => s.status === 'pending' });
|
||||||
|
const Api = useApi();
|
||||||
useEffect(() => Api.subscribeToAuthenticatedUser((user) => setUser(user)), []);
|
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setAnchorLanguageMenu(null);
|
setAnchorLanguageMenu(null);
|
||||||
@@ -59,9 +56,9 @@ const Header: FC = () => {
|
|||||||
<IconButton size="large" onClick={(event) => setAnchorLanguageMenu(event.currentTarget)}>
|
<IconButton size="large" onClick={(event) => setAnchorLanguageMenu(event.currentTarget)}>
|
||||||
<Translate sx={{ color: 'white' }} />
|
<Translate sx={{ color: 'white' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{user ? (
|
{Api.authenticatedUser ? (
|
||||||
<IconButton onClick={(event) => setAnchorUserMenu(event.currentTarget)} sx={{ p: 0 }}>
|
<IconButton onClick={(event) => setAnchorUserMenu(event.currentTarget)} sx={{ p: 0 }}>
|
||||||
<Avatar alt={user.username} src={`${user.image}`}>
|
<Avatar alt={Api.authenticatedUser.username} src={`${Api.authenticatedUser.image}`}>
|
||||||
<Person />
|
<Person />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useMatch, useNavigate, useRouter } from '@tanstack/react-router';
|
|||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
import { Trans } from 'react-i18next/TransWithoutContext';
|
import { Trans } from 'react-i18next/TransWithoutContext';
|
||||||
import Api from '../../../api/Api';
|
import { useApi } from '../../../api/Api';
|
||||||
import { ROUTES } from '../../../types/Routes';
|
import { ROUTES } from '../../../types/Routes';
|
||||||
import RegisterDialog from '../../Dialogs/Register/RegisterDialog';
|
import RegisterDialog from '../../Dialogs/Register/RegisterDialog';
|
||||||
import LoginForm from '../../Forms/Login/LoginForm';
|
import LoginForm from '../../Forms/Login/LoginForm';
|
||||||
@@ -20,7 +20,7 @@ const UserMenu: FC<Props> = ({ anchorEl, handleClose }) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const match = useMatch({ from: '/profile/', strict: true, shouldThrow: false });
|
const match = useMatch({ from: '/profile/', strict: true, shouldThrow: false });
|
||||||
|
|
||||||
const user = Api.getAuthenticatedUser();
|
const Api = useApi();
|
||||||
|
|
||||||
const _handleClose = () => {
|
const _handleClose = () => {
|
||||||
setRegister(false);
|
setRegister(false);
|
||||||
@@ -47,7 +47,7 @@ const UserMenu: FC<Props> = ({ anchorEl, handleClose }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{user ? (
|
{Api.authenticatedUser ? (
|
||||||
[
|
[
|
||||||
<MenuItem
|
<MenuItem
|
||||||
selected={!!match}
|
selected={!!match}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|||||||
import { Link, useNavigate } from '@tanstack/react-router';
|
import { Link, useNavigate } from '@tanstack/react-router';
|
||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Api from '../../api/Api';
|
import { useApi } from '../../api/Api';
|
||||||
import { PostAuth, PostNonAuth } from '../../types/Post';
|
import { PostAuth, PostNonAuth } from '../../types/Post';
|
||||||
import convertDate from '../../utils/date';
|
import convertDate from '../../utils/date';
|
||||||
import PostEditDialog from '../Dialogs/PostEdit/PostEditDialog';
|
import PostEditDialog from '../Dialogs/PostEdit/PostEditDialog';
|
||||||
@@ -37,22 +37,23 @@ const Post: FC<Props> = ({ post, disableActions }) => {
|
|||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const [error, setError] = useState<any>();
|
const [error, setError] = useState<any>();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const Api = useApi();
|
||||||
|
|
||||||
const deleteMutation = useMutation({
|
const deleteMutation = useMutation({
|
||||||
mutationFn: (id: number) => {
|
mutationFn: (id: number) => {
|
||||||
return Api.deletePost(id);
|
return Api.deletePost(id);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
avatar={
|
avatar={
|
||||||
!disableActions && 'id' in post.user ? (
|
!disableActions && 'id' in post.user ? (
|
||||||
post.user.id !== Api.getAuthenticatedUser()?.id ? (
|
post.user.id !== Api.authenticatedUser?.id ? (
|
||||||
<MUILink component={Link} to="/profile/$id" params={{ id: post.user.id }}>
|
<MUILink component={Link} to="/profile/$id" params={{ id: post.user.id }}>
|
||||||
<Avatar alt={post.user.username} src={`${post.user.image}`}>
|
<Avatar alt={post.user.username} src={`${post.user.image}`}>
|
||||||
<Person />
|
<Person />
|
||||||
@@ -73,7 +74,7 @@ const Post: FC<Props> = ({ post, disableActions }) => {
|
|||||||
}
|
}
|
||||||
title={
|
title={
|
||||||
!disableActions && 'id' in post.user ? (
|
!disableActions && 'id' in post.user ? (
|
||||||
post.user.id !== Api.getAuthenticatedUser()?.id ? (
|
post.user.id !== Api.authenticatedUser?.id ? (
|
||||||
<MUILink component={Link} to="/profile/$id" params={{ id: post.user.id }}>
|
<MUILink component={Link} to="/profile/$id" params={{ id: post.user.id }}>
|
||||||
{post.user.username}
|
{post.user.username}
|
||||||
</MUILink>
|
</MUILink>
|
||||||
@@ -96,7 +97,7 @@ const Post: FC<Props> = ({ post, disableActions }) => {
|
|||||||
|
|
||||||
<CardActions>
|
<CardActions>
|
||||||
{!disableActions &&
|
{!disableActions &&
|
||||||
(Api.isAdmin() || ('id' in post.user && post.user.id === Api.getAuthenticatedUser()?.id)) && (
|
(Api.authenticatedUser?.isAdmin || ('id' in post.user && post.user.id === Api.authenticatedUser?.id)) && (
|
||||||
<>
|
<>
|
||||||
<Button size="small" onClick={() => setEditOpen(true)}>
|
<Button size="small" onClick={() => setEditOpen(true)}>
|
||||||
{t('Edit')}
|
{t('Edit')}
|
||||||
@@ -104,7 +105,7 @@ const Post: FC<Props> = ({ post, disableActions }) => {
|
|||||||
<PostEditDialog post={post as PostAuth} open={editOpen} onClose={() => setEditOpen(false)} />
|
<PostEditDialog post={post as PostAuth} open={editOpen} onClose={() => setEditOpen(false)} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!disableActions && Api.isAdmin() && (
|
{!disableActions && Api.authenticatedUser?.isAdmin && (
|
||||||
<>
|
<>
|
||||||
<Button size="small" color="error" onClick={() => setDeleteOpen(true)}>
|
<Button size="small" color="error" onClick={() => setDeleteOpen(true)}>
|
||||||
{t('Delete')}
|
{t('Delete')}
|
||||||
@@ -152,7 +153,7 @@ const Post: FC<Props> = ({ post, disableActions }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Alert severity="error" variant="filled" sx={{ width: '100%' }}>
|
<Alert severity="error" variant="filled" sx={{ width: '100%' }}>
|
||||||
{error && <ErrorComponent error={error} context="delete" color="white" />}
|
{error && <ErrorComponent error={error} context="deletePost" color="white" />}
|
||||||
</Alert>
|
</Alert>
|
||||||
</Snackbar>
|
</Snackbar>
|
||||||
<Snackbar open={deleteMutation.isPending} message={t('Deleting')} />
|
<Snackbar open={deleteMutation.isPending} message={t('Deleting')} />
|
||||||
|
|||||||
+8
-65
@@ -1,13 +1,9 @@
|
|||||||
import { QueryClient, QueryClientProvider, useQueryClient, useQueryErrorResetBoundary } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { ErrorRouteComponent, RouterProvider, createRouter, useRouter } from '@tanstack/react-router';
|
|
||||||
import { StrictMode, useEffect } from 'react';
|
|
||||||
import ReactDOM from 'react-dom/client';
|
|
||||||
import Api from './api/Api';
|
|
||||||
import { ERRORS } from './components/Error/Errors';
|
|
||||||
|
|
||||||
// Import the generated route tree
|
|
||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||||
import { routeTree } from './routeTree.gen';
|
import { StrictMode } from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import ApiProvider from './api/Api';
|
||||||
|
import Router from './router';
|
||||||
|
|
||||||
// Import i18n
|
// Import i18n
|
||||||
import './i18n';
|
import './i18n';
|
||||||
@@ -21,61 +17,6 @@ import '@fontsource/roboto/700.css';
|
|||||||
// Query Client
|
// Query Client
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
//TODO: REAUTH HERE
|
|
||||||
|
|
||||||
//TODO: Make nice
|
|
||||||
export const Error: ErrorRouteComponent = ({ error }) => {
|
|
||||||
const router = useRouter();
|
|
||||||
const queryErrorResetBoundary = useQueryErrorResetBoundary();
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Reset the query error boundary
|
|
||||||
queryErrorResetBoundary.reset();
|
|
||||||
}, [queryErrorResetBoundary]);
|
|
||||||
|
|
||||||
//TODO: Split display, show something went wrong with retry on all, show session expired and back to main on auth
|
|
||||||
if ('code' in error && error.code === ERRORS.NOT_ALLOWED) {
|
|
||||||
Api.logOut().finally(() => {
|
|
||||||
queryClient.clear();
|
|
||||||
router.invalidate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{error.message}
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
router.invalidate();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
retry
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a new router instance
|
|
||||||
const router = createRouter({
|
|
||||||
routeTree,
|
|
||||||
context: {
|
|
||||||
queryClient,
|
|
||||||
},
|
|
||||||
defaultPreload: 'intent',
|
|
||||||
// Since we're using React Query, we don't want loader calls to ever be stale
|
|
||||||
// This will ensure that the loader is always called when the route is preloaded or visited
|
|
||||||
defaultPreloadStaleTime: 0,
|
|
||||||
basepath: process.env.NODE_ENV === 'development' ? 'phpCourse/exam/dist' : '/phpCourse/exam',
|
|
||||||
defaultErrorComponent: Error,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register the router instance for type safety
|
|
||||||
declare module '@tanstack/react-router' {
|
|
||||||
interface Register {
|
|
||||||
router: typeof router;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the app
|
// Render the app
|
||||||
const rootElement = document.getElementById('root')!;
|
const rootElement = document.getElementById('root')!;
|
||||||
if (!rootElement.innerHTML) {
|
if (!rootElement.innerHTML) {
|
||||||
@@ -83,7 +24,9 @@ if (!rootElement.innerHTML) {
|
|||||||
root.render(
|
root.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<RouterProvider router={router} />
|
<ApiProvider>
|
||||||
|
<Router />
|
||||||
|
</ApiProvider>
|
||||||
{process.env.NODE_ENV === 'development' && <ReactQueryDevtools initialIsOpen={false} />}
|
{process.env.NODE_ENV === 'development' && <ReactQueryDevtools initialIsOpen={false} />}
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { queryOptions } from '@tanstack/react-query';
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
import Api from '../api/Api';
|
import { useApi } from '../api/Api';
|
||||||
|
|
||||||
export const postsQueryOptions = (page?: number) =>
|
export const postsQueryOptions = (Api: ReturnType<typeof useApi>, page?: number) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: ['posts', { page: page ?? 0, hasAuth: Api.hasAuth() }],
|
queryKey: ['posts', { page: page ?? 0, hasAuth: Api.hasAuth }],
|
||||||
queryFn: () => Api.posts(page),
|
queryFn: async () => await Api.posts(page),
|
||||||
});
|
});
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
import { queryOptions } from '@tanstack/react-query';
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
import Api from '../api/Api';
|
import { useApi } from '../api/Api';
|
||||||
|
|
||||||
export const profileSelfQueryOptions = queryOptions({
|
export const profileSelfQueryOptions = (Api: ReturnType<typeof useApi>) =>
|
||||||
queryKey: ['profile'],
|
queryOptions({
|
||||||
queryFn: () => Api.user(),
|
queryKey: ['profile'],
|
||||||
});
|
queryFn: async () => await Api.user(),
|
||||||
|
});
|
||||||
|
|
||||||
export const profileQueryOptions = (id?: number) =>
|
export const profileQueryOptions = (Api: ReturnType<typeof useApi>, id?: number) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: ['profile', { id }],
|
queryKey: ['profile', { id }],
|
||||||
queryFn: () => Api.user(id),
|
queryFn: async () => await Api.user(id),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const profilePostsQueryOptions = (id: number) =>
|
export const profilePostsQueryOptions = (Api: ReturnType<typeof useApi>, id: number) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: ['profilePosts', { id }],
|
queryKey: ['profilePosts', { id }],
|
||||||
queryFn: () => Api.userPosts(id),
|
queryFn: async () => await Api.userPosts(id),
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { useQueryClient, useQueryErrorResetBoundary } from '@tanstack/react-query';
|
||||||
|
import { createRouter, ErrorRouteComponent, RouterProvider, useRouter } from '@tanstack/react-router';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useApi } from './api/Api';
|
||||||
|
|
||||||
|
import { routeTree } from './routeTree.gen';
|
||||||
|
|
||||||
|
//TODO: REAUTH HERE
|
||||||
|
|
||||||
|
//TODO: Make nice
|
||||||
|
export const Error: ErrorRouteComponent = ({ error }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const queryErrorResetBoundary = useQueryErrorResetBoundary();
|
||||||
|
const _queryClient = useQueryClient();
|
||||||
|
const Api = useApi();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Reset the query error boundary
|
||||||
|
queryErrorResetBoundary.reset();
|
||||||
|
}, [queryErrorResetBoundary]);
|
||||||
|
|
||||||
|
//TODO: Split display, show something went wrong with retry on all, show session expired and back to main on auth
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{error.message}
|
||||||
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
_queryClient.clear();
|
||||||
|
if (Api.hasAuth) await Api.logOut();
|
||||||
|
router.invalidate();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a new router instance
|
||||||
|
const router = createRouter({
|
||||||
|
routeTree,
|
||||||
|
context: {
|
||||||
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
queryClient: undefined as any,
|
||||||
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
Api: undefined as any,
|
||||||
|
},
|
||||||
|
defaultPreload: 'intent',
|
||||||
|
// Since we're using React Query, we don't want loader calls to ever be stale
|
||||||
|
// This will ensure that the loader is always called when the route is preloaded or visited
|
||||||
|
defaultPreloadStaleTime: 0,
|
||||||
|
basepath: process.env.NODE_ENV === 'development' ? 'phpCourse/exam/dist' : '/phpCourse/exam',
|
||||||
|
//defaultErrorComponent: Error,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register the router instance for type safety
|
||||||
|
declare module '@tanstack/react-router' {
|
||||||
|
interface Register {
|
||||||
|
router: typeof router;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Router = () => {
|
||||||
|
const Api = useApi();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return <RouterProvider router={router} context={{ queryClient, Api }} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Router;
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import { QueryClient } from '@tanstack/react-query';
|
import { QueryClient, useQueryErrorResetBoundary } from '@tanstack/react-query';
|
||||||
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router';
|
import { createRootRouteWithContext, ErrorRouteComponent, Outlet, useRouter } from '@tanstack/react-router';
|
||||||
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
|
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useApi } from '../api/Api';
|
||||||
import Header from '../components/Header/Header';
|
import Header from '../components/Header/Header';
|
||||||
|
|
||||||
const Root = () => {
|
const Root = () => {
|
||||||
@@ -18,6 +20,31 @@ const Root = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
|
const ErrorComponent: ErrorRouteComponent = ({ error }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const queryErrorResetBoundary = useQueryErrorResetBoundary();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Reset the query error boundary
|
||||||
|
queryErrorResetBoundary.reset();
|
||||||
|
}, [queryErrorResetBoundary]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{error.message}
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
// Invalidate the route to reload the loader, and reset any router error boundaries
|
||||||
|
router.invalidate();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createRootRouteWithContext<{ queryClient: QueryClient; Api: ReturnType<typeof useApi> }>()({
|
||||||
component: Root,
|
component: Root,
|
||||||
|
errorComponent: ErrorComponent,
|
||||||
});
|
});
|
||||||
@@ -12,15 +12,16 @@ import { useSuspenseQuery } from '@tanstack/react-query';
|
|||||||
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
|
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Api from '../api/Api';
|
import { useApi } from '../api/Api';
|
||||||
import PostForm from '../components/Forms/Post/PostForm';
|
import PostForm from '../components/Forms/Post/PostForm';
|
||||||
import Post from '../components/Post/Post';
|
import Post from '../components/Post/Post';
|
||||||
import { postsQueryOptions } from '../queries/postsQuery';
|
import { postsQueryOptions } from '../queries/postsQuery';
|
||||||
import { ROUTES } from '../types/Routes';
|
import { ROUTES } from '../types/Routes';
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
|
const Api = useApi();
|
||||||
const { page } = Route.useSearch();
|
const { page } = Route.useSearch();
|
||||||
const { data: postsQuery, isFetching } = useSuspenseQuery(postsQueryOptions(page));
|
const { data: postsQuery, isFetching } = useSuspenseQuery(postsQueryOptions(Api, page));
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -62,7 +63,7 @@ const Home = () => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
{Api.hasAuth() ? (
|
{Api.hasAuth ? (
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<PostForm />
|
<PostForm />
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -79,7 +80,8 @@ const Home = () => {
|
|||||||
|
|
||||||
export const Route = createFileRoute(ROUTES.INDEX)({
|
export const Route = createFileRoute(ROUTES.INDEX)({
|
||||||
loaderDeps: ({ search: { page } }) => ({ page }),
|
loaderDeps: ({ search: { page } }) => ({ page }),
|
||||||
loader: ({ context: { queryClient }, deps: { page } }) => queryClient.ensureQueryData(postsQueryOptions(page)),
|
loader: ({ context: { queryClient, Api }, deps: { page } }) =>
|
||||||
|
queryClient.ensureQueryData(postsQueryOptions(Api, page)),
|
||||||
validateSearch: (search: Record<string, unknown>): { page?: number } => {
|
validateSearch: (search: Record<string, unknown>): { page?: number } => {
|
||||||
return {
|
return {
|
||||||
page: search?.page !== undefined ? Number(search?.page ?? 0) : undefined,
|
page: search?.page !== undefined ? Number(search?.page ?? 0) : undefined,
|
||||||
|
|||||||
@@ -2,21 +2,35 @@ import { Snackbar } from '@mui/material';
|
|||||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||||
import { createFileRoute, redirect } from '@tanstack/react-router';
|
import { createFileRoute, redirect } from '@tanstack/react-router';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import Api from '../../api/Api';
|
import { useApi } from '../../api/Api';
|
||||||
|
import { ERRORS } from '../../components/Error/Errors';
|
||||||
import Profile from '../../components/Profile/Profile';
|
import Profile from '../../components/Profile/Profile';
|
||||||
import { profilePostsQueryOptions, profileQueryOptions } from '../../queries/profileQuery';
|
import { profilePostsQueryOptions, profileQueryOptions } from '../../queries/profileQuery';
|
||||||
import { PostAuth } from '../../types/Post';
|
import { PostAuth } from '../../types/Post';
|
||||||
import { ROUTES } from '../../types/Routes';
|
import { ROUTES } from '../../types/Routes';
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
|
const Api = useApi();
|
||||||
const { id } = Route.useParams();
|
const { id } = Route.useParams();
|
||||||
const { data: profileQuery, isFetching: isFetchingProfile } = useSuspenseQuery(profileQueryOptions(id));
|
const {
|
||||||
const { data: profilePostsQuery, isFetching: isFetchingPosts } = useSuspenseQuery(profilePostsQueryOptions(id));
|
data: profileQuery,
|
||||||
|
isFetching: isFetchingProfile,
|
||||||
|
failureReason,
|
||||||
|
} = useSuspenseQuery(profileQueryOptions(Api, id));
|
||||||
|
const { data: profilePostsQuery, isFetching: isFetchingPosts } = useSuspenseQuery(profilePostsQueryOptions(Api, id));
|
||||||
|
|
||||||
|
if (failureReason && 'code' in failureReason && failureReason.code === ERRORS.UNAUTHORIZED) {
|
||||||
|
throw failureReason;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Snackbar open={isFetchingProfile || isFetchingPosts} message={t('Updating')} />
|
<Snackbar open={isFetchingProfile || isFetchingPosts} message={t('Updating')} />
|
||||||
<Profile user={profileQuery} posts={profilePostsQuery.data as PostAuth[]} canEdit={Api.isAdmin()} />
|
<Profile
|
||||||
|
user={profileQuery}
|
||||||
|
posts={profilePostsQuery.data as PostAuth[]}
|
||||||
|
canEdit={Api.authenticatedUser?.isAdmin}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -26,13 +40,15 @@ export const Route = createFileRoute(`${ROUTES.PROFILE}/$id`)({
|
|||||||
parse: ({ id }) => ({ id: parseInt(id) }),
|
parse: ({ id }) => ({ id: parseInt(id) }),
|
||||||
stringify: ({ id }) => ({ id: id.toString() }),
|
stringify: ({ id }) => ({ id: id.toString() }),
|
||||||
},
|
},
|
||||||
loader: ({ context: { queryClient }, params: { id } }) => {
|
loader: ({ context: { queryClient, Api }, params: { id } }) => {
|
||||||
queryClient.ensureQueryData(profileQueryOptions(id));
|
console.log('LOAD PROFILE');
|
||||||
queryClient.ensureQueryData(profilePostsQueryOptions(id));
|
queryClient.ensureQueryData(profileQueryOptions(Api, id));
|
||||||
|
queryClient.ensureQueryData(profilePostsQueryOptions(Api, id));
|
||||||
},
|
},
|
||||||
beforeLoad: ({ params: { id } }) => {
|
beforeLoad: ({ params: { id }, context: { Api } }) => {
|
||||||
if (!Api.hasAuth()) throw redirect({ to: ROUTES.INDEX });
|
console.log('BEFORE PROFILE');
|
||||||
if (id === Api.getAuthenticatedUser()?.id) throw redirect({ to: ROUTES.PROFILE });
|
if (!Api.hasAuth) throw redirect({ to: ROUTES.INDEX });
|
||||||
|
if (id === Api.authenticatedUser?.id) throw redirect({ to: ROUTES.PROFILE });
|
||||||
},
|
},
|
||||||
component: ProfilePage,
|
component: ProfilePage,
|
||||||
});
|
});
|
||||||
@@ -2,18 +2,28 @@ import { Snackbar } from '@mui/material';
|
|||||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||||
import { createFileRoute, redirect } from '@tanstack/react-router';
|
import { createFileRoute, redirect } from '@tanstack/react-router';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import Api from '../../api/Api';
|
import { useApi } from '../../api/Api';
|
||||||
|
import { ERRORS } from '../../components/Error/Errors';
|
||||||
import Profile from '../../components/Profile/Profile';
|
import Profile from '../../components/Profile/Profile';
|
||||||
import { profilePostsQueryOptions, profileSelfQueryOptions } from '../../queries/profileQuery';
|
import { profilePostsQueryOptions, profileSelfQueryOptions } from '../../queries/profileQuery';
|
||||||
import { PostAuth } from '../../types/Post';
|
import { PostAuth } from '../../types/Post';
|
||||||
import { ROUTES } from '../../types/Routes';
|
import { ROUTES } from '../../types/Routes';
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
const { data: profileQuery, isFetching: isFetchingProfile } = useSuspenseQuery(profileSelfQueryOptions);
|
const Api = useApi();
|
||||||
|
const {
|
||||||
|
data: profileQuery,
|
||||||
|
isFetching: isFetchingProfile,
|
||||||
|
failureReason,
|
||||||
|
} = useSuspenseQuery(profileSelfQueryOptions(Api));
|
||||||
const { data: profilePostsQuery, isFetching: isFetchingPosts } = useSuspenseQuery(
|
const { data: profilePostsQuery, isFetching: isFetchingPosts } = useSuspenseQuery(
|
||||||
profilePostsQueryOptions(Api.getAuthenticatedUser()?.id ?? 0)
|
profilePostsQueryOptions(Api, Api.authenticatedUser?.id ?? 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (failureReason && 'code' in failureReason && failureReason.code === ERRORS.UNAUTHORIZED) {
|
||||||
|
throw failureReason;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Snackbar open={isFetchingProfile || isFetchingPosts} message={t('Updating')} />
|
<Snackbar open={isFetchingProfile || isFetchingPosts} message={t('Updating')} />
|
||||||
@@ -23,12 +33,12 @@ const ProfilePage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Route = createFileRoute(`${ROUTES.PROFILE}/`)({
|
export const Route = createFileRoute(`${ROUTES.PROFILE}/`)({
|
||||||
loader: ({ context: { queryClient } }) => {
|
loader: ({ context: { queryClient, Api } }) => {
|
||||||
queryClient.ensureQueryData(profileSelfQueryOptions);
|
queryClient.ensureQueryData(profileSelfQueryOptions(Api));
|
||||||
queryClient.ensureQueryData(profilePostsQueryOptions(Api.getAuthenticatedUser()?.id ?? 0));
|
queryClient.ensureQueryData(profilePostsQueryOptions(Api, Api.authenticatedUser?.id ?? 0));
|
||||||
},
|
},
|
||||||
beforeLoad: () => {
|
beforeLoad: ({ context: { Api } }) => {
|
||||||
if (!Api.hasAuth()) throw redirect({ to: ROUTES.INDEX });
|
if (!Api.hasAuth) throw redirect({ to: ROUTES.INDEX });
|
||||||
},
|
},
|
||||||
component: ProfilePage,
|
component: ProfilePage,
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user