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 { UserProvider } from './hooks/useCurrentUser'
|
||||||
|
|
||||||
import { AdminPage } from './pages/AdminPage'
|
import { AdminPage } from './pages/AdminPage'
|
||||||
|
import { ErrorPage } from './pages/ErrorPage'
|
||||||
import { HomePage } from './pages/HomePage'
|
import { HomePage } from './pages/HomePage'
|
||||||
import { LoginPage } from './pages/LoginPage'
|
import { LoginPage } from './pages/LoginPage'
|
||||||
|
import { NotFoundPage } from './pages/NotFoundPage'
|
||||||
import { ProblemPage } from './pages/ProblemPage'
|
import { ProblemPage } from './pages/ProblemPage'
|
||||||
import { ProfilePage } from './pages/ProfilePage'
|
import { ProfilePage } from './pages/ProfilePage'
|
||||||
import { ScoresPage } from './pages/ScoresPage'
|
import { ScoresPage } from './pages/ScoresPage'
|
||||||
import { UserPage } from './pages/UserPage'
|
import { UserPage } from './pages/UserPage'
|
||||||
|
|
||||||
const Redirect = ({ to }: { to: string }) => {
|
// const Redirect = ({ to }: { to: string }) => {
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
route(prependBaseUrl(to), true)
|
// route(prependBaseUrl(to), true)
|
||||||
}, [])
|
// }, [])
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<>
|
// <>
|
||||||
Redirecting to <pre>{to}</pre>...
|
// Redirecting to <pre>{to}</pre>...
|
||||||
</>
|
// </>
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const App = ({ url }: { url?: string }) => {
|
export const App = ({ url }: { url?: string }) => {
|
||||||
// during server side rendering don't prepend the BASE_URL
|
// during server side rendering don't prepend the BASE_URL
|
||||||
@@ -60,10 +62,13 @@ export const App = ({ url }: { url?: string }) => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
path={pbu('/scores')}
|
path={pbu('/scores')}
|
||||||
/>
|
/>
|
||||||
<Redirect
|
<ErrorPage
|
||||||
|
// @ts-ignore
|
||||||
|
path={pbu('/error')}
|
||||||
|
/>
|
||||||
|
<NotFoundPage
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
default
|
default
|
||||||
to="/"
|
|
||||||
/>
|
/>
|
||||||
</Router>
|
</Router>
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const ROLE_LABEL: Record<UserRole, string> = {
|
|||||||
['student']: 'Studente',
|
['student']: 'Studente',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Header = ({ }) => {
|
export const Header = ({}) => {
|
||||||
const [user] = useCurrentUser()
|
const [user] = useCurrentUser()
|
||||||
|
|
||||||
return (
|
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;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
p+p {
|
p + p {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,11 +476,11 @@ main.page-scores {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
&:nth-child(3n + 1)+.cell+.cell::after {
|
&:nth-child(3n + 1) + .cell + .cell::after {
|
||||||
background: #00000006;
|
background: #00000006;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(3n + 2)+.cell::after {
|
&:nth-child(3n + 2) + .cell::after {
|
||||||
background: #00000006;
|
background: #00000006;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -529,6 +529,8 @@ header {
|
|||||||
background: #fdfdfd;
|
background: #fdfdfd;
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
font-size: 42px;
|
font-size: 42px;
|
||||||
font-family: 'Lato';
|
font-family: 'Lato';
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
@@ -544,6 +546,8 @@ header {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
z-index: 0;
|
||||||
|
|
||||||
.nav-left {
|
.nav-left {
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -797,8 +801,7 @@ header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// On mobile
|
// On mobile
|
||||||
@media screen and (max-width: $device-s-width),
|
@media screen and (max-width: $device-s-width), (pointer: coarse) {
|
||||||
(pointer: coarse) {
|
|
||||||
main {
|
main {
|
||||||
padding: 2rem 1rem 6rem;
|
padding: 2rem 1rem 6rem;
|
||||||
|
|
||||||
@@ -824,16 +827,15 @@ header {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
||||||
.nav-left, .nav-right {
|
.nav-left,
|
||||||
|
.nav-right {
|
||||||
left: unset;
|
left: unset;
|
||||||
right: unset;
|
right: unset;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.problem {
|
.problem {
|
||||||
@@ -874,8 +876,7 @@ header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $device-m-width),
|
@media screen and (max-width: $device-m-width), (pointer: coarse) {
|
||||||
(pointer: coarse) {
|
|
||||||
.markdown-editor {
|
.markdown-editor {
|
||||||
grid-template-columns: auto;
|
grid-template-columns: auto;
|
||||||
grid-template-rows: auto auto;
|
grid-template-rows: auto auto;
|
||||||
@@ -885,4 +886,4 @@ header {
|
|||||||
justify-self: center;
|
justify-self: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,5 +48,13 @@
|
|||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^4.8.4"
|
"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"
|
// Load ".env"
|
||||||
dotenv.config()
|
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 = {
|
const config = {
|
||||||
isDevelopment: process.env.MODE === 'development',
|
isDevelopment: process.env.MODE === 'development',
|
||||||
@@ -26,12 +26,12 @@ if (config.isDevelopment) {
|
|||||||
console.log(`[Config] PORT = ${config.port}`)
|
console.log(`[Config] PORT = ${config.port}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function mountIndexHtmlRoutes(r: Router, serveIndexHtml: Handler) {
|
// function mountIndexHtmlRoutes(r: Router, serveIndexHtml: Handler) {
|
||||||
for (const route of HTML_ROUTES) {
|
// for (const route of HTML_ROUTES) {
|
||||||
console.log(`[Server] Mounted index html for "${route}"`)
|
// console.log(`[Server] Mounted index html for "${route}"`)
|
||||||
r.get(route, serveIndexHtml)
|
// r.get(route, serveIndexHtml)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
async function createDevRouter() {
|
async function createDevRouter() {
|
||||||
const r = express.Router()
|
const r = express.Router()
|
||||||
@@ -42,7 +42,8 @@ async function createDevRouter() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
r.use(vite.middlewares)
|
r.use(vite.middlewares)
|
||||||
mountIndexHtmlRoutes(r, async (req, res, next) => {
|
|
||||||
|
r.use('*', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const indexHtml = await fs.readFile(path.resolve('./index.html'), 'utf-8')
|
const indexHtml = await fs.readFile(path.resolve('./index.html'), 'utf-8')
|
||||||
const transformedTemplate = await vite.transformIndexHtml(req.originalUrl, indexHtml)
|
const transformedTemplate = await vite.transformIndexHtml(req.originalUrl, indexHtml)
|
||||||
@@ -66,10 +67,6 @@ async function createDevRouter() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
r.use('*', (_req, res) => {
|
|
||||||
res.redirect('/')
|
|
||||||
})
|
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +78,9 @@ async function createProductionRouter() {
|
|||||||
|
|
||||||
const r = express.Router()
|
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 transformedTemplate = await fs.readFile(path.resolve('./dist/entry-client/index.html'), 'utf-8')
|
||||||
|
|
||||||
const { html, metadata } = render(req.originalUrl)
|
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))
|
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
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user