Skip to content

Commit

Permalink
feat: connect questions to video
Browse files Browse the repository at this point in the history
  • Loading branch information
ezhil56x committed Oct 5, 2024
1 parent 328721b commit 0fc8190
Show file tree
Hide file tree
Showing 16 changed files with 591 additions and 58 deletions.
357 changes: 355 additions & 2 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- AlterTable
ALTER TABLE "Question" ADD COLUMN "videoId" INTEGER;

-- CreateIndex
CREATE INDEX "Question_videoId_idx" ON "Question"("videoId");

-- AddForeignKey
ALTER TABLE "Question" ADD CONSTRAINT "Question_videoId_fkey" FOREIGN KEY ("videoId") REFERENCES "Content"("id") ON DELETE SET NULL ON UPDATE CASCADE;
4 changes: 4 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ model Content {
comments Comment[]
commentsCount Int @default(0)
bookmark Bookmark[]
questions Question[]
}

model CourseContent {
Expand Down Expand Up @@ -266,9 +267,12 @@ model Question {
answers Answer[]
votes Vote[]
tags String[]
video Content? @relation(fields: [videoId], references: [id])
videoId Int?
updatedAt DateTime @updatedAt
@@index([authorId])
@@index([videoId])
}

model Answer {
Expand Down
3 changes: 2 additions & 1 deletion src/actions/question/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const createQuestionHandler = async (
};
}

const { title, content, tags } = data;
const { title, content, tags, videoId } = data;

// Create initial slug
let slug = generateHandle(title);
Expand Down Expand Up @@ -61,6 +61,7 @@ const createQuestionHandler = async (
title,
content,
tags,
videoId,
authorId: session.user.id,
slug, // Include the slug
},
Expand Down
2 changes: 2 additions & 0 deletions src/actions/question/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ export const QuestionInsertSchema = z.object({
title: z.string().min(5, 'Question title too short'),
content: z.string().min(0, 'Question content too short'),
tags: z.array(z.string()).optional(),
videoId: z.number().optional(),
});

export const QuestionUpdateSchema = z.object({
title: z.string().min(5, 'Question title too short'),
content: z.string().min(0, 'Question content too short'),
tags: z.array(z.string()).optional(),
videoId: z.number().optional(),
questionId: z.number(),
});
export const QuestionDeleteSchema = z.object({
Expand Down
15 changes: 14 additions & 1 deletion src/actions/question/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ export interface QuestionQuery {
name: boolean;
};
};
video: {
select: {
id: boolean;
title: boolean;
};
};
votes: {
where: {
userId: string | undefined;
Expand All @@ -57,7 +63,7 @@ export interface QuestionQuery {
};
where?: {
authorId?: string;

videoId?: number;
title?: {
contains: string;
};
Expand All @@ -76,13 +82,20 @@ export interface Author {
email?: string | null;
}

export interface Video {
id: number;
title: string;
}

export interface ExtendedQuestion extends Question {
author: Author;
video: Video;
votes: any[];
}

export interface ExtendedAnswer extends Answer {
author: Author;
video: Video;
votes: any[];
responses: ExtendedAnswer[];
}
1 change: 1 addition & 0 deletions src/actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface QueryParams {
page?: number;
tabtype?: TabType;
search?: string;
videoId?: number;
date?: string;
type?: CommentType;
parentId?: number;
Expand Down
6 changes: 6 additions & 0 deletions src/app/(main)/(pages)/question/[slug]/@question/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ const SingleQuestionPage = async ({
name: true,
},
},
video: {
select: {
id: true,
title: true,
},
},
votes: {
where: {
userId: sessionId,
Expand Down
32 changes: 22 additions & 10 deletions src/app/(main)/(pages)/question/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,32 @@ const getQuestionsWithQuery = async (
select: { userId: true, voteType: true },
},
author: { select: { id: true, name: true } },
video: {
select: {
id: true,
title: true,
},
},
},
};

const searchQuery = searchParams.search
? {
where: {
...additionalQuery.where,
title: {
contains: searchParams.search,
mode: 'insensitive',
const searchQuery =
searchParams.search || searchParams.videoId
? {
where: {
...additionalQuery.where,
...(searchParams.search && {
title: {
contains: searchParams.search,
mode: 'insensitive',
},
}),
...(searchParams.videoId && {
videoId: parseInt(searchParams.videoId.toString(), 10),
}),
},
},
}
: {};
}
: {};

const dateFilter = searchParams.date;
if (dateFilter) {
Expand Down
62 changes: 62 additions & 0 deletions src/components/NewPostDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { getUpdatedUrl, searchParamsToObject } from '@/lib/utils';
import { FormPostInput } from './posts/form/form-input';
import { FormPostErrors } from './posts/form/form-errors';
import { X } from 'lucide-react';
import { SearchBar } from './search/SearchBar';

export const NewPostDialog = () => {
const { theme } = useTheme();
Expand All @@ -29,6 +30,8 @@ export const NewPostDialog = () => {
const tagInputRef = useRef<HTMLInputElement | null>(null);
const [value, setValue] = useState<string>('**Hello world!!!**');
const [tags, setTags] = useState<string[]>([]);
const [videoId, setVideoId] = useState<string>('');
const [videoTitle, setVideoTitle] = useState<string>('');
const containerRef = useRef<HTMLDivElement>(null);
const { ref, onOpen, onClose } = useModal();
const handleMarkdownChange = (newValue?: string) => {
Expand All @@ -55,6 +58,8 @@ export const NewPostDialog = () => {
toast.success(`Question "${data.title}" created`);
formRef?.current?.reset();
setValue('');
setVideoId('');
setVideoTitle('');
router.push(`/question/${data.slug}`);
setTags([]);
handleOnCloseClick();
Expand All @@ -76,10 +81,12 @@ export const NewPostDialog = () => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const title = formData.get('title');
const videoId = formData.get('videoId');
execute({
title: title?.toString() || '',
content: value,
tags,
videoId: videoId ? parseInt(videoId.toString(), 10) : undefined,
});
};

Expand All @@ -102,6 +109,19 @@ export const NewPostDialog = () => {
setTags(tags.filter((t) => t !== tag));
};

const handleSearch = (
videoUrl?: string,
videoId?: number,
videoTitle?: string,
) => {
if (videoUrl && videoId && videoTitle) {
setVideoId(videoId.toString());
setVideoTitle(videoTitle);
} else {
toast.error('Something went wrong while selecting the video');
}
};

return (
<Modal ref={ref} onClose={handleOnCloseClick}>
<div className="fixed inset-0 bg-black/50 backdrop-blur-md" />
Expand Down Expand Up @@ -176,6 +196,48 @@ export const NewPostDialog = () => {
/>
</div>
</div>
<div className="flex w-full flex-col gap-2">
<h3 className="wmde-markdown-var text-lg font-bold tracking-tighter">
Link to a video
</h3>

{videoId ? (
<div className="flex w-full items-center gap-2">
<FormPostInput
id="videoTitle"
placeholder="Select a video from the search"
value={videoTitle}
errors={fieldErrors}
className="w-full"
disabled={true}
/>
<Button
type="button"
className="flex-shrink-0"
onClick={() => {
setVideoId('');
setVideoTitle('');
}}
>
Clear
</Button>
</div>
) : (
<SearchBar
onCardClick={handleSearch}
shouldRedirect={false}
/>
)}

{videoId && (
<FormPostInput
id="videoId"
value={videoId}
errors={fieldErrors}
type="hidden"
/>
)}
</div>

<div
data-color-mode={theme}
Expand Down
16 changes: 12 additions & 4 deletions src/components/admin/ContentRendererClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,19 @@ export const ContentRendererClient = ({
<h2 className="line-clamp-2 text-wrap text-2xl font-extrabold capitalize tracking-tight text-primary md:text-3xl">
{content.title}
</h2>
{metadata.slides ? (
<Link href={metadata.slides} target="_blank">
<Button className="gap-2">Lecture Slides</Button>
<div className="flex gap-2">
<Link href={'/question?newPost=open'}>
<Button className="gap-2">Ask a question</Button>
</Link>
) : null}
<Link href={`/question?videoId=${content.id}`}>
<Button className="gap-2">View questions</Button>
</Link>
{metadata.slides ? (
<Link href={metadata.slides} target="_blank">
<Button className="gap-2">Lecture Slides</Button>
</Link>
) : null}
</div>
</div>

{!showChapters && metadata.segments?.length > 0 && (
Expand Down
67 changes: 37 additions & 30 deletions src/components/posts/PostCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,38 +183,45 @@ const PostCard: React.FC<IProps> = ({
</div>
)}

<div className="flex flex-wrap items-center gap-2">
<VoteForm
upvotes={post.upvotes}
downvotes={post.downvotes}
questionId={isAnswer ? undefined : post.id}
answerId={isAnswer ? post.id : undefined}
key={post.id}
votesArr={post.votes || []}
slug={isExtendedQuestion(post) ? post.slug : ''}
/>
{reply && (
<Button
variant="outline"
size="sm"
onClick={(e) => {
e.stopPropagation();
setEnableReply((prev) => !prev);
}}
className="text-xs sm:text-sm"
>
<Reply className="mr-1 size-4" />
{enableReply ? 'Close' : 'Reply'}
</Button>
)}
{!isAnswer && (
<p className="text-xs tracking-tight text-primary/70 sm:text-sm">
{formatNumber(post.totalanswers)}{' '}
{post.totalanswers === 1 ? 'reply' : 'replies'}
</p>
<div className="flex flex-wrap items-center gap-4">
<div className="flex flex-row items-center gap-2">
<VoteForm
upvotes={post.upvotes}
downvotes={post.downvotes}
questionId={isAnswer ? undefined : post.id}
answerId={isAnswer ? post.id : undefined}
key={post.id}
votesArr={post.votes || []}
slug={isExtendedQuestion(post) ? post.slug : ''}
/>
{reply && (
<Button
variant="outline"
size="sm"
onClick={(e) => {
e.stopPropagation();
setEnableReply((prev) => !prev);
}}
className="text-xs sm:text-sm"
>
<Reply className="mr-1 size-4" />
{enableReply ? 'Close' : 'Reply'}
</Button>
)}
{!isAnswer && (
<p className="text-xs tracking-tight text-primary/70 sm:text-sm">
{formatNumber(post.totalanswers)}{' '}
{post.totalanswers === 1 ? 'reply' : 'replies'}
</p>
)}
</div>
{post.video && (
<div className="flex items-center gap-2 sm:ml-auto sm:gap-2 md:ml-0 lg:ml-auto">
<Tag name={`lecture:${post.video.id}`} />
<Tag name={`${post.video.title}`} />
</div>
)}
</div>

{enableReply && (
<form
onSubmit={handleSubmit}
Expand Down
Loading

0 comments on commit 0fc8190

Please sign in to comment.