Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions @/components/ui/avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"

import { cn } from "@/lib/utils"

const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName

const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName

const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName

export { Avatar, AvatarImage, AvatarFallback }
Binary file added public/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
125 changes: 125 additions & 0 deletions src/components/Dropdowns/MyCollectionDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { CopyPlus, PencilIcon, Trash } from "lucide-react";
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { CollectionService } from "../../services/Collection.service";
import { ProblemService } from "../../services/Problem.service";
import { transformCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel2CreateCollectionRequest } from "../../types/adapters/Collection.adapter";
import { transformCreateCollectionRequestForm2CreateCollectionRequestForm } from "../../types/adapters/CreateCollectionRequestForm.adapter";
import { CollectionModel } from "../../types/models/Collection.model";
import DeleteCollectionConfirmationDialog from "../Dialogs/DeleteCollectionConfirmationDialog";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../shadcn/DropdownMenu";
import { toast } from "../shadcn/UseToast";

const MyCollectionDropdown = ({
children,
collection,
}: {
children: React.ReactNode;
collection: CollectionModel;
}) => {
const accountId = String(localStorage.getItem("account_id"));
const navigate = useNavigate();
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);

const handleCloneCollection = async () => {
const response = await CollectionService.get(
collection.collection_id,
accountId
);

let createRequest =
transformCollectionPopulateCollectionProblemsPopulateProblemAndCollectionGroupPermissionsPopulateGroupModel2CreateCollectionRequest(
response.data
);

createRequest.title += " (Copy)";

const { request, groups, problemIds, problemGroupPermissions } =
transformCreateCollectionRequestForm2CreateCollectionRequestForm(
createRequest
);

CollectionService.create(accountId, request)
.then((response) => {
return CollectionService.updateProblem(
response.data.collection_id,
problemIds
);
})
.then((response) => {
return CollectionService.updateGroupPermissions(
response.data.collection_id,
accountId,
groups
);
})
.then((response) => {
let promise = [];
for (const problem of problemGroupPermissions) {
promise.push(
ProblemService.updateGroupPermissions(
problem.problem_id,
accountId,
problem.groupPermissions
)
);
}

return {
promise: Promise.all(promise),
collection_id: response.data.collection_id,
};
})
.then((response) => {
toast({
title: "Create Completed",
});
window.open(`/my/collections/${response.collection_id}`,'_blank');
});
};

return (
<DropdownMenu>
<DeleteCollectionConfirmationDialog
collection={collection}
open={openDeleteDialog}
setOpen={setOpenDeleteDialog}
afterDelete={() => window.location.reload()}
/>
<DropdownMenuTrigger>{children}</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem disabled>
<div className="font-medium">{collection.name}</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() =>
navigate(`/my/collections/${collection.collection_id}/edit`)
}
>
<PencilIcon className="mr-2" size={16} />
Edit Collection
</DropdownMenuItem>
<DropdownMenuItem onClick={handleCloneCollection}>
<CopyPlus className="mr-2" size={16} />
Clone Collection
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => setOpenDeleteDialog(true)}
className="text-red-400"
>
<Trash className="mr-2" size={16} />
Delete Collection
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};

export default MyCollectionDropdown;
125 changes: 125 additions & 0 deletions src/components/Dropdowns/MyCourseDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { CopyPlus, PencilIcon, Trash } from "lucide-react";
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { CollectionService } from "../../services/Collection.service";
import { TopicService } from "../../services/Topic.service";
import { transformCreateCourseRequestForm2CreateTopicRequest } from "../../types/adapters/CreateCourseRequestForm.adapter";
import { transformTopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel2CreateCourseRequest } from "../../types/adapters/Topic.adapter";
import { TopicModel } from "../../types/models/Topic.model";
import DeleteCourseConfirmationDialog from "../Dialogs/DeleteCourseConfirmationDialog";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../shadcn/DropdownMenu";
import { toast } from "../shadcn/UseToast";

const MyCourseDropdown = ({
children,
course,
}: {
children: React.ReactNode;
course: TopicModel;
}) => {
const accountId = String(localStorage.getItem("account_id"));
const navigate = useNavigate();
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);

const handleCloneCourse = async () => {
const response = await TopicService.get(
accountId,
course.topic_id
);

let createRequest =
transformTopicPopulateTopicCollectionPopulateCollectionAndTopicGroupPermissionPopulateGroupModel2CreateCourseRequest(
response.data
);

createRequest.title += " (Copy)";

const { formData, groups, collectionIds, collectionGroupsPermissions } =
transformCreateCourseRequestForm2CreateTopicRequest(
createRequest
);

TopicService.create(accountId, formData)
.then((response) => {
return TopicService.updateCollections(
response.data.topic_id,
collectionIds
);
})
.then((response) => {
return TopicService.updateGroupPermissions(
response.data.topic_id,
accountId,
groups
);
})
.then((response) => {
let promise = [];
for (const collection of collectionGroupsPermissions) {
promise.push(
CollectionService.updateGroupPermissions(
collection.collection_id,
accountId,
collection.groupPermissions
)
);
}

return {
promise: Promise.all(promise),
topic_id: response.data.topic_id,
};
})
.then(({ topic_id }) => {
toast({
title: "Create Completed",
});
window.open(`/my/courses/${topic_id}`,'_blank');
});
};

return (
<DropdownMenu>
<DeleteCourseConfirmationDialog
course={course}
open={openDeleteDialog}
setOpen={setOpenDeleteDialog}
afterDelete={() => window.location.reload()}
/>
<DropdownMenuTrigger>{children}</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem disabled>
<div className="font-medium">{course.name}</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() =>
navigate(`/my/courses/${course.topic_id}/edit`)
}
>
<PencilIcon className="mr-2" size={16} />
Edit Course
</DropdownMenuItem>
<DropdownMenuItem onClick={handleCloneCourse}>
<CopyPlus className="mr-2" size={16} />
Clone Course
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => setOpenDeleteDialog(true)}
className="text-red-400"
>
<Trash className="mr-2" size={16} />
Delete Course
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};

export default MyCourseDropdown;
4 changes: 3 additions & 1 deletion src/components/Forms/CreateCollectionForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ArrowLeft } from "lucide-react";
import React, { useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { useBeforeUnload, useNavigate, useSearchParams } from "react-router-dom";
import { CreateCollectionRequestForm } from "../../../types/forms/CreateCollectionRequestForm";
import { Tabs, TabsList, TabsTrigger } from "../../shadcn/Tabs";
import FormSaveButton from "../FormSaveButton";
Expand Down Expand Up @@ -49,6 +49,8 @@ const CreateCollectionForm = ({
});
};

useBeforeUnload(() => "Some change has not been saved yet. Are you sure to close this tab?")

return (
<div className="w-[96%] mx-auto mt-10">
<div className="flex justify-between">
Expand Down
Loading