feat: added not-found and error pages
This commit is contained in:
+17
-12
@@ -6,24 +6,26 @@ import { ServerContext } from './hooks'
|
||||
import { UserProvider } from './hooks/useCurrentUser'
|
||||
|
||||
import { AdminPage } from './pages/AdminPage'
|
||||
import { ErrorPage } from './pages/ErrorPage'
|
||||
import { HomePage } from './pages/HomePage'
|
||||
import { LoginPage } from './pages/LoginPage'
|
||||
import { NotFoundPage } from './pages/NotFoundPage'
|
||||
import { ProblemPage } from './pages/ProblemPage'
|
||||
import { ProfilePage } from './pages/ProfilePage'
|
||||
import { ScoresPage } from './pages/ScoresPage'
|
||||
import { UserPage } from './pages/UserPage'
|
||||
|
||||
const Redirect = ({ to }: { to: string }) => {
|
||||
useEffect(() => {
|
||||
route(prependBaseUrl(to), true)
|
||||
}, [])
|
||||
// const Redirect = ({ to }: { to: string }) => {
|
||||
// useEffect(() => {
|
||||
// route(prependBaseUrl(to), true)
|
||||
// }, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
Redirecting to <pre>{to}</pre>...
|
||||
</>
|
||||
)
|
||||
}
|
||||
// return (
|
||||
// <>
|
||||
// Redirecting to <pre>{to}</pre>...
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
export const App = ({ url }: { url?: string }) => {
|
||||
// during server side rendering don't prepend the BASE_URL
|
||||
@@ -60,10 +62,13 @@ export const App = ({ url }: { url?: string }) => {
|
||||
// @ts-ignore
|
||||
path={pbu('/scores')}
|
||||
/>
|
||||
<Redirect
|
||||
<ErrorPage
|
||||
// @ts-ignore
|
||||
path={pbu('/error')}
|
||||
/>
|
||||
<NotFoundPage
|
||||
// @ts-ignore
|
||||
default
|
||||
to="/"
|
||||
/>
|
||||
</Router>
|
||||
</UserProvider>
|
||||
|
||||
@@ -9,7 +9,7 @@ const ROLE_LABEL: Record<UserRole, string> = {
|
||||
['student']: 'Studente',
|
||||
}
|
||||
|
||||
export const Header = ({ }) => {
|
||||
export const Header = ({}) => {
|
||||
const [user] = useCurrentUser()
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Header } from '../components/Header'
|
||||
|
||||
function splitFirst(s: string, sep: string): string[] {
|
||||
const i = s.indexOf(sep)
|
||||
|
||||
if (i === -1) {
|
||||
return [s, '']
|
||||
} else {
|
||||
return [s.substring(0, i), s.substring(i + sep.length)]
|
||||
}
|
||||
}
|
||||
|
||||
const defaultErrorMessage = <>Purtroppo c'è stato un errore inaspettato</>
|
||||
|
||||
export const ErrorPage = ({ url }: { url: string }) => {
|
||||
const query = Object.fromEntries(
|
||||
splitFirst(url, '?')[1]
|
||||
.split('&')
|
||||
.map(kv => splitFirst(kv, '='))
|
||||
.map(([k, v]) => [k, decodeURIComponent(v)])
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main class="page-error">
|
||||
<div class="title">Errore</div>
|
||||
<p>{query.message.trim().length > 0 ? query.message : defaultErrorMessage}</p>
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Header } from '../components/Header'
|
||||
|
||||
export const NotFoundPage = ({}) => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main class="page-not-found">
|
||||
<div class="title">Pagina non trovata!</div>
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
+13
-12
@@ -223,7 +223,7 @@ p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p+p {
|
||||
p + p {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
@@ -476,11 +476,11 @@ main.page-scores {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:nth-child(3n + 1)+.cell+.cell::after {
|
||||
&:nth-child(3n + 1) + .cell + .cell::after {
|
||||
background: #00000006;
|
||||
}
|
||||
|
||||
&:nth-child(3n + 2)+.cell::after {
|
||||
&:nth-child(3n + 2) + .cell::after {
|
||||
background: #00000006;
|
||||
}
|
||||
}
|
||||
@@ -529,6 +529,8 @@ header {
|
||||
background: #fdfdfd;
|
||||
|
||||
.logo {
|
||||
z-index: 1;
|
||||
|
||||
font-size: 42px;
|
||||
font-family: 'Lato';
|
||||
font-weight: 300;
|
||||
@@ -544,6 +546,8 @@ header {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
z-index: 0;
|
||||
|
||||
.nav-left {
|
||||
gap: 1rem;
|
||||
display: flex;
|
||||
@@ -797,8 +801,7 @@ header {
|
||||
}
|
||||
|
||||
// On mobile
|
||||
@media screen and (max-width: $device-s-width),
|
||||
(pointer: coarse) {
|
||||
@media screen and (max-width: $device-s-width), (pointer: coarse) {
|
||||
main {
|
||||
padding: 2rem 1rem 6rem;
|
||||
|
||||
@@ -824,16 +827,15 @@ header {
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
.nav-left, .nav-right {
|
||||
.nav-left,
|
||||
.nav-right {
|
||||
left: unset;
|
||||
right: unset;
|
||||
position: relative;
|
||||
|
||||
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.problem {
|
||||
@@ -874,8 +876,7 @@ header {
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $device-m-width),
|
||||
(pointer: coarse) {
|
||||
@media screen and (max-width: $device-m-width), (pointer: coarse) {
|
||||
.markdown-editor {
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: auto auto;
|
||||
@@ -885,4 +886,4 @@ header {
|
||||
justify-self: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,5 +48,13 @@
|
||||
"npm-run-all": "^4.1.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 140,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "consistent",
|
||||
"tabWidth": 4,
|
||||
"semi": false,
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { RenderFunction } from './shared/ssr'
|
||||
// Load ".env"
|
||||
dotenv.config()
|
||||
|
||||
const HTML_ROUTES = ['/', '/login', '/problem/:id', '/u/:uid', '/admin', '/profile', '/scores']
|
||||
// const HTML_ROUTES = ['/', '/login', '/problem/:id', '/u/:uid', '/admin', '/profile', '/scores']
|
||||
|
||||
const config = {
|
||||
isDevelopment: process.env.MODE === 'development',
|
||||
@@ -26,12 +26,12 @@ if (config.isDevelopment) {
|
||||
console.log(`[Config] PORT = ${config.port}`)
|
||||
}
|
||||
|
||||
function mountIndexHtmlRoutes(r: Router, serveIndexHtml: Handler) {
|
||||
for (const route of HTML_ROUTES) {
|
||||
console.log(`[Server] Mounted index html for "${route}"`)
|
||||
r.get(route, serveIndexHtml)
|
||||
}
|
||||
}
|
||||
// function mountIndexHtmlRoutes(r: Router, serveIndexHtml: Handler) {
|
||||
// for (const route of HTML_ROUTES) {
|
||||
// console.log(`[Server] Mounted index html for "${route}"`)
|
||||
// r.get(route, serveIndexHtml)
|
||||
// }
|
||||
// }
|
||||
|
||||
async function createDevRouter() {
|
||||
const r = express.Router()
|
||||
@@ -42,7 +42,8 @@ async function createDevRouter() {
|
||||
})
|
||||
|
||||
r.use(vite.middlewares)
|
||||
mountIndexHtmlRoutes(r, async (req, res, next) => {
|
||||
|
||||
r.use('*', async (req, res, next) => {
|
||||
try {
|
||||
const indexHtml = await fs.readFile(path.resolve('./index.html'), 'utf-8')
|
||||
const transformedTemplate = await vite.transformIndexHtml(req.originalUrl, indexHtml)
|
||||
@@ -66,10 +67,6 @@ async function createDevRouter() {
|
||||
}
|
||||
})
|
||||
|
||||
r.use('*', (_req, res) => {
|
||||
res.redirect('/')
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -81,7 +78,9 @@ async function createProductionRouter() {
|
||||
|
||||
const r = express.Router()
|
||||
|
||||
mountIndexHtmlRoutes(r, async (req, res) => {
|
||||
r.use('/', express.static('dist/entry-client'))
|
||||
|
||||
r.use('*', async (req, res) => {
|
||||
const transformedTemplate = await fs.readFile(path.resolve('./dist/entry-client/index.html'), 'utf-8')
|
||||
|
||||
const { html, metadata } = render(req.originalUrl)
|
||||
@@ -96,12 +95,6 @@ async function createProductionRouter() {
|
||||
res.send(transformedTemplate.replace('<!-- INJECT META TAGS -->', metaTagsHtml).replace('<!-- SSR OUTLET -->', html))
|
||||
})
|
||||
|
||||
r.use('/', express.static('dist/entry-client'))
|
||||
|
||||
r.use('*', (_req, res) => {
|
||||
res.redirect('/')
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user