mirror of
https://github.com/Steffo99/festa.git
synced 2024-12-22 14:44:21 +00:00
Make big changes to how mood, errors and loading are handled
This commit is contained in:
parent
7717dfc3c7
commit
02d7251b16
19 changed files with 362 additions and 120 deletions
|
@ -6,6 +6,7 @@ import { usePromise, UsePromiseStatus } from "../../generic/loading/promise"
|
||||||
import { FestaIcon } from "../../generic/renderers/fontawesome"
|
import { FestaIcon } from "../../generic/renderers/fontawesome"
|
||||||
import { Tool } from "../../generic/toolbar/tool"
|
import { Tool } from "../../generic/toolbar/tool"
|
||||||
import cursor from "../../../styles/cursor.module.css"
|
import cursor from "../../../styles/cursor.module.css"
|
||||||
|
import mood from "../../../styles/mood.module.css"
|
||||||
|
|
||||||
|
|
||||||
export type ToolToggleEditingProps = {
|
export type ToolToggleEditingProps = {
|
||||||
|
@ -43,7 +44,7 @@ export function ToolToggleEditing(props: ToolToggleEditingProps) {
|
||||||
save()
|
save()
|
||||||
setEditing(EditingMode.VIEW)
|
setEditing(EditingMode.VIEW)
|
||||||
}}
|
}}
|
||||||
className={"positive"}
|
className={mood.positive}
|
||||||
>
|
>
|
||||||
<FestaIcon icon={faSave} />
|
<FestaIcon icon={faSave} />
|
||||||
</Tool>
|
</Tool>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Component, ErrorInfo, ReactNode } from "react";
|
import { Component, ErrorInfo, ReactNode } from "react";
|
||||||
import { ViewNotice } from "../views/notice";
|
import { ViewNotice } from "../views/notice";
|
||||||
import { ErrorBlock } from "./renderers";
|
import { ErrorBlock, ErrorMain } from "./renderers";
|
||||||
|
|
||||||
|
|
||||||
export type ErrorBoundaryProps = {
|
export type ErrorBoundaryProps = {
|
||||||
|
@ -20,7 +20,7 @@ export type ErrorBoundaryState = {
|
||||||
*
|
*
|
||||||
* To be used in `pages/_app`.
|
* To be used in `pages/_app`.
|
||||||
*/
|
*/
|
||||||
export class PageErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
export class ErrorBoundaryPage extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||||
constructor(props: ErrorBoundaryProps) {
|
constructor(props: ErrorBoundaryProps) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = { error: undefined, errorInfo: undefined }
|
this.state = { error: undefined, errorInfo: undefined }
|
||||||
|
@ -37,7 +37,7 @@ export class PageErrorBoundary extends Component<ErrorBoundaryProps, ErrorBounda
|
||||||
return (
|
return (
|
||||||
<ViewNotice
|
<ViewNotice
|
||||||
notice={
|
notice={
|
||||||
<ErrorBlock text={this.props.text} error={this.state.error} />
|
<ErrorMain text={this.props.text} error={this.state.error} />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -54,7 +54,7 @@ export class PageErrorBoundary extends Component<ErrorBoundaryProps, ErrorBounda
|
||||||
*
|
*
|
||||||
* To be used in other components.
|
* To be used in other components.
|
||||||
*/
|
*/
|
||||||
export class BlockErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
export class ErrorBoundaryBlock extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||||
constructor(props: ErrorBoundaryProps) {
|
constructor(props: ErrorBoundaryProps) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = { error: undefined, errorInfo: undefined }
|
this.state = { error: undefined, errorInfo: undefined }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.errorBlock > pre {
|
.errorBlock > pre, .errorMain > pre {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
@ -6,3 +6,15 @@
|
||||||
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.errorMain {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorMain > .errorIcon {
|
||||||
|
font-size: 4em;
|
||||||
|
}
|
|
@ -2,114 +2,164 @@ import { faCircleExclamation } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FestaIcon } from "../renderers/fontawesome";
|
import { FestaIcon } from "../renderers/fontawesome";
|
||||||
import { default as classNames } from "classnames"
|
import { default as classNames } from "classnames"
|
||||||
import style from "./renderers.module.css"
|
import style from "./renderers.module.css"
|
||||||
import { memo } from "react";
|
import mood from "../../../styles/mood.module.css"
|
||||||
|
import { ComponentPropsWithoutRef, memo } from "react";
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
|
|
||||||
export type ErrorTraceProps = {
|
/**
|
||||||
|
* Props of {@link ErrorTrace}.
|
||||||
|
*/
|
||||||
|
type ErrorTraceProps = ComponentPropsWithoutRef<"code"> & {
|
||||||
|
/**
|
||||||
|
* The error to render the stack trace of.
|
||||||
|
*/
|
||||||
error: Error,
|
error: Error,
|
||||||
inline: boolean,
|
|
||||||
|
/**
|
||||||
|
* Whether error messages in JSON format should be prettified or not.
|
||||||
|
*/
|
||||||
|
prettify: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const ErrorTrace = memo((props: ErrorTraceProps) => {
|
/**
|
||||||
if (props.error instanceof AxiosError) {
|
* Component rendering the details of an {@link Error}.
|
||||||
if (props.error.response) {
|
*
|
||||||
if (props.error.response.data) {
|
* Not to use by itself; should be used to display the error in other error renderers.
|
||||||
const json = JSON.stringify(props.error.response.data, undefined, props.inline ? undefined : 4).replaceAll("\\n", "\n")
|
*/
|
||||||
|
const ErrorTrace = memo(({ error, prettify, ...props }: ErrorTraceProps) => {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
if (error.response) {
|
||||||
|
if (error.response.data) {
|
||||||
|
const json = JSON.stringify(error.response.data, undefined, prettify ? 4 : undefined).replaceAll("\\n", "\n")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<code>
|
<code {...props}>
|
||||||
<b>API {props.error.response.status}</b>
|
<span>
|
||||||
:
|
<b>API {error.response.status}</b>
|
||||||
{json}
|
:
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{json}
|
||||||
|
</span>
|
||||||
</code>
|
</code>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<code>
|
<code {...props}>
|
||||||
<b>HTTP {props.error.response.status}</b>
|
<span>
|
||||||
:
|
<b>HTTP {error.response.status}</b>
|
||||||
{props.error.message}
|
:
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{error.message}
|
||||||
|
</span>
|
||||||
</code>
|
</code>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<code>
|
<code {...props}>
|
||||||
<b>{props.error.code}</b>
|
<span>
|
||||||
:
|
<b>{error.code}</b>
|
||||||
{props.error.message}
|
:
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{error.message}
|
||||||
|
</span>
|
||||||
</code>
|
</code>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<code>
|
<code {...props}>
|
||||||
<b>{props.error.name}</b>
|
<span>
|
||||||
:
|
<b>{error.name}</b>
|
||||||
{props.error.message}
|
:
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{error.message}
|
||||||
|
</span>
|
||||||
</code>
|
</code>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
ErrorTrace.displayName = "ErrorTrace"
|
ErrorTrace.displayName = "ErrorTrace"
|
||||||
|
|
||||||
|
|
||||||
export type ErrorInlineProps = {
|
/**
|
||||||
|
* Props for "error" renderers.
|
||||||
|
*/
|
||||||
|
export type ErrorProps = {
|
||||||
error: Error,
|
error: Error,
|
||||||
text?: string
|
text?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component rendering a `span` element containing an error passed to it as props.
|
* Inline component for rendering errors.
|
||||||
*
|
*
|
||||||
* May or may not include some text to display to the user.
|
* It displays an error {@link FestaIcon}, followed by some optional text, and finally the {@link ErrorTrace}.
|
||||||
*/
|
*/
|
||||||
export const ErrorInline = memo((props: ErrorInlineProps) => {
|
export const ErrorInline = memo(({ error, text }: ErrorProps) => {
|
||||||
return (
|
return (
|
||||||
<span className={classNames("negative", style.error, style.errorInline)}>
|
<span className={classNames(mood.negative, style.error, style.errorInline)}>
|
||||||
<FestaIcon icon={faCircleExclamation} />
|
<FestaIcon icon={faCircleExclamation} className={style.errorIcon} />
|
||||||
|
{!!text && <>
|
||||||
{props.text ?
|
|
||||||
<>
|
<span className={style.errorText}>
|
||||||
<span>
|
{text}
|
||||||
{props.text}
|
</span>
|
||||||
</span>
|
|
||||||
|
</>}
|
||||||
</>
|
<ErrorTrace error={error} prettify={false} className={style.errorTrace} />
|
||||||
: null}
|
|
||||||
<ErrorTrace error={props.error} inline={true} />
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
ErrorInline.displayName = "ErrorInline"
|
ErrorInline.displayName = "ErrorInline"
|
||||||
|
|
||||||
|
|
||||||
export type ErrorBlockProps = {
|
|
||||||
error: Error,
|
|
||||||
text: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component rendering a `div` element containing an error passed to it as props.
|
* Block component for rendering errors.
|
||||||
*
|
*
|
||||||
* Must include some text to display to the user.
|
* It displays an inline error {@link FestaIcon}, followed by some **required** text, with the {@link ErrorTrace} below.
|
||||||
*/
|
*/
|
||||||
export const ErrorBlock = memo((props: ErrorBlockProps) => {
|
export const ErrorBlock = memo(({ error, text }: ErrorProps & { text: string }) => {
|
||||||
return (
|
return (
|
||||||
<div className={classNames("negative", style.error, style.errorBlock)}>
|
<div className={classNames(mood.negative, style.error, style.errorBlock)}>
|
||||||
<p>
|
<p>
|
||||||
<FestaIcon icon={faCircleExclamation} />
|
<FestaIcon icon={faCircleExclamation} className={style.errorIcon} />
|
||||||
|
|
||||||
<span>
|
<span className={style.errorText}>
|
||||||
{props.text}
|
{text}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<pre>
|
<pre>
|
||||||
<ErrorTrace error={props.error} inline={false} />
|
<ErrorTrace error={error} prettify={false} className={style.errorTrace} />
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
ErrorBlock.displayName = "ErrorBlock"
|
ErrorBlock.displayName = "ErrorBlock"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block component for rendering errors at the center of the page.
|
||||||
|
*
|
||||||
|
* It displays an inline error {@link FestaIcon}, followed by some **required** text, with the {@link ErrorTrace} below.
|
||||||
|
*/
|
||||||
|
export const ErrorMain = memo(({ error, text }: ErrorProps & { text: string }) => {
|
||||||
|
return (
|
||||||
|
<div className={classNames(mood.negative, style.error, style.errorMain)}>
|
||||||
|
<FestaIcon icon={faCircleExclamation} className={style.errorIcon} />
|
||||||
|
<p className={style.errorText}>
|
||||||
|
{text}
|
||||||
|
</p>
|
||||||
|
<pre>
|
||||||
|
<ErrorTrace error={error} prettify={false} className={style.errorTrace} />
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
ErrorMain.displayName = "ErrorMain"
|
||||||
|
|
17
components/generic/loading/boundaries.tsx
Normal file
17
components/generic/loading/boundaries.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { ReactNode, Suspense } from "react"
|
||||||
|
import { LoadingMain } from "./renderers"
|
||||||
|
|
||||||
|
|
||||||
|
export type LoadingBoundaryProps = {
|
||||||
|
text?: string,
|
||||||
|
children: ReactNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function LoadingBoundaryPage({ text, children }: LoadingBoundaryProps) {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<LoadingMain text={text} />}>
|
||||||
|
{children}
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
11
components/generic/loading/renderers.module.css
Normal file
11
components/generic/loading/renderers.module.css
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
.loadingMain {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingMain > .loadingIcon {
|
||||||
|
font-size: 4em;
|
||||||
|
}
|
62
components/generic/loading/renderers.tsx
Normal file
62
components/generic/loading/renderers.tsx
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { faAsterisk } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { memo } from "react";
|
||||||
|
import { FestaIcon } from "../renderers/fontawesome";
|
||||||
|
import style from "./renderers.module.css"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for "loading" renderers, such as {@link LoadingInline} and {@link LoadingMain}.
|
||||||
|
*/
|
||||||
|
export type LoadingProps = {
|
||||||
|
text?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inline component displaying an animated loading icon with an optional message displayed on the right.
|
||||||
|
*
|
||||||
|
* @see {@link ErrorInline}
|
||||||
|
*/
|
||||||
|
export const LoadingInline = memo(({ text }: LoadingProps) => {
|
||||||
|
return (
|
||||||
|
<span className={classNames(style.loading, style.loadingInline)}>
|
||||||
|
<FestaIcon
|
||||||
|
icon={faAsterisk}
|
||||||
|
spin
|
||||||
|
className={style.loadingIcon}
|
||||||
|
/>
|
||||||
|
{!!text && <>
|
||||||
|
|
||||||
|
<span className={style.loadingText}>
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
</>}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
LoadingInline.displayName = "LoadingInline"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block component displaying a big loading icon with an optional message displayed below.
|
||||||
|
*
|
||||||
|
* @see {@link ErrorMain}
|
||||||
|
*/
|
||||||
|
export const LoadingMain = memo(({ text }: LoadingProps) => {
|
||||||
|
return (
|
||||||
|
<div className={classNames(style.loading, style.loadingMain)}>
|
||||||
|
<FestaIcon
|
||||||
|
icon={faAsterisk}
|
||||||
|
spin
|
||||||
|
className={style.loadingIcon}
|
||||||
|
/>
|
||||||
|
{!!text &&
|
||||||
|
<p className={style.loadingText}>
|
||||||
|
{text}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
LoadingMain.displayName = "LoadingMain"
|
|
@ -1,22 +0,0 @@
|
||||||
import { faAsterisk } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { memo } from "react";
|
|
||||||
import { FestaIcon } from "../renderers/fontawesome";
|
|
||||||
|
|
||||||
|
|
||||||
export type LoadingTextInlineProps = {
|
|
||||||
text: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component displaying an animated loading message for the user.
|
|
||||||
*/
|
|
||||||
export const LoadingTextInline = memo((props: LoadingTextInlineProps) => {
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
<FestaIcon icon={faAsterisk} spin />
|
|
||||||
|
|
||||||
{props.text}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
LoadingTextInline.displayName = "LoadingTextInline"
|
|
|
@ -1,4 +1,4 @@
|
||||||
.view-notice {
|
.viewNotice {
|
||||||
display: grid;
|
display: grid;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,10 @@ import { useAxiosRequest } from "../../auth/requests"
|
||||||
import { ErrorBlock } from "../../generic/errors/renderers"
|
import { ErrorBlock } from "../../generic/errors/renderers"
|
||||||
import { promiseMultiplexer } from "../../generic/loading/promise"
|
import { promiseMultiplexer } from "../../generic/loading/promise"
|
||||||
import { swrMultiplexer } from "../../generic/loading/swr"
|
import { swrMultiplexer } from "../../generic/loading/swr"
|
||||||
import { LoadingTextInline } from "../../generic/loading/textInline"
|
import { LoadingInline } from "../../generic/loading/renderers"
|
||||||
import { FestaIcon } from "../../generic/renderers/fontawesome"
|
import { FestaIcon } from "../../generic/renderers/fontawesome"
|
||||||
import style from "./events.module.css"
|
import style from "./events.module.css"
|
||||||
|
import mood from "../../../styles/mood.module.css"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -88,7 +89,7 @@ const LandingActionEventsFormCreate = () => {
|
||||||
<button
|
<button
|
||||||
aria-label={t("landingEventsCreateSubmitLabel")}
|
aria-label={t("landingEventsCreateSubmitLabel")}
|
||||||
disabled={!name}
|
disabled={!name}
|
||||||
className={classNames(style.landingActionEventsFormCreateSubmit, "positive")}
|
className={classNames(style.landingActionEventsFormCreateSubmit, mood.positive)}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
run({ data: { name } })
|
run({ data: { name } })
|
||||||
|
@ -100,7 +101,7 @@ const LandingActionEventsFormCreate = () => {
|
||||||
),
|
),
|
||||||
pending: ({ }) => (
|
pending: ({ }) => (
|
||||||
<p>
|
<p>
|
||||||
<LoadingTextInline text={t("landingEventsCreatePending")} />
|
<LoadingInline text={t("landingEventsCreatePending")} />
|
||||||
</p>
|
</p>
|
||||||
),
|
),
|
||||||
rejected: ({ error }) => (
|
rejected: ({ error }) => (
|
||||||
|
@ -111,7 +112,7 @@ const LandingActionEventsFormCreate = () => {
|
||||||
fulfilled: ({ result }) => {
|
fulfilled: ({ result }) => {
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
<LoadingTextInline text={t("landingEventsCreateFulfilled", name)} />
|
<LoadingInline text={t("landingEventsCreateFulfilled", name)} />
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -127,7 +128,7 @@ export const LandingActionEvents = () => {
|
||||||
hook: apiHook,
|
hook: apiHook,
|
||||||
loading: () => (
|
loading: () => (
|
||||||
<p>
|
<p>
|
||||||
<LoadingTextInline text={t("landingEventsLoading")} />
|
<LoadingInline text={t("landingEventsLoading")} />
|
||||||
</p>
|
</p>
|
||||||
),
|
),
|
||||||
ready: (data) => (<>
|
ready: (data) => (<>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useAxiosRequest } from "../../auth/requests"
|
||||||
import { TelegramLoginButton } from "../../auth/telegram/loginButton"
|
import { TelegramLoginButton } from "../../auth/telegram/loginButton"
|
||||||
import { ErrorBlock } from "../../generic/errors/renderers"
|
import { ErrorBlock } from "../../generic/errors/renderers"
|
||||||
import { promiseMultiplexer } from "../../generic/loading/promise"
|
import { promiseMultiplexer } from "../../generic/loading/promise"
|
||||||
import { LoadingTextInline } from "../../generic/loading/textInline"
|
import { LoadingInline } from "../../generic/loading/renderers"
|
||||||
import { useDefinedContext } from "../../../utils/definedContext"
|
import { useDefinedContext } from "../../../utils/definedContext"
|
||||||
import { AuthContext } from "../../auth/base"
|
import { AuthContext } from "../../auth/base"
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from "next/router"
|
||||||
|
@ -32,7 +32,7 @@ export const LandingActionLogin = () => {
|
||||||
</>,
|
</>,
|
||||||
pending: ({ }) => (
|
pending: ({ }) => (
|
||||||
<p>
|
<p>
|
||||||
<LoadingTextInline text={t("landingLoginTelegramPending")} />
|
<LoadingInline text={t("landingLoginTelegramPending")} />
|
||||||
</p>
|
</p>
|
||||||
),
|
),
|
||||||
fulfilled: ({ result }) => {
|
fulfilled: ({ result }) => {
|
||||||
|
@ -40,7 +40,7 @@ export const LandingActionLogin = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
<LoadingTextInline text={t("landingLoginTelegramFulfilled")} />
|
<LoadingInline text={t("landingLoginTelegramFulfilled")} />
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,12 +3,13 @@ import '@fortawesome/fontawesome-svg-core/styles.css'
|
||||||
import { AppProps } from 'next/app'
|
import { AppProps } from 'next/app'
|
||||||
import { appWithTranslation, useTranslation } from 'next-i18next'
|
import { appWithTranslation, useTranslation } from 'next-i18next'
|
||||||
import { AxiosSWRFetcherProvider } from '../components/auth/requests'
|
import { AxiosSWRFetcherProvider } from '../components/auth/requests'
|
||||||
import { PageErrorBoundary } from '../components/generic/errors/boundaries'
|
import { ErrorBoundaryPage } from '../components/generic/errors/boundaries'
|
||||||
import { AuthContextProvider } from '../components/auth/provider'
|
import { AuthContextProvider } from '../components/auth/provider'
|
||||||
import { PostcardRenderer } from '../components/postcard/renderer'
|
import { PostcardRenderer } from '../components/postcard/renderer'
|
||||||
import { config as fontAwesomeConfig } from '@fortawesome/fontawesome-svg-core'
|
import { config as fontAwesomeConfig } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { PostcardContextProvider } from '../components/postcard/provider'
|
import { PostcardContextProvider } from '../components/postcard/provider'
|
||||||
import defaultPostcard from "../public/postcards/adi-goldstein-Hli3R6LKibo-unsplash.jpg"
|
import defaultPostcard from "../public/postcards/adi-goldstein-Hli3R6LKibo-unsplash.jpg"
|
||||||
|
import { LoadingBoundaryPage } from '../components/generic/loading/boundaries'
|
||||||
|
|
||||||
|
|
||||||
fontAwesomeConfig.autoAddCss = false
|
fontAwesomeConfig.autoAddCss = false
|
||||||
|
@ -18,18 +19,22 @@ const App = ({ Component, pageProps }: AppProps): JSX.Element => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageErrorBoundary text={t("genericError")}>
|
<ErrorBoundaryPage text={t("genericError")}>
|
||||||
<AxiosSWRFetcherProvider>
|
<AxiosSWRFetcherProvider>
|
||||||
<PostcardContextProvider defaultPostcard={defaultPostcard}>
|
<PostcardContextProvider defaultPostcard={defaultPostcard}>
|
||||||
<AuthContextProvider storageKey="auth">
|
<AuthContextProvider storageKey="auth">
|
||||||
<AxiosSWRFetcherProvider>
|
<AxiosSWRFetcherProvider>
|
||||||
<PostcardRenderer />
|
<PostcardRenderer />
|
||||||
<Component {...pageProps} />
|
<ErrorBoundaryPage text={t("genericError")}>
|
||||||
|
<LoadingBoundaryPage>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</LoadingBoundaryPage>
|
||||||
|
</ErrorBoundaryPage>
|
||||||
</AxiosSWRFetcherProvider>
|
</AxiosSWRFetcherProvider>
|
||||||
</AuthContextProvider>
|
</AuthContextProvider>
|
||||||
</PostcardContextProvider>
|
</PostcardContextProvider>
|
||||||
</AxiosSWRFetcherProvider>
|
</AxiosSWRFetcherProvider>
|
||||||
</PageErrorBoundary>
|
</ErrorBoundaryPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
36
pages/debug/error.tsx
Normal file
36
pages/debug/error.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { NextPage, NextPageContext } from "next";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||||
|
import { ErrorBlock, ErrorMain } from "../../components/generic/errors/renderers";
|
||||||
|
import { LoadingMain } from "../../components/generic/loading/renderers";
|
||||||
|
import { ViewNotice } from "../../components/generic/views/notice";
|
||||||
|
import { Postcard } from "../../components/postcard/changer";
|
||||||
|
import debugPostcard from "../../public/postcards/markus-spiske-iar-afB0QQw-unsplash.jpg"
|
||||||
|
|
||||||
|
|
||||||
|
export async function getStaticProps(context: NextPageContext) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...(await serverSideTranslations(context.locale ?? "it-IT", ["common"]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Page500: NextPage = (props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Postcard
|
||||||
|
src={debugPostcard}
|
||||||
|
/>
|
||||||
|
<ViewNotice
|
||||||
|
notice={<>
|
||||||
|
<ErrorMain error={new Error("Example")} text={t("debugError")} />
|
||||||
|
</>}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Page500
|
36
pages/debug/loading.tsx
Normal file
36
pages/debug/loading.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { NextPage, NextPageContext } from "next";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||||
|
import { ErrorBlock } from "../../components/generic/errors/renderers";
|
||||||
|
import { LoadingMain } from "../../components/generic/loading/renderers";
|
||||||
|
import { ViewNotice } from "../../components/generic/views/notice";
|
||||||
|
import { Postcard } from "../../components/postcard/changer";
|
||||||
|
import debugPostcard from "../../public/postcards/markus-spiske-iar-afB0QQw-unsplash.jpg"
|
||||||
|
|
||||||
|
|
||||||
|
export async function getStaticProps(context: NextPageContext) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...(await serverSideTranslations(context.locale ?? "it-IT", ["common"]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Page500: NextPage = (props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Postcard
|
||||||
|
src={debugPostcard}
|
||||||
|
/>
|
||||||
|
<ViewNotice
|
||||||
|
notice={<>
|
||||||
|
<LoadingMain text={t("debugLoading")} />
|
||||||
|
</>}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Page500
|
|
@ -19,8 +19,12 @@ import { ViewContent } from '../../components/generic/views/content'
|
||||||
import { useAxios } from '../../components/auth/requests'
|
import { useAxios } from '../../components/auth/requests'
|
||||||
import { faAsterisk } from '@fortawesome/free-solid-svg-icons'
|
import { faAsterisk } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { FestaIcon } from '../../components/generic/renderers/fontawesome'
|
import { FestaIcon } from '../../components/generic/renderers/fontawesome'
|
||||||
import { usePromise, UsePromiseStatus } from '../../components/generic/loading/promise'
|
import { promiseMultiplexer, usePromise, UsePromiseStatus } from '../../components/generic/loading/promise'
|
||||||
import { EditingContextProvider } from '../../components/generic/editable/provider'
|
import { EditingContextProvider } from '../../components/generic/editable/provider'
|
||||||
|
import { swrMultiplexer } from '../../components/generic/loading/swr'
|
||||||
|
import { LoadingMain, LoadingInline } from '../../components/generic/loading/renderers'
|
||||||
|
import { ViewNotice } from '../../components/generic/views/notice'
|
||||||
|
import { ErrorBlock } from '../../components/generic/errors/renderers'
|
||||||
|
|
||||||
|
|
||||||
export async function getServerSideProps(context: NextPageContext) {
|
export async function getServerSideProps(context: NextPageContext) {
|
||||||
|
@ -40,52 +44,75 @@ type PageEventProps = {
|
||||||
|
|
||||||
const PageEvent: NextPage<PageEventProps> = ({ slug }) => {
|
const PageEvent: NextPage<PageEventProps> = ({ slug }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { data, isValidating, mutate } = useSWR<Event>(`/api/events/${slug}`)
|
const swrHook = useSWR<Event>(`/api/events/${slug}`)
|
||||||
const [auth,] = useDefinedContext(AuthContext)
|
const [auth,] = useDefinedContext(AuthContext)
|
||||||
const axios = useAxios()
|
const axios = useAxios()
|
||||||
|
|
||||||
const save = useCallback(
|
const save = useCallback(
|
||||||
async () => {
|
async () => {
|
||||||
if (data === undefined) {
|
if (swrHook.data === undefined) {
|
||||||
console.warn("[PageEvent] Tried to save while no data was available.")
|
console.warn("[PageEvent] Tried to save while no data was available.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await axios.patch(`/api/events/${slug}`, data)
|
await axios.patch(`/api/events/${slug}`, swrHook.data)
|
||||||
mutate(data)
|
swrHook.mutate(swrHook.data)
|
||||||
console.debug("[PageEvent] Saved updated data successfully!")
|
console.debug("[PageEvent] Saved updated data successfully!")
|
||||||
},
|
},
|
||||||
[axios, data]
|
[axios, swrHook]
|
||||||
)
|
)
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Head>
|
<Head>
|
||||||
<title key="title">{data?.name ?? slug} - {t("siteTitle")}</title>
|
<title key="title">{swrHook.data?.name ?? slug} - {t("siteTitle")}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<Postcard
|
<Postcard
|
||||||
src={data?.postcard || defaultPostcard}
|
src={swrHook.data?.postcard || defaultPostcard}
|
||||||
/>
|
/>
|
||||||
<WIPBanner />
|
<WIPBanner />
|
||||||
<EditingContextProvider>
|
<EditingContextProvider>
|
||||||
<ViewContent
|
{swrMultiplexer({
|
||||||
title={
|
hook: swrHook,
|
||||||
<EditableText
|
loading: () => (
|
||||||
value={data?.name ?? slug}
|
<ViewNotice
|
||||||
onChange={e => mutate(async state => state ? { ...state, name: e.target.value } : undefined, { revalidate: false })}
|
notice={
|
||||||
placeholder={t("eventTitlePlaceholder")}
|
<LoadingMain text={t("eventLoading")} />
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
}
|
),
|
||||||
content={
|
ready: (data) => (
|
||||||
<EditableMarkdown
|
<ViewContent
|
||||||
value={data?.description ?? ""}
|
title={
|
||||||
onChange={e => mutate(async state => state ? { ...state, description: e.target.value } : undefined, { revalidate: false })}
|
<EditableText
|
||||||
placeholder={t("eventDescriptionPlaceholder")}
|
value={data?.name ?? slug}
|
||||||
|
onChange={e => swrHook.mutate(async state => state ? { ...state, name: e.target.value } : undefined, { revalidate: false })}
|
||||||
|
placeholder={t("eventTitlePlaceholder")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
content={
|
||||||
|
<EditableMarkdown
|
||||||
|
value={data?.description ?? ""}
|
||||||
|
onChange={e => swrHook.mutate(async state => state ? { ...state, description: e.target.value } : undefined, { revalidate: false })}
|
||||||
|
placeholder={t("eventDescriptionPlaceholder")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
}
|
),
|
||||||
/>
|
error: (error) => (
|
||||||
|
<ViewNotice
|
||||||
|
notice={
|
||||||
|
<ErrorBlock
|
||||||
|
text={t("eventError")}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
<ToolBar vertical="vadapt" horizontal="right">
|
<ToolBar vertical="vadapt" horizontal="right">
|
||||||
<ToolToggleVisibility />
|
<ToolToggleVisibility />
|
||||||
{data && auth?.userId === data?.creatorId &&
|
{swrHook.data && auth?.userId === swrHook.data?.creatorId &&
|
||||||
<ToolToggleEditing
|
<ToolToggleEditing
|
||||||
save={save}
|
save={save}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -19,5 +19,9 @@
|
||||||
"toolToggleEditingSave": "Salva modifiche",
|
"toolToggleEditingSave": "Salva modifiche",
|
||||||
"toolToggleEditingEdit": "Modifica",
|
"toolToggleEditingEdit": "Modifica",
|
||||||
"eventNamePlaceholder": "Nome evento",
|
"eventNamePlaceholder": "Nome evento",
|
||||||
"eventDescriptionPlaceholder": "Descrizione evento in **Markdown**"
|
"eventDescriptionPlaceholder": "Descrizione evento in **Markdown**",
|
||||||
|
"eventLoading": "Caricamento dei dettagli dell'evento in corso...",
|
||||||
|
"eventError": "Si è verificato il seguente errore nel recupero dei dettagli dell'evento:",
|
||||||
|
"debugLoading": "Questo è un esempio di caricamento full-page.",
|
||||||
|
"debugError": "Questo è un esempio di errore full-page."
|
||||||
}
|
}
|
BIN
public/postcards/markus-spiske-iar-afB0QQw-unsplash.jpg
(Stored with Git LFS)
Normal file
BIN
public/postcards/markus-spiske-iar-afB0QQw-unsplash.jpg
(Stored with Git LFS)
Normal file
Binary file not shown.
|
@ -1,6 +1,5 @@
|
||||||
@import "color-schemes.css";
|
@import "color-schemes.css";
|
||||||
@import "elements.css";
|
@import "elements.css";
|
||||||
@import "mood.css";
|
|
||||||
@import "flex.css";
|
@import "flex.css";
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue