Skip to content
Open
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
98 changes: 30 additions & 68 deletions client/src/components/CustomForms/AuthenticatedStatusMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,83 +1,45 @@
import { Message, Icon, MessageProps } from "semantic-ui-react";
import { IconUserCircle, IconKey } from "@tabler/icons-react";
import Breakpoint from "../util/Breakpoints";
import { User } from "../../types";

interface AuthenticatedStatusMessageProps extends MessageProps {
interface AuthenticatedStatusMessageProps {
user: User;
className?: string;
}

const AuthenticatedStatusMessage: React.FC<AuthenticatedStatusMessageProps> = ({
user,
...rest
className,
}) => {
if (user.isAuthenticated) {
return (
<Message info {...rest}>
<Message.Content>
<Breakpoint name="desktop">
<Icon.Group size="big">
<Icon name="user circle" />
<Icon corner name="key" />
</Icon.Group>
<span className="ml-1p">
You're logged into Conductor as{" "}
<strong>
{user.firstName} {user.lastName}
</strong>
.
</span>
</Breakpoint>
<Breakpoint name="mobileOrTablet">
<div className="text-center">
<div>
<Icon.Group size="big">
<Icon name="user circle" />
<Icon corner name="key" />
</Icon.Group>
</div>
<p>
You're logged into Conductor as{" "}
<strong>
{user.firstName} {user.lastName}
</strong>
.
</p>
</div>
</Breakpoint>
</Message.Content>
</Message>
);
}
const msgText = user.isAuthenticated
? <>You're logged into Conductor as <strong>{user.firstName} {user.lastName}</strong>.</>
: <>You are not logged in. You must login to Conductor to register for this event.</>;

const colorClass = user.isAuthenticated
? "bg-blue-50 border-blue-200 text-blue-800"
: "bg-yellow-50 border-yellow-200 text-yellow-800";

return (
<Message warning {...rest}>
<Message.Content>
<Breakpoint name="desktop">
<Icon.Group size="big">
<Icon name="user circle" />
<Icon corner name="key" />
</Icon.Group>
<span className="ml-1p">
You are not logged in. You must login to Conductor to register for
this event.
</span>
</Breakpoint>
<Breakpoint name="mobileOrTablet">
<div className="text-center">
<div>
<Icon.Group size="big">
<Icon name="user circle" />
<Icon corner name="key" />
</Icon.Group>
</div>
<p>
You are not logged in. You must login to Conductor to register for
this event.
</p>
<div className={`flex items-start gap-3 p-4 border rounded-md ${colorClass} ${className ?? ""}`}>
<Breakpoint name="desktop">
<div className="flex items-center gap-3">
<div className="relative shrink-0">
<IconUserCircle size={24} />
<IconKey size={12} className="absolute -bottom-1 -right-1" />
</div>
<span className="text-sm">{msgText}</span>
</div>
</Breakpoint>
<Breakpoint name="mobileOrTablet">
<div className="text-center w-full text-sm">
<div className="flex justify-center mb-2 relative inline-block">
<IconUserCircle size={24} />
<IconKey size={12} className="absolute -bottom-1 -right-1" />
</div>
</Breakpoint>
</Message.Content>
</Message>
<p>{msgText}</p>
</div>
</Breakpoint>
</div>
);
};

Expand Down
40 changes: 24 additions & 16 deletions client/src/components/CustomForms/DeleteBlockModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Modal, Button, Icon, ModalProps } from "semantic-ui-react";
import { Modal, Button } from "@libretexts/davis-react";
import { IconTrash } from "@tabler/icons-react";

interface DeleteBlockModalProps extends ModalProps {
interface DeleteBlockModalProps {
show: boolean;
blockType: "heading" | "textBlock" | "prompt";
onSave: () => void;
Expand All @@ -14,24 +15,31 @@ const DeleteBlockModal: React.FC<DeleteBlockModalProps> = ({
onSave,
onRequestClose,
loading,
...rest
}) => {
return (
<Modal open={show} onClose={() => onRequestClose()} {...rest}>
<Modal.Header>Delete Block</Modal.Header>
<Modal.Content>
<Modal open={show} onClose={onRequestClose} size="sm">
<Modal.Header>
<Modal.Title>Delete Block</Modal.Title>
<Modal.Close />
</Modal.Header>
<Modal.Body>
<p>
Are you sure you want to delete this <strong>{blockType}</strong>{" "}
block?
Are you sure you want to delete this <strong>{blockType}</strong> block?
</p>
</Modal.Content>
<Modal.Actions>
<Button onClick={() => onRequestClose()}>Cancel</Button>
<Button loading={loading ?? false} color="red" onClick={() => onSave()}>
<Icon name="trash" />
Delete Block
</Button>
</Modal.Actions>
</Modal.Body>
<Modal.Footer>
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={onRequestClose}>Cancel</Button>
<Button
variant="destructive"
icon={<IconTrash size={16} />}
loading={loading ?? false}
onClick={onSave}
>
Delete Block
</Button>
</div>
</Modal.Footer>
</Modal>
);
};
Expand Down
109 changes: 58 additions & 51 deletions client/src/components/CustomForms/EditableFormBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import { useEffect } from "react";
import DOMPurify from "dompurify";
import { marked } from "marked";
import { Segment, Label, Button, Icon, SegmentProps } from "semantic-ui-react";
import { IconArrowUp, IconArrowDown, IconPencil, IconTrash } from "@tabler/icons-react";
import { Button } from "@libretexts/davis-react";
import {
isCustomFormHeadingOrTextBlock,
isCustomFormPromptBlock,
} from "../../utils/typeHelpers";
import { CustomFormElement } from "../../types";
import { getFriendlyUIType } from "../../utils/customFormHelpers";

interface EditableFormBlockProps extends SegmentProps {
interface EditableFormBlockProps {
item: CustomFormElement;
disabled?: boolean;
onMove: (item: CustomFormElement, direction: "up" | "down") => void;
onRequestEdit: (order: number) => void;
onRequestDelete: (order: number) => void;
}

const EditableFormBlock: React.FC<EditableFormBlockProps> = ({
item,
disabled,
onMove,
onRequestEdit,
onRequestDelete,
...rest
}) => {
const { disabled } = rest;

useEffect(() => {
DOMPurify.addHook("afterSanitizeAttributes", (node) => {
if ("target" in node) {
Expand All @@ -43,59 +43,66 @@ const EditableFormBlock: React.FC<EditableFormBlockProps> = ({
__html: DOMPurify.sanitize(marked(textToRender, { breaks: true })),
};

const typeLabel =
item.uiType === "prompt" && isCustomFormPromptBlock(item)
? item.promptRequired
? `(Required)`
: `(Optional)`
: "";

return (
<Segment {...rest}>
<Label attached="top left" className="peerreview-rubricedit-label">
<Button.Group size="tiny">
<Button
icon="arrow up"
onClick={() => onMove(item, "up")}
disabled={disabled}
/>
<Button
icon="arrow down"
onClick={() => onMove(item, "down")}
disabled={disabled}
/>
</Button.Group>
<span className="ml-1r">
<strong>#{item.order}:</strong> {getFriendlyUIType(item.uiType)}{" "}
{item.uiType === "prompt" &&
isCustomFormPromptBlock(item) &&
item.promptRequired
? "(Required)"
: item.uiType === "prompt" &&
isCustomFormPromptBlock(item) &&
!item.promptRequired
? "(Optional)"
: ""}
</span>
</Label>
<div className="flex-row-div">
<div className="left-flex prose prose-code:before:hidden prose-code:after:hidden" dangerouslySetInnerHTML={renderedTextHTML} />
<div className="right-flex">
<Button.Group>
<Button
className="peerreview-rubricedit-editblockbtn"
color="teal"
onClick={() => onRequestEdit(item.order)}
<div className="border border-gray-200 rounded-lg bg-white mb-2">
<div className="flex items-center justify-between px-3 py-2 bg-gray-50 border-b border-gray-200 rounded-t-lg">
<div className="flex items-center gap-2">
<div className="flex gap-1">
<button
type="button"
title="Move Up"
className="p-1 hover:bg-gray-200 rounded text-gray-600 disabled:opacity-40 disabled:cursor-not-allowed"
onClick={() => onMove(item, "up")}
disabled={disabled}
>
<Icon name="pencil" />
Edit {getFriendlyUIType(item.uiType)}
</Button>
<Button
color="red"
onClick={() => onRequestDelete(item.order)}
<IconArrowUp size={14} />
</button>
<button
type="button"
title="Move Down"
className="p-1 hover:bg-gray-200 rounded text-gray-600 disabled:opacity-40 disabled:cursor-not-allowed"
onClick={() => onMove(item, "down")}
disabled={disabled}
>
<Icon name="trash" />
Delete
</Button>
</Button.Group>
<IconArrowDown size={14} />
</button>
</div>
<span className="text-xs text-gray-600 font-medium">
<strong>#{item.order}:</strong> {getFriendlyUIType(item.uiType)}{" "}
{typeLabel && <span className="font-normal text-gray-500">{typeLabel}</span>}
</span>
</div>
<div className="flex gap-2">
<Button
variant="secondary"
icon={<IconPencil size={14} />}
onClick={() => onRequestEdit(item.order)}
disabled={disabled}
>
Edit {getFriendlyUIType(item.uiType)}
</Button>
<Button
variant="destructive"
icon={<IconTrash size={14} />}
onClick={() => onRequestDelete(item.order)}
disabled={disabled}
>
Delete
</Button>
</div>
</div>
</Segment>
<div
className="p-4 prose prose-code:before:hidden prose-code:after:hidden max-w-none text-sm"
dangerouslySetInnerHTML={renderedTextHTML}
/>
</div>
);
};

Expand Down
10 changes: 5 additions & 5 deletions client/src/components/CustomForms/HeadingBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Header, HeaderProps } from "semantic-ui-react";
import { CustomFormHeading } from "../../types";

interface HeadingBlockProps extends HeaderProps {
interface HeadingBlockProps {
item: CustomFormHeading;
className?: string;
}

const HeadingBlock: React.FC<HeadingBlockProps> = ({ item, ...rest }) => {
const HeadingBlock: React.FC<HeadingBlockProps> = ({ item, className }) => {
return (
<Header as="h4" key={item.order} dividing {...rest}>
<h4 className={`text-base font-semibold border-b border-gray-200 pb-1 mb-3 ${className ?? ""}`}>
{item.text}
</Header>
</h4>
);
};

Expand Down
Loading
Loading