diff --git a/packages/openneuro-app/src/scripts/authentication/profile.ts b/packages/openneuro-app/src/scripts/authentication/profile.ts index 139139443..85bbb89ad 100644 --- a/packages/openneuro-app/src/scripts/authentication/profile.ts +++ b/packages/openneuro-app/src/scripts/authentication/profile.ts @@ -3,6 +3,7 @@ import jwtDecode from "jwt-decode" export interface OpenNeuroTokenProfile { sub: string admin: boolean + email?: string iat: number exp: number scopes?: string[] diff --git a/packages/openneuro-app/src/scripts/contributors/__tests__/contributor-list.spec.tsx b/packages/openneuro-app/src/scripts/contributors/__tests__/contributor-list.spec.tsx index 07c6a4bde..ddc8e7946 100644 --- a/packages/openneuro-app/src/scripts/contributors/__tests__/contributor-list.spec.tsx +++ b/packages/openneuro-app/src/scripts/contributors/__tests__/contributor-list.spec.tsx @@ -4,6 +4,8 @@ import { MockedProvider } from "@apollo/client/testing" import { ContributorsListDisplay } from "../contributors-list" import { GET_USERS } from "../../queries/users" import type { Contributor } from "../../types/datacite" +import * as profileModule from "../../authentication/profile" +import { vi } from "vitest" describe("ContributorsListDisplay", () => { const baseContributors: Contributor[] = [ @@ -34,13 +36,15 @@ describe("ContributorsListDisplay", () => { result: { data: { users: { - users: [{ - id: "1", - name: "Jane Doe", - avatar: "", - orcid: "", - __typename: "User", - }], + users: [ + { + id: "1", + name: "Jane Doe", + avatar: "", + orcid: "", + __typename: "User", + }, + ], totalCount: 1, __typename: "UsersResponse", }, @@ -55,13 +59,15 @@ describe("ContributorsListDisplay", () => { result: { data: { users: { - users: [{ - id: "2", - name: "John Smith", - avatar: "", - orcid: "", - __typename: "User", - }], + users: [ + { + id: "2", + name: "John Smith", + avatar: "", + orcid: "", + __typename: "User", + }, + ], totalCount: 1, __typename: "UsersResponse", }, @@ -81,6 +87,17 @@ describe("ContributorsListDisplay", () => { }, ] + // Mock getProfile so hasEdit === true + beforeEach(() => { + vi.spyOn(profileModule, "getProfile").mockReturnValue({ + sub: "123", + email: "test@example.com", + admin: false, + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 3600, + }) + }) + const renderComponent = (props = {}) => render( @@ -95,14 +112,12 @@ describe("ContributorsListDisplay", () => { it("renders contributors list", async () => { renderComponent() - // Match Jane Doe expect( await screen.findByText((content) => ["Jane", "Doe"].every((part) => content.includes(part)) ), ).toBeInTheDocument() - // Match John Smith expect( screen.getByText((content) => ["John", "Smith"].every((part) => content.includes(part)) @@ -136,9 +151,10 @@ describe("ContributorsListDisplay", () => { const addButton = await screen.findByText("Add Contributor") fireEvent.click(addButton) - expect( - await screen.findAllByPlaceholderText("Type name or ORCID (or add new)"), - ).toHaveLength(3) + const nameInputs = await screen.findAllByPlaceholderText( + "Type name or ORCID (or add new)", + ) + expect(nameInputs).toHaveLength(3) }) it("shows an error when trying to submit with empty name", async () => { diff --git a/packages/openneuro-app/src/scripts/contributors/contributors-list.tsx b/packages/openneuro-app/src/scripts/contributors/contributors-list.tsx index 823a7ddc8..9d5746bbd 100644 --- a/packages/openneuro-app/src/scripts/contributors/contributors-list.tsx +++ b/packages/openneuro-app/src/scripts/contributors/contributors-list.tsx @@ -1,12 +1,16 @@ import React, { useEffect, useState } from "react" +import { toast } from "react-toastify" import { gql, useMutation } from "@apollo/client" import * as Sentry from "@sentry/react" import type { Contributor } from "../types/datacite" import { SingleContributorDisplay } from "./contributor" +import ToastContent from "../common/partials/toast-content" import { Loading } from "../components/loading/Loading" import { ContributorFormRow } from "./contributor-form-row" import { cloneContributor } from "./contributor-utils" import { CREATE_CONTRIBUTOR_CITATION_EVENT } from "../queries/datasetEvents" +import { useCookies } from "react-cookie" +import { getProfile } from "../authentication/profile" interface ContributorsListDisplayProps { contributors: Contributor[] | null | undefined @@ -60,8 +64,37 @@ export const ContributorsListDisplay: React.FC = ( const [editingContributors, setEditingContributors] = useState< Contributor[] >(contributors?.map((c) => ({ ...c, order: c.order ?? 0 })) || []) - const [errors, setErrors] = useState>({}) + const [cookies] = useCookies() + const profile = getProfile(cookies) + + const hasEdit = Boolean(profile?.email) + + const handleEditClick = () => { + if (!hasEdit) { + toast.error( + + Connect an email to make contributions to OpenNeuro. See our{" "} + + ORCID documentation + {" "} + for detailed instructions. + + } + />, + ) + return + } + setIsEditing(true) + } useEffect(() => { if (contributors) { @@ -106,6 +139,7 @@ export const ContributorsListDisplay: React.FC = ( }, }, ) + const [createContributorCitationEvent] = useMutation( CREATE_CONTRIBUTOR_CITATION_EVENT, { @@ -114,6 +148,7 @@ export const ContributorsListDisplay: React.FC = ( }, }, ) + const handleChange = ( index: number, field: keyof Contributor, @@ -276,7 +311,7 @@ export const ContributorsListDisplay: React.FC = ( {editable && !isEditing && (