feat: workout + exercise route with store, front end fetching with suspend
This commit is contained in:
parent
553ca30d12
commit
db89c3e551
45
src/app/(frontend)/[tenant]/dashboard/layout.tsx
Normal file
45
src/app/(frontend)/[tenant]/dashboard/layout.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { SidebarLeft } from '@/components/sidebar-left'
|
||||||
|
import {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbPage,
|
||||||
|
} from '@/components/ui/breadcrumb'
|
||||||
|
import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'
|
||||||
|
import { Separator } from '@radix-ui/react-separator'
|
||||||
|
import { SidebarRight } from '@/components/sidebar-right'
|
||||||
|
import { DashboardContent } from '@/components/Dashboard'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
// TODO: invesigate error blocking this from being serverside
|
||||||
|
const DashboardLayout = ({ children }: Props) => {
|
||||||
|
return (
|
||||||
|
<SidebarProvider>
|
||||||
|
<SidebarLeft />
|
||||||
|
<SidebarInset>
|
||||||
|
<header className="sticky top-0 flex h-14 shrink-0 items-center gap-2 bg-background">
|
||||||
|
<div className="flex flex-1 items-center gap-2 px-3">
|
||||||
|
<SidebarTrigger />
|
||||||
|
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbList>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage className="line-clamp-1">Dashboard</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</BreadcrumbList>
|
||||||
|
</Breadcrumb>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{children}
|
||||||
|
</SidebarInset>
|
||||||
|
<SidebarRight />
|
||||||
|
</SidebarProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DashboardLayout
|
||||||
@ -1,21 +1,19 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
|
import { DashboardContentSection } from '@/components/Dashboard'
|
||||||
import { Tenant, User } from '@/payload-types'
|
import { Tenant, User } from '@/payload-types'
|
||||||
import useGlobal from '@/stores'
|
import useGlobal from '@/stores'
|
||||||
|
|
||||||
type Props = {
|
const DashboardPageClient = () => {
|
||||||
user?: User
|
|
||||||
tenant?: Tenant
|
|
||||||
}
|
|
||||||
|
|
||||||
const DashboardPageClient = (props?: Props) => {
|
|
||||||
const { user, tenant } = useGlobal()
|
const { user, tenant } = useGlobal()
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto h-24 w-full max-w-3xl rounded-xl bg-muted/50">
|
<DashboardContentSection>
|
||||||
<p>Testing Dashboard Zustand Data Here</p>
|
<p>Dashboard</p>
|
||||||
<p>{user?.email}</p>
|
<p>Username: {user?.username}</p>
|
||||||
<p>{tenant?.name}</p>
|
<p>User Email: {user?.email}</p>
|
||||||
</div>
|
<p>Tenant: {tenant?.name}</p>
|
||||||
|
<p>Tenant Slug: {tenant?.slug}</p>
|
||||||
|
</DashboardContentSection>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,41 +1,5 @@
|
|||||||
import { SidebarLeft } from '@/components/sidebar-left'
|
|
||||||
import { SidebarRight } from '@/components/sidebar-right'
|
|
||||||
import {
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
|
||||||
} from '@/components/ui/breadcrumb'
|
|
||||||
import { Separator } from '@/components/ui/separator'
|
|
||||||
import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'
|
|
||||||
import DashboardPageClient from './page.client'
|
import DashboardPageClient from './page.client'
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return <DashboardPageClient />
|
||||||
<SidebarProvider>
|
|
||||||
<SidebarLeft />
|
|
||||||
<SidebarInset>
|
|
||||||
<header className="sticky top-0 flex h-14 shrink-0 items-center gap-2 bg-background">
|
|
||||||
<div className="flex flex-1 items-center gap-2 px-3">
|
|
||||||
<SidebarTrigger />
|
|
||||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbList>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbPage className="line-clamp-1">Dashboard</BreadcrumbPage>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<section className="flex flex-1 flex-col gap-4 p-4">
|
|
||||||
<DashboardPageClient />
|
|
||||||
|
|
||||||
<div className="mx-auto h-24 w-full max-w-3xl rounded-xl bg-muted/50" />
|
|
||||||
<div className="mx-auto h-[100vh] w-full max-w-3xl rounded-xl bg-muted/50" />
|
|
||||||
</section>
|
|
||||||
</SidebarInset>
|
|
||||||
<SidebarRight />
|
|
||||||
</SidebarProvider>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,43 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Exercise, ExerciseType, Workout } from '@/payload-types'
|
||||||
|
import useWorkouts from '@/stores/Workouts'
|
||||||
|
import { PaginatedDocs } from 'payload'
|
||||||
|
import { ReactNode, use, useEffect } from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: ReactNode
|
||||||
|
getTenantWorkoutsPromise: Promise<PaginatedDocs<Workout>>
|
||||||
|
getTenantExercisesPromise: Promise<PaginatedDocs<Exercise>>
|
||||||
|
getTenantExerciseTypesPromise: Promise<PaginatedDocs<ExerciseType>>
|
||||||
|
}
|
||||||
|
const WorkoutsLayoutSuspendedFrontend = (props: Props) => {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
getTenantExerciseTypesPromise,
|
||||||
|
getTenantExercisesPromise,
|
||||||
|
getTenantWorkoutsPromise,
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const exerciseTypeResponse = use(getTenantExerciseTypesPromise)
|
||||||
|
const exerciseResponse = use(getTenantExercisesPromise)
|
||||||
|
const workoutResponse = use(getTenantWorkoutsPromise)
|
||||||
|
|
||||||
|
const { setExerciseTypes, setExercises, setWorkouts } = useWorkouts()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (exerciseTypeResponse?.docs?.length) setExerciseTypes(exerciseTypeResponse)
|
||||||
|
}, [exerciseTypeResponse])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (exerciseResponse?.docs?.length) setExercises(exerciseResponse)
|
||||||
|
}, [exerciseResponse])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (workoutResponse?.docs?.length) setWorkouts(workoutResponse)
|
||||||
|
}, [workoutResponse])
|
||||||
|
|
||||||
|
return <>{children}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WorkoutsLayoutSuspendedFrontend
|
||||||
84
src/app/(frontend)/[tenant]/dashboard/workouts/layout.tsx
Normal file
84
src/app/(frontend)/[tenant]/dashboard/workouts/layout.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { ReactNode } from 'react'
|
||||||
|
import configPromise from '@payload-config'
|
||||||
|
import { getPayload, PaginatedDocs } from 'payload'
|
||||||
|
import WorkoutsLayoutSuspendedFrontend from './layout.client'
|
||||||
|
import { Exercise, ExerciseType } from '@/payload-types'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
params: Promise<{
|
||||||
|
tenant?: string
|
||||||
|
}>
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
const WorkoutsLayout = async (props: Props) => {
|
||||||
|
const { params, children } = props
|
||||||
|
const { tenant: tenantSlug } = await params
|
||||||
|
|
||||||
|
const payload = await getPayload({ config: configPromise })
|
||||||
|
|
||||||
|
const getExerciseTypesPromise = payload.find({
|
||||||
|
collection: 'exerciseTypes',
|
||||||
|
limit: 50,
|
||||||
|
depth: 0,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
'tenant.slug': {
|
||||||
|
equals: tenantSlug,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) as Promise<PaginatedDocs<ExerciseType>>
|
||||||
|
|
||||||
|
const getTenantExercisesPromise = payload.find({
|
||||||
|
collection: 'exercises',
|
||||||
|
limit: 20,
|
||||||
|
depth: 0,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
type: true,
|
||||||
|
muscleGroup: true,
|
||||||
|
difficulty: true,
|
||||||
|
equipmentNeeded: true,
|
||||||
|
instructions: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
'tenant.slug': {
|
||||||
|
equals: tenantSlug,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) as Promise<PaginatedDocs<Exercise>>
|
||||||
|
|
||||||
|
const getWorkoutsPromise = payload.find({
|
||||||
|
collection: 'workouts',
|
||||||
|
limit: 20,
|
||||||
|
depth: 0,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
type: true,
|
||||||
|
difficulty: true,
|
||||||
|
description: true,
|
||||||
|
durationMinutes: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
'tenant.slug': {
|
||||||
|
equals: tenantSlug,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) as Promise<PaginatedDocs<ExerciseType>>
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WorkoutsLayoutSuspendedFrontend
|
||||||
|
getTenantExerciseTypesPromise={getExerciseTypesPromise}
|
||||||
|
getTenantExercisesPromise={getTenantExercisesPromise}
|
||||||
|
getTenantWorkoutsPromise={getWorkoutsPromise}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</WorkoutsLayoutSuspendedFrontend>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WorkoutsLayout
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import useWorkouts from '@/stores/Workouts'
|
||||||
|
|
||||||
|
const WorkoutsPageClient = () => {
|
||||||
|
const { exerciseTypes, exercises, workouts } = useWorkouts()
|
||||||
|
console.log({
|
||||||
|
exerciseTypes,
|
||||||
|
exercises,
|
||||||
|
workouts,
|
||||||
|
})
|
||||||
|
return <div>Workout page client</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WorkoutsPageClient
|
||||||
20
src/app/(frontend)/[tenant]/dashboard/workouts/page.tsx
Normal file
20
src/app/(frontend)/[tenant]/dashboard/workouts/page.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { DashboardContent, DashboardContentSection } from '@/components/Dashboard'
|
||||||
|
// import WorkoutsPageClient from './page.client'
|
||||||
|
|
||||||
|
const WorkoutsPage = () => {
|
||||||
|
return (
|
||||||
|
<DashboardContent className="grid grid-cols-2">
|
||||||
|
<DashboardContentSection className="col-span-1">
|
||||||
|
<h1>Workouts</h1>
|
||||||
|
</DashboardContentSection>
|
||||||
|
<DashboardContentSection className="col-span-1">
|
||||||
|
<h1>Exercises</h1>
|
||||||
|
</DashboardContentSection>
|
||||||
|
<DashboardContentSection className="col-span-2">
|
||||||
|
<h1>Clients</h1>
|
||||||
|
</DashboardContentSection>
|
||||||
|
</DashboardContent>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WorkoutsPage
|
||||||
@ -2,12 +2,12 @@ import configPromise from '@payload-config'
|
|||||||
import { getPayload, PaginatedDocs } from 'payload'
|
import { getPayload, PaginatedDocs } from 'payload'
|
||||||
import { headers as getHeaders } from 'next/headers.js'
|
import { headers as getHeaders } from 'next/headers.js'
|
||||||
import { ReactNode, Suspense } from 'react'
|
import { ReactNode, Suspense } from 'react'
|
||||||
import RootLayoutSuspenseFrontend from './layout.suspense'
|
import RootLayoutSuspenseFrontend from './layout.client'
|
||||||
import { Tenant } from '@/payload-types'
|
import { Tenant } from '@/payload-types'
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: 'Next.js',
|
title: 'Biotracker',
|
||||||
description: 'Generated by Next.js',
|
description: 'Developed by Beitzah.Tech',
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
18
src/components/Dashboard/DashboardContent.tsx
Normal file
18
src/components/Dashboard/DashboardContent.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { cn } from '@/utilities/ui'
|
||||||
|
import { ClassValue } from 'clsx'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: ReactNode
|
||||||
|
className?: ClassValue
|
||||||
|
}
|
||||||
|
const DashboardContent = (props: Props) => {
|
||||||
|
const { children, className } = props
|
||||||
|
return (
|
||||||
|
<div className={cn('flex flex-1 flex-col gap-4 p-4 mx-auto w-full max-w-5xl', className || '')}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DashboardContent
|
||||||
19
src/components/Dashboard/DashboardContentSection.tsx
Normal file
19
src/components/Dashboard/DashboardContentSection.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { cn } from '@/utilities/ui'
|
||||||
|
import { ClassValue } from 'clsx'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: ReactNode
|
||||||
|
className?: ClassValue
|
||||||
|
}
|
||||||
|
const DashboardContentSection = (props: Props) => {
|
||||||
|
const { children, className } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={cn('mx-auto w-full p-6 rounded-xl bg-muted/50', className || '')}>
|
||||||
|
{children}
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DashboardContentSection
|
||||||
4
src/components/Dashboard/index.tsx
Normal file
4
src/components/Dashboard/index.tsx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import DashboardContent from './DashboardContent'
|
||||||
|
import DashboardContentSection from './DashboardContentSection'
|
||||||
|
|
||||||
|
export { DashboardContent, DashboardContentSection }
|
||||||
28
src/stores/Workouts.ts
Normal file
28
src/stores/Workouts.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { create } from 'zustand'
|
||||||
|
import { Exercise, ExerciseType, Workout } from '@/payload-types'
|
||||||
|
import { PaginatedDocs } from 'payload'
|
||||||
|
|
||||||
|
export type WorkoutsProps = {
|
||||||
|
exercises?: PaginatedDocs<Exercise>,
|
||||||
|
exerciseTypes?: PaginatedDocs<ExerciseType>,
|
||||||
|
workouts?: PaginatedDocs<Workout>,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkoutsMethods = {
|
||||||
|
setExercises: (exercises?: PaginatedDocs<Exercise>) => void,
|
||||||
|
setExerciseTypes: (exerciseTypes?: PaginatedDocs<ExerciseType>) => void,
|
||||||
|
setWorkouts: (workouts?: PaginatedDocs<Workout>) => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkoutsStore = WorkoutsProps & WorkoutsMethods
|
||||||
|
|
||||||
|
const useWorkouts = create<WorkoutsStore>((set) => ({
|
||||||
|
exercises: undefined,
|
||||||
|
exerciseTypes: undefined,
|
||||||
|
workouts: undefined,
|
||||||
|
setExercises: (exercises?: PaginatedDocs<Exercise>) => set(() => ({ exercises: exercises })),
|
||||||
|
setExerciseTypes: (exerciseTypes?: PaginatedDocs<ExerciseType>) => set(() => ({ exerciseTypes: exerciseTypes })),
|
||||||
|
setWorkouts: (workouts?: PaginatedDocs<Workout>) => set(() => ({ workouts: workouts })),
|
||||||
|
}))
|
||||||
|
|
||||||
|
export default useWorkouts
|
||||||
Loading…
x
Reference in New Issue
Block a user