mirror of
https://github.com/Steffo99/festa.git
synced 2024-12-22 22:54:22 +00:00
Rework PageEvent to not use editables
This commit is contained in:
parent
b94b28853e
commit
729f136eb7
4 changed files with 108 additions and 89 deletions
42
components/events/actions/edit.tsx
Normal file
42
components/events/actions/edit.tsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { Event } from "@prisma/client"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
import { KeyedMutator } from "swr"
|
||||||
|
import { ViewContent } from "../../generic/views/content"
|
||||||
|
|
||||||
|
|
||||||
|
export type EventsActionViewProps = {
|
||||||
|
data: Event,
|
||||||
|
mutate: KeyedMutator<Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const EventsActionEdit = ({ data, mutate }: EventsActionViewProps) => {
|
||||||
|
return (
|
||||||
|
<ViewContent
|
||||||
|
title={
|
||||||
|
useMemo(
|
||||||
|
() => (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={data.name}
|
||||||
|
onChange={e => mutate({ ...data, name: e.target.value }, { revalidate: false })}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[data.name]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
content={<>
|
||||||
|
{useMemo(
|
||||||
|
() => (
|
||||||
|
<textarea
|
||||||
|
rows={12}
|
||||||
|
value={data.description}
|
||||||
|
onChange={e => mutate({ ...data, description: e.target.value }, { revalidate: false })}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[data.description]
|
||||||
|
)}
|
||||||
|
</>}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
24
components/events/actions/view.tsx
Normal file
24
components/events/actions/view.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { Event } from "@prisma/client"
|
||||||
|
import { memo } from "react"
|
||||||
|
import { FestaMarkdownRenderer } from "../../generic/renderers/markdown"
|
||||||
|
import { ViewContent } from "../../generic/views/content"
|
||||||
|
|
||||||
|
|
||||||
|
export type EventsActionViewProps = {
|
||||||
|
data: Event,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const EventsActionView = memo(({ data }: EventsActionViewProps) => {
|
||||||
|
return (
|
||||||
|
<ViewContent
|
||||||
|
title={<div style={{ padding: "10px" }}>
|
||||||
|
{data.name}
|
||||||
|
</div>}
|
||||||
|
content={<div>
|
||||||
|
<FestaMarkdownRenderer code={data.description} />
|
||||||
|
</div>}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
EventsActionView.displayName = "EventsActionView"
|
|
@ -1,29 +1,27 @@
|
||||||
import { faAsterisk, faPencil, faSave } from "@fortawesome/free-solid-svg-icons"
|
import { faAsterisk, faPencil, faSave } from "@fortawesome/free-solid-svg-icons"
|
||||||
import { useTranslation } from "next-i18next"
|
import { useTranslation } from "next-i18next"
|
||||||
import { useDefinedContext } from "../../../utils/definedContext"
|
|
||||||
import { EditingContext, EditingMode } from "../../generic/editable/base"
|
|
||||||
import { usePromise, UsePromiseStatus } from "../../generic/loading/promise"
|
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"
|
import mood from "../../../styles/mood.module.css"
|
||||||
|
import { Dispatch } from "react"
|
||||||
|
|
||||||
|
|
||||||
export type ToolToggleEditingProps = {
|
export type ToolToggleEditingProps = {
|
||||||
|
editing: boolean,
|
||||||
|
setEditing: Dispatch<boolean>,
|
||||||
save: () => Promise<void>,
|
save: () => Promise<void>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ToolBar {@link Tool} which switches between {@link EditingMode}s of the surrounding context.
|
* ToolBar {@link Tool} which allows the user to start editing an event and then save their changes.
|
||||||
*
|
|
||||||
* It calls an async function to save data when switching from edit mode to view mode, preventing the user from switching back again until the data is saved, but allowing them to view the updated resource.
|
|
||||||
*/
|
*/
|
||||||
export function ToolToggleEditing(props: ToolToggleEditingProps) {
|
export function ToolToggleEditing({ editing, setEditing, save: upperSave }: ToolToggleEditingProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [editing, setEditing] = useDefinedContext(EditingContext)
|
|
||||||
|
|
||||||
const { run: save, status: saveStatus } = usePromise<void, void>(props.save)
|
const { run: save, status: saveStatus } = usePromise<void, void>(upperSave)
|
||||||
|
|
||||||
if (saveStatus === UsePromiseStatus.PENDING) {
|
if (saveStatus === UsePromiseStatus.PENDING) {
|
||||||
return (
|
return (
|
||||||
|
@ -36,13 +34,13 @@ export function ToolToggleEditing(props: ToolToggleEditingProps) {
|
||||||
</Tool>
|
</Tool>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else if (editing === EditingMode.EDIT) {
|
else if (editing) {
|
||||||
return (
|
return (
|
||||||
<Tool
|
<Tool
|
||||||
aria-label={t("toolToggleEditingSave")}
|
aria-label={t("toolToggleEditingSave")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
save()
|
save()
|
||||||
setEditing(EditingMode.VIEW)
|
setEditing(false)
|
||||||
}}
|
}}
|
||||||
className={mood.positive}
|
className={mood.positive}
|
||||||
>
|
>
|
||||||
|
@ -55,7 +53,7 @@ export function ToolToggleEditing(props: ToolToggleEditingProps) {
|
||||||
<Tool
|
<Tool
|
||||||
aria-label={t("toolToggleEditingEdit")}
|
aria-label={t("toolToggleEditingEdit")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditing(EditingMode.EDIT)
|
setEditing(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FestaIcon icon={faPencil} />
|
<FestaIcon icon={faPencil} />
|
||||||
|
|
|
@ -6,8 +6,6 @@ import defaultPostcard from "../../public/postcards/adi-goldstein-Hli3R6LKibo-un
|
||||||
import { Postcard } from '../../components/postcard/changer'
|
import { Postcard } from '../../components/postcard/changer'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { Event } from '@prisma/client'
|
import { Event } from '@prisma/client'
|
||||||
import { EditableMarkdown, EditableText } from '../../components/generic/editable/inputs'
|
|
||||||
import { EditingContext, EditingMode } from '../../components/generic/editable/base'
|
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { ToolBar } from '../../components/generic/toolbar/bar'
|
import { ToolBar } from '../../components/generic/toolbar/bar'
|
||||||
import { ToolToggleEditing } from '../../components/events/toolbar/toolToggleEditing'
|
import { ToolToggleEditing } from '../../components/events/toolbar/toolToggleEditing'
|
||||||
|
@ -15,17 +13,10 @@ import { ToolToggleVisibility } from '../../components/postcard/toolbar/toolTogg
|
||||||
import { WIPBanner } from '../../components/generic/wip/banner'
|
import { WIPBanner } from '../../components/generic/wip/banner'
|
||||||
import { AuthContext } from '../../components/auth/base'
|
import { AuthContext } from '../../components/auth/base'
|
||||||
import { useDefinedContext } from '../../utils/definedContext'
|
import { useDefinedContext } from '../../utils/definedContext'
|
||||||
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 { FestaIcon } from '../../components/generic/renderers/fontawesome'
|
|
||||||
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'
|
|
||||||
import { database } from '../../utils/prismaClient'
|
import { database } from '../../utils/prismaClient'
|
||||||
|
import { EventsActionEdit } from '../../components/events/actions/edit'
|
||||||
|
import { EventsActionView } from '../../components/events/actions/view'
|
||||||
|
|
||||||
|
|
||||||
export async function getServerSideProps(context: NextPageContext) {
|
export async function getServerSideProps(context: NextPageContext) {
|
||||||
|
@ -34,7 +25,7 @@ export async function getServerSideProps(context: NextPageContext) {
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
slug,
|
slug,
|
||||||
event: await database.event.findUnique({ where: { slug } }),
|
fallbackData: await database.event.findUnique({ where: { slug } }),
|
||||||
...(await serverSideTranslations(context.locale ?? "it-IT", ["common"]))
|
...(await serverSideTranslations(context.locale ?? "it-IT", ["common"]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,87 +34,51 @@ export async function getServerSideProps(context: NextPageContext) {
|
||||||
|
|
||||||
type PageEventProps = {
|
type PageEventProps = {
|
||||||
slug: string,
|
slug: string,
|
||||||
event: Event,
|
fallbackData: Event,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const PageEvent: NextPage<PageEventProps> = ({ slug, event }) => {
|
const PageEvent: NextPage<PageEventProps> = ({ slug, fallbackData }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const swrHook = useSWR<Event>(`/api/events/${slug}`, { fallback: event })
|
|
||||||
const [auth,] = useDefinedContext(AuthContext)
|
|
||||||
const axios = useAxios()
|
const axios = useAxios()
|
||||||
|
|
||||||
|
const { data, mutate } = useSWR<Event>(`/api/events/${slug}`, { fallbackData })
|
||||||
|
const [auth,] = useDefinedContext(AuthContext)
|
||||||
|
const [eventEditing, eventSetEditing] = useState<boolean>(false)
|
||||||
|
|
||||||
const save = useCallback(
|
const save = useCallback(
|
||||||
async () => {
|
async () => {
|
||||||
if (swrHook.data === undefined) {
|
const response = await axios.patch<Event>(`/api/events/${slug}`, data!)
|
||||||
console.warn("[PageEvent] Tried to save while no data was available.")
|
mutate(response.data, { revalidate: false })
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await axios.patch(`/api/events/${slug}`, swrHook.data)
|
|
||||||
swrHook.mutate(swrHook.data)
|
|
||||||
console.debug("[PageEvent] Saved updated data successfully!")
|
|
||||||
},
|
},
|
||||||
[axios, swrHook]
|
[axios, data]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const eventName = data?.name ?? slug
|
||||||
|
const eventPostcard = data?.postcard || defaultPostcard
|
||||||
|
const eventCanEdit = auth && data && auth.userId === data.creatorId
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Head>
|
<Head>
|
||||||
<title key="title">{swrHook.data?.name ?? slug} - {t("siteTitle")}</title>
|
<title key="title">{eventName} - {t("siteTitle")}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<Postcard
|
<Postcard src={eventPostcard} />
|
||||||
src={swrHook.data?.postcard || defaultPostcard}
|
|
||||||
/>
|
|
||||||
<WIPBanner />
|
<WIPBanner />
|
||||||
<EditingContextProvider>
|
<ToolBar vertical="vadapt" horizontal="right">
|
||||||
{swrMultiplexer({
|
<ToolToggleVisibility />
|
||||||
hook: swrHook,
|
{eventCanEdit &&
|
||||||
loading: () => (
|
<ToolToggleEditing
|
||||||
<ViewNotice
|
editing={eventEditing}
|
||||||
notice={
|
setEditing={eventSetEditing}
|
||||||
<LoadingMain text={t("eventLoading")} />
|
save={save}
|
||||||
}
|
/>
|
||||||
/>
|
}
|
||||||
),
|
</ToolBar>
|
||||||
ready: (data) => (
|
{eventEditing ?
|
||||||
<ViewContent
|
<EventsActionEdit data={data!} mutate={mutate} />
|
||||||
title={
|
:
|
||||||
<EditableText
|
<EventsActionView data={data!} />
|
||||||
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 />
|
|
||||||
{swrHook.data && auth?.userId === swrHook.data?.creatorId &&
|
|
||||||
<ToolToggleEditing
|
|
||||||
save={save}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</ToolBar>
|
|
||||||
</EditingContextProvider>
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue