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'
|
||||
|
||||
import { DashboardContentSection } from '@/components/Dashboard'
|
||||
import { Tenant, User } from '@/payload-types'
|
||||
import useGlobal from '@/stores'
|
||||
|
||||
type Props = {
|
||||
user?: User
|
||||
tenant?: Tenant
|
||||
}
|
||||
|
||||
const DashboardPageClient = (props?: Props) => {
|
||||
const DashboardPageClient = () => {
|
||||
const { user, tenant } = useGlobal()
|
||||
return (
|
||||
<div className="mx-auto h-24 w-full max-w-3xl rounded-xl bg-muted/50">
|
||||
<p>Testing Dashboard Zustand Data Here</p>
|
||||
<p>{user?.email}</p>
|
||||
<p>{tenant?.name}</p>
|
||||
</div>
|
||||
<DashboardContentSection>
|
||||
<p>Dashboard</p>
|
||||
<p>Username: {user?.username}</p>
|
||||
<p>User Email: {user?.email}</p>
|
||||
<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'
|
||||
|
||||
export default function Page() {
|
||||
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>
|
||||
<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>
|
||||
)
|
||||
return <DashboardPageClient />
|
||||
}
|
||||
|
||||
@ -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 { headers as getHeaders } from 'next/headers.js'
|
||||
import { ReactNode, Suspense } from 'react'
|
||||
import RootLayoutSuspenseFrontend from './layout.suspense'
|
||||
import RootLayoutSuspenseFrontend from './layout.client'
|
||||
import { Tenant } from '@/payload-types'
|
||||
|
||||
export const metadata = {
|
||||
title: 'Next.js',
|
||||
description: 'Generated by Next.js',
|
||||
title: 'Biotracker',
|
||||
description: 'Developed by Beitzah.Tech',
|
||||
}
|
||||
|
||||
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