From a6f285576fda4118cd0bfce704a803b7d3f26f6b Mon Sep 17 00:00:00 2001 From: cade Date: Mon, 26 Aug 2024 20:52:01 -0600 Subject: [PATCH] add session templates, various ui/ux improvements --- app/(pages)/(with-nav)/inputs/page.tsx | 4 +- .../(with-nav)/notifications/[tab]/page.tsx | 2 - .../(with-nav)/subjects/(list)/page.tsx | 2 - .../(with-nav)/subjects/[subjectId]/page.tsx | 3 - app/(pages)/(with-nav)/templates/layout.tsx | 30 +- app/(pages)/(with-nav)/templates/page.tsx | 6 +- .../(layout)/account/(layout)/[tab]/page.tsx | 2 - .../@modal/(layout)/inputs/[inputId]/page.tsx | 3 - .../create/from-input/[inputId]/page.tsx | 3 - .../@modal/(layout)/inputs/create/page.tsx | 3 - .../[subjectId]/events/[eventId]/page.tsx | 3 - .../[subjectId]/insights/[insightId]/page.tsx | 5 - .../share/[subjectId]/insights/page.tsx | 3 - .../[missionId]/sessions/[sessionId]/page.tsx | 5 - .../[missionId]/sessions/page.tsx | 3 - .../subjects/[subjectId]/edit/page.tsx | 3 - .../event-types/[eventTypeId]/edit/page.tsx | 8 +- .../event-types/[eventTypeId]/page.tsx | 3 - .../[subjectId]/event-types/create/page.tsx | 8 +- .../[subjectId]/events/[eventId]/page.tsx | 3 - .../insights/[insightId]/edit/page.tsx | 5 - .../[subjectId]/insights/[insightId]/page.tsx | 5 - .../[subjectId]/insights/create/page.tsx | 5 - .../subjects/[subjectId]/insights/page.tsx | 3 - .../training-plans/[missionId]/edit/page.tsx | 7 +- .../sessions/[sessionId]/edit/page.tsx | 32 +- .../[missionId]/sessions/[sessionId]/page.tsx | 5 - .../[order]/from-session/[sessionId]/page.tsx | 32 +- .../sessions/create/[order]/page.tsx | 28 +- .../[missionId]/sessions/page.tsx | 5 - .../training-plans/create/page.tsx | 11 +- .../@modal/(layout)/subjects/create/page.tsx | 3 - .../event-types/[templateId]/page.tsx | 5 +- .../from-event-type/[eventTypeId]/page.tsx | 6 +- .../templates/event-types/create/page.tsx | 5 - .../templates/modules/[templateId]/page.tsx | 5 +- .../templates/modules/create/page.tsx | 5 - .../sessions/[templateId]/loading.tsx | 5 + .../templates/sessions/[templateId]/page.tsx | 46 +++ .../from-session/[sessionId]/loading.tsx | 5 + .../create/from-session/[sessionId]/page.tsx | 57 +++ .../templates/sessions/create/loading.tsx | 5 + .../templates/sessions/create/page.tsx | 34 ++ app/(pages)/@modal/(layout)/upgrade/page.tsx | 3 - app/(pages)/share/[subjectId]/page.tsx | 3 - app/_components/account-menu.tsx | 2 +- app/_components/button.tsx | 6 +- app/_components/calendar.tsx | 2 +- app/_components/dropdown-menu.tsx | 2 +- app/_components/event-comment.tsx | 10 +- app/_components/event-menu.tsx | 6 +- app/_components/event-type-form.tsx | 84 +++- app/_components/event-type-menu.tsx | 213 ++-------- app/_components/event-type-template-form.tsx | 32 +- app/_components/event-types.tsx | 5 +- app/_components/filterable-inputs.tsx | 5 +- app/_components/filterable-templates.tsx | 5 +- app/_components/input-form.tsx | 21 +- app/_components/input-menu.tsx | 6 +- app/_components/input.tsx | 9 +- app/_components/insight-menu.tsx | 6 +- app/_components/insight.tsx | 2 +- app/_components/insights-page.tsx | 2 +- app/_components/module-card.tsx | 2 +- app/_components/module-form-section.tsx | 43 +- app/_components/module-template-form.tsx | 30 +- app/_components/notifications.tsx | 6 +- app/_components/popover.tsx | 2 +- app/_components/select.tsx | 14 +- app/_components/session-form.tsx | 383 ++++++++++-------- app/_components/session-layout.tsx | 107 ----- app/_components/session-menu.tsx | 144 +++---- app/_components/session-page.tsx | 153 ++++--- app/_components/session-template-form.tsx | 178 ++++++++ app/_components/sessions-page.tsx | 2 +- .../subject-events-date-filter.tsx | 2 +- app/_components/subject-form.tsx | 22 +- app/_components/subject-list.tsx | 7 +- app/_components/subject-loading.tsx | 4 +- app/_components/subject-menu.tsx | 18 +- app/_components/subject-page.tsx | 14 +- app/_components/switch.tsx | 2 +- app/_components/template-menu.tsx | 6 +- app/_components/timeline-event-card.tsx | 2 +- app/_components/timeline-session-card.tsx | 6 +- app/_components/tip.tsx | 6 +- app/_components/training-plan-menu.tsx | 14 +- .../training-plan-use-template-modal.tsx | 51 +++ app/_components/training-plans.tsx | 5 +- app/_mutations/upsert-event-type-template.ts | 34 ++ ...-template.ts => upsert-module-template.ts} | 12 +- app/_mutations/upsert-session-template.ts | 36 ++ app/_queries/list-templates-with-data.ts | 5 +- app/_types/event-type-template-data-json.ts | 4 + ...a-json.ts => module-template-data-json.ts} | 2 +- app/_types/session-template-data-json.ts | 7 + app/_utilities/format-title.ts | 8 - app/_utilities/get-form-cache-key.ts | 12 +- tailwind.css | 8 +- 99 files changed, 1162 insertions(+), 1034 deletions(-) create mode 100644 app/(pages)/@modal/(layout)/templates/sessions/[templateId]/loading.tsx create mode 100644 app/(pages)/@modal/(layout)/templates/sessions/[templateId]/page.tsx create mode 100644 app/(pages)/@modal/(layout)/templates/sessions/create/from-session/[sessionId]/loading.tsx create mode 100644 app/(pages)/@modal/(layout)/templates/sessions/create/from-session/[sessionId]/page.tsx create mode 100644 app/(pages)/@modal/(layout)/templates/sessions/create/loading.tsx create mode 100644 app/(pages)/@modal/(layout)/templates/sessions/create/page.tsx delete mode 100644 app/_components/session-layout.tsx create mode 100644 app/_components/session-template-form.tsx create mode 100644 app/_components/training-plan-use-template-modal.tsx create mode 100644 app/_mutations/upsert-event-type-template.ts rename app/_mutations/{upsert-template.ts => upsert-module-template.ts} (75%) create mode 100644 app/_mutations/upsert-session-template.ts create mode 100644 app/_types/event-type-template-data-json.ts rename app/_types/{template-data-json.ts => module-template-data-json.ts} (57%) create mode 100644 app/_types/session-template-data-json.ts delete mode 100644 app/_utilities/format-title.ts diff --git a/app/(pages)/(with-nav)/inputs/page.tsx b/app/(pages)/(with-nav)/inputs/page.tsx index 43170e89..c5da86ff 100644 --- a/app/(pages)/(with-nav)/inputs/page.tsx +++ b/app/(pages)/(with-nav)/inputs/page.tsx @@ -1,11 +1,9 @@ import Empty from '@/_components/empty'; import FilterableInputs from '@/_components/filterable-inputs'; import listInputs from '@/_queries/list-inputs'; -import InformationCircleIcon from '@heroicons/react/24/outline/ExclamationCircleIcon'; +import InformationCircleIcon from '@heroicons/react/24/outline/InformationCircleIcon'; import { sortBy } from 'lodash'; -export const metadata = { title: 'Inputs' }; - const Page = async () => { const { data: inputs } = await listInputs(); diff --git a/app/(pages)/(with-nav)/notifications/[tab]/page.tsx b/app/(pages)/(with-nav)/notifications/[tab]/page.tsx index 3082a16a..85efddf5 100644 --- a/app/(pages)/(with-nav)/notifications/[tab]/page.tsx +++ b/app/(pages)/(with-nav)/notifications/[tab]/page.tsx @@ -7,8 +7,6 @@ interface PageProps { }; } -export const metadata = { title: 'Notifications' }; - const Page = async ({ params: { tab } }: PageProps) => { if (!['archive', 'inbox'].includes(tab)) return null; diff --git a/app/(pages)/(with-nav)/subjects/(list)/page.tsx b/app/(pages)/(with-nav)/subjects/(list)/page.tsx index 108552ea..cae87c3a 100644 --- a/app/(pages)/(with-nav)/subjects/(list)/page.tsx +++ b/app/(pages)/(with-nav)/subjects/(list)/page.tsx @@ -8,8 +8,6 @@ import listSubjects, { ListSubjectsData } from '@/_queries/list-subjects'; import InformationCircleIcon from '@heroicons/react/24/outline/InformationCircleIcon'; import PlusIcon from '@heroicons/react/24/outline/PlusIcon'; -export const metadata = { title: 'Subjects' }; - const Page = async () => { const [{ data: subjects }, user] = await Promise.all([ listSubjects(), diff --git a/app/(pages)/(with-nav)/subjects/[subjectId]/page.tsx b/app/(pages)/(with-nav)/subjects/[subjectId]/page.tsx index b4b0888a..763a5b03 100644 --- a/app/(pages)/(with-nav)/subjects/[subjectId]/page.tsx +++ b/app/(pages)/(with-nav)/subjects/[subjectId]/page.tsx @@ -1,13 +1,10 @@ import SubjectPage from '@/_components/subject-page'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { subjectId: string }; searchParams: { from?: string; limit?: string; to?: string }; } -export const metadata = { title: formatTitle(['Subjects', 'Events']) }; - const Page = ({ params: { subjectId }, searchParams: { from, limit, to }, diff --git a/app/(pages)/(with-nav)/templates/layout.tsx b/app/(pages)/(with-nav)/templates/layout.tsx index bbeaea68..74e37768 100644 --- a/app/(pages)/(with-nav)/templates/layout.tsx +++ b/app/(pages)/(with-nav)/templates/layout.tsx @@ -16,26 +16,34 @@ const Layout = ({ children }: LayoutProps) => ( - Event type template +
+ Event type template +
+
+ + +
+ Training plan template +
+
+ + +
+ Session template +
- {/**/} - {/* */} - {/* Training plan template*/} - {/**/} - {/**/} - {/* */} - {/* Session template*/} - {/**/} - Module template +
+ Module template +
diff --git a/app/(pages)/(with-nav)/templates/page.tsx b/app/(pages)/(with-nav)/templates/page.tsx index 07bf14d7..3c560dd8 100644 --- a/app/(pages)/(with-nav)/templates/page.tsx +++ b/app/(pages)/(with-nav)/templates/page.tsx @@ -1,9 +1,7 @@ import Empty from '@/_components/empty'; import FilterableTemplates from '@/_components/filterable-templates'; import listTemplates from '@/_queries/list-templates'; -import InformationCircleIcon from '@heroicons/react/24/outline/ExclamationCircleIcon'; - -export const metadata = { title: 'Templates' }; +import InformationCircleIcon from '@heroicons/react/24/outline/InformationCircleIcon'; const Page = async () => { const { data: templates } = await listTemplates(); @@ -14,7 +12,7 @@ const Page = async () => { Templates define reusable content for
- event types and session modules. + event types and training plans. ); } diff --git a/app/(pages)/@modal/(layout)/account/(layout)/[tab]/page.tsx b/app/(pages)/@modal/(layout)/account/(layout)/[tab]/page.tsx index b7ec7024..557bcc65 100644 --- a/app/(pages)/@modal/(layout)/account/(layout)/[tab]/page.tsx +++ b/app/(pages)/@modal/(layout)/account/(layout)/[tab]/page.tsx @@ -9,8 +9,6 @@ interface PageProps { }; } -export const metadata = { title: 'Account' }; - const Page = async ({ params: { tab } }: PageProps) => { const user = await getCurrentUser(); if (!user || !['email', 'password', 'profile'].includes(tab)) return null; diff --git a/app/(pages)/@modal/(layout)/inputs/[inputId]/page.tsx b/app/(pages)/@modal/(layout)/inputs/[inputId]/page.tsx index 9d6e3a78..58084191 100644 --- a/app/(pages)/@modal/(layout)/inputs/[inputId]/page.tsx +++ b/app/(pages)/@modal/(layout)/inputs/[inputId]/page.tsx @@ -3,7 +3,6 @@ import * as Modal from '@/_components/modal'; import PageModalHeader from '@/_components/page-modal-header'; import getInput from '@/_queries/get-input'; import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -11,8 +10,6 @@ interface PageProps { }; } -export const metadata = { title: formatTitle(['Inputs', 'Edit']) }; - const Page = async ({ params: { inputId } }: PageProps) => { const [{ data: input }, { data: subjects }] = await Promise.all([ getInput(inputId), diff --git a/app/(pages)/@modal/(layout)/inputs/create/from-input/[inputId]/page.tsx b/app/(pages)/@modal/(layout)/inputs/create/from-input/[inputId]/page.tsx index e35d5d56..3a870873 100644 --- a/app/(pages)/@modal/(layout)/inputs/create/from-input/[inputId]/page.tsx +++ b/app/(pages)/@modal/(layout)/inputs/create/from-input/[inputId]/page.tsx @@ -3,7 +3,6 @@ import * as Modal from '@/_components/modal'; import PageModalHeader from '@/_components/page-modal-header'; import getInput from '@/_queries/get-input'; import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -11,8 +10,6 @@ interface PageProps { }; } -export const metadata = { title: formatTitle(['Inputs', 'New']) }; - const Page = async ({ params: { inputId } }: PageProps) => { const [{ data: subjects }, { data: input }] = await Promise.all([ listSubjectsByTeamId(), diff --git a/app/(pages)/@modal/(layout)/inputs/create/page.tsx b/app/(pages)/@modal/(layout)/inputs/create/page.tsx index 65591f90..e7f0f7fe 100644 --- a/app/(pages)/@modal/(layout)/inputs/create/page.tsx +++ b/app/(pages)/@modal/(layout)/inputs/create/page.tsx @@ -2,9 +2,6 @@ import InputForm from '@/_components/input-form'; import * as Modal from '@/_components/modal'; import PageModalHeader from '@/_components/page-modal-header'; import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; -import formatTitle from '@/_utilities/format-title'; - -export const metadata = { title: formatTitle(['Inputs', 'New']) }; const Page = async () => { const { data: subjects } = await listSubjectsByTeamId(); diff --git a/app/(pages)/@modal/(layout)/share/[subjectId]/events/[eventId]/page.tsx b/app/(pages)/@modal/(layout)/share/[subjectId]/events/[eventId]/page.tsx index fc01d917..a846cdfc 100644 --- a/app/(pages)/@modal/(layout)/share/[subjectId]/events/[eventId]/page.tsx +++ b/app/(pages)/@modal/(layout)/share/[subjectId]/events/[eventId]/page.tsx @@ -1,5 +1,4 @@ import EventPage from '@/_components/event-page'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -8,8 +7,6 @@ interface PageProps { }; } -export const metadata = { title: formatTitle(['Subjects', 'Event']) }; - const Page = async ({ params: { eventId, subjectId } }: PageProps) => ( ); diff --git a/app/(pages)/@modal/(layout)/share/[subjectId]/insights/[insightId]/page.tsx b/app/(pages)/@modal/(layout)/share/[subjectId]/insights/[insightId]/page.tsx index 77d24dcc..458258d8 100644 --- a/app/(pages)/@modal/(layout)/share/[subjectId]/insights/[insightId]/page.tsx +++ b/app/(pages)/@modal/(layout)/share/[subjectId]/insights/[insightId]/page.tsx @@ -1,15 +1,10 @@ import InsightPage from '@/_components/insight-page'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { insightId: string; subjectId: string }; searchParams: { from?: string; to?: string }; } -export const metadata = { - title: formatTitle(['Subjects', 'Insight']), -}; - const Page = async ({ params: { insightId, subjectId }, searchParams: { from, to }, diff --git a/app/(pages)/@modal/(layout)/share/[subjectId]/insights/page.tsx b/app/(pages)/@modal/(layout)/share/[subjectId]/insights/page.tsx index 4426ac96..eb4b5fbd 100644 --- a/app/(pages)/@modal/(layout)/share/[subjectId]/insights/page.tsx +++ b/app/(pages)/@modal/(layout)/share/[subjectId]/insights/page.tsx @@ -1,13 +1,10 @@ import InsightsPage from '@/_components/insights-page'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { subjectId: string }; searchParams: { foo?: string; from?: string; to?: string }; } -export const metadata = { title: formatTitle(['Subjects', 'Insights']) }; - const Page = async ({ params: { subjectId }, searchParams }: PageProps) => ( ); diff --git a/app/(pages)/@modal/(layout)/share/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/page.tsx b/app/(pages)/@modal/(layout)/share/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/page.tsx index faac828f..be893f1c 100644 --- a/app/(pages)/@modal/(layout)/share/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/page.tsx +++ b/app/(pages)/@modal/(layout)/share/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/page.tsx @@ -1,5 +1,4 @@ import SessionPage from '@/_components/session-page'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -9,10 +8,6 @@ interface PageProps { }; } -export const metadata = { - title: formatTitle(['Subjects', 'Training plans', 'Sessions']), -}; - const Page = async ({ params: { missionId, sessionId, subjectId }, }: PageProps) => ( diff --git a/app/(pages)/@modal/(layout)/share/[subjectId]/training-plans/[missionId]/sessions/page.tsx b/app/(pages)/@modal/(layout)/share/[subjectId]/training-plans/[missionId]/sessions/page.tsx index 1732723a..2923580b 100644 --- a/app/(pages)/@modal/(layout)/share/[subjectId]/training-plans/[missionId]/sessions/page.tsx +++ b/app/(pages)/@modal/(layout)/share/[subjectId]/training-plans/[missionId]/sessions/page.tsx @@ -1,5 +1,4 @@ import SessionsPage from '@/_components/sessions-page'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -8,8 +7,6 @@ interface PageProps { }; } -export const metadata = { title: formatTitle(['Subjects', 'Training plans']) }; - const Page = ({ params: { missionId, subjectId } }: PageProps) => ( ); diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/edit/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/edit/page.tsx index cf5ec70b..e051a2ea 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/edit/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/edit/page.tsx @@ -2,7 +2,6 @@ import * as Modal from '@/_components/modal'; import PageModalHeader from '@/_components/page-modal-header'; import SubjectForm from '@/_components/subject-form'; import getSubject from '@/_queries/get-subject'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -10,8 +9,6 @@ interface PageProps { }; } -export const metadata = { title: formatTitle(['Subjects', 'Edit']) }; - const Page = async ({ params: { subjectId } }: PageProps) => { const { data: subject } = await getSubject(subjectId); if (!subject) return null; diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/event-types/[eventTypeId]/edit/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/event-types/[eventTypeId]/edit/page.tsx index a3b79d73..a1bb1bfc 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/event-types/[eventTypeId]/edit/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/event-types/[eventTypeId]/edit/page.tsx @@ -1,10 +1,10 @@ import EventTypeForm from '@/_components/event-type-form'; +import TemplateType from '@/_constants/enum-template-type'; import getEventTypeWithInputs from '@/_queries/get-event-type-with-inputs'; import getSubject from '@/_queries/get-subject'; import listInputsBySubjectId from '@/_queries/list-inputs-by-subject-id'; import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; import listTemplatesWithData from '@/_queries/list-templates-with-data'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -13,10 +13,6 @@ interface PageProps { }; } -export const metadata = { - title: formatTitle(['Subjects', 'Event types', 'Edit']), -}; - const Page = async ({ params: { eventTypeId, subjectId } }: PageProps) => { const [ { data: subject }, @@ -29,7 +25,7 @@ const Page = async ({ params: { eventTypeId, subjectId } }: PageProps) => { getEventTypeWithInputs(eventTypeId), listInputsBySubjectId(subjectId), listSubjectsByTeamId(), - listTemplatesWithData(), + listTemplatesWithData({ type: TemplateType.EventType }), ]); if ( diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/event-types/[eventTypeId]/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/event-types/[eventTypeId]/page.tsx index 9779f91f..ecf0aa98 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/event-types/[eventTypeId]/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/event-types/[eventTypeId]/page.tsx @@ -5,7 +5,6 @@ import PageModalHeader from '@/_components/page-modal-header'; import getCurrentUser from '@/_queries/get-current-user'; import getEventTypeWithInputsAndOptions from '@/_queries/get-event-type-with-inputs-and-options'; import getSubject from '@/_queries/get-subject'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -14,8 +13,6 @@ interface PageProps { }; } -export const metadata = { title: formatTitle(['Subjects', 'Event']) }; - const Page = async ({ params: { eventTypeId, subjectId } }: PageProps) => { const [{ data: subject }, { data: eventType }, user] = await Promise.all([ getSubject(subjectId), diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/event-types/create/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/event-types/create/page.tsx index 2ee1bc8a..33d10e12 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/event-types/create/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/event-types/create/page.tsx @@ -1,9 +1,9 @@ import EventTypeForm from '@/_components/event-type-form'; +import TemplateType from '@/_constants/enum-template-type'; import getSubject from '@/_queries/get-subject'; import listInputsBySubjectId from '@/_queries/list-inputs-by-subject-id'; import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; import listTemplatesWithData from '@/_queries/list-templates-with-data'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -11,10 +11,6 @@ interface PageProps { }; } -export const metadata = { - title: formatTitle(['Subjects', 'Event types', 'New']), -}; - const Page = async ({ params: { subjectId } }: PageProps) => { const [ { data: subject }, @@ -25,7 +21,7 @@ const Page = async ({ params: { subjectId } }: PageProps) => { getSubject(subjectId), listInputsBySubjectId(subjectId), listSubjectsByTeamId(), - listTemplatesWithData(), + listTemplatesWithData({ type: TemplateType.EventType }), ]); if (!subject || !availableInputs || !subjects || !availableTemplates) { diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/events/[eventId]/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/events/[eventId]/page.tsx index 7062b4c5..d638bdf5 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/events/[eventId]/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/events/[eventId]/page.tsx @@ -1,5 +1,4 @@ import EventPage from '@/_components/event-page'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -8,8 +7,6 @@ interface PageProps { }; } -export const metadata = { title: formatTitle(['Subjects', 'Event']) }; - const Page = async ({ params: { eventId, subjectId } }: PageProps) => ( ); diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/[insightId]/edit/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/[insightId]/edit/page.tsx index 542e5c47..72cf0402 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/[insightId]/edit/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/[insightId]/edit/page.tsx @@ -4,7 +4,6 @@ import PageModalHeader from '@/_components/page-modal-header'; import Number from '@/_constants/enum-number'; import getInsight from '@/_queries/get-insight'; import listEvents from '@/_queries/list-events'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -13,10 +12,6 @@ interface PageProps { }; } -export const metadata = { - title: formatTitle(['Subjects', 'Insights', 'Edit']), -}; - const Page = async ({ params: { insightId, subjectId } }: PageProps) => { const [{ data: events }, { data: insight }] = await Promise.all([ listEvents(subjectId, { from: 0, to: Number.FourByteSignedIntMax - 1 }), diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/[insightId]/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/[insightId]/page.tsx index 951a76e9..9e419d60 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/[insightId]/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/[insightId]/page.tsx @@ -1,15 +1,10 @@ import InsightPage from '@/_components/insight-page'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { insightId: string; subjectId: string }; searchParams: { from?: string; to?: string }; } -export const metadata = { - title: formatTitle(['Subjects', 'Insight']), -}; - const Page = async ({ params: { insightId, subjectId }, searchParams: { from, to }, diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/create/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/create/page.tsx index dcca540b..fcc19112 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/create/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/create/page.tsx @@ -3,7 +3,6 @@ import * as Modal from '@/_components/modal'; import PageModalHeader from '@/_components/page-modal-header'; import Number from '@/_constants/enum-number'; import listEvents from '@/_queries/list-events'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -11,10 +10,6 @@ interface PageProps { }; } -export const metadata = { - title: formatTitle(['Subjects', 'Insights', 'New']), -}; - const Page = async ({ params: { subjectId } }: PageProps) => { const { data: events } = await listEvents(subjectId, { from: 0, diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/page.tsx index cc13bf92..ae3963f4 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/insights/page.tsx @@ -1,13 +1,10 @@ import InsightsPage from '@/_components/insights-page'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { subjectId: string }; searchParams: { from?: string; to?: string }; } -export const metadata = { title: formatTitle(['Subjects', 'Insights']) }; - const Page = async ({ params: { subjectId }, searchParams }: PageProps) => ( ); diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/edit/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/edit/page.tsx index b64824d8..1fa671e0 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/edit/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/edit/page.tsx @@ -3,7 +3,6 @@ import PageModalHeader from '@/_components/page-modal-header'; import TrainingPlanForm from '@/_components/training-plan-form'; import getSubject from '@/_queries/get-subject'; import getTrainingPlan from '@/_queries/get-training-plan'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -12,10 +11,6 @@ interface PageProps { }; } -export const metadata = { - title: formatTitle(['Subjects', 'Training plans', 'Edit']), -}; - const Page = async ({ params: { missionId, subjectId } }: PageProps) => { const [{ data: subject }, { data: mission }] = await Promise.all([ getSubject(subjectId), @@ -26,7 +21,7 @@ const Page = async ({ params: { missionId, subjectId } }: PageProps) => { return ( - + ); diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/edit/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/edit/page.tsx index 9f0504dd..4c818845 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/edit/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/edit/page.tsx @@ -1,4 +1,5 @@ import SessionForm from '@/_components/session-form'; +import TemplateType from '@/_constants/enum-template-type'; import getCurrentUser from '@/_queries/get-current-user'; import getSession from '@/_queries/get-session'; import getSubject from '@/_queries/get-subject'; @@ -6,7 +7,6 @@ import getTrainingPlanWithSessions from '@/_queries/get-training-plan-with-sessi import listInputsBySubjectId from '@/_queries/list-inputs-by-subject-id'; import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; import listTemplatesWithData from '@/_queries/list-templates-with-data'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -17,37 +17,36 @@ interface PageProps { }; } -export const metadata = { - title: formatTitle(['Subjects', 'Training plans', 'Sessions', 'Edit']), -}; - const Page = async ({ params: { missionId, sessionId, subjectId }, }: PageProps) => { const [ - { data: subject }, + { data: availableInputs }, + { data: availableModuleTemplates }, + { data: availableSessionTemplates }, { data: mission }, { data: session }, - { data: availableInputs }, - { data: availableTemplates }, + { data: subject }, { data: subjects }, user, ] = await Promise.all([ - getSubject(subjectId), + listInputsBySubjectId(subjectId), + listTemplatesWithData({ type: TemplateType.Module }), + listTemplatesWithData({ type: TemplateType.Session }), getTrainingPlanWithSessions(missionId, { draft: true }), getSession(sessionId), - listInputsBySubjectId(subjectId), - listTemplatesWithData(), + getSubject(subjectId), listSubjectsByTeamId(), getCurrentUser(), ]); if ( - !subject || + !availableInputs || + !availableModuleTemplates || + !availableSessionTemplates || !mission || !session || - !availableInputs || - !availableTemplates || + !subject || !subjects || !user || subject.team_id !== user.id @@ -58,11 +57,12 @@ const Page = async ({ return ( ); }; diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/page.tsx index c30708f7..f4aeb707 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/page.tsx @@ -1,5 +1,4 @@ import SessionPage from '@/_components/session-page'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -9,10 +8,6 @@ interface PageProps { }; } -export const metadata = { - title: formatTitle(['Subjects', 'Training plans', 'Sessions']), -}; - const Page = async ({ params: { missionId, sessionId, subjectId }, }: PageProps) => ( diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/from-session/[sessionId]/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/from-session/[sessionId]/page.tsx index 3cfff7b1..ebd452c0 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/from-session/[sessionId]/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/from-session/[sessionId]/page.tsx @@ -1,4 +1,5 @@ import SessionForm from '@/_components/session-form'; +import TemplateType from '@/_constants/enum-template-type'; import getCurrentUser from '@/_queries/get-current-user'; import getSession from '@/_queries/get-session'; import getSubject from '@/_queries/get-subject'; @@ -6,7 +7,6 @@ import getTrainingPlanWithSessions from '@/_queries/get-training-plan-with-sessi import listInputsBySubjectId from '@/_queries/list-inputs-by-subject-id'; import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; import listTemplatesWithData from '@/_queries/list-templates-with-data'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -17,37 +17,36 @@ interface PageProps { }; } -export const metadata = { - title: formatTitle(['Subjects', 'Training plans', 'Sessions', 'New']), -}; - const Page = async ({ params: { missionId, order, sessionId, subjectId }, }: PageProps) => { const [ - { data: subject }, + { data: availableInputs }, + { data: availableModuleTemplates }, + { data: availableSessionTemplates }, { data: mission }, { data: session }, - { data: availableInputs }, - { data: availableTemplates }, + { data: subject }, { data: subjects }, user, ] = await Promise.all([ - getSubject(subjectId), + listInputsBySubjectId(subjectId), + listTemplatesWithData({ type: TemplateType.Module }), + listTemplatesWithData({ type: TemplateType.Session }), getTrainingPlanWithSessions(missionId, { draft: true }), getSession(sessionId), - listInputsBySubjectId(subjectId), - listTemplatesWithData(), + getSubject(subjectId), listSubjectsByTeamId(), getCurrentUser(), ]); if ( - !subject || + !availableInputs || + !availableModuleTemplates || + !availableSessionTemplates || !mission || !session || - !availableInputs || - !availableTemplates || + !subject || !subjects || !user || subject.team_id !== user.id @@ -58,13 +57,14 @@ const Page = async ({ return ( ); }; diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/page.tsx index 11924540..01a3b965 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/page.tsx @@ -1,11 +1,11 @@ import SessionForm from '@/_components/session-form'; +import TemplateType from '@/_constants/enum-template-type'; import getCurrentUser from '@/_queries/get-current-user'; import getSubject from '@/_queries/get-subject'; import getTrainingPlanWithSessions from '@/_queries/get-training-plan-with-sessions'; import listInputsBySubjectId from '@/_queries/list-inputs-by-subject-id'; import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; import listTemplatesWithData from '@/_queries/list-templates-with-data'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -15,24 +15,22 @@ interface PageProps { }; } -export const metadata = { - title: formatTitle(['Subjects', 'Training plans', 'Sessions', 'New']), -}; - const Page = async ({ params: { missionId, order, subjectId } }: PageProps) => { const [ - { data: subject }, + { data: availableInputs }, + { data: availableModuleTemplates }, + { data: availableSessionTemplates }, { data: mission }, + { data: subject }, { data: subjects }, - { data: availableInputs }, - { data: availableTemplates }, user, ] = await Promise.all([ - getSubject(subjectId), + listInputsBySubjectId(subjectId), + listTemplatesWithData({ type: TemplateType.Module }), + listTemplatesWithData({ type: TemplateType.Session }), getTrainingPlanWithSessions(missionId, { draft: true }), + getSubject(subjectId), listSubjectsByTeamId(), - listInputsBySubjectId(subjectId), - listTemplatesWithData(), getCurrentUser(), ]); @@ -41,7 +39,8 @@ const Page = async ({ params: { missionId, order, subjectId } }: PageProps) => { !mission || !subjects || !availableInputs || - !availableTemplates || + !availableModuleTemplates || + !availableSessionTemplates || !user || subject.team_id !== user.id ) { @@ -51,11 +50,12 @@ const Page = async ({ params: { missionId, order, subjectId } }: PageProps) => { return ( ); }; diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/page.tsx index 139cdf66..f0cb7d8b 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/[missionId]/sessions/page.tsx @@ -1,5 +1,4 @@ import SessionsPage from '@/_components/sessions-page'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -8,10 +7,6 @@ interface PageProps { }; } -export const metadata = { - title: formatTitle(['Subjects', 'Training plans', 'Sessions']), -}; - const Page = ({ params: { missionId, subjectId } }: PageProps) => ( ); diff --git a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/create/page.tsx b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/create/page.tsx index 437b292d..039caab5 100644 --- a/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/create/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/[subjectId]/training-plans/create/page.tsx @@ -1,8 +1,8 @@ import * as Modal from '@/_components/modal'; import PageModalHeader from '@/_components/page-modal-header'; import TrainingPlanForm from '@/_components/training-plan-form'; +import TrainingPlanUseTemplateModal from '@/_components/training-plan-use-template-modal'; import getSubject from '@/_queries/get-subject'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -10,17 +10,16 @@ interface PageProps { }; } -export const metadata = { - title: formatTitle(['Subjects', 'Training plans', 'New']), -}; - const Page = async ({ params: { subjectId } }: PageProps) => { const { data: subject } = await getSubject(subjectId); if (!subject) return null; return ( - + } + title="New training plan" + /> ); diff --git a/app/(pages)/@modal/(layout)/subjects/create/page.tsx b/app/(pages)/@modal/(layout)/subjects/create/page.tsx index d87a7491..2e4f75ab 100644 --- a/app/(pages)/@modal/(layout)/subjects/create/page.tsx +++ b/app/(pages)/@modal/(layout)/subjects/create/page.tsx @@ -1,9 +1,6 @@ import * as Modal from '@/_components/modal'; import PageModalHeader from '@/_components/page-modal-header'; import SubjectForm from '@/_components/subject-form'; -import formatTitle from '@/_utilities/format-title'; - -export const metadata = { title: formatTitle(['Subjects', 'New']) }; const Page = () => ( diff --git a/app/(pages)/@modal/(layout)/templates/event-types/[templateId]/page.tsx b/app/(pages)/@modal/(layout)/templates/event-types/[templateId]/page.tsx index 5860ae3d..fe30095c 100644 --- a/app/(pages)/@modal/(layout)/templates/event-types/[templateId]/page.tsx +++ b/app/(pages)/@modal/(layout)/templates/event-types/[templateId]/page.tsx @@ -4,7 +4,6 @@ import PageModalHeader from '@/_components/page-modal-header'; import getTemplate from '@/_queries/get-template'; import listInputs from '@/_queries/list-inputs'; import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -12,8 +11,6 @@ interface PageProps { }; } -export const metadata = { title: formatTitle(['Templates', 'Edit']) }; - const Page = async ({ params: { templateId } }: PageProps) => { const [{ data: template }, { data: availableInputs }, { data: subjects }] = await Promise.all([ @@ -26,7 +23,7 @@ const Page = async ({ params: { templateId } }: PageProps) => { return ( - + { const [{ data: eventType }, { data: availableInputs }, { data: subjects }] = await Promise.all([ @@ -37,8 +34,7 @@ const Page = async ({ params: { eventTypeId } }: PageProps) => { content: eventType.content, inputIds: eventType.inputs.map((i) => i.input_id), }, - id: eventType.id, - name: eventType.name!, + name: eventType.name ?? '', }} /> diff --git a/app/(pages)/@modal/(layout)/templates/event-types/create/page.tsx b/app/(pages)/@modal/(layout)/templates/event-types/create/page.tsx index 4bb2e724..e2ed9d77 100644 --- a/app/(pages)/@modal/(layout)/templates/event-types/create/page.tsx +++ b/app/(pages)/@modal/(layout)/templates/event-types/create/page.tsx @@ -3,11 +3,6 @@ import * as Modal from '@/_components/modal'; import PageModalHeader from '@/_components/page-modal-header'; import listInputs from '@/_queries/list-inputs'; import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; -import formatTitle from '@/_utilities/format-title'; - -export const metadata = { - title: formatTitle(['Templates', 'New']), -}; const Page = async () => { const [{ data: availableInputs }, { data: subjects }] = await Promise.all([ diff --git a/app/(pages)/@modal/(layout)/templates/modules/[templateId]/page.tsx b/app/(pages)/@modal/(layout)/templates/modules/[templateId]/page.tsx index dff1a0e7..84c042d7 100644 --- a/app/(pages)/@modal/(layout)/templates/modules/[templateId]/page.tsx +++ b/app/(pages)/@modal/(layout)/templates/modules/[templateId]/page.tsx @@ -4,7 +4,6 @@ import PageModalHeader from '@/_components/page-modal-header'; import getTemplate from '@/_queries/get-template'; import listInputs from '@/_queries/list-inputs'; import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { @@ -12,8 +11,6 @@ interface PageProps { }; } -export const metadata = { title: formatTitle(['Templates', 'Edit']) }; - const Page = async ({ params: { templateId } }: PageProps) => { const [{ data: template }, { data: availableInputs }, { data: subjects }] = await Promise.all([ @@ -26,7 +23,7 @@ const Page = async ({ params: { templateId } }: PageProps) => { return ( - + { const [{ data: availableInputs }, { data: subjects }] = await Promise.all([ diff --git a/app/(pages)/@modal/(layout)/templates/sessions/[templateId]/loading.tsx b/app/(pages)/@modal/(layout)/templates/sessions/[templateId]/loading.tsx new file mode 100644 index 00000000..e3d948d8 --- /dev/null +++ b/app/(pages)/@modal/(layout)/templates/sessions/[templateId]/loading.tsx @@ -0,0 +1,5 @@ +import PageModalLoading from '@/_components/page-modal-loading'; + +const Loading = PageModalLoading; + +export default Loading; diff --git a/app/(pages)/@modal/(layout)/templates/sessions/[templateId]/page.tsx b/app/(pages)/@modal/(layout)/templates/sessions/[templateId]/page.tsx new file mode 100644 index 00000000..842e3d89 --- /dev/null +++ b/app/(pages)/@modal/(layout)/templates/sessions/[templateId]/page.tsx @@ -0,0 +1,46 @@ +import * as Modal from '@/_components/modal'; +import PageModalHeader from '@/_components/page-modal-header'; +import SessionTemplateForm from '@/_components/session-template-form'; +import TemplateType from '@/_constants/enum-template-type'; +import getTemplate from '@/_queries/get-template'; +import listInputs from '@/_queries/list-inputs'; +import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; +import listTemplatesWithData from '@/_queries/list-templates-with-data'; + +interface PageProps { + params: { + templateId: string; + }; +} + +const Page = async ({ params: { templateId } }: PageProps) => { + const [ + { data: availableInputs }, + { data: availableTemplates }, + { data: subjects }, + { data: template }, + ] = await Promise.all([ + listInputs(), + listTemplatesWithData({ type: TemplateType.Module }), + listSubjectsByTeamId(), + getTemplate(templateId), + ]); + + if (!availableInputs || !availableTemplates || !subjects || !template) { + return null; + } + + return ( + + + + + ); +}; + +export default Page; diff --git a/app/(pages)/@modal/(layout)/templates/sessions/create/from-session/[sessionId]/loading.tsx b/app/(pages)/@modal/(layout)/templates/sessions/create/from-session/[sessionId]/loading.tsx new file mode 100644 index 00000000..e3d948d8 --- /dev/null +++ b/app/(pages)/@modal/(layout)/templates/sessions/create/from-session/[sessionId]/loading.tsx @@ -0,0 +1,5 @@ +import PageModalLoading from '@/_components/page-modal-loading'; + +const Loading = PageModalLoading; + +export default Loading; diff --git a/app/(pages)/@modal/(layout)/templates/sessions/create/from-session/[sessionId]/page.tsx b/app/(pages)/@modal/(layout)/templates/sessions/create/from-session/[sessionId]/page.tsx new file mode 100644 index 00000000..07ea9101 --- /dev/null +++ b/app/(pages)/@modal/(layout)/templates/sessions/create/from-session/[sessionId]/page.tsx @@ -0,0 +1,57 @@ +import * as Modal from '@/_components/modal'; +import PageModalHeader from '@/_components/page-modal-header'; +import SessionTemplateForm from '@/_components/session-template-form'; +import TemplateType from '@/_constants/enum-template-type'; +import getSession from '@/_queries/get-session'; +import listInputs from '@/_queries/list-inputs'; +import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; +import listTemplatesWithData from '@/_queries/list-templates-with-data'; + +interface PageProps { + params: { + sessionId: string; + }; +} + +const Page = async ({ params: { sessionId } }: PageProps) => { + const [ + { data: availableInputs }, + { data: availableTemplates }, + { data: session }, + { data: subjects }, + ] = await Promise.all([ + listInputs(), + listTemplatesWithData({ type: TemplateType.Module }), + getSession(sessionId), + listSubjectsByTeamId(), + ]); + + if (!availableInputs || !availableTemplates || !session || !subjects) { + return null; + } + + return ( + + + ({ + content: module.content, + inputIds: module.inputs.map((i) => i.input_id), + name: module.name, + })), + }, + name: session.title ?? '', + }} + /> + + ); +}; + +export default Page; diff --git a/app/(pages)/@modal/(layout)/templates/sessions/create/loading.tsx b/app/(pages)/@modal/(layout)/templates/sessions/create/loading.tsx new file mode 100644 index 00000000..e3d948d8 --- /dev/null +++ b/app/(pages)/@modal/(layout)/templates/sessions/create/loading.tsx @@ -0,0 +1,5 @@ +import PageModalLoading from '@/_components/page-modal-loading'; + +const Loading = PageModalLoading; + +export default Loading; diff --git a/app/(pages)/@modal/(layout)/templates/sessions/create/page.tsx b/app/(pages)/@modal/(layout)/templates/sessions/create/page.tsx new file mode 100644 index 00000000..8b74a82c --- /dev/null +++ b/app/(pages)/@modal/(layout)/templates/sessions/create/page.tsx @@ -0,0 +1,34 @@ +import * as Modal from '@/_components/modal'; +import PageModalHeader from '@/_components/page-modal-header'; +import SessionTemplateForm from '@/_components/session-template-form'; +import TemplateType from '@/_constants/enum-template-type'; +import listInputs from '@/_queries/list-inputs'; +import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; +import listTemplatesWithData from '@/_queries/list-templates-with-data'; + +const Page = async () => { + const [ + { data: availableInputs }, + { data: availableTemplates }, + { data: subjects }, + ] = await Promise.all([ + listInputs(), + listTemplatesWithData({ type: TemplateType.Module }), + listSubjectsByTeamId(), + ]); + + if (!availableInputs || !availableTemplates || !subjects) return null; + + return ( + + + + + ); +}; + +export default Page; diff --git a/app/(pages)/@modal/(layout)/upgrade/page.tsx b/app/(pages)/@modal/(layout)/upgrade/page.tsx index 51d71b8f..5a18d166 100644 --- a/app/(pages)/@modal/(layout)/upgrade/page.tsx +++ b/app/(pages)/@modal/(layout)/upgrade/page.tsx @@ -3,9 +3,6 @@ import PageModalBackButton from '@/_components/page-modal-back-button'; import PageModalHeader from '@/_components/page-modal-header'; import UpgradePlanButton from '@/_components/upgrade-plan-button'; import getCurrentUser from '@/_queries/get-current-user'; -import formatTitle from '@/_utilities/format-title'; - -export const metadata = { title: formatTitle(['Upgrade plan']) }; const Page = async () => { const user = await getCurrentUser(); diff --git a/app/(pages)/share/[subjectId]/page.tsx b/app/(pages)/share/[subjectId]/page.tsx index 385b3237..5af68719 100644 --- a/app/(pages)/share/[subjectId]/page.tsx +++ b/app/(pages)/share/[subjectId]/page.tsx @@ -1,13 +1,10 @@ import SubjectPage from '@/_components/subject-page'; -import formatTitle from '@/_utilities/format-title'; interface PageProps { params: { subjectId: string }; searchParams: { foo?: string; from?: string; limit?: string; to?: string }; } -export const metadata = { title: formatTitle(['Subjects', 'Events']) }; - const Page = async ({ params: { subjectId }, searchParams: { from, limit, to }, diff --git a/app/_components/account-menu.tsx b/app/_components/account-menu.tsx index cd86fcb9..0e4911c6 100644 --- a/app/_components/account-menu.tsx +++ b/app/_components/account-menu.tsx @@ -29,7 +29,7 @@ const AccountMenu = ({ user }: AccountMenuProps) => { return ( -
+
- -
-
- -
+ +
+
- + deleteComment(id)} diff --git a/app/_components/event-menu.tsx b/app/_components/event-menu.tsx index 91b34294..24fe3530 100644 --- a/app/_components/event-menu.tsx +++ b/app/_components/event-menu.tsx @@ -25,18 +25,18 @@ const EventMenu = ({ className, eventId, isModal }: EventMenuProps) => { ) : (
-
+
)} - + { diff --git a/app/_components/event-type-form.tsx b/app/_components/event-type-form.tsx index b4c017a9..64feed00 100644 --- a/app/_components/event-type-form.tsx +++ b/app/_components/event-type-form.tsx @@ -1,7 +1,6 @@ 'use client'; import Button from '@/_components/button'; -import EventTypeMenu from '@/_components/event-type-menu'; import Input from '@/_components/input'; import InputForm from '@/_components/input-form'; import * as Modal from '@/_components/modal'; @@ -17,7 +16,11 @@ import { GetInputData } from '@/_queries/get-input'; import { ListInputsBySubjectIdData } from '@/_queries/list-inputs-by-subject-id'; import { ListSubjectsByTeamIdData } from '@/_queries/list-subjects-by-team-id'; import { ListTemplatesWithDataData } from '@/_queries/list-templates-with-data'; +import { EventTypeTemplateDataJson } from '@/_types/event-type-template-data-json'; +import forceArray from '@/_utilities/force-array'; import getFormCacheKey from '@/_utilities/get-form-cache-key'; +import DocumentTextIcon from '@heroicons/react/24/outline/DocumentTextIcon'; +import { useToggle } from '@uidotdev/usehooks'; import { useRouter } from 'next/navigation'; import { useState, useTransition } from 'react'; import { Controller, useFieldArray } from 'react-hook-form'; @@ -47,6 +50,7 @@ const EventTypeForm = ({ useState>(null); const [isTransitioning, startTransition] = useTransition(); + const [useTemplateModal, toggleUseTemplateModal] = useToggle(false); const cacheKey = getFormCacheKey.eventType({ id: eventType?.id, subjectId }); const form = useCachedForm(cacheKey, { @@ -66,16 +70,76 @@ const EventTypeForm = ({ - availableInputs={availableInputs} - availableTemplates={availableTemplates} - eventTypeId={eventType?.id} - form={form} - subjectId={subjectId} - subjects={subjects} - /> + + + + + + + + Use a template + + Selecting a template will overwrite any existing event type + values. + +
+ 'No templates.'} - onChange={(t) => { - const template = - t as NonNullable[0]; - - const data = template?.data as TemplateDataJson; - - const inputs = availableInputs.filter(({ id }) => - forceArray(data?.inputIds).includes(id), - ) as PathValue; - - form.setValue( - 'name' as Path, - template?.name as PathValue>, - { shouldDirty: true }, - ); - - form.setValue( - 'content' as Path, - data?.content as PathValue>, - { shouldDirty: true }, - ); - - form.setValue( - 'inputs' as Path, - inputs as PathValue>, - { shouldDirty: true }, - ); - - toggleUseTemplateModal(); - }} - options={availableTemplates} - placeholder="Select a template…" - value={null} - /> -
- e.preventDefault()}> - - -
-
-
-
- setCreateTemplateModal(null)} - open={!!createTemplateModal} - > - e.preventDefault()}> - { - const { content, inputs, name } = form.getValues(); - - setCreateTemplateModal({ - data: { - content, - inputIds: inputs.map(({ id }: { id: string }) => id), - }, - name, - }); - }} - > - - New template - - - - - - setCreateTemplateModal(null)} - title="New event type template" - /> - setCreateTemplateModal(null)} - onSubmit={() => { - setCreateTemplateModal(null); - router.refresh(); - }} - subjects={subjects} - template={createTemplateModal} - /> - - - - - - )} - {eventTypeId && ( - <> - {!form && ( - <> - - - Edit - - - - New template - - - )} - { - await deleteEventType(eventTypeId); - if (form || isModal) router.back(); - }} - /> - - )} + + + + Edit + + + + New template + + { + await deleteEventType(eventTypeId); + if (isModal) router.back(); + }} + />
diff --git a/app/_components/event-type-template-form.tsx b/app/_components/event-type-template-form.tsx index ebe1a722..d4def377 100644 --- a/app/_components/event-type-template-form.tsx +++ b/app/_components/event-type-template-form.tsx @@ -9,14 +9,13 @@ import PageModalHeader from '@/_components/page-modal-header'; import RichTextarea from '@/_components/rich-textarea'; import Select, { IOption } from '@/_components/select'; import UnsavedChangesBanner from '@/_components/unsaved-changes-banner'; -import TemplateType from '@/_constants/enum-template-type'; import useCachedForm from '@/_hooks/use-cached-form'; -import upsertTemplate from '@/_mutations/upsert-template'; +import upsertEventTypeTemplate from '@/_mutations/upsert-event-type-template'; import { GetInputData } from '@/_queries/get-input'; import { GetTemplateData } from '@/_queries/get-template'; import { ListInputsData } from '@/_queries/list-inputs'; import { ListSubjectsByTeamIdData } from '@/_queries/list-subjects-by-team-id'; -import { TemplateDataJson } from '@/_types/template-data-json'; +import { EventTypeTemplateDataJson } from '@/_types/event-type-template-data-json'; import getFormCacheKey from '@/_utilities/get-form-cache-key'; import stopPropagation from '@/_utilities/stop-propagation'; import { sortBy } from 'lodash'; @@ -28,8 +27,6 @@ interface EventTypeTemplateFormProps { availableInputs: NonNullable; disableCache?: boolean; isDuplicate?: boolean; - onClose?: () => void; - onSubmit?: () => void; subjects: NonNullable; template?: Partial; } @@ -44,8 +41,6 @@ const EventTypeTemplateForm = ({ availableInputs, disableCache, isDuplicate, - onClose, - onSubmit, subjects, template, }: EventTypeTemplateFormProps) => { @@ -53,9 +48,8 @@ const EventTypeTemplateForm = ({ useState>(null); const [isTransitioning, startTransition] = useTransition(); - const router = useRouter(); - const templateData = template?.data as TemplateDataJson; + const templateData = template?.data as EventTypeTemplateDataJson; const cacheKey = getFormCacheKey.eventTypeTemplate({ id: template?.id, @@ -67,8 +61,8 @@ const EventTypeTemplateForm = ({ { defaultValues: { content: templateData?.content ?? '', - inputs: availableInputs.filter(({ id }) => - templateData?.inputIds?.includes(id), + inputs: availableInputs.filter((input) => + templateData?.inputIds?.includes(input.id), ), name: template?.name ?? '', }, @@ -84,17 +78,17 @@ const EventTypeTemplateForm = ({ onSubmit={stopPropagation( form.handleSubmit((values) => startTransition(async () => { - const res = await upsertTemplate( - { templateId: template?.id, type: TemplateType.EventType }, + const res = await upsertEventTypeTemplate( + { templateId: template?.id }, values, ); if (res?.error) { form.setError('root', { message: res.error, type: 'custom' }); - } else if (res?.data) { - onSubmit?.(); - if (!onClose) router.back(); + return; } + + router.back(); }), ), )} @@ -157,11 +151,7 @@ const EventTypeTemplateForm = ({
{form.formState.errors.root.message}
)}
- + Close
    {filteredInputs.map((input) => ( -
  • +
    {filteredTemplates.map((template) => ( -
  • +
diff --git a/app/_components/input-menu.tsx b/app/_components/input-menu.tsx index 1afd75a7..6353282e 100644 --- a/app/_components/input-menu.tsx +++ b/app/_components/input-menu.tsx @@ -13,14 +13,14 @@ interface InputMenuProps { const InputMenu = ({ inputId }: InputMenuProps) => ( -
-
+
+
- + { label?: ReactNode; - right?: ReactNode; tooltip?: ReactNode; } @@ -16,7 +15,6 @@ const Input = forwardRef( disabled, id, label, - right, type, name, tooltip, @@ -42,7 +40,7 @@ const Input = forwardRef(
- {right && ( -
- {right} -
- )}
); }, diff --git a/app/_components/insight-menu.tsx b/app/_components/insight-menu.tsx index feb787ea..51c4c9ec 100644 --- a/app/_components/insight-menu.tsx +++ b/app/_components/insight-menu.tsx @@ -14,14 +14,14 @@ interface InsightMenuProps { const InsightMenu = ({ insightId, subjectId }: InsightMenuProps) => ( -
-
+
+
- + + + + + + Use a template + + Selecting a template will overwrite any existing session + modules. + +
+ - - e.preventDefault()}> - - - - - - - Schedule session - - - Scheduled sessions are not visible to clients until the - specified time. - -
- { - if (e.key !== 'Enter') return; - e.preventDefault(); + + ) : ( + 'Schedule' + )} + + + + + + + Schedule session + + + Scheduled sessions are not visible to clients until the + specified time. + +
+ { + if (e.key !== 'Enter') return; + e.preventDefault(); + toggleScheduleModal(false); + }} + step={60} + type="datetime-local" + {...form.register('scheduledFor')} + /> +
+ - -
- e.preventDefault()}> - - + > + Clear + +
-
-
-
- -
+ e.preventDefault()}> + + +
+
+
+
+ +
+
    ( availableInputs={availableInputs} - availableTemplates={availableTemplates} + availableTemplates={availableModuleTemplates} eventTypeArray={modulesArray} eventTypeIndex={eventTypeIndex} eventTypeKey={module.key} @@ -329,10 +366,10 @@ const SessionForm = ({
-
+
- Modules break up your session and allow you to capture inputs at - different points. You can add as many modules as you need. + Modules break up your sessions into sections with inputs. You can + add as many modules as you need.
- {form.formState.errors.root && ( -
- {form.formState.errors.root.message} -
- )} -
- {draft && ( - - )} +
+ {form.formState.errors.root && ( +
+ {form.formState.errors.root.message} +
+ )} +
+ {draft && ( -
- form={form} /> - - + )} + +
+ form={form} /> + ['sessions']; - subjectId: string; -} - -const SessionLayout = ({ - children, - highestOrder, - isCreate, - isEdit, - isPublic, - missionId, - nextSessionId, - order, - previousSessionId, - sessionId, - sessions, - subjectId, -}: SessionLayoutProps) => { - const isEditOrCreate = isCreate || isEdit; - const currentSession = sessions.find(({ id }) => id === sessionId); - const sessionOrder = order ? Number(order) : currentSession?.order; - if (typeof sessionOrder === 'undefined') return null; - const shareOrSubjects = isPublic ? 'share' : 'subjects'; - - if ( - isEditOrCreate && - sessions.length > 0 && - !previousSessionId && - !nextSessionId - ) { - sessions.some((session) => { - if (session.order < sessionOrder) { - previousSessionId = session.id; - } else if (session.order > sessionOrder) { - nextSessionId = session.id; - return true; - } - }); - } - - const editSuffix = isEditOrCreate ? '/edit' : ''; - const nextSessionOrder = highestOrder + 1; - - return ( - <> - - {children} - - ); -}; - -export default SessionLayout; diff --git a/app/_components/session-menu.tsx b/app/_components/session-menu.tsx index 2ddf5efd..661a0168 100644 --- a/app/_components/session-menu.tsx +++ b/app/_components/session-menu.tsx @@ -6,35 +6,29 @@ import IconButton from '@/_components/icon-button'; import deleteSession from '@/_mutations/delete-session'; import moveSession from '@/_mutations/move-session'; import ArrowDownIcon from '@heroicons/react/24/outline/ArrowDownIcon'; -import ArrowLeftIcon from '@heroicons/react/24/outline/ArrowLeftIcon'; -import ArrowRightIcon from '@heroicons/react/24/outline/ArrowRightIcon'; import ArrowUpIcon from '@heroicons/react/24/outline/ArrowUpIcon'; import DocumentDuplicateIcon from '@heroicons/react/24/outline/DocumentDuplicateIcon'; import EllipsisVerticalIcon from '@heroicons/react/24/outline/EllipsisVerticalIcon'; -import EyeIcon from '@heroicons/react/24/outline/EyeIcon'; import PencilIcon from '@heroicons/react/24/outline/PencilIcon'; +import PlusIcon from '@heroicons/react/24/outline/PlusIcon'; import { useRouter } from 'next/navigation'; import { useTransition } from 'react'; interface SessionMenuProps { highestPublishedOrder: number; isDraft: boolean; - isDuplicate?: boolean; - isEdit?: boolean; isList?: boolean; isView?: boolean; missionId: string; nextSessionOrder: number; order: number; - sessionId?: string; + sessionId: string; subjectId: string; } const SessionMenu = ({ highestPublishedOrder, isDraft, - isDuplicate, - isEdit, isList, isView, missionId, @@ -50,45 +44,41 @@ const SessionMenu = ({ return ( - {isView || isEdit ? ( + {isView ? ( } /> ) : ( -
-
+
+
)} - - {isEdit && !isDraft && ( - - - View - - )} - {(isList || isView) && ( - - - Edit - - )} - {sessionId && !isDuplicate ? ( + + + + Edit + + + + Duplicate + + + + New template + + {isList && ( <> - - - Duplicate - = highestPublishedOrder} loading={isMoveRightTransitioning} @@ -105,17 +95,8 @@ const SessionMenu = ({ ) } > - {isList ? ( - <> - - Move up - - ) : ( - <> - - Move right - - )} + + Move up - {isList ? ( - <> - - Move down - - ) : ( - <> - - Move left - - )} - - { - await deleteSession({ - currentOrder: order, - missionId: missionId, - sessionId, - }); - - if (!isList) { - router.replace( - `/subjects/${subjectId}/training-plans/${missionId}/sessions`, - ); - } - }} - /> - - ) : ( - <> - - - Move left - - - - Move right + + Move down )} + { + await deleteSession({ + currentOrder: order, + missionId: missionId, + sessionId, + }); + + if (!isList) { + router.replace( + `/subjects/${subjectId}/training-plans/${missionId}/sessions`, + ); + } + }} + /> diff --git a/app/_components/session-page.tsx b/app/_components/session-page.tsx index ba4f1dd0..889de68a 100644 --- a/app/_components/session-page.tsx +++ b/app/_components/session-page.tsx @@ -1,11 +1,11 @@ import Button from '@/_components/button'; import DateTime from '@/_components/date-time'; import Empty from '@/_components/empty'; +import IconButton from '@/_components/icon-button'; import * as Modal from '@/_components/modal'; import ModuleCard from '@/_components/module-card'; import PageModalBackButton from '@/_components/page-modal-back-button'; import PageModalHeader from '@/_components/page-modal-header'; -import SessionLayout from '@/_components/session-layout'; import SessionMenu from '@/_components/session-menu'; import getCurrentUser from '@/_queries/get-current-user'; import getPublicSessionWithDetails from '@/_queries/get-public-session-with-details'; @@ -18,6 +18,8 @@ import firstIfArray from '@/_utilities/first-if-array'; import parseSessions from '@/_utilities/parse-sessions'; import ArrowUpRightIcon from '@heroicons/react/24/outline/ArrowUpRightIcon'; import CalendarDaysIcon from '@heroicons/react/24/outline/CalendarDaysIcon'; +import ChevronLeftIcon from '@heroicons/react/24/outline/ChevronLeftIcon'; +import ChevronRightIcon from '@heroicons/react/24/outline/ChevronRightIcon'; interface SessionPageProps { isPublic?: boolean; @@ -45,11 +47,16 @@ const SessionPage = async ({ ]); if (!subject || !trainingPlan || !session) return null; + const currentSession = trainingPlan.sessions.find((s) => s.id === sessionId); + if (!currentSession) return null; + const isTeamMember = !!user && subject.team_id === user.id; const shareOrSubjects = isPublic ? 'share' : 'subjects'; - const { + let { + // eslint-disable-next-line prefer-const highestOrder, + // eslint-disable-next-line prefer-const highestPublishedOrder, nextSessionId, previousSessionId, @@ -59,6 +66,21 @@ const SessionPage = async ({ sessions: trainingPlan.sessions, }); + if ( + trainingPlan.sessions.length > 0 && + !previousSessionId && + !nextSessionId + ) { + trainingPlan.sessions.some((session) => { + if (session.order < currentSession.order) { + previousSessionId = session.id; + } else if (session.order > currentSession.order) { + nextSessionId = session.id; + return true; + } + }); + } + return ( - - {session.scheduled_for && - new Date(session.scheduled_for) > new Date() ? ( - - -

- Scheduled for{' '} - -

-
- ) : ( - <> - {session.title && ( -

- {session.title} -

- )} -
    - {session.modules.map((module, i) => { - const event = firstIfArray(module.event); - const previousModuleEvent = session.modules[i - 1]?.event; + + {session.scheduled_for && new Date(session.scheduled_for) > new Date() ? ( + + +

    + Scheduled for{' '} + +

    +
    + ) : ( + <> + {session.title && ( +

    {session.title}

    + )} +
      + {session.modules.map((module, i) => { + const event = firstIfArray(module.event); + const previousModuleEvent = session.modules[i - 1]?.event; - return ( -
    • - -
    • - ); - })} -
    - - )} - + return ( +
  • + +
  • + ); + })} +
+ + )} ; + availableTemplates: NonNullable; + disableCache?: boolean; + isDuplicate?: boolean; + subjects: NonNullable; + template?: Partial; +} + +export type SessionTemplateFormValues = { + modules: Array<{ + content: string; + inputs: Array; + name?: string | null; + }>; + name: string; +}; + +const SessionTemplateForm = ({ + availableInputs, + availableTemplates, + disableCache, + isDuplicate, + subjects, + template, +}: SessionTemplateFormProps) => { + const [isTransitioning, startTransition] = useTransition(); + const router = useRouter(); + const sensors = DndCore.useSensors(DndCore.useSensor(DndCore.PointerSensor)); + const templateData = template?.data as SessionTemplateDataJson; + + const cacheKey = getFormCacheKey.sessionTemplate({ + id: template?.id, + isDuplicate, + }); + + const form = useCachedForm( + cacheKey, + { + defaultValues: { + modules: templateData?.modules?.length + ? templateData.modules.map((module) => ({ + content: module.content, + inputs: availableInputs.filter((input) => + module.inputIds?.includes(input.id), + ), + name: module.name, + })) + : [{ content: '', inputs: [], name: '' }], + name: template?.name ?? '', + }, + }, + { disableCache }, + ); + + const modulesArray = useFieldArray({ + control: form.control, + keyName: 'key', + name: 'modules', + }); + + return ( +
+ startTransition(async () => { + const res = await upsertSessionTemplate( + { templateId: template?.id }, + values, + ); + + if (res?.error) { + form.setError('root', { message: res.error, type: 'custom' }); + } else { + router.back(); + } + }), + ), + )} + > + +
+
    + { + if (!over || active.id === over.id) return; + + modulesArray.move( + modulesArray.fields.findIndex((f) => f.key === active.id), + modulesArray.fields.findIndex((f) => f.key === over.id), + ); + }} + sensors={sensors} + > + eventType.key)} + strategy={DndSortable.verticalListSortingStrategy} + > + {modulesArray.fields.map((module, eventTypeIndex) => ( + + availableInputs={availableInputs} + availableTemplates={availableTemplates} + eventTypeArray={modulesArray} + eventTypeIndex={eventTypeIndex} + eventTypeKey={module.key} + form={form} + hasOnlyOne={modulesArray.fields.length === 1} + key={module.key} + subjects={subjects} + /> + ))} + + +
+ +
+ {form.formState.errors.root && ( +
{form.formState.errors.root.message}
+ )} +
+ + Close + + +
+ {!disableCache && ( + form={form} /> + )} + + ); +}; + +export default SessionTemplateForm; diff --git a/app/_components/sessions-page.tsx b/app/_components/sessions-page.tsx index 7c186f23..921384c2 100644 --- a/app/_components/sessions-page.tsx +++ b/app/_components/sessions-page.tsx @@ -104,7 +104,7 @@ const SessionsPage = async ({ return (
  • - + { {!!linkArray.fields.length && (
      {linkArray.fields.map((link, linkIndex) => ( -
    • +
    • ( } - label="Delete link" - onClick={() => linkArray.remove(linkIndex)} - tabIndex={-1} - /> - } {...field} /> )} @@ -172,6 +163,15 @@ const SubjectForm = ({ subject }: SubjectFormProps) => { /> )} /> +
      + } + label="Delete link" + onClick={() => linkArray.remove(linkIndex)} + tabIndex={-1} + /> +
    • ))}
    diff --git a/app/_components/subject-list.tsx b/app/_components/subject-list.tsx index c95b24e5..cb89b1b8 100644 --- a/app/_components/subject-list.tsx +++ b/app/_components/subject-list.tsx @@ -19,10 +19,7 @@ const SubjectList = async ({ {!!teamSubjects.length && (
      {teamSubjects.map((subject) => ( -
    • +
    • ( 'z-30 max-w-[20rem] rounded border border-alpha-2 bg-bg-3 px-6 py-4 text-center drop-shadow', tipClassName, )} - sideOffset={0} + sideOffset={-2} {...rest} > {children} diff --git a/app/_components/training-plan-menu.tsx b/app/_components/training-plan-menu.tsx index 92c92820..f9c7b888 100644 --- a/app/_components/training-plan-menu.tsx +++ b/app/_components/training-plan-menu.tsx @@ -6,6 +6,7 @@ import IconButton from '@/_components/icon-button'; import deleteTrainingPlan from '@/_mutations/delete-training-plan'; import EllipsisVerticalIcon from '@heroicons/react/24/outline/EllipsisVerticalIcon'; import PencilIcon from '@heroicons/react/24/outline/PencilIcon'; +import PlusIcon from '@heroicons/react/24/outline/PlusIcon'; import { useRouter } from 'next/navigation'; interface TrainingPlanMenuProps { @@ -27,15 +28,15 @@ const TrainingPlanMenu = ({ {isModal ? ( } /> ) : ( -
      -
      +
      +
      )} - + Edit name + + + New template + { diff --git a/app/_components/training-plan-use-template-modal.tsx b/app/_components/training-plan-use-template-modal.tsx new file mode 100644 index 00000000..81f5d1c8 --- /dev/null +++ b/app/_components/training-plan-use-template-modal.tsx @@ -0,0 +1,51 @@ +'use client'; + +import Button from '@/_components/button'; +import * as Modal from '@/_components/modal'; +import Select from '@/_components/select'; +import DocumentTextIcon from '@heroicons/react/24/outline/DocumentTextIcon'; + +const TrainingPlanUseTemplateModal = () => ( + + + + + + + + Use a template + + Who said creating an entire training plan had to be hard? + +
      +