mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-06 18:31:00 +00:00
Improvements to courseform ui, fix draftcourse delete
This commit is contained in:
parent
128234c7ad
commit
b2d9d2bbe6
@ -80,6 +80,7 @@ model Course {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Additional resources
|
||||||
model Resource {
|
model Resource {
|
||||||
id String @id // Client generates UUID
|
id String @id // Client generates UUID
|
||||||
userId String
|
userId String
|
||||||
@ -93,6 +94,7 @@ model Resource {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Additional resources
|
||||||
model Draft {
|
model Draft {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
userId String
|
userId String
|
||||||
|
@ -2,12 +2,22 @@ import React from "react";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||||
import { formatUnixTimestamp } from "@/utils/time";
|
import { formatUnixTimestamp } from "@/utils/time";
|
||||||
|
import { Button } from 'primereact/button';
|
||||||
|
|
||||||
const SelectedContentItem = ({ content }) => {
|
const SelectedContentItem = ({ content, onRemove }) => {
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full border-2 rounded-lg border-gray-700 p-2 rounded-tr-none rounded-br-none">
|
<div className="w-full border-2 rounded-lg border-gray-700 p-2 rounded-tr-none rounded-br-none relative">
|
||||||
|
<Button
|
||||||
|
icon="pi pi-times"
|
||||||
|
className="absolute top-2 right-2 py-1 px-2 w-auto h-auto"
|
||||||
|
severity="danger"
|
||||||
|
size="small"
|
||||||
|
rounded
|
||||||
|
onClick={onRemove}
|
||||||
|
aria-label="Remove"
|
||||||
|
/>
|
||||||
<div className="flex flex-row gap-4">
|
<div className="flex flex-row gap-4">
|
||||||
<Image
|
<Image
|
||||||
alt="content thumbnail"
|
alt="content thumbnail"
|
||||||
|
@ -13,6 +13,7 @@ import { useNDKContext } from "@/context/NDKContext";
|
|||||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||||
import { findKind0Fields } from '@/utils/nostr';
|
import { findKind0Fields } from '@/utils/nostr';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
|
import { formatDateTime } from '@/utils/time';
|
||||||
import 'primeicons/primeicons.css';
|
import 'primeicons/primeicons.css';
|
||||||
|
|
||||||
const MDDisplay = dynamic(
|
const MDDisplay = dynamic(
|
||||||
@ -353,6 +354,7 @@ export default function DraftCourseDetails({ processedEvent, draftId, lessons })
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="pt-8 text-sm text-gray-400">{processedEvent?.createdAt && formatDateTime(processedEvent?.createdAt)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col max-tab:mt-12 max-mob:mt-12'>
|
<div className='flex flex-col max-tab:mt-12 max-mob:mt-12'>
|
||||||
{processedEvent && (
|
{processedEvent && (
|
||||||
|
@ -3,6 +3,7 @@ import { Tag } from "primereact/tag";
|
|||||||
import { Message } from "primereact/message";
|
import { Message } from "primereact/message";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||||
|
import { formatDateTime, formatUnixTimestamp } from "@/utils/time";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
const MDDisplay = dynamic(
|
const MDDisplay = dynamic(
|
||||||
@ -17,6 +18,7 @@ const DraftCourseLesson = ({ lesson, course }) => {
|
|||||||
const [isPublished, setIsPublished] = useState(false);
|
const [isPublished, setIsPublished] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lesson?.kind) {
|
if (lesson?.kind) {
|
||||||
|
console.log(lesson);
|
||||||
setIsPublished(true);
|
setIsPublished(true);
|
||||||
} else {
|
} else {
|
||||||
setIsPublished(false);
|
setIsPublished(false);
|
||||||
@ -52,6 +54,13 @@ const DraftCourseLesson = ({ lesson, course }) => {
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
{
|
||||||
|
lesson?.createdAt ? (
|
||||||
|
<p className="pt-8 text-sm text-gray-400">{formatDateTime(lesson?.createdAt)}</p>
|
||||||
|
) : (
|
||||||
|
<p className="pt-8 text-sm text-gray-400">{formatUnixTimestamp(lesson?.published_at)}</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
<div className='flex flex-row w-full mt-6 items-center'>
|
<div className='flex flex-row w-full mt-6 items-center'>
|
||||||
{isPublished ? (
|
{isPublished ? (
|
||||||
<Message severity="success" text="published" />
|
<Message severity="success" text="published" />
|
||||||
|
@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Dropdown } from 'primereact/dropdown';
|
import { Dropdown } from 'primereact/dropdown';
|
||||||
import { Button } from 'primereact/button';
|
import { Button } from 'primereact/button';
|
||||||
import { Dialog } from 'primereact/dialog';
|
import { Dialog } from 'primereact/dialog';
|
||||||
|
import { Accordion, AccordionTab } from 'primereact/accordion';
|
||||||
import ResourceForm from '../ResourceForm';
|
import ResourceForm from '../ResourceForm';
|
||||||
import WorkshopForm from '../WorkshopForm';
|
import WorkshopForm from '../WorkshopForm';
|
||||||
import ContentDropdownItem from '@/components/content/dropdowns/ContentDropdownItem';
|
import ContentDropdownItem from '@/components/content/dropdowns/ContentDropdownItem';
|
||||||
@ -82,18 +83,31 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent }) => {
|
|||||||
console.log("contentOptions", contentOptions);
|
console.log("contentOptions", contentOptions);
|
||||||
}, [contentOptions]);
|
}, [contentOptions]);
|
||||||
|
|
||||||
const handleContentSelect = (selectedContent) => {
|
const handleContentSelect = (selectedContent, index) => {
|
||||||
if (selectedContent && !lessons.some(lesson => lesson.id === selectedContent.id)) {
|
if (selectedContent) {
|
||||||
setLessons([...lessons, { ...selectedContent, index: lessons.length }]);
|
const updatedLessons = [...lessons];
|
||||||
|
updatedLessons[index] = { ...selectedContent, index };
|
||||||
|
setLessons(updatedLessons);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRemoveContent = (index) => {
|
||||||
|
const updatedLessons = [...lessons];
|
||||||
|
updatedLessons[index] = { index }; // Reset the lesson to an empty state
|
||||||
|
setLessons(updatedLessons);
|
||||||
|
};
|
||||||
|
|
||||||
const removeLesson = (index) => {
|
const removeLesson = (index) => {
|
||||||
const updatedLessons = lessons.filter((_, i) => i !== index)
|
const updatedLessons = lessons.filter((_, i) => i !== index)
|
||||||
.map((lesson, newIndex) => ({ ...lesson, index: newIndex }));
|
.map((lesson, newIndex) => ({ ...lesson, index: newIndex }));
|
||||||
setLessons(updatedLessons);
|
setLessons(updatedLessons);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addNewLesson = (e) => {
|
||||||
|
e.preventDefault(); // Prevent form submission
|
||||||
|
setLessons([...lessons, { index: lessons.length }]);
|
||||||
|
};
|
||||||
|
|
||||||
const handleNewResourceSave = (newResource) => {
|
const handleNewResourceSave = (newResource) => {
|
||||||
setLessons([...lessons, { ...newResource, index: lessons.length }]);
|
setLessons([...lessons, { ...newResource, index: lessons.length }]);
|
||||||
setShowResourceForm(false);
|
setShowResourceForm(false);
|
||||||
@ -104,35 +118,58 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent }) => {
|
|||||||
setShowWorkshopForm(false);
|
setShowWorkshopForm(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AccordianHeader = ({lesson, index}) => {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<p>Lesson {index + 1}</p>
|
||||||
|
<Button icon="pi pi-times" className="p-button-danger" onClick={() => removeLesson(index)} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<h3>Lessons</h3>
|
<h3>Lessons</h3>
|
||||||
|
<Accordion multiple>
|
||||||
{lessons.map((lesson, index) => (
|
{lessons.map((lesson, index) => (
|
||||||
<div key={lesson.id} className="flex mt-4">
|
<AccordionTab key={index} header={<AccordianHeader lesson={lesson} index={index} />}>
|
||||||
<SelectedContentItem content={{ ...lesson, index }} />
|
|
||||||
<Button
|
|
||||||
icon="pi pi-times"
|
|
||||||
className="p-button-danger rounded-tl-none rounded-bl-none"
|
|
||||||
onClick={() => removeLesson(index)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className="p-inputgroup flex-1 mt-4">
|
<div className="p-inputgroup flex-1 mt-4">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
options={contentOptions}
|
options={contentOptions}
|
||||||
onChange={(e) => handleContentSelect(e.value)}
|
onChange={(e) => handleContentSelect(e.value, index)}
|
||||||
placeholder="Select Existing Lesson"
|
placeholder="Select Existing Lesson"
|
||||||
optionLabel="label"
|
optionLabel="label"
|
||||||
optionGroupLabel="label"
|
optionGroupLabel="label"
|
||||||
optionGroupChildren="items"
|
optionGroupChildren="items"
|
||||||
itemTemplate={(option) => <ContentDropdownItem content={option.value} onSelect={handleContentSelect} />}
|
itemTemplate={(option) => <ContentDropdownItem content={option.value} onSelect={(content) => handleContentSelect(content, index)} />}
|
||||||
value={null}
|
value={lesson.id ? lesson : null}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex mt-4">
|
<div className="flex mt-4">
|
||||||
|
{lesson.id ? null : (
|
||||||
|
<>
|
||||||
<Button label="New Resource" onClick={() => setShowResourceForm(true)} className="mr-2" />
|
<Button label="New Resource" onClick={() => setShowResourceForm(true)} className="mr-2" />
|
||||||
<Button label="New Workshop" onClick={() => setShowWorkshopForm(true)} />
|
<Button label="New Workshop" onClick={() => setShowWorkshopForm(true)} className="mr-2" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{lesson.id && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<SelectedContentItem
|
||||||
|
content={lesson}
|
||||||
|
onRemove={() => handleRemoveContent(index)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AccordionTab>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
|
<Button
|
||||||
|
label="Add New Lesson"
|
||||||
|
onClick={addNewLesson}
|
||||||
|
className="mt-4"
|
||||||
|
type="button" // Explicitly set type to "button"
|
||||||
|
/>
|
||||||
|
|
||||||
<Dialog visible={showResourceForm} onHide={() => setShowResourceForm(false)} header="Create New Resource">
|
<Dialog visible={showResourceForm} onHide={() => setShowResourceForm(false)} header="Create New Resource">
|
||||||
<ResourceForm onSave={handleNewResourceSave} isPaid={isPaidCourse} />
|
<ResourceForm onSave={handleNewResourceSave} isPaid={isPaidCourse} />
|
||||||
|
@ -91,7 +91,15 @@ export const updateCourseDraft = async (id, data) => {
|
|||||||
|
|
||||||
// Delete a CourseDraft by its ID
|
// Delete a CourseDraft by its ID
|
||||||
export const deleteCourseDraft = async (id) => {
|
export const deleteCourseDraft = async (id) => {
|
||||||
|
return await prisma.$transaction(async (prisma) => {
|
||||||
|
// First, delete all associated DraftLessons
|
||||||
|
await prisma.draftLesson.deleteMany({
|
||||||
|
where: { courseDraftId: id },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then, delete the CourseDraft
|
||||||
return await prisma.courseDraft.delete({
|
return await prisma.courseDraft.delete({
|
||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
@ -1,6 +1,12 @@
|
|||||||
export const formatUnixTimestamp = (time) => {
|
export const formatUnixTimestamp = (time) => {
|
||||||
const date = new Date(time * 1000); // Convert to milliseconds
|
const date = new Date(time * 1000); // Convert to milliseconds
|
||||||
return date.toDateString();
|
return date.toLocaleDateString("en-US", {
|
||||||
|
timeZone: "UTC",
|
||||||
|
weekday: "long",
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatDateTime = (isoDate) => {
|
export const formatDateTime = (isoDate) => {
|
||||||
@ -12,10 +18,7 @@ export const formatDateTime = (isoDate) => {
|
|||||||
weekday: "long", // "long" for full name, "short" for abbreviated
|
weekday: "long", // "long" for full name, "short" for abbreviated
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "long", // "long" for full name, "short" for abbreviated
|
month: "long", // "long" for full name, "short" for abbreviated
|
||||||
day: "numeric",
|
day: "numeric"
|
||||||
hour: "numeric",
|
|
||||||
minute: "numeric",
|
|
||||||
second: "numeric",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return formattedDate;
|
return formattedDate;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user