diff --git a/src/invites/components/InvitesList.tsx b/src/invites/components/InvitesList.tsx index 3d242a38..cba73a94 100644 --- a/src/invites/components/InvitesList.tsx +++ b/src/invites/components/InvitesList.tsx @@ -14,7 +14,7 @@ export const InvitesListView = ({ invites }) => { export const InvitesList = ({ currentUser }) => { // Get invitations const [invites] = useQuery(getInvites, { - where: { email: currentUser!.email }, + where: { email: currentUser!.email.toLowerCase() }, orderBy: { id: "asc" }, include: { project: true }, }) diff --git a/src/invites/queries/getInvites.ts b/src/invites/queries/getInvites.ts index ed06afc9..caecc37d 100644 --- a/src/invites/queries/getInvites.ts +++ b/src/invites/queries/getInvites.ts @@ -7,8 +7,30 @@ interface GetInvitationInput export default resolver.pipe( resolver.authorize(), async ({ where, orderBy, include }: GetInvitationInput) => { + // Normalize email filter to be case-insensitive if present + const normalizedWhere: Prisma.InvitationWhereInput | undefined = where + ? { ...where } + : undefined + + if (normalizedWhere && typeof (normalizedWhere as any).email === "string") { + ;(normalizedWhere as any).email = { + equals: (normalizedWhere as any).email, + mode: "insensitive", + } + } else if ( + normalizedWhere && + (normalizedWhere as any).email && + typeof (normalizedWhere as any).email === "object" && + typeof (normalizedWhere as any).email.equals === "string" + ) { + ;(normalizedWhere as any).email = { + ...(normalizedWhere as any).email, + mode: "insensitive", + } + } + const invites = await db.invitation.findMany({ - where, + where: normalizedWhere, orderBy, include, }) diff --git a/src/notes/components/NotesEditor.tsx b/src/notes/components/NotesEditor.tsx index 3970b53e..5a33ab15 100644 --- a/src/notes/components/NotesEditor.tsx +++ b/src/notes/components/NotesEditor.tsx @@ -35,6 +35,14 @@ import { HeadingNode, QuoteNode } from "@lexical/rich-text" import { CodeNode } from "@lexical/code" import { LinkNode } from "@lexical/link" +const normalizeVisibility = ( + v: "PRIVATE" | "PM_ONLY" | "CONTRIBUTORS" | "SHARED" | undefined +): "PRIVATE" | "PM_ONLY" | "CONTRIBUTORS" => { + if (v === "SHARED") return "CONTRIBUTORS" + if (v === "PRIVATE" || v === "PM_ONLY" || v === "CONTRIBUTORS") return v + return "PRIVATE" +} + function ResetFormatOnEnterPlugin() { const [editor] = useLexicalComposerContext() useEffect(() => { @@ -198,16 +206,25 @@ export default function NoteEditor({ const [lastSavedAt, setLastSavedAt] = useState(null) const [title, setTitle] = useState(initialTitle || "") const [visibility, setVisibility] = useState<"PRIVATE" | "PM_ONLY" | "CONTRIBUTORS">( - initialVisibility + normalizeVisibility(initialVisibility as any) ) const didInitialChange = useRef(false) + useEffect(() => { + setVisibility(normalizeVisibility(initialVisibility as any)) + }, [initialVisibility]) + + const effectiveReadOnly = useMemo(() => { + if (visibility === "CONTRIBUTORS" && canSetContributors) return false + return readOnly + }, [readOnly, visibility, canSetContributors]) + const initialConfig = useMemo( () => ({ namespace: "staple-notes", nodes: [ListNode, ListItemNode, HeadingNode, QuoteNode, CodeNode, LinkNode], onError: (e: any) => console.error(e), - editable: !readOnly, + editable: !effectiveReadOnly, editorState: (editor: any) => { if (initialJSON) { editor.setEditorState(editor.parseEditorState(initialJSON)) @@ -220,7 +237,7 @@ export default function NoteEditor({ } }, }), - [initialJSON, initialMarkdown, readOnly] + [initialJSON, initialMarkdown, effectiveReadOnly] ) const save = useCallback( @@ -234,7 +251,7 @@ export default function NoteEditor({ }) contentJSON = editorState.toJSON() - const visibilityForMutation = (visibility === "CONTRIBUTORS" ? "SHARED" : visibility) as any + const visibilityForMutation = visibility as any if (!currentNoteId) { const created = await createNoteMutation({ @@ -287,7 +304,7 @@ export default function NoteEditor({ onSaveAndClose={(state) => save(state, true)} isSaving={isSaving} lastSavedAt={lastSavedAt} - readOnly={readOnly} + readOnly={effectiveReadOnly} onClose={onClose} visibility={visibility} onVisibilityChange={setVisibility} @@ -307,7 +324,7 @@ export default function NoteEditor({ { - if (readOnly) return + if (effectiveReadOnly) return if (!didInitialChange.current) { didInitialChange.current = true return diff --git a/src/notes/components/NotesPanel.tsx b/src/notes/components/NotesPanel.tsx index f23b9621..de1409be 100644 --- a/src/notes/components/NotesPanel.tsx +++ b/src/notes/components/NotesPanel.tsx @@ -22,7 +22,12 @@ export const NotesPanel = ({ projectId }: { projectId: number }) => { ? [...notes].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()) : [] - const canEdit = (n: any) => !!(n && (n as any).editable) + const canEditRow = (n: any) => { + if (!n) return false + const ownerEditable = !!n.editable + const pmOverride = !!n.canSetContributors && n.visibility === "CONTRIBUTORS" + return ownerEditable || pmOverride + } return (
@@ -76,7 +81,11 @@ export const NotesPanel = ({ projectId }: { projectId: number }) => { initialTitle={n.title ?? ""} initialMarkdown={n.contentMarkdown ?? ""} initialJSON={n.contentJSON} + initialVisibility={n.visibility} className="shadow" + readOnly={!canEditRow(n)} + canSetContributors={!!n.canSetContributors} + onClose={() => setEditingId(null)} onSaved={async () => { setEditingId(null) await refetch() @@ -126,7 +135,7 @@ export const NotesPanel = ({ projectId }: { projectId: number }) => {
)}