Skip to content

Commit

Permalink
feat: add the idea of collections to posts (#313)
Browse files Browse the repository at this point in the history
* add the idea of collections to posts

adds the TreeList view, postType selector on create, and some other initial aspects

* fix: hydration error

* fix: add types

* fix: space

* fix: types

* edit post skeleton

* fix: package lock

* feat: You can now add lessons to a course.

* feat: Adds a toggle to look at all posts or just my posts.

* fix: m3eh

* promises

* pkg
  • Loading branch information
joelhooks authored Nov 21, 2024
1 parent 3768705 commit 115b317
Show file tree
Hide file tree
Showing 26 changed files with 2,403 additions and 193 deletions.
11 changes: 10 additions & 1 deletion apps/egghead/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "concurrently --kill-others-on-fail \"npm:dev:*\"",
"dev:next": "next dev",
"dev:next": "NODE_OPTIONS='--inspect' next dev --turbo",
"dev:inngest": "pnpx inngest-cli@latest dev --no-discovery -u http://localhost:3000/api/inngest",
"dev:party": "pnpx partykit dev",
"tunnel": "ngrok http --domain=neatly-diverse-goldfish.ngrok-free.app 3000",
Expand All @@ -21,6 +21,11 @@
"coupons": "dotenv tsx src/scripts/bootstrap-basic-coupons.ts"
},
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.1.3",
"@atlaskit/pragmatic-drag-and-drop-flourish": "^1.0.4",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
"@atlaskit/pragmatic-drag-and-drop-live-region": "^1.0.3",
"@atlaskit/tokens": "^1.42.1",
"@auth/core": "^0.37.2",
"@aws-sdk/client-s3": "^3.525.0",
"@aws-sdk/s3-request-presigner": "^3.441.0",
Expand Down Expand Up @@ -72,6 +77,7 @@
"inngest": "^3.22.5",
"lodash": "^4.17.21",
"lucide-react": "^0.288.0",
"memoize-one": "^6.0.0",
"nanoid": "^5.0.2",
"next": "15.0.3",
"next-auth": "5.0.0-beta.25",
Expand Down Expand Up @@ -99,6 +105,8 @@
"tailwind-merge": "^1.14.0",
"tailwind-scrollbar": "^3.0.0",
"tailwindcss-animate": "^1.0.7",
"tailwindcss-radix": "^2.8.0",
"tiny-invariant": "^1.3.1",
"typesense": "^1.8.2",
"uploadthing": "6.13.2",
"uuid": "^9.0.1",
Expand All @@ -112,6 +120,7 @@
"@types/pg": "^8.11.6",
"@types/pluralize": "^0.0.33",
"@types/react": "npm:[email protected]",
"@types/react-dom": "npm:[email protected]",
"@types/react-gravatar": "^2.6.13",
"concurrently": "^8.2.2",
"dotenv-cli": "^7.3.0",
Expand Down
38 changes: 31 additions & 7 deletions apps/egghead/src/app/(content)/posts/[slug]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@ import { subject } from '@casl/ability'

import { EditPostForm } from '../../_components/edit-post-form'

function EditPostSkeleton() {
return (
<div className="animate-pulse space-y-6 p-6">
<div className="h-8 w-3/4 rounded bg-gray-200" />
<div className="space-y-4">
<div className="h-40 rounded bg-gray-200" />
<div className="h-12 w-1/2 rounded bg-gray-200" />
</div>
<div className="space-y-2">
<div className="h-4 w-1/4 rounded bg-gray-200" />
<div className="h-10 rounded bg-gray-200" />
</div>
<div className="flex gap-2">
<div className="h-10 w-24 rounded bg-gray-200" />
<div className="h-10 w-24 rounded bg-gray-200" />
</div>
</div>
)
}

export const dynamic = 'force-dynamic'

export default async function PostPage(props: {
Expand Down Expand Up @@ -40,15 +60,19 @@ export default async function PostPage(props: {
await getAllEggheadTagsCached()
const tagLoader = getTags()

console.log({ videoResourceLoader, tagLoader, post })

return (
<Layout>
<EditPostForm
key={post.id}
post={post}
videoResourceLoader={videoResourceLoader}
videoResourceId={videoResource?.id}
tagLoader={tagLoader}
/>
<React.Suspense fallback={<EditPostSkeleton />}>
<EditPostForm
key={post.id}
post={post}
videoResourceLoader={videoResourceLoader}
videoResourceId={videoResource?.id}
tagLoader={tagLoader}
/>
</React.Suspense>
</Layout>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import { useRouter } from 'next/navigation'
import { PostUploader } from '@/app/(content)/posts/_components/post-uploader'
import { NewResourceWithVideoForm } from '@/components/resources-crud/new-resource-with-video-form'
import { PostSchema } from '@/lib/posts'
import { createPost } from '@/lib/posts-query'
import { getVideoResource } from '@/lib/video-resource-query'
import { signOut } from 'next-auth/react'
Expand All @@ -12,7 +14,6 @@ import {
type ContentResource,
} from '@coursebuilder/core/schemas'
import { Card, CardContent, CardFooter, CardHeader } from '@coursebuilder/ui'
import { NewResourceWithVideoForm } from '@coursebuilder/ui/resources-crud/new-resource-with-video-form'

export function CreatePost() {
const router = useRouter()
Expand All @@ -25,12 +26,13 @@ export function CreatePost() {
`/${pluralize(resource.type)}/${resource.fields?.slug || resource.id}/edit`,
)
}}
createResource={async ({ title, videoResourceId }) => {
createResource={async ({ title, videoResourceId, postType }) => {
try {
return ContentResourceSchema.parse(
await createPost({
title,
videoResourceId,
postType,
}),
)
} catch (error) {
Expand All @@ -41,6 +43,7 @@ export function CreatePost() {
}
}}
getVideoResource={getVideoResource}
availableResourceTypes={['lesson', 'article', 'podcast', 'course']}
>
{(handleSetVideoResourceId: (id: string) => void) => {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import { NewLessonVideoForm } from '@/components/resources-crud/new-lesson-video
import AdvancedTagSelector from '@/components/resources-crud/tag-selector'
import { env } from '@/env.mjs'
import { useTranscript } from '@/hooks/use-transcript'
import { Post, PostSchema, PostTypeSchema } from '@/lib/posts'
import {
Post,
POST_TYPES_WITH_VIDEO,
PostSchema,
PostTypeSchema,
} from '@/lib/posts'
import { addTagToPost, removeTagFromPost } from '@/lib/posts-query'
import { EggheadTag } from '@/lib/tags'
import { api } from '@/trpc/react'
Expand Down Expand Up @@ -109,76 +114,78 @@ export const PostMetadataFormFields: React.FC<{
})
return (
<>
<div>
<Suspense
fallback={
<>
<div className="bg-muted flex aspect-video h-full w-full items-center justify-center p-5">
video is loading
</div>
</>
}
>
{videoResourceId ? (
replacingVideo ? (
<div>
<NewLessonVideoForm
parentResourceId={post.id}
onVideoUploadCompleted={(videoResourceId) => {
setReplacingVideo(false)
setVideoUploadStatus('finalizing upload')
setVideoResourceId(videoResourceId)
}}
onVideoResourceCreated={(videoResourceId) =>
setVideoResourceId(videoResourceId)
}
/>
<Button
variant="ghost"
type="button"
onClick={() => setReplacingVideo(false)}
>
Cancel Replace Video
</Button>
</div>
) : (
{POST_TYPES_WITH_VIDEO.includes(post.fields.postType) && (
<div>
<Suspense
fallback={
<>
{videoResource && videoResource.state === 'ready' ? (
<div>
<PostPlayer videoResource={videoResource} />
<Button
variant="ghost"
type="button"
onClick={() => setReplacingVideo(true)}
>
Replace Video
</Button>
</div>
) : videoResource ? (
<div className="bg-muted flex aspect-video h-full w-full items-center justify-center p-5">
video is {videoResource.state}
</div>
) : (
<div className="bg-muted flex aspect-video h-full w-full items-center justify-center p-5">
video is {videoUploadStatus}
</div>
)}
<div className="bg-muted flex aspect-video h-full w-full items-center justify-center p-5">
video is loading
</div>
</>
)
) : (
<NewLessonVideoForm
parentResourceId={post.id}
onVideoUploadCompleted={(videoResourceId) => {
setVideoUploadStatus('finalizing upload')
setVideoResourceId(videoResourceId)
}}
onVideoResourceCreated={(videoResourceId) =>
setVideoResourceId(videoResourceId)
}
/>
)}
</Suspense>
</div>
}
>
{videoResourceId ? (
replacingVideo ? (
<div>
<NewLessonVideoForm
parentResourceId={post.id}
onVideoUploadCompleted={(videoResourceId) => {
setReplacingVideo(false)
setVideoUploadStatus('finalizing upload')
setVideoResourceId(videoResourceId)
}}
onVideoResourceCreated={(videoResourceId) =>
setVideoResourceId(videoResourceId)
}
/>
<Button
variant="ghost"
type="button"
onClick={() => setReplacingVideo(false)}
>
Cancel Replace Video
</Button>
</div>
) : (
<>
{videoResource && videoResource.state === 'ready' ? (
<div>
<PostPlayer videoResource={videoResource} />
<Button
variant="ghost"
type="button"
onClick={() => setReplacingVideo(true)}
>
Replace Video
</Button>
</div>
) : videoResource ? (
<div className="bg-muted flex aspect-video h-full w-full items-center justify-center p-5">
video is {videoResource.state}
</div>
) : (
<div className="bg-muted flex aspect-video h-full w-full items-center justify-center p-5">
video is {videoUploadStatus}
</div>
)}
</>
)
) : (
<NewLessonVideoForm
parentResourceId={post.id}
onVideoUploadCompleted={(videoResourceId) => {
setVideoUploadStatus('finalizing upload')
setVideoResourceId(videoResourceId)
}}
onVideoResourceCreated={(videoResourceId) =>
setVideoResourceId(videoResourceId)
}
/>
)}
</Suspense>
</div>
)}
<FormField
control={form.control}
name="id"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Post, PostSchema } from '@/lib/posts'
import { updatePost } from '@/lib/posts-query'
import { EggheadTag } from '@/lib/tags'
import { zodResolver } from '@hookform/resolvers/zod'
import { CheckIcon } from 'lucide-react'
import { CheckIcon, ListOrderedIcon } from 'lucide-react'
import { useSession } from 'next-auth/react'
import { useTheme } from 'next-themes'
import { useForm, type UseFormReturn } from 'react-hook-form'
Expand All @@ -21,6 +21,7 @@ import { VideoResource } from '@coursebuilder/core/schemas/video-resource'
import { EditResourcesFormDesktop } from '@coursebuilder/ui/resources-crud/edit-resources-form-desktop'

import PublishPostChecklist from './publish-post-checklist'
import { ResourceResourcesList } from './resource-resources-list'

const NewPostFormSchema = z.object({
title: z.string().min(2).max(90),
Expand Down Expand Up @@ -93,7 +94,24 @@ export function EditPostForm({
hostUrl={env.NEXT_PUBLIC_PARTY_KIT_URL}
user={session?.data?.user}
onSave={onPostSave}
theme={theme}
tools={[
{
id: 'resources',
icon: () => (
<ListOrderedIcon
strokeWidth={1.5}
size={24}
width={18}
height={18}
/>
),
toolComponent: (
<div className="h-[var(--pane-layout-height)] overflow-y-auto py-5">
<ResourceResourcesList resource={post} />
</div>
),
},
{
id: 'publish-checklist',
label: 'Publish Checklist',
Expand All @@ -104,7 +122,6 @@ export function EditPostForm({
},
{ id: 'assistant' },
]}
theme={theme}
>
<PostMetadataFormFields
form={form}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client'

import { useRouter, useSearchParams } from 'next/navigation'

import { Button } from '@coursebuilder/ui'

export function PostsFilterToggle({ canManageAll }: { canManageAll: boolean }) {
const router = useRouter()
const searchParams = useSearchParams()
const showingAll = searchParams.get('view') === 'all'

if (!canManageAll) return null

const handleToggle = () => {
const newPath = showingAll ? '/posts' : '/posts?view=all'
router.push(newPath)
}

return (
<Button
variant="ghost"
size="sm"
onClick={handleToggle}
className="text-muted-foreground hover:text-foreground"
>
{showingAll ? '👤 Show My Posts' : '👥 Show All Posts'}
</Button>
)
}
Loading

0 comments on commit 115b317

Please sign in to comment.