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 { Tool } from "../../generic/toolbar/tool"
|
||||
import cursor from "../../../styles/cursor.module.css"
|
||||
import mood from "../../../styles/mood.module.css"
|
||||
|
||||
|
||||
export type ToolToggleEditingProps = {
|
||||
|
@ -43,7 +44,7 @@ export function ToolToggleEditing(props: ToolToggleEditingProps) {
|
|||
save()
|
||||
setEditing(EditingMode.VIEW)
|
||||
}}
|
||||
className={"positive"}
|
||||
className={mood.positive}
|
||||
>
|
||||
<FestaIcon icon={faSave} />
|
||||
</Tool>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Component, ErrorInfo, ReactNode } from "react";
|
||||
import { ViewNotice } from "../views/notice";
|
||||
import { ErrorBlock } from "./renderers";
|
||||
import { ErrorBlock, ErrorMain } from "./renderers";
|
||||
|
||||
|
||||
export type ErrorBoundaryProps = {
|
||||
|
@ -20,7 +20,7 @@ export type ErrorBoundaryState = {
|
|||
*
|
||||
* To be used in `pages/_app`.
|
||||
*/
|
||||
export class PageErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
export class ErrorBoundaryPage extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props)
|
||||
this.state = { error: undefined, errorInfo: undefined }
|
||||
|
@ -37,7 +37,7 @@ export class PageErrorBoundary extends Component<ErrorBoundaryProps, ErrorBounda
|
|||
return (
|
||||
<ViewNotice
|
||||
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.
|
||||
*/
|
||||
export class BlockErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
export class ErrorBoundaryBlock extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props)
|
||||
this.state = { error: undefined, errorInfo: undefined }
|
||||
|
|
|
@ -1,8 +1,20 @@
|
|||
.errorBlock > pre {
|
||||
.errorBlock > pre, .errorMain > pre {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
|
||||
max-width: 100%;
|
||||
|
||||
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 { default as classNames } from "classnames"
|
||||
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";
|
||||
|
||||
|
||||
export type ErrorTraceProps = {
|
||||
/**
|
||||
* Props of {@link ErrorTrace}.
|
||||
*/
|
||||
type ErrorTraceProps = ComponentPropsWithoutRef<"code"> & {
|
||||
/**
|
||||
* The error to render the stack trace of.
|
||||
*/
|
||||
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) {
|
||||
if (props.error.response) {
|
||||
if (props.error.response.data) {
|
||||
const json = JSON.stringify(props.error.response.data, undefined, props.inline ? undefined : 4).replaceAll("\\n", "\n")
|
||||
/**
|
||||
* Component rendering the details of an {@link Error}.
|
||||
*
|
||||
* Not to use by itself; should be used to display the error in other error renderers.
|
||||
*/
|
||||
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 (
|
||||
<code>
|
||||
<b>API {props.error.response.status}</b>
|
||||
:
|
||||
{json}
|
||||
<code {...props}>
|
||||
<span>
|
||||
<b>API {error.response.status}</b>
|
||||
:
|
||||
</span>
|
||||
<span>
|
||||
{json}
|
||||
</span>
|
||||
</code>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<code>
|
||||
<b>HTTP {props.error.response.status}</b>
|
||||
:
|
||||
{props.error.message}
|
||||
<code {...props}>
|
||||
<span>
|
||||
<b>HTTP {error.response.status}</b>
|
||||
:
|
||||
</span>
|
||||
<span>
|
||||
{error.message}
|
||||
</span>
|
||||
</code>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<code>
|
||||
<b>{props.error.code}</b>
|
||||
:
|
||||
{props.error.message}
|
||||
<code {...props}>
|
||||
<span>
|
||||
<b>{error.code}</b>
|
||||
:
|
||||
</span>
|
||||
<span>
|
||||
{error.message}
|
||||
</span>
|
||||
</code>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<code>
|
||||
<b>{props.error.name}</b>
|
||||
:
|
||||
{props.error.message}
|
||||
<code {...props}>
|
||||
<span>
|
||||
<b>{error.name}</b>
|
||||
:
|
||||
</span>
|
||||
<span>
|
||||
{error.message}
|
||||
</span>
|
||||
</code>
|
||||
)
|
||||
})
|
||||
ErrorTrace.displayName = "ErrorTrace"
|
||||
|
||||
|
||||
export type ErrorInlineProps = {
|
||||
/**
|
||||
* Props for "error" renderers.
|
||||
*/
|
||||
export type ErrorProps = {
|
||||
error: Error,
|
||||
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 (
|
||||
<span className={classNames("negative", style.error, style.errorInline)}>
|
||||
<FestaIcon icon={faCircleExclamation} />
|
||||
|
||||
{props.text ?
|
||||
<>
|
||||
<span>
|
||||
{props.text}
|
||||
</span>
|
||||
|
||||
</>
|
||||
: null}
|
||||
<ErrorTrace error={props.error} inline={true} />
|
||||
<span className={classNames(mood.negative, style.error, style.errorInline)}>
|
||||
<FestaIcon icon={faCircleExclamation} className={style.errorIcon} />
|
||||
{!!text && <>
|
||||
|
||||
<span className={style.errorText}>
|
||||
{text}
|
||||
</span>
|
||||
|
||||
</>}
|
||||
<ErrorTrace error={error} prettify={false} className={style.errorTrace} />
|
||||
</span>
|
||||
)
|
||||
})
|
||||
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 (
|
||||
<div className={classNames("negative", style.error, style.errorBlock)}>
|
||||
<div className={classNames(mood.negative, style.error, style.errorBlock)}>
|
||||
<p>
|
||||
<FestaIcon icon={faCircleExclamation} />
|
||||
<FestaIcon icon={faCircleExclamation} className={style.errorIcon} />
|
||||
|
||||
<span>
|
||||
{props.text}
|
||||
<span className={style.errorText}>
|
||||
{text}
|
||||
</span>
|
||||
</p>
|
||||
<pre>
|
||||
<ErrorTrace error={props.error} inline={false} />
|
||||
<ErrorTrace error={error} prettify={false} className={style.errorTrace} />
|
||||
</pre>
|
||||
</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;
|
||||
flex-direction: column;
|
||||
|
||||
|
|
|
@ -10,9 +10,10 @@ import { useAxiosRequest } from "../../auth/requests"
|
|||
import { ErrorBlock } from "../../generic/errors/renderers"
|
||||
import { promiseMultiplexer } from "../../generic/loading/promise"
|
||||
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 style from "./events.module.css"
|
||||
import mood from "../../../styles/mood.module.css"
|
||||
|
||||
|
||||
/**
|
||||
|
@ -88,7 +89,7 @@ const LandingActionEventsFormCreate = () => {
|
|||
<button
|
||||
aria-label={t("landingEventsCreateSubmitLabel")}
|
||||
disabled={!name}
|
||||
className={classNames(style.landingActionEventsFormCreateSubmit, "positive")}
|
||||
className={classNames(style.landingActionEventsFormCreateSubmit, mood.positive)}
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
run({ data: { name } })
|
||||
|
@ -100,7 +101,7 @@ const LandingActionEventsFormCreate = () => {
|
|||
),
|
||||
pending: ({ }) => (
|
||||
<p>
|
||||
<LoadingTextInline text={t("landingEventsCreatePending")} />
|
||||
<LoadingInline text={t("landingEventsCreatePending")} />
|
||||
</p>
|
||||
),
|
||||
rejected: ({ error }) => (
|
||||
|
@ -111,7 +112,7 @@ const LandingActionEventsFormCreate = () => {
|
|||
fulfilled: ({ result }) => {
|
||||
return (
|
||||
<p>
|
||||
<LoadingTextInline text={t("landingEventsCreateFulfilled", name)} />
|
||||
<LoadingInline text={t("landingEventsCreateFulfilled", name)} />
|
||||
</p>
|
||||
)
|
||||
},
|
||||
|
@ -127,7 +128,7 @@ export const LandingActionEvents = () => {
|
|||
hook: apiHook,
|
||||
loading: () => (
|
||||
<p>
|
||||
<LoadingTextInline text={t("landingEventsLoading")} />
|
||||
<LoadingInline text={t("landingEventsLoading")} />
|
||||
</p>
|
||||
),
|
||||
ready: (data) => (<>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useAxiosRequest } from "../../auth/requests"
|
|||
import { TelegramLoginButton } from "../../auth/telegram/loginButton"
|
||||
import { ErrorBlock } from "../../generic/errors/renderers"
|
||||
import { promiseMultiplexer } from "../../generic/loading/promise"
|
||||
import { LoadingTextInline } from "../../generic/loading/textInline"
|
||||
import { LoadingInline } from "../../generic/loading/renderers"
|
||||
import { useDefinedContext } from "../../../utils/definedContext"
|
||||
import { AuthContext } from "../../auth/base"
|
||||
import { useRouter } from "next/router"
|
||||
|
@ -32,7 +32,7 @@ export const LandingActionLogin = () => {
|
|||
</>,
|
||||
pending: ({ }) => (
|
||||
<p>
|
||||
<LoadingTextInline text={t("landingLoginTelegramPending")} />
|
||||
<LoadingInline text={t("landingLoginTelegramPending")} />
|
||||
</p>
|
||||
),
|
||||
fulfilled: ({ result }) => {
|
||||
|
@ -40,7 +40,7 @@ export const LandingActionLogin = () => {
|
|||
|
||||
return (
|
||||
<p>
|
||||
<LoadingTextInline text={t("landingLoginTelegramFulfilled")} />
|
||||
<LoadingInline text={t("landingLoginTelegramFulfilled")} />
|
||||
</p>
|
||||
)
|
||||
},
|
||||
|
|
|
@ -3,12 +3,13 @@ import '@fortawesome/fontawesome-svg-core/styles.css'
|
|||
import { AppProps } from 'next/app'
|
||||
import { appWithTranslation, useTranslation } from 'next-i18next'
|
||||
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 { PostcardRenderer } from '../components/postcard/renderer'
|
||||
import { config as fontAwesomeConfig } from '@fortawesome/fontawesome-svg-core'
|
||||
import { PostcardContextProvider } from '../components/postcard/provider'
|
||||
import defaultPostcard from "../public/postcards/adi-goldstein-Hli3R6LKibo-unsplash.jpg"
|
||||
import { LoadingBoundaryPage } from '../components/generic/loading/boundaries'
|
||||
|
||||
|
||||
fontAwesomeConfig.autoAddCss = false
|
||||
|
@ -18,18 +19,22 @@ const App = ({ Component, pageProps }: AppProps): JSX.Element => {
|
|||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<PageErrorBoundary text={t("genericError")}>
|
||||
<ErrorBoundaryPage text={t("genericError")}>
|
||||
<AxiosSWRFetcherProvider>
|
||||
<PostcardContextProvider defaultPostcard={defaultPostcard}>
|
||||
<AuthContextProvider storageKey="auth">
|
||||
<AxiosSWRFetcherProvider>
|
||||
<PostcardRenderer />
|
||||
<Component {...pageProps} />
|
||||
<ErrorBoundaryPage text={t("genericError")}>
|
||||
<LoadingBoundaryPage>
|
||||
<Component {...pageProps} />
|
||||
</LoadingBoundaryPage>
|
||||
</ErrorBoundaryPage>
|
||||
</AxiosSWRFetcherProvider>
|
||||
</AuthContextProvider>
|
||||
</PostcardContextProvider>
|
||||
</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 { faAsterisk } from '@fortawesome/free-solid-svg-icons'
|
||||
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 { 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) {
|
||||
|
@ -40,52 +44,75 @@ type PageEventProps = {
|
|||
|
||||
const PageEvent: NextPage<PageEventProps> = ({ slug }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data, isValidating, mutate } = useSWR<Event>(`/api/events/${slug}`)
|
||||
const swrHook = useSWR<Event>(`/api/events/${slug}`)
|
||||
const [auth,] = useDefinedContext(AuthContext)
|
||||
const axios = useAxios()
|
||||
|
||||
const save = useCallback(
|
||||
async () => {
|
||||
if (data === undefined) {
|
||||
if (swrHook.data === undefined) {
|
||||
console.warn("[PageEvent] Tried to save while no data was available.")
|
||||
return
|
||||
}
|
||||
|
||||
await axios.patch(`/api/events/${slug}`, data)
|
||||
mutate(data)
|
||||
await axios.patch(`/api/events/${slug}`, swrHook.data)
|
||||
swrHook.mutate(swrHook.data)
|
||||
console.debug("[PageEvent] Saved updated data successfully!")
|
||||
},
|
||||
[axios, data]
|
||||
[axios, swrHook]
|
||||
)
|
||||
|
||||
return <>
|
||||
<Head>
|
||||
<title key="title">{data?.name ?? slug} - {t("siteTitle")}</title>
|
||||
<title key="title">{swrHook.data?.name ?? slug} - {t("siteTitle")}</title>
|
||||
</Head>
|
||||
<Postcard
|
||||
src={data?.postcard || defaultPostcard}
|
||||
src={swrHook.data?.postcard || defaultPostcard}
|
||||
/>
|
||||
<WIPBanner />
|
||||
<EditingContextProvider>
|
||||
<ViewContent
|
||||
title={
|
||||
<EditableText
|
||||
value={data?.name ?? slug}
|
||||
onChange={e => mutate(async state => state ? { ...state, name: e.target.value } : undefined, { revalidate: false })}
|
||||
placeholder={t("eventTitlePlaceholder")}
|
||||
{swrMultiplexer({
|
||||
hook: swrHook,
|
||||
loading: () => (
|
||||
<ViewNotice
|
||||
notice={
|
||||
<LoadingMain text={t("eventLoading")} />
|
||||
}
|
||||
/>
|
||||
}
|
||||
content={
|
||||
<EditableMarkdown
|
||||
value={data?.description ?? ""}
|
||||
onChange={e => mutate(async state => state ? { ...state, description: e.target.value } : undefined, { revalidate: false })}
|
||||
placeholder={t("eventDescriptionPlaceholder")}
|
||||
),
|
||||
ready: (data) => (
|
||||
<ViewContent
|
||||
title={
|
||||
<EditableText
|
||||
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">
|
||||
<ToolToggleVisibility />
|
||||
{data && auth?.userId === data?.creatorId &&
|
||||
{swrHook.data && auth?.userId === swrHook.data?.creatorId &&
|
||||
<ToolToggleEditing
|
||||
save={save}
|
||||
/>
|
||||
|
|
|
@ -19,5 +19,9 @@
|
|||
"toolToggleEditingSave": "Salva modifiche",
|
||||
"toolToggleEditingEdit": "Modifica",
|
||||
"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 "elements.css";
|
||||
@import "mood.css";
|
||||
@import "flex.css";
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue