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
55 changes: 54 additions & 1 deletion website/src/pages/documents/document-info.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ export const container = style({
export const header = style({
position: "relative",
textAlign: "center",
marginBottom: "32px",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "4px",
})

export const editButton = style({
Expand Down Expand Up @@ -85,3 +88,53 @@ export const link = style({
color: "#4A90E2",
textDecoration: "underline",
})

export const message = style({
display: "flex",
justifyContent: "center",
alignItems: "center",
position: "fixed",
borderRadius: "8px",
zIndex: 9999,
fontSize: "16px",
background: "white",
color: "#black",
boxShadow: "0 6px 14px rgba(0,0,0,0.3)",
})

export const messageCloseButton = style({
marginLeft: "auto",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
})

export const globalMessageOverlay = style({
position: "fixed",
inset: 0,
background: "rgba(0, 0, 0, 0.6)",
display: "flex",
alignItems: "center",
justifyContent: "center",
zIndex: 9999,
})

export const globalMessageBox = style({
background: "white",
padding: "20px 24px",
borderRadius: "10px",
minWidth: "320px",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
gap: "12px",
})

export const success = style({
borderLeft: "6px solid #2ecc71",
})

export const error = style({
borderLeft: "6px solid #e74c3c",
})
81 changes: 70 additions & 11 deletions website/src/pages/documents/document-info.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import "@reach/dialog/styles.css"
import React, { Fragment } from "react"
import { Helmet } from "react-helmet"
import { MdClose } from "react-icons/md/index"
import { unstable_Form as Form } from "reakit"
import { useCredentials } from "src/auth"
import { Link } from "src/components"
import { UserRole, useCredentials, useUserRole } from "src/auth"
import { IconButton, Link } from "src/components"
import { useForm } from "src/edit-doc-data-form-context"
import EditDocPanel, { EditButton } from "src/edit-doc-data-panel"
import * as Dailp from "src/graphql/dailp"
Expand All @@ -22,14 +23,21 @@ export type TabSegment = Dailp.DocumentMetadataUpdate | Document
export type Document = NonNullable<Dailp.AnnotatedDocumentQuery["document"]>

export const DocumentInfo = ({ doc }: { doc: Document }) => {
const isBrowser = typeof window !== "undefined"

const [{ data }, reexecuteQuery] = Dailp.useDocumentDetailsQuery({
pause: !isBrowser,
variables: { slug: doc.slug! },
})
const token = useCredentials()
const { form } = useForm()
const { isEditing, setIsEditing } = useEditing()
const [, updateDocument] = Dailp.useUpdateDocumentMetadataMutation()

// Define which roles have metadata editing permissions
const userRole = useUserRole()
const canEdit = userRole === UserRole.Editor || userRole === UserRole.Admin

const [citation, setCitation] = React.useState<string>("")

// Initialize citation format from localStorage
Expand All @@ -40,6 +48,12 @@ export const DocumentInfo = ({ doc }: { doc: Document }) => {
return "apa"
})

// Success/failure message for editing metadata
const [message, setMessage] = React.useState<null | {
type: "success" | "error"
message: string
}>(null)

const docData: Dailp.AnnotatedDoc = data?.document as Dailp.AnnotatedDoc

// if (!docData) {
Expand Down Expand Up @@ -133,10 +147,40 @@ export const DocumentInfo = ({ doc }: { doc: Document }) => {
})

await reexecuteQuery({ requestPolicy: "network-only" })

setIsEditing(false)

setTimeout(() => {
setMessage({
type: "success",
message: "Metadata updated successfully!",
})
}, 250)

return { ok: true }
} catch (error) {
console.error("Failed to update document:", error)
setIsEditing(false)

const errorMessage =
error instanceof Error
? error.message
: typeof error === "string"
? error
: JSON.stringify(error)

console.error("Failed to update metadata:", error)

setTimeout(() => {
setMessage({
type: "error",
message: `Failed to update metadata: ${errorMessage}`,
})
}, 250)

return {
ok: false,
error: errorMessage,
}
}
}

Expand Down Expand Up @@ -204,6 +248,8 @@ export const DocumentInfo = ({ doc }: { doc: Document }) => {
<p className={styles.subtitle}>
{/* {docData.uploadedAt && `Uploaded ${new Date(docData.uploadedAt).toLocaleDateString()}`}
{docData.editedAt && ` • Last Edited ${new Date(docData.editedAt).toLocaleDateString()}`} */}

{canEdit && token && !isEditing && <EditButton />}
</p>
</div>

Expand Down Expand Up @@ -319,14 +365,7 @@ export const DocumentInfo = ({ doc }: { doc: Document }) => {
{/* If the user is logged in, then display an edit button on the word
panel along with its corresponding formatted header. Otherwise, display
the normal word panel. */}
{token ? (
<>
{!isEditing && <>{metadataDisplay}</>}
<EditButton />
</>
) : (
<>{metadataDisplay}</>
)}
{!isEditing && metadataDisplay}

{isEditing ? (
<EditDocumentModal
Expand All @@ -347,6 +386,26 @@ export const DocumentInfo = ({ doc }: { doc: Document }) => {
<Fragment>
{panel}

{message && (
<div className={styles.globalMessageOverlay}>
<div
className={`${styles.globalMessageBox} ${
message.type === "success" ? styles.success : styles.error
}`}
>
<span>{message.message}</span>

<IconButton
className={styles.messageCloseButton}
onClick={() => setMessage(null)}
aria-label="Close message"
>
<MdClose size={18} />
</IconButton>
</div>
</div>
)}

{docData.sources && docData.sources.length > 0 ? (
<section className={fullWidth}>
Original document provided courtesy of{" "}
Expand Down
19 changes: 19 additions & 0 deletions website/src/pages/documents/edit-document-modal.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const overlay = style({

export const modal = style({
backgroundColor: "white",
position: "relative",
borderRadius: "45px",
padding: "32px",
maxWidth: "600px",
Expand Down Expand Up @@ -183,3 +184,21 @@ export const addTagButton = style({
},
},
})

export const successBanner = style({
marginTop: "12px",
padding: "10px 12px",
borderRadius: "8px",
backgroundColor: "#e6ffed",
color: "#137333",
fontSize: "14px",
})

export const errorBanner = style({
marginTop: "12px",
padding: "10px 12px",
borderRadius: "8px",
backgroundColor: "#ffe6e6",
color: "#a50e0e",
fontSize: "14px",
})
27 changes: 24 additions & 3 deletions website/src/pages/documents/edit-document-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import { plugins } from "@citation-js/core"
import type React from "react"
import { useEffect, useMemo, useState } from "react"
import DatePicker from "react-date-picker"
import { MdClose } from "react-icons/md/index"
import TextareaAutosize from "react-textarea-autosize"
import { v4 as uuidv4 } from "uuid"
import { IconButton } from "src/components"
import { form } from "src/edit-word-feature.css"
import * as Dailp from "src/graphql/dailp"
import { UserRole, useUserRole } from "../../auth"
import { useTagSelector } from "../../hooks/use-tag-selector"
import * as css from "../../mode.css"
import { buildCitationMetadata } from "../../utils/build-citation-metadata"
import Cite from "../../utils/citation-config"
import { Dropdown } from "./dropdown"
Expand All @@ -18,7 +21,7 @@ import { TagSelector } from "./tag-selector"
export type EditDocumentModalProps = {
isOpen: boolean
onClose: () => void
onSubmit: (data: any) => void
onSubmit: (data: any) => Promise<{ ok: boolean; error?: string }>
documentMetadata: Dailp.AnnotatedDoc
initialCiteFormat?: string
}
Expand Down Expand Up @@ -489,7 +492,7 @@ export const EditDocumentModal: React.FC<EditDocumentModalProps> = ({
setIsEditing(false)
}

const handleSubmit = (e: React.FormEvent) => {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()

// Format date for submission
Expand Down Expand Up @@ -573,6 +576,17 @@ export const EditDocumentModal: React.FC<EditDocumentModalProps> = ({
citeFormat: citeFormat,
}

// Return if editing was successful or if there was an error
let result

try {
result = await onSubmit(updatedMetadata)
} catch (err) {
return { ok: false, error: "Network or server error" }
}

return result

// Update backup state to new submitted state
// setBackupState({
// title,
Expand All @@ -586,14 +600,21 @@ export const EditDocumentModal: React.FC<EditDocumentModalProps> = ({
// })

// setIsEditing(false)
onSubmit(updatedMetadata)
// onClose()
}

// Pass UUID of the keyword
return (
<div className={styles.overlay}>
<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
<IconButton
className={css.closeButton}
onClick={onClose}
aria-label="Close modal"
>
<MdClose size={32} />
</IconButton>

<h2 className={styles.title}>Editing Document Information</h2>
<p className={styles.subtitle}>* indicates a required field</p>

Expand Down
Loading