feat: various UI updates

This commit is contained in:
swve 2023-06-21 21:13:01 +02:00
parent 52bc9e556b
commit d5ad9e2f2f
10 changed files with 214 additions and 291 deletions

View file

@ -10,6 +10,7 @@ import { Check } from "lucide-react";
import { markActivityAsComplete } from "@services/courses/activity"; import { markActivityAsComplete } from "@services/courses/activity";
import ToolTip from "@components/UI/Tooltip/Tooltip"; import ToolTip from "@components/UI/Tooltip/Tooltip";
import DocumentPdfActivity from "@components/Pages/Activities/DocumentPdf/DocumentPdf"; import DocumentPdfActivity from "@components/Pages/Activities/DocumentPdf/DocumentPdf";
import ActivityIndicators from "@components/Pages/Courses/ActivityIndicators";
interface ActivityClientProps { interface ActivityClientProps {
activityid: string; activityid: string;
@ -35,7 +36,7 @@ function ActivityClient(props: ActivityClientProps) {
return ( return (
<> <>
<ActivityLayout> <div className="max-w-7xl mx-auto px-4 space-y-5 pt-0">
<pre style={{ display: "none" }}>{JSON.stringify(activity, null, 2)}</pre> <pre style={{ display: "none" }}>{JSON.stringify(activity, null, 2)}</pre>
<ActivityTopWrapper> <ActivityTopWrapper>
<ActivityThumbnail> <ActivityThumbnail>
@ -48,31 +49,10 @@ function ActivityClient(props: ActivityClientProps) {
<h1>{course.course.name}</h1> <h1>{course.course.name}</h1>
</ActivityInfo> </ActivityInfo>
</ActivityTopWrapper> </ActivityTopWrapper>
<ChaptersWrapper> <ActivityIndicators current_activity={activityid} orgslug={orgslug} course={course} />
{course.chapters.map((chapter: any) => {
return (
<>
<div style={{ display: "flex", flexDirection: "row" }} key={chapter.chapter_id}>
{chapter.activities.map((activity: any) => {
return (
<ToolTip sideOffset={-5} slateBlack content={activity.name} key={activity.id}>
<Link href={getUriWithOrg(orgslug, "") + `/course/${courseid}/activity/${activity.id.replace("activity_", "")}`}>
<ChapterIndicator
done={course.trail.activities_marked_complete && course.trail.activities_marked_complete.includes(activity.id) && course.trail.status == "ongoing"}
active={"activity_" + activityid === activity.id ? true : false} key={activity.id}
/>
</Link>
</ToolTip>
);
})}
</div>
&nbsp;&nbsp;&nbsp;&nbsp;
</>
);
})}
</ChaptersWrapper>
{activity ? ( {activity ? (
<div className="p-7 pt-2 drop-shadow-sm rounded-lg bg-white">
<CourseContent> <CourseContent>
{activity.type == "dynamic" && <Canva content={activity.content} activity={activity} />} {activity.type == "dynamic" && <Canva content={activity.content} activity={activity} />}
{/* todo : use apis & streams instead of this */} {/* todo : use apis & streams instead of this */}
@ -102,14 +82,14 @@ function ActivityClient(props: ActivityClientProps) {
)} )}
</ActivityMarkerWrapper> </ActivityMarkerWrapper>
</CourseContent> </CourseContent>
</div>
) : (<div></div>)} ) : (<div></div>)}
{<div style={{ height: "100px" }}></div>} {<div style={{ height: "100px" }}></div>}
</ActivityLayout> </div>
</> </>
); );
} }
const ActivityLayout = styled.div``;
const ActivityThumbnail = styled.div` const ActivityThumbnail = styled.div`
padding-right: 30px; padding-right: 30px;
@ -133,32 +113,8 @@ const ActivityInfo = styled.div`
} }
`; `;
const ChaptersWrapper = styled.div`
display: flex;
// row
flex-direction: row;
width: 100%;
width: 1300px;
margin: 0 auto;
`;
const ChapterIndicator = styled.div < { active?: boolean, done?: boolean } > `
border-radius: 20px;
height: 5px;
background: #151515;
border-radius: 3px;
width: 35px;
background-color: ${props => props.done ? "green" : (props.active ? "#9d9d9d" : "black")};
margin: 10px;
margin-bottom: 0px;
margin-left: 0px;
&:hover {
cursor: pointer;
background-color: #9d9d9d;
}
`;
const ActivityTopWrapper = styled.div` const ActivityTopWrapper = styled.div`
width: 1300px; width: 1300px;
@ -171,7 +127,6 @@ const CourseContent = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: white; background-color: white;
min-height: 600px;
`; `;
const ActivityMarkerWrapper = styled.div` const ActivityMarkerWrapper = styled.div`

View file

@ -2,68 +2,74 @@
import { EyeOpenIcon, Pencil2Icon } from "@radix-ui/react-icons"; import { EyeOpenIcon, Pencil2Icon } from "@radix-ui/react-icons";
import { removeCourse, startCourse } from "@services/courses/activity"; import { removeCourse, startCourse } from "@services/courses/activity";
import Link from "next/link"; import Link from "next/link";
import React from "react"; import React, { use } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { getAPIUrl, getBackendUrl, getUriWithOrg } from "@services/config/config"; import { getAPIUrl, getBackendUrl, getUriWithOrg } from "@services/config/config";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
import ToolTip from "@components/UI/Tooltip/Tooltip"; import ToolTip from "@components/UI/Tooltip/Tooltip";
import PageLoading from "@components/Pages/PageLoading"; import PageLoading from "@components/Pages/PageLoading";
import { revalidateTags } from "@services/utils/ts/requests"; import { revalidateTags } from "@services/utils/ts/requests";
import ActivityIndicators from "@components/Pages/Courses/ActivityIndicators";
import { useRouter } from "next/navigation";
const CourseClient = (props: any) => { const CourseClient = (props: any) => {
const courseid = props.courseid; const courseid = props.courseid;
const orgslug = props.orgslug; const orgslug = props.orgslug;
const course = props.course; const course = props.course;
const router = useRouter();
async function startCourseUI() { async function startCourseUI() {
// Create activity // Create activity
await startCourse("course_" + courseid, orgslug); await startCourse("course_" + courseid, orgslug);
revalidateTags(['courses']); revalidateTags(['courses']);
router.refresh();
} }
async function quitCourse() { async function quitCourse() {
// Close activity // Close activity
let activity = await removeCourse("course_" + courseid, orgslug); let activity = await removeCourse("course_" + courseid, orgslug);
// Mutate course // Mutate course
revalidateTags(['courses']); revalidateTags(['courses']);
router.refresh();
} }
console.log(course); console.log(course);
return ( return (
<> <>
{!course ? ( {!course ? (
<PageLoading></PageLoading> <PageLoading></PageLoading>
) : ( ) : (
<CoursePageLayout className="pt-6"> <div className="max-w-7xl mx-auto px-4 py-10 tracking-tight">
<p className="text-lg font-bold">Course</p> <div className="pb-3">
<h1 className="text-3xl font-bold flex items-center space-x-5"> <p className="text-md font-bold text-gray-400 pb-2">Course</p>
{course.course.name}{" "} <h1 className="text-3xl -mt-3 font-bold">
<Link href={getUriWithOrg(orgslug, "") + `/course/${courseid}/edit`} rel="noopener noreferrer"> {course.course.name}
<Pencil2Icon />
</Link>{" "}
</h1> </h1>
</div>
<CourseThumbnailWrapper>
<img src={`${getBackendUrl()}content/uploads/img/${course.course.thumbnail}`} alt="" />
</CourseThumbnailWrapper>
<CourseMetaWrapper> <div className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl relative w-auto h-[300px] bg-cover bg-center mb-4" style={{ backgroundImage: `url(${getBackendUrl()}content/uploads/img/${course.course.thumbnail})` }}>
<CourseMetaLeft>
<h2 className="py-3 font-bold">Description</h2>
<BoxWrapper> </div>
<ActivityIndicators orgslug={orgslug} course={course} />
<div className="flex flex-row pt-10">
<div className="course_metadata_left grow space-y-2">
<h2 className="py-3 text-2xl font-bold">Description</h2>
<StyledBox>
<p className="py-3">{course.course.description}</p> <p className="py-3">{course.course.description}</p>
</BoxWrapper> </StyledBox>
<h2 className="py-3 text-2xl font-bold">What you will learn</h2>
<StyledBox>
<h2 className="py-3 font-bold">What you will learn</h2>
<BoxWrapper>
<p className="py-3">{course.learnings == ![] ? "no data" : course.learnings}</p> <p className="py-3">{course.learnings == ![] ? "no data" : course.learnings}</p>
</BoxWrapper> </StyledBox>
<h2 className="py-3 font-bold">Course Lessons</h2> <h2 className="py-3 text-2xl font-bold">Course Lessons</h2>
<StyledBox>
<BoxWrapper>
{course.chapters.map((chapter: any) => { {course.chapters.map((chapter: any) => {
return ( return (
<div <div
@ -88,142 +94,32 @@ const CourseClient = (props: any) => {
</div> </div>
); );
})} })}
</BoxWrapper> </StyledBox>
</CourseMetaLeft>
<CourseMetaRight> </div>
<div className="course_metadata_right w-64 flex items-center ml-10 h-28 p-3 bg-white rounded-md justify-center drop-shadow-[0_33px_13px_rgba(0,0,0,0.042)] transition-all">
{course.trail.status == "ongoing" ? ( {course.trail.status == "ongoing" ? (
<button style={{ backgroundColor: "red" }} onClick={quitCourse}> <button className="py-2 px-5 rounded-xl text-white font-bold h-12 w-[200px] drop-shadow-md bg-red-600 hover:bg-red-700 hover:cursor-pointer" onClick={quitCourse}>
Quit Course Quit Course
</button> </button>
) : ( ) : (
<button onClick={startCourseUI}>Start Course</button> <button className="py-2 px-5 rounded-xl text-white font-bold h-12 w-[200px] drop-shadow-md bg-black hover:bg-gray-900 hover:cursor-pointer" onClick={startCourseUI}>Start Course</button>
)} )}
</CourseMetaRight> </div>
</CourseMetaWrapper> </div>
</CoursePageLayout> </div>
)} )}
</> </>
); );
}; };
const CourseThumbnailWrapper = styled.div`
display: flex;
padding-bottom: 20px;
img {
width: 100%;
height: 300px;
object-fit: cover;
object-position: center;
background: url(), #d9d9d9;
border: 1px solid rgba(255, 255, 255, 0.19);
box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.42);
border-radius: 7px;
}
`;
const CoursePageLayout = styled.div`
width: 1300px;
margin: 0 auto;
p {
margin-bottom: 0px;
letter-spacing: -0.05em;
color: #727272; const StyledBox = (props: any) => (
font-weight: 700; <div className="p-3 pl-10 bg-white rounded-md w-[100%] h-auto drop-shadow-[0_33px_13px_rgba(0,0,0,0.042)]">
} {props.children}
h1 { </div>
margin-top: 5px;
letter-spacing: -0.05em;
margin-bottom: 10px;
}
`;
const ChaptersWrapper = styled.div` );
display: flex;
width: 100%;
`;
const CourseIndicator = styled.div< { active?: boolean, done?: boolean } >`
border-radius: 20px;
height: 5px;
background: #151515;
border-radius: 3px;
background-color: ${props => props.done ? "green" : "black"};
width: 40px;
margin: 10px;
margin-bottom: 20px;
margin-left: 0px;
&:hover {
cursor: pointer;
background-color: #9d9d9d;
}
`;
const ChapterSeparator = styled.div`
display: flex;
flex-direction: row;
padding-right: 7px;
`;
const BoxWrapper = styled.div`
background: #ffffff;
box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.03);
border-radius: 7px;
padding: 20px;
padding-top: 7px;
padding-left: 30px;
p {
font-family: "DM Sans";
font-style: normal;
font-weight: 500;
line-height: 16px;
letter-spacing: -0.02em;
color: #9d9d9d;
}
`;
const CourseMetaWrapper = styled.div`
display: flex;
justify-content: space-between;
`;
const CourseMetaLeft = styled.div`
width: 80%;
`;
const CourseMetaRight = styled.div`
background: #ffffff;
box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.03);
border-radius: 7px;
padding: 20px;
width: 30%;
display: flex;
height: 100%;
justify-content: center;
margin-left: 50px;
margin-top: 20px;
button {
width: 100%;
height: 50px;
background: #151515;
border-radius: 15px;
border: none;
color: white;
font-weight: 700;
font-family: "DM Sans";
font-size: 16px;
letter-spacing: -0.05em;
transition: all 0.2s ease;
box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.42);
&:hover {
cursor: pointer;
background: #000000;
}
}
`;
export default CourseClient; export default CourseClient;

View file

@ -15,6 +15,7 @@ import { createActivity, createFileActivity, createExternalVideoActivity } from
import { getOrganizationContextInfo } from "@services/organizations/orgs"; import { getOrganizationContextInfo } from "@services/organizations/orgs";
import Modal from "@components/UI/Modal/Modal"; import Modal from "@components/UI/Modal/Modal";
import { denyAccessToUser } from "@services/utils/react/middlewares/views"; import { denyAccessToUser } from "@services/utils/react/middlewares/views";
import { Folders, Package2 } from "lucide-react";
function CourseEdit(params: any) { function CourseEdit(params: any) {
@ -243,21 +244,7 @@ function CourseEdit(params: any) {
<Page> <Page>
<Title> <Title>
Edit Course {" "} Edit Course {" "}
<Modal
isDialogOpen={newChapterModal}
onOpenChange={setNewChapterModal}
minHeight="sm"
dialogContent={<NewChapterModal
closeModal={closeNewChapterModal}
submitChapter={submitChapter}
></NewChapterModal>}
dialogTitle="Create chapter"
dialogDescription="Add a new chapter to the course"
dialogTrigger={
<button> Add chapter +
</button>
}
/>
<button <button
onClick={() => { onClick={() => {
@ -287,7 +274,7 @@ function CourseEdit(params: any) {
<br /> <br />
{winReady && ( {winReady && (
<ChapterlistWrapper> <div className="flex flex-col max-w-7xl justify-center items-center mx-auto">
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
<Droppable key="chapters" droppableId="chapters" type="chapter"> <Droppable key="chapters" droppableId="chapters" type="chapter">
{(provided) => ( {(provided) => (
@ -312,7 +299,24 @@ function CourseEdit(params: any) {
)} )}
</Droppable> </Droppable>
</DragDropContext> </DragDropContext>
</ChapterlistWrapper> <Modal
isDialogOpen={newChapterModal}
onOpenChange={setNewChapterModal}
minHeight="sm"
dialogContent={<NewChapterModal
closeModal={closeNewChapterModal}
submitChapter={submitChapter}
></NewChapterModal>}
dialogTitle="Create chapter"
dialogDescription="Add a new chapter to the course"
dialogTrigger={
<div className="flex max-w-7xl bg-black shadow rounded-md text-white justify-center space-x-2 p-3 w-72 hover:bg-gray-900 hover:cursor-pointer">
<Folders size={20} />
<div>Add chapter +</div>
</div>
}
/>
</div>
)} )}
</Page > </Page >
</> </>
@ -351,9 +355,5 @@ const Page = styled.div`
} }
} }
`; `;
const ChapterlistWrapper = styled.div`
display: flex;
padding-left: 30px;
justify-content: center;
`;
export default CourseEdit; export default CourseEdit;

View file

@ -73,6 +73,7 @@ const OrgHomePage = async (params: any) => {
</div> </div>
{/* Courses */} {/* Courses */}
<div className='h-5'></div>
<Title title="Courses" type="cou" /> <Title title="Courses" type="cou" />
<div className="home_courses flex flex-wrap"> <div className="home_courses flex flex-wrap">
{courses.map((course: any) => ( {courses.map((course: any) => (
@ -92,13 +93,13 @@ const OrgHomePage = async (params: any) => {
}; };
const Title = (props: any) => { const Title = (props: any) => {
return ( return (
<div className="home_category_title flex my-5"> <div className="home_category_title flex my-5 items-center">
<div className="rounded-full ring-1 ring-slate-900/5 shadow-sm p-2 my-auto mr-4"> <div className="rounded-full ring-1 ring-slate-900/5 shadow-sm p-2 my-auto mr-4">
<Image className="" src={props.type == "col" ? CollectionsLogo : CoursesLogo} alt="Courses logo" /> <Image className="" src={props.type == "col" ? CollectionsLogo : CoursesLogo} alt="Courses logo" />
</div> </div>
<h1 className="font-bold text-lg">{props.title}</h1> <h1 className="font-bold text-2xl">{props.title}</h1>
</div> </div>
) )
} }

View file

@ -36,14 +36,6 @@ const InfoCalloutWrapper = styled.div`
} }
`; `;
const DragHandle = styled.div`
position: absolute;
top: 0;
left: 0;
width: 1rem;
height: 100%;
cursor: move;
z-index: 1;
`;
export default InfoCalloutComponent; export default InfoCalloutComponent;

View file

@ -64,7 +64,7 @@ function Canva(props: Editor) {
const CanvaWrapper = styled.div` const CanvaWrapper = styled.div`
padding-top: 20px; padding-top: 20px;
width: 1300px; width: 100%;
margin: 0 auto; margin: 0 auto;
`; `;

View file

@ -11,7 +11,7 @@ function Activity(props: any) {
<Draggable key={props.activity.id} draggableId={props.activity.id} index={props.index}> <Draggable key={props.activity.id} draggableId={props.activity.id} index={props.index}>
{(provided) => ( {(provided) => (
<div <div
className="flex flex-row items-center py-2 my-3 rounded-md justify-center bg-gray-50 hover:bg-gray-100 space-x-2" key={props.activity.id} {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef}> className="flex flex-row items-center py-2 my-3 rounded-md justify-center bg-gray-50 hover:bg-gray-100 space-x-2 w-auto" key={props.activity.id} {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef}>
<p>{props.activity.name} </p> <p>{props.activity.name} </p>
<Link <Link
href={getUriWithOrg(props.orgslug, "") + `/course/${props.courseid}/activity/${props.activity.id.replace("activity_", "")}`} href={getUriWithOrg(props.orgslug, "") + `/course/${props.courseid}/activity/${props.activity.id.replace("activity_", "")}`}

View file

@ -2,6 +2,7 @@ import React from "react";
import styled from "styled-components"; import styled from "styled-components";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import Activity from "./Activity"; import Activity from "./Activity";
import { PlusSquare } from "lucide-react";
function Chapter(props: any) { function Chapter(props: any) {
return ( return (
@ -16,14 +17,7 @@ function Chapter(props: any) {
> >
<h3 className="pt-3 font-bold text-md"> <h3 className="pt-3 font-bold text-md">
{props.info.list.chapter.name} {props.info.list.chapter.name}
<button
onClick={() => {
props.openNewActivityModal(props.info.list.chapter.id);
}}
>
Create Activity
</button>
<button <button
@ -37,13 +31,24 @@ function Chapter(props: any) {
<Droppable key={props.info.list.chapter.id} droppableId={props.info.list.chapter.id} type="activity"> <Droppable key={props.info.list.chapter.id} droppableId={props.info.list.chapter.id} type="activity">
{(provided) => ( {(provided) => (
<ActivitiesList {...provided.droppableProps} ref={provided.innerRef}> <ActivitiesList {...provided.droppableProps} ref={provided.innerRef}>
<div className="flex flex-col ">
{props.info.list.activities.map((activity: any, index: any) => ( {props.info.list.activities.map((activity: any, index: any) => (
<Activity orgslug={props.orgslug} courseid={props.courseid} key={activity.id} activity={activity} index={index}></Activity> <Activity orgslug={props.orgslug} courseid={props.courseid} key={activity.id} activity={activity} index={index}></Activity>
))} ))}
{provided.placeholder} {provided.placeholder}
<div onClick={() => {
props.openNewActivityModal(props.info.list.chapter.id);
}} className="flex space-x-2 items-center py-2 my-3 rounded-md justify-center outline outline-3 text-slate-500 outline-slate-200 bg-slate-50 hover:cursor-pointer">
<PlusSquare className="" size={17} />
<div className="text-sm mx-auto my-auto items-center font-bold">Add Activity</div>
</div>
</div>
</ActivitiesList> </ActivitiesList>
)} )}
</Droppable> </Droppable>
</ChapterWrapper> </ChapterWrapper>
)} )}
</Draggable> </Draggable>

View file

@ -0,0 +1,73 @@
import ToolTip from '@components/UI/Tooltip/Tooltip'
import { getUriWithOrg } from '@services/config/config'
import Link from 'next/link'
import React from 'react'
interface Props {
course: any
orgslug: string
current_activity?: any
}
function ActivityIndicators(props: Props) {
const course = props.course
const orgslug = props.orgslug
const courseid = course.course.course_id.replace("course_", "")
const done_activity_style = 'bg-teal-600 hover:bg-teal-700'
const black_activity_style = 'bg-black hover:bg-gray-700'
const current_activity_style = 'bg-gray-600 animate-pulse hover:bg-gray-700'
function isActivityDone(activity: any) {
if (course.trail.activities_marked_complete && course.trail.activities_marked_complete.includes(activity.id) && course.trail.status == "ongoing") {
return true
}
return false
}
function isActivityCurrent(activity: any) {
let activityid = activity.id.replace("activity_", "")
if (props.current_activity && props.current_activity == activityid) {
return true
}
return false
}
function getActivityClass(activity: any) {
if (isActivityDone(activity)) {
return done_activity_style
}
if (isActivityCurrent(activity)) {
return current_activity_style
}
return black_activity_style
}
return (
<div className='grid grid-flow-col justify-stretch space-x-6'>
{course.chapters.map((chapter: any) => {
return (
<>
<div className='grid grid-flow-col justify-stretch space-x-2'>
{chapter.activities.map((activity: any) => {
return (
<ToolTip sideOffset={-5} slateBlack content={activity.name} key={activity.id}>
<Link href={getUriWithOrg(orgslug, "") + `/course/${courseid}/activity/${activity.id.replace("activity_", "")}`}>
<div className={`h-[7px] w-auto ${getActivityClass(activity)} rounded-lg shadow-md`}></div>
</Link>
</ToolTip>
);
})}
</div>
</>
);
})}
</div>
)
}
export default ActivityIndicators

View file

@ -79,7 +79,7 @@ const contentClose = keyframes({
const DialogOverlay = styled(Dialog.Overlay, { const DialogOverlay = styled(Dialog.Overlay, {
backgroundColor: blackA.blackA9, backgroundColor: blackA.blackA9,
position: 'fixed', position: 'fixed',
zIndex: 500,
inset: 0, inset: 0,
animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`, animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
'&[data-state="closed"]': { '&[data-state="closed"]': {
@ -111,6 +111,7 @@ const DialogContent = styled(Dialog.Content, {
backgroundColor: 'white', backgroundColor: 'white',
borderRadius: 18, borderRadius: 18,
zIndex: 501,
boxShadow: 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px', boxShadow: 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
position: 'fixed', position: 'fixed',
top: '50%', top: '50%',