mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: add delete confirmation screens
This commit is contained in:
parent
acc73f018e
commit
631f927853
4 changed files with 166 additions and 18 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
|
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
|
||||||
import { AuthContext } from '@components/Security/AuthProvider';
|
import { AuthContext } from '@components/Security/AuthProvider';
|
||||||
|
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal';
|
||||||
import { getUriWithOrg } from '@services/config/config';
|
import { getUriWithOrg } from '@services/config/config';
|
||||||
import { deleteCollection } from '@services/courses/collections';
|
import { deleteCollection } from '@services/courses/collections';
|
||||||
import { revalidateTags } from '@services/utils/ts/requests';
|
import { revalidateTags } from '@services/utils/ts/requests';
|
||||||
|
|
@ -26,9 +27,17 @@ const CollectionAdminEditsArea = (props: any) => {
|
||||||
return (
|
return (
|
||||||
<AuthenticatedClientElement orgId={props.org_id} checkMethod='roles'>
|
<AuthenticatedClientElement orgId={props.org_id} checkMethod='roles'>
|
||||||
<div className="flex space-x-2 py-2">
|
<div className="flex space-x-2 py-2">
|
||||||
<button className="rounded-md text-sm px-3 font-bold text-red-800 bg-red-200 w-16 flex justify-center items-center" onClick={() => deleteCollectionUI(props.collection_id)}>
|
<ConfirmationModal
|
||||||
|
confirmationMessage="Are you sure you want to delete this collection?"
|
||||||
|
confirmationButtonText="Delete Collection"
|
||||||
|
dialogTitle={"Delete " + props.collection.name + " ?"}
|
||||||
|
dialogTrigger={
|
||||||
|
<button className="rounded-md text-sm px-3 font-bold text-red-800 bg-red-200 w-16 flex justify-center items-center" >
|
||||||
Delete <Trash size={10}></Trash>
|
Delete <Trash size={10}></Trash>
|
||||||
</button>
|
</button>}
|
||||||
|
functionToExecute={() => deleteCollectionUI(props.collection_id)}
|
||||||
|
status='warning'
|
||||||
|
></ConfirmationModal>
|
||||||
</div>
|
</div>
|
||||||
</AuthenticatedClientElement>
|
</AuthenticatedClientElement>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWra
|
||||||
import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle';
|
import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle';
|
||||||
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
|
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement';
|
||||||
import { getCourseThumbnailMediaDirectory } from '@services/media/media';
|
import { getCourseThumbnailMediaDirectory } from '@services/media/media';
|
||||||
|
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal';
|
||||||
|
|
||||||
interface CourseProps {
|
interface CourseProps {
|
||||||
orgslug: string;
|
orgslug: string;
|
||||||
|
|
@ -48,6 +49,7 @@ function Courses(props: CourseProps) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<GeneralWrapperStyled>
|
<GeneralWrapperStyled>
|
||||||
|
|
||||||
<div className='flex flex-wrap justify-between'>
|
<div className='flex flex-wrap justify-between'>
|
||||||
<TypeOfContentTitle title="Courses" type="cou" />
|
<TypeOfContentTitle title="Courses" type="cou" />
|
||||||
<AuthenticatedClientElement checkMethod='roles' orgId={props.org_id}>
|
<AuthenticatedClientElement checkMethod='roles' orgId={props.org_id}>
|
||||||
|
|
@ -94,9 +96,17 @@ function Courses(props: CourseProps) {
|
||||||
const AdminEditsArea = (props: { orgSlug: string, courseId: string, course: any, deleteCourses: any }) => {
|
const AdminEditsArea = (props: { orgSlug: string, courseId: string, course: any, deleteCourses: any }) => {
|
||||||
return (
|
return (
|
||||||
<AuthenticatedClientElement checkMethod='roles' orgId={props.course.org_id}><div className="flex space-x-2 py-2">
|
<AuthenticatedClientElement checkMethod='roles' orgId={props.course.org_id}><div className="flex space-x-2 py-2">
|
||||||
<button className="rounded-md text-sm px-3 font-bold text-red-800 bg-red-200 w-16 flex justify-center items-center" onClick={() => props.deleteCourses(props.courseId)}>
|
<ConfirmationModal
|
||||||
|
confirmationButtonText='Delete Course'
|
||||||
|
confirmationMessage='Are you sure you want to delete this course?'
|
||||||
|
dialogTitle={'Delete ' + props.course.name + ' ?'}
|
||||||
|
dialogTrigger={
|
||||||
|
<button className="rounded-md text-sm px-3 font-bold text-red-800 bg-red-200 w-16 flex justify-center items-center" >
|
||||||
Delete <Trash size={10}></Trash>
|
Delete <Trash size={10}></Trash>
|
||||||
</button>
|
</button>}
|
||||||
|
functionToExecute={() => props.deleteCourses(props.courseId)}
|
||||||
|
status='warning'
|
||||||
|
></ConfirmationModal>
|
||||||
<Link href={getUriWithOrg(props.orgSlug, "/course/" + removeCoursePrefix(props.courseId) + "/edit")}>
|
<Link href={getUriWithOrg(props.orgSlug, "/course/" + removeCoursePrefix(props.courseId) + "/edit")}>
|
||||||
<button className="rounded-md text-sm px-3 font-bold text-orange-800 bg-orange-200 w-16 flex justify-center items-center">
|
<button className="rounded-md text-sm px-3 font-bold text-orange-800 bg-orange-200 w-16 flex justify-center items-center">
|
||||||
Edit <Edit2 size={10}></Edit2>
|
Edit <Edit2 size={10}></Edit2>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ 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, Trash, Trash2 } from "lucide-react";
|
import { PlusSquare, Trash, Trash2 } from "lucide-react";
|
||||||
|
import ConfirmationModal from "@components/StyledElements/ConfirmationModal/ConfirmationModal";
|
||||||
|
|
||||||
function Chapter(props: any) {
|
function Chapter(props: any) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -19,16 +20,18 @@ function Chapter(props: any) {
|
||||||
<h3 className="flex space-x-2 pt-3 font-bold text-md items-center">
|
<h3 className="flex space-x-2 pt-3 font-bold text-md items-center">
|
||||||
<p>{props.info.list.chapter.name}
|
<p>{props.info.list.chapter.name}
|
||||||
</p>
|
</p>
|
||||||
|
<ConfirmationModal
|
||||||
|
confirmationButtonText="Delete Chapter"
|
||||||
|
confirmationMessage="Are you sure you want to delete this chapter?"
|
||||||
|
dialogTitle={"Delete " + props.info.list.chapter.name + " ?"}
|
||||||
|
dialogTrigger={
|
||||||
|
<button className="rounded-md text-sm px-3 font-bold text-red-800 bg-red-200 w-16 flex justify-center items-center" >
|
||||||
|
Delete <Trash size={10}></Trash>
|
||||||
|
</button>}
|
||||||
|
functionToExecute={() => props.deleteChapter(props.info.list.chapter.id)}
|
||||||
|
status='warning'
|
||||||
|
></ConfirmationModal>
|
||||||
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="hover:cursor-pointer p-1 rounded-md bg-slate-100"
|
|
||||||
onClick={() => {
|
|
||||||
props.deleteChapter(props.info.list.chapter.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Trash2 className="text-slate-500" size={16} />
|
|
||||||
</div>
|
|
||||||
</h3>
|
</h3>
|
||||||
<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) => (
|
||||||
|
|
@ -68,8 +71,6 @@ const ChapterWrapper = styled.div`
|
||||||
border: 1px solid rgba(255, 255, 255, 0.19);
|
border: 1px solid rgba(255, 255, 255, 0.19);
|
||||||
box-shadow: 0px 13px 33px -13px rgb(0 0 0 / 12%);
|
box-shadow: 0px 13px 33px -13px rgb(0 0 0 / 12%);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
|
||||||
h3{
|
h3{
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
'use client';
|
||||||
|
import React from 'react';
|
||||||
|
import * as Dialog from '@radix-ui/react-dialog';
|
||||||
|
import { styled, keyframes } from '@stitches/react';
|
||||||
|
import { blackA, } from '@radix-ui/colors';
|
||||||
|
import { AlertTriangle, Info } from 'lucide-react';
|
||||||
|
|
||||||
|
type ModalParams = {
|
||||||
|
confirmationMessage: string;
|
||||||
|
confirmationButtonText: string;
|
||||||
|
dialogTitle: string;
|
||||||
|
functionToExecute: any;
|
||||||
|
dialogTrigger?: React.ReactNode;
|
||||||
|
status?: "warning" | "info";
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConfirmationModal = (params: ModalParams) => {
|
||||||
|
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
|
||||||
|
const warningColors = 'bg-red-100 text-red-600'
|
||||||
|
const infoColors = 'bg-blue-100 text-blue-600'
|
||||||
|
const warningButtonColors = 'text-white bg-red-500 hover:bg-red-600'
|
||||||
|
const infoButtonColors = 'text-white bg-blue-500 hover:bg-blue-600'
|
||||||
|
|
||||||
|
|
||||||
|
const onOpenChange = React.useCallback(
|
||||||
|
(open: any) => {
|
||||||
|
setIsDialogOpen(open);
|
||||||
|
},
|
||||||
|
[setIsDialogOpen]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Root open={isDialogOpen} onOpenChange={onOpenChange}>
|
||||||
|
{params.dialogTrigger ? (
|
||||||
|
<Dialog.Trigger asChild >
|
||||||
|
{params.dialogTrigger}
|
||||||
|
</Dialog.Trigger>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Dialog.Portal>
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogContent >
|
||||||
|
<div className='h-26 flex space-x-4 tracking-tight'>
|
||||||
|
<div className={`icon p-6 rounded-xl flex items-center align-content-center ${params.status === 'warning' ? warningColors : infoColors}`}>
|
||||||
|
{params.status === 'warning' ? <AlertTriangle size={35} /> : <Info size={35} />}
|
||||||
|
</div>
|
||||||
|
<div className="text pt-1 space-x-0 w-auto flex-grow">
|
||||||
|
<div className="text-xl font-bold text-black ">
|
||||||
|
{params.dialogTitle}
|
||||||
|
</div>
|
||||||
|
<div className="text-md text-gray-500 w-60 leading-tight">
|
||||||
|
{params.confirmationMessage}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row-reverse pt-2">
|
||||||
|
<div className={`rounded-md text-sm px-3 py-2 font-bold flex justify-center items-center hover:cursor-pointer ${params.status === 'warning' ? warningButtonColors : infoButtonColors}
|
||||||
|
hover:shadow-lg transition duration-300 ease-in-out
|
||||||
|
`}
|
||||||
|
onClick={() => { params.functionToExecute(); setIsDialogOpen(false) }}>
|
||||||
|
{params.confirmationButtonText}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog.Portal>
|
||||||
|
</Dialog.Root>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
const overlayShow = keyframes({
|
||||||
|
'0%': { opacity: 0 },
|
||||||
|
'100%': { opacity: 1 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const overlayClose = keyframes({
|
||||||
|
'0%': { opacity: 1 },
|
||||||
|
'100%': { opacity: 0 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const contentShow = keyframes({
|
||||||
|
'0%': { opacity: 0, transform: 'translate(-50%, -50%) scale(.96)' },
|
||||||
|
'100%': { opacity: 1, transform: 'translate(-50%, -50%) scale(1)' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const contentClose = keyframes({
|
||||||
|
'0%': { opacity: 1, transform: 'translate(-50%, -50%) scale(1)' },
|
||||||
|
'100%': { opacity: 0, transform: 'translate(-50%, -52%) scale(.96)' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const DialogOverlay = styled(Dialog.Overlay, {
|
||||||
|
backgroundColor: blackA.blackA9,
|
||||||
|
position: 'fixed',
|
||||||
|
zIndex: 500,
|
||||||
|
inset: 0,
|
||||||
|
animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||||
|
'&[data-state="closed"]': {
|
||||||
|
animation: `${overlayClose} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const DialogContent = styled(Dialog.Content, {
|
||||||
|
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 18,
|
||||||
|
zIndex: 501,
|
||||||
|
boxShadow: 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
|
||||||
|
position: 'fixed',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 'auto',
|
||||||
|
minWidth: '500px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
height: 'auto',
|
||||||
|
maxHeight: '85vh',
|
||||||
|
maxWidth: '600px',
|
||||||
|
padding: 11,
|
||||||
|
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||||
|
'&:focus': { outline: 'none' },
|
||||||
|
|
||||||
|
'&[data-state="closed"]': {
|
||||||
|
animation: `${contentClose} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
|
||||||
|
},
|
||||||
|
transition: "max-height 0.3s ease-out",
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default ConfirmationModal;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue