diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index d4638617..d0678ec5 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,5 +1,17 @@ # @baseapp-frontend/components +## 0.0.52 + +### Patch Changes + +- Added a popover menu for action options in the ActionsOverlay component. +- Introduced message update functionality with edit mode. +- Implemented hover overlay modes for enhanced action display. +- Introduced new DefaultHoverOverlay and ThreeDotsMenuHoverOverlay components for action overlays. + +- Updated dependencies + - @baseapp-frontend/design-system@0.0.32 + ## 0.0.51 ### Patch Changes diff --git a/packages/components/__generated__/MessageUpdateMutation.graphql.ts b/packages/components/__generated__/MessageUpdateMutation.graphql.ts new file mode 100644 index 00000000..228e669f --- /dev/null +++ b/packages/components/__generated__/MessageUpdateMutation.graphql.ts @@ -0,0 +1,277 @@ +/** + * @generated SignedSource<<13703386b1a0dc3c0c167df73f3cc551>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ + +/* eslint-disable */ +// @ts-nocheck +import { ConcreteRequest, Mutation } from 'relay-runtime' +import { FragmentRefs } from 'relay-runtime' + +export type ChatRoomEditMessageInput = { + clientMutationId?: string | null | undefined + content: string + id: string +} +export type MessageUpdateMutation$variables = { + input: ChatRoomEditMessageInput +} +export type MessageUpdateMutation$data = { + readonly chatRoomEditMessage: + | { + readonly errors: + | ReadonlyArray< + | { + readonly field: string + readonly messages: ReadonlyArray + } + | null + | undefined + > + | null + | undefined + readonly message: + | { + readonly node: + | { + readonly content: string | null | undefined + readonly id: string + readonly ' $fragmentSpreads': FragmentRefs<'MessageItemFragment'> + } + | null + | undefined + } + | null + | undefined + } + | null + | undefined +} +export type MessageUpdateMutation = { + response: MessageUpdateMutation$data + variables: MessageUpdateMutation$variables +} + +const node: ConcreteRequest = (function () { + var v0 = [ + { + defaultValue: null, + kind: 'LocalArgument', + name: 'input', + }, + ], + v1 = [ + { + kind: 'Variable', + name: 'input', + variableName: 'input', + }, + ], + v2 = { + alias: null, + args: null, + kind: 'ScalarField', + name: 'id', + storageKey: null, + }, + v3 = { + alias: null, + args: null, + kind: 'ScalarField', + name: 'content', + storageKey: null, + }, + v4 = { + alias: null, + args: null, + concreteType: 'ErrorType', + kind: 'LinkedField', + name: 'errors', + plural: true, + selections: [ + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'field', + storageKey: null, + }, + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'messages', + storageKey: null, + }, + ], + storageKey: null, + }, + v5 = [v2 /*: any*/] + return { + fragment: { + argumentDefinitions: v0 /*: any*/, + kind: 'Fragment', + metadata: null, + name: 'MessageUpdateMutation', + selections: [ + { + alias: null, + args: v1 /*: any*/, + concreteType: 'ChatRoomEditMessagePayload', + kind: 'LinkedField', + name: 'chatRoomEditMessage', + plural: false, + selections: [ + { + alias: null, + args: null, + concreteType: 'MessageEdge', + kind: 'LinkedField', + name: 'message', + plural: false, + selections: [ + { + alias: null, + args: null, + concreteType: 'Message', + kind: 'LinkedField', + name: 'node', + plural: false, + selections: [ + v2 /*: any*/, + v3 /*: any*/, + { + args: null, + kind: 'FragmentSpread', + name: 'MessageItemFragment', + }, + ], + storageKey: null, + }, + ], + storageKey: null, + }, + v4 /*: any*/, + ], + storageKey: null, + }, + ], + type: 'Mutation', + abstractKey: null, + }, + kind: 'Request', + operation: { + argumentDefinitions: v0 /*: any*/, + kind: 'Operation', + name: 'MessageUpdateMutation', + selections: [ + { + alias: null, + args: v1 /*: any*/, + concreteType: 'ChatRoomEditMessagePayload', + kind: 'LinkedField', + name: 'chatRoomEditMessage', + plural: false, + selections: [ + { + alias: null, + args: null, + concreteType: 'MessageEdge', + kind: 'LinkedField', + name: 'message', + plural: false, + selections: [ + { + alias: null, + args: null, + concreteType: 'Message', + kind: 'LinkedField', + name: 'node', + plural: false, + selections: [ + v2 /*: any*/, + v3 /*: any*/, + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'created', + storageKey: null, + }, + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'extraData', + storageKey: null, + }, + { + alias: null, + args: null, + concreteType: 'Message', + kind: 'LinkedField', + name: 'inReplyTo', + plural: false, + selections: v5 /*: any*/, + storageKey: null, + }, + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'isRead', + storageKey: null, + }, + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'pk', + storageKey: null, + }, + { + alias: null, + args: null, + concreteType: 'Profile', + kind: 'LinkedField', + name: 'profile', + plural: false, + selections: v5 /*: any*/, + storageKey: null, + }, + { + alias: null, + args: null, + kind: 'ScalarField', + name: 'verb', + storageKey: null, + }, + ], + storageKey: null, + }, + ], + storageKey: null, + }, + v4 /*: any*/, + ], + storageKey: null, + }, + ], + }, + params: { + cacheID: 'f9b41a8750ea7be70eae5f1bccf57cbd', + id: null, + metadata: {}, + name: 'MessageUpdateMutation', + operationKind: 'mutation', + text: 'mutation MessageUpdateMutation(\n $input: ChatRoomEditMessageInput!\n) {\n chatRoomEditMessage(input: $input) {\n message {\n node {\n id\n content\n ...MessageItemFragment\n }\n }\n errors {\n field\n messages\n }\n }\n}\n\nfragment MessageItemFragment on Message {\n id\n content\n created\n extraData\n inReplyTo {\n id\n }\n isRead\n pk\n profile {\n id\n }\n verb\n}\n', + }, + } +})() + +;(node as any).hash = 'b118f4853919251adbfb8bcdbf909ca5' + +export default node diff --git a/packages/components/__mocks__/next-font.ts b/packages/components/__mocks__/next-font.ts index 49207c8e..bd54f96c 100644 --- a/packages/components/__mocks__/next-font.ts +++ b/packages/components/__mocks__/next-font.ts @@ -1,3 +1,4 @@ module.exports = require('@baseapp-frontend/test/__mocks__/next-font.ts') +export * from '@baseapp-frontend/test/__mocks__/next-font' export {} diff --git a/packages/components/modules/__shared__/ActionsOverlay/DefaultHoverOverlay/index.tsx b/packages/components/modules/__shared__/ActionsOverlay/DefaultHoverOverlay/index.tsx new file mode 100644 index 00000000..f64a2c38 --- /dev/null +++ b/packages/components/modules/__shared__/ActionsOverlay/DefaultHoverOverlay/index.tsx @@ -0,0 +1,50 @@ +import React, { FC } from 'react' + +import { IconButton, TrashCanIcon } from '@baseapp-frontend/design-system' + +import { ActionOverlayTooltipContainer } from '../styled' +import { DefaultHoverOverlayProps } from './types' + +const DefaultHoverOverlay: FC = ({ + offsetRight, + offsetTop, + enableDelete, + isDeletingItem, + handleDeleteDialogOpen, + actions = [], + handleLongPressItemOptionsClose, +}) => ( + + {enableDelete && ( + + + + )} + {actions?.map(({ label, icon, onClick, disabled, hasPermission, closeOnClick }) => { + if (!hasPermission) return null + + const handleClick = () => { + onClick?.() + if (closeOnClick) { + handleLongPressItemOptionsClose() + } + } + + return ( + + {icon} + + ) + })} + +) + +export default DefaultHoverOverlay diff --git a/packages/components/modules/__shared__/ActionsOverlay/DefaultHoverOverlay/types.ts b/packages/components/modules/__shared__/ActionsOverlay/DefaultHoverOverlay/types.ts new file mode 100644 index 00000000..09570fc0 --- /dev/null +++ b/packages/components/modules/__shared__/ActionsOverlay/DefaultHoverOverlay/types.ts @@ -0,0 +1,10 @@ +import { ActionOverlayProps } from '../types' + +export interface DefaultHoverOverlayProps + extends Pick< + ActionOverlayProps, + 'actions' | 'offsetRight' | 'offsetTop' | 'enableDelete' | 'isDeletingItem' + > { + handleDeleteDialogOpen: () => void + handleLongPressItemOptionsClose: () => void +} diff --git a/packages/components/modules/__shared__/ActionsOverlay/ThreeDotsMenuHoverOverlay/index.tsx b/packages/components/modules/__shared__/ActionsOverlay/ThreeDotsMenuHoverOverlay/index.tsx new file mode 100644 index 00000000..6b48e31a --- /dev/null +++ b/packages/components/modules/__shared__/ActionsOverlay/ThreeDotsMenuHoverOverlay/index.tsx @@ -0,0 +1,68 @@ +import React, { FC } from 'react' + +import { IconButton, Popover, ThreeDotsIcon, TrashCanIcon } from '@baseapp-frontend/design-system' + +import { MenuItem, MenuList, Typography } from '@mui/material' + +import { ActionOverlayTooltipContainer } from '../styled' +import { ThreeDotsMenuHoverOverlayProps } from './types' + +const ThreeDotsMenuHoverOverlay: FC = ({ + offsetRight, + offsetTop, + enableDelete, + isDeletingItem, + handleDeleteDialogOpen, + actions = [], + handleClosePopover, + popover, +}) => ( + + + + + + + {actions?.map(({ label, icon, onClick, disabled, hasPermission }) => { + if (!hasPermission) return null + + return ( + { + onClick?.() + handleClosePopover() + }} + disabled={disabled} + > + {icon} + + {label} + + + ) + })} + {enableDelete && ( + + + + Delete + + + )} + + + +) + +export default ThreeDotsMenuHoverOverlay diff --git a/packages/components/modules/__shared__/ActionsOverlay/ThreeDotsMenuHoverOverlay/types.ts b/packages/components/modules/__shared__/ActionsOverlay/ThreeDotsMenuHoverOverlay/types.ts new file mode 100644 index 00000000..65658d82 --- /dev/null +++ b/packages/components/modules/__shared__/ActionsOverlay/ThreeDotsMenuHoverOverlay/types.ts @@ -0,0 +1,18 @@ +import { Dispatch, MouseEvent, SetStateAction } from 'react' + +import { ActionOverlayProps } from '../types' + +export interface ThreeDotsMenuHoverOverlayProps + extends Pick< + ActionOverlayProps, + 'actions' | 'offsetRight' | 'offsetTop' | 'enableDelete' | 'isDeletingItem' + > { + handleDeleteDialogOpen: () => void + handleClosePopover: () => void + popover: { + open: HTMLElement | null + onOpen: (event: MouseEvent) => void + onClose: () => void + setOpen: Dispatch> + } +} diff --git a/packages/components/modules/__shared__/ActionsOverlay/__storybook__/stories.tsx b/packages/components/modules/__shared__/ActionsOverlay/__storybook__/stories.tsx index aaef39d7..4e61b79b 100644 --- a/packages/components/modules/__shared__/ActionsOverlay/__storybook__/stories.tsx +++ b/packages/components/modules/__shared__/ActionsOverlay/__storybook__/stories.tsx @@ -1,8 +1,9 @@ -import { ArchiveIcon } from '@baseapp-frontend/design-system' +import { ArchiveIcon, PenEditIcon } from '@baseapp-frontend/design-system' import type { Meta, StoryObj } from '@storybook/react' import ActionsOverlay from '..' +import { HOVER_OVERLAY_MODES } from '../constants' import ActionsOverlayOnButton from './ActionsOverlayOnButton' const meta: Meta = { @@ -36,3 +37,26 @@ export const DefaultActionsOverlay: Story = { ], }, } + +export const ActionsOverlayWithThreeDotsMenu: Story = { + name: 'ActionsOverlay with ThreeDotsMenu', + args: { + title: 'Button', + enableDelete: true, + isDeletingItem: false, + handleDeleteItem: () => {}, + ContainerProps: { + flexDirection: 'row-reverse', + }, + hoverOverlayMode: HOVER_OVERLAY_MODES.THREE_DOTS_MENU, + actions: [ + { + label: 'Edit', + icon: , + onClick: () => {}, + hasPermission: true, + closeOnClick: true, + }, + ], + }, +} diff --git a/packages/components/modules/__shared__/ActionsOverlay/constants.ts b/packages/components/modules/__shared__/ActionsOverlay/constants.ts new file mode 100644 index 00000000..ba73fe66 --- /dev/null +++ b/packages/components/modules/__shared__/ActionsOverlay/constants.ts @@ -0,0 +1,4 @@ +export const HOVER_OVERLAY_MODES = { + DEFAULT: 'default', + THREE_DOTS_MENU: 'three-dots-menu', +} as const diff --git a/packages/components/modules/__shared__/ActionsOverlay/index.tsx b/packages/components/modules/__shared__/ActionsOverlay/index.tsx index d8e6ed79..ff56b0dc 100644 --- a/packages/components/modules/__shared__/ActionsOverlay/index.tsx +++ b/packages/components/modules/__shared__/ActionsOverlay/index.tsx @@ -5,13 +5,17 @@ import { SwipeableDrawer as DefaultSwipeableDrawer, IconButton, TrashCanIcon, + usePopover, } from '@baseapp-frontend/design-system' import { LoadingButton } from '@mui/lab' import { Box, Divider, Typography } from '@mui/material' import { LongPressCallbackReason, useLongPress } from 'use-long-press' -import { ActionOverlayTooltipContainer, IconButtonContentContainer } from './styled' +import DefaultHoverOverlay from './DefaultHoverOverlay' +import ThreeDotsMenuHoverOverlay from './ThreeDotsMenuHoverOverlay' +import { HOVER_OVERLAY_MODES } from './constants' +import { IconButtonContentContainer } from './styled' import { ActionOverlayProps, LongPressHandler } from './types' const ActionsOverlay = forwardRef( @@ -28,6 +32,7 @@ const ActionsOverlay = forwardRef( ContainerProps = {}, SwipeableDrawerProps = {}, SwipeableDrawer = DefaultSwipeableDrawer, + hoverOverlayMode = HOVER_OVERLAY_MODES.DEFAULT, }, ref, ) => { @@ -38,6 +43,13 @@ const ActionsOverlay = forwardRef( shouldOpenItemOptions: false, }) + const popover = usePopover() + + const handleClosePopover = () => { + setIsHoveringItem(false) + popover.onClose() + } + const longPressHandlers = useLongPress( (e: React.MouseEvent | React.TouchEvent) => { e.stopPropagation() @@ -70,6 +82,7 @@ const ActionsOverlay = forwardRef( const handleDeleteDialogClose = () => { setIsDeleteDialogOpen(false) setIsHoveringItem(false) + popover.onClose() } const deviceHasHover = @@ -80,6 +93,7 @@ const ActionsOverlay = forwardRef( handleDeleteItem?.() handleLongPressItemOptionsClose() setIsHoveringItem(false) + popover.onClose() } const renderDeleteDialog = () => ( @@ -139,7 +153,9 @@ const ActionsOverlay = forwardRef( {icon} - {label} + + {label} + ) @@ -170,44 +186,38 @@ const ActionsOverlay = forwardRef( } if (deviceHasHover && isHoveringItem) { - return ( - - {enableDelete && ( - - - - )} - {actions?.map(({ label, icon, onClick, disabled, hasPermission, closeOnClick }) => { - if (!hasPermission) return null - - const handleClick = () => { - onClick?.() - if (closeOnClick) { - handleLongPressItemOptionsClose() - } - } - - return ( - - {icon} - - ) - })} - - ) + if (hoverOverlayMode === HOVER_OVERLAY_MODES.DEFAULT) { + return ( + + ) + } + + if (hoverOverlayMode === HOVER_OVERLAY_MODES.THREE_DOTS_MENU) { + return ( + + ) + } } return
} @@ -216,9 +226,16 @@ const ActionsOverlay = forwardRef( setIsHoveringItem(true)} - onMouseLeave={() => setIsHoveringItem(false)} + onMouseLeave={handleClosePopover} position="relative" {...longPressHandlers()} + {...(hoverOverlayMode === HOVER_OVERLAY_MODES.THREE_DOTS_MENU && { + sx: { + width: '100%', + display: 'flex', + justifyContent: 'flex-end', + }, + })} {...ContainerProps} > {renderDeleteDialog()} diff --git a/packages/components/modules/__shared__/ActionsOverlay/types.ts b/packages/components/modules/__shared__/ActionsOverlay/types.ts index 941e5471..463fc0e6 100644 --- a/packages/components/modules/__shared__/ActionsOverlay/types.ts +++ b/packages/components/modules/__shared__/ActionsOverlay/types.ts @@ -1,9 +1,12 @@ import { FC } from 'react' import type { SwipeableDrawerProps } from '@baseapp-frontend/design-system' +import { ValueOf } from '@baseapp-frontend/utils' import { BoxProps } from '@mui/material' +import { HOVER_OVERLAY_MODES } from './constants' + export type OverlayAction = { label: string icon: JSX.Element @@ -27,6 +30,7 @@ export interface ActionOverlayProps extends ActionOverlayTooltipContainerProps { ContainerProps?: Partial SwipeableDrawer?: FC SwipeableDrawerProps?: Partial + hoverOverlayMode?: ValueOf } export interface ActionOverlayTooltipContainerProps extends BoxProps { diff --git a/packages/components/modules/__shared__/SocialInput/SubmitActions/types.ts b/packages/components/modules/__shared__/SocialInput/SubmitActions/types.ts index 72d9cc0c..7e4c7fcc 100644 --- a/packages/components/modules/__shared__/SocialInput/SubmitActions/types.ts +++ b/packages/components/modules/__shared__/SocialInput/SubmitActions/types.ts @@ -6,6 +6,7 @@ export interface SubmitActionsProps { formId: string disabled?: boolean ariaLabel?: string + cancelAriaLabel?: string SendMessageIcon?: FC SendMessageIconProps?: SvgIconProps handleEditCancel?: () => void diff --git a/packages/components/modules/__shared__/SocialInput/styled.tsx b/packages/components/modules/__shared__/SocialInput/styled.tsx index 0cc0ce3d..7a976e90 100644 --- a/packages/components/modules/__shared__/SocialInput/styled.tsx +++ b/packages/components/modules/__shared__/SocialInput/styled.tsx @@ -5,6 +5,7 @@ export const Form = styled('form')(({ theme }) => ({ border: `1px solid ${theme.palette.grey[200]}`, borderRadius: 8, bottom: 0, + width: '100%', marginBottom: theme.spacing(2), position: 'sticky', zIndex: 10, diff --git a/packages/components/modules/comments/CommentUpdate/CommentUpdateSubmitActions/index.tsx b/packages/components/modules/__shared__/UpdateSubmitActions/index.tsx similarity index 64% rename from packages/components/modules/comments/CommentUpdate/CommentUpdateSubmitActions/index.tsx rename to packages/components/modules/__shared__/UpdateSubmitActions/index.tsx index 17911a3e..0c955a07 100644 --- a/packages/components/modules/comments/CommentUpdate/CommentUpdateSubmitActions/index.tsx +++ b/packages/components/modules/__shared__/UpdateSubmitActions/index.tsx @@ -2,16 +2,17 @@ import React, { FC } from 'react' import { CheckMarkIcon, CloseIcon, IconButton } from '@baseapp-frontend/design-system' -import { CommentUpdateSubmitActionsProps } from './types' +import { UpdateSubmitActionsProps } from './types' -const CommentUpdateSubmitActions: FC = ({ +const UpdateSubmitActions: FC = ({ formId, disabled = false, ariaLabel = 'save comment edit', + cancelAriaLabel = 'cancel comment edit', handleEditCancel = () => {}, }) => (
- + @@ -20,4 +21,4 @@ const CommentUpdateSubmitActions: FC = ({
) -export default CommentUpdateSubmitActions +export default UpdateSubmitActions diff --git a/packages/components/modules/comments/CommentUpdate/CommentUpdateSubmitActions/types.ts b/packages/components/modules/__shared__/UpdateSubmitActions/types.ts similarity index 56% rename from packages/components/modules/comments/CommentUpdate/CommentUpdateSubmitActions/types.ts rename to packages/components/modules/__shared__/UpdateSubmitActions/types.ts index ef9db204..30c9b99a 100644 --- a/packages/components/modules/comments/CommentUpdate/CommentUpdateSubmitActions/types.ts +++ b/packages/components/modules/__shared__/UpdateSubmitActions/types.ts @@ -1,6 +1,7 @@ -export interface CommentUpdateSubmitActionsProps { +export interface UpdateSubmitActionsProps { formId: string disabled?: boolean ariaLabel?: string + cancelAriaLabel?: string handleEditCancel?: () => void } diff --git a/packages/components/modules/__shared__/index.ts b/packages/components/modules/__shared__/index.ts index be8faae9..0a3f45fa 100644 --- a/packages/components/modules/__shared__/index.ts +++ b/packages/components/modules/__shared__/index.ts @@ -8,3 +8,6 @@ export type * from './ReactionButton/types' export { default as Timestamp } from './Timestamp' export type * from './Timestamp/types' + +export { default as UpdateSubmitActions } from './UpdateSubmitActions' +export type * from './UpdateSubmitActions/types' diff --git a/packages/components/modules/comments/CommentUpdate/index.tsx b/packages/components/modules/comments/CommentUpdate/index.tsx index 52229a7d..4b213984 100644 --- a/packages/components/modules/comments/CommentUpdate/index.tsx +++ b/packages/components/modules/comments/CommentUpdate/index.tsx @@ -8,13 +8,13 @@ import { zodResolver } from '@hookform/resolvers/zod' import { useForm } from 'react-hook-form' import DefaultSocialInput from '../../__shared__/SocialInput' +import UpdateSubmitActions from '../../__shared__/UpdateSubmitActions' import { SOCIAL_UPSERT_FORM, SOCIAL_UPSERT_FORM_VALIDATION_SCHEMA, } from '../../__shared__/constants' import { SocialUpsertForm } from '../../__shared__/types' import { useCommentUpdateMutation } from '../graphql/mutations/CommentUpdate' -import CommentUpdateSubmitActions from './CommentUpdateSubmitActions' import { CommentUpdateProps } from './types' /** @@ -66,7 +66,7 @@ import { CommentUpdateProps } from './types' * form={form} * submit={onSubmit} * isLoading={isMutationInFlight} - * SubmitActions={CommentUpdateSubmitActions} + * SubmitActions={UpdateSubmitActions} * /> * ); * }; @@ -137,7 +137,7 @@ const CommentUpdate: FC = ({ form={form} formId="comment-update" autoFocusInput - SubmitActions={CommentUpdateSubmitActions} + SubmitActions={UpdateSubmitActions} SubmitActionsProps={{ handleEditCancel, formId: 'comment-update', diff --git a/packages/components/modules/messages/MessageUpdate/index.tsx b/packages/components/modules/messages/MessageUpdate/index.tsx new file mode 100644 index 00000000..18e5560d --- /dev/null +++ b/packages/components/modules/messages/MessageUpdate/index.tsx @@ -0,0 +1,153 @@ +'use client' + +import { FC, useEffect, useRef } from 'react' + +import { setFormRelayErrors } from '@baseapp-frontend/utils' + +import { zodResolver } from '@hookform/resolvers/zod' +import { useForm } from 'react-hook-form' + +import { UpdateSubmitActions } from '../../__shared__' +import DefaultSocialInput from '../../__shared__/SocialInput' +import { + SOCIAL_UPSERT_FORM, + SOCIAL_UPSERT_FORM_VALIDATION_SCHEMA, +} from '../../__shared__/constants' +import { SocialUpsertForm } from '../../__shared__/types' +import { useMessageUpdateMutation } from '../graphql/mutations/MessageUpdate' +import { MessageUpdateProps } from './types' + +/** + * ### MessageUpdate Component + * + * @description + * This is a **BaseApp** feature. + * + * If you believe your changes should be in the BaseApp, please read the **CONTRIBUTING.md** guide. + * + * This component reuses the `SocialInput` component, adding a layer of `GraphQL` mutation and `form` setup to handle message updates. + * + * It leverages the `useMessageUpdateMutation` mutation for updating messages and integrates form validation using + * `react-hook-form` and Zod for schema validation. + * + * ### Extending the Component + * If you need to customize the form validation schema or the GraphQL query, this component serves as a base. + * You can copy the structure and replace the query or validation logic to fit your specific requirements. + * + * #### Example: + * ```ts + * import { useForm } from 'react-hook-form'; + * import { zodResolver } from '@hookform/resolvers/zod'; + * import { myCustomSchema } from './myCustomSchema'; + * import { useMyCustomUpdateMutation } from './myCustomMutation'; + * import SocialInput from './SocialInput'; + * + * const MyCustomMessageUpdate = ({ message, onCancel }) => { + * const form = useForm({ + * resolver: zodResolver(myCustomSchema), + * defaultValues: { body: message.content ?? '' }, + * }); + * const [commitUpdate, isMutationInFlight] = useMyCustomUpdateMutation(); + * + * const onSubmit = (data) => { + * commitUpdate({ + * variables: { input: { id: message.id, content: data.body } }, + * onCompleted: (response) => { + * // handle response + * form.reset(); + * onCancel(); + * }, + * onError: console.error, + * }); + * }; + * + * return ( + * + * ); + * }; + * ``` + */ +const MessageUpdate: FC = ({ + message, + onCancel, + SocialInput = DefaultSocialInput, + SocialInputProps = {}, +}) => { + const inputRef = useRef(null) + + const form = useForm({ + defaultValues: { body: message.content ?? '' }, + resolver: zodResolver(SOCIAL_UPSERT_FORM_VALIDATION_SCHEMA), + }) + + const [commitUpdate, isMutationInFlight] = useMessageUpdateMutation() + + const onSubmit = async (data: SocialUpsertForm) => { + if (isMutationInFlight) return + + commitUpdate({ + variables: { + input: { + id: message.id, + content: data?.body, + }, + }, + onCompleted: (response, errors) => { + if (errors) { + // TODO: handle errors + console.error(errors) + return + } + const mutationErrors = response?.chatRoomEditMessage?.errors + setFormRelayErrors(form, mutationErrors) + + if (!mutationErrors?.length) { + onCancel() + form.reset() + } + }, + // TODO: handle errors + onError: console.error, + }) + } + + const handleEditCancel = () => { + onCancel() + form.setValue(SOCIAL_UPSERT_FORM.body, message.content ?? '') + } + + useEffect(() => { + if (inputRef.current) { + const { length } = inputRef.current.value + inputRef.current.setSelectionRange(length, length) + inputRef.current.focus() + } + }, [inputRef]) + + return ( + + ) +} + +export default MessageUpdate diff --git a/packages/components/modules/messages/MessageUpdate/types.ts b/packages/components/modules/messages/MessageUpdate/types.ts new file mode 100644 index 00000000..a20ae461 --- /dev/null +++ b/packages/components/modules/messages/MessageUpdate/types.ts @@ -0,0 +1,11 @@ +import { FC } from 'react' + +import { MessageItemFragment$data } from '../../../__generated__/MessageItemFragment.graphql' +import { type SocialInputProps } from '../../__shared__/SocialInput/types' + +export interface MessageUpdateProps { + message: MessageItemFragment$data + onCancel: () => void + SocialInput?: FC + SocialInputProps?: Partial +} diff --git a/packages/components/modules/messages/MessagesList/MessagesGroup/MessageItem/index.tsx b/packages/components/modules/messages/MessagesList/MessagesGroup/MessageItem/index.tsx index da6c391d..83d081ee 100644 --- a/packages/components/modules/messages/MessagesList/MessagesGroup/MessageItem/index.tsx +++ b/packages/components/modules/messages/MessagesList/MessagesGroup/MessageItem/index.tsx @@ -1,10 +1,14 @@ -import { FC } from 'react' +import { FC, useRef, useState } from 'react' import { useCurrentProfile } from '@baseapp-frontend/authentication' +import { CopyIcon, DownloadIcon, PenEditIcon } from '@baseapp-frontend/design-system' import { Typography } from '@mui/material' import { useFragment } from 'react-relay' +import ActionsOverlay from '../../../../__shared__/ActionsOverlay' +import { HOVER_OVERLAY_MODES } from '../../../../__shared__/ActionsOverlay/constants' +import MessageUpdate from '../../../MessageUpdate' import { MessageItemFragment } from '../../../graphql/fragments/MessageItem' import { MessageItemContainer } from './styled' import { MessageItemProps } from './types' @@ -13,17 +17,77 @@ const MessageItem: FC = ({ messageRef, isFirstGroupedMessage } const { currentProfile } = useCurrentProfile() const message = useFragment(MessageItemFragment, messageRef) const isOwnMessage = currentProfile?.id === message?.profile?.id + const messageCardRef = useRef(null) - return ( - + const [isEditMode, setIsEditMode] = useState(false) + + const renderMessageContent = () => { + if (isEditMode) { + return setIsEditMode(false)} /> + } + + return ( {message?.content} - + ) + } + + return ( + , + label: 'Copy', + onClick: () => {}, // TODO: Implement copy message + hasPermission: true, + }, + { + disabled: false, + icon: , + label: 'Edit', + onClick: () => { + setIsEditMode(true) + }, + hasPermission: isOwnMessage, + closeOnClick: true, + }, + { + disabled: false, + icon: , + label: 'Download Attachments', + onClick: () => {}, // TODO: Implement download attachments + hasPermission: true, + }, + ]} + hoverOverlayMode={HOVER_OVERLAY_MODES.THREE_DOTS_MENU} + enableDelete + handleDeleteItem={() => {}} // TODO: Implement delete message + isDeletingItem={false} + ContainerProps={{ + flexDirection: isOwnMessage ? 'row' : 'row-reverse', + }} + ref={messageCardRef} + > + + {renderMessageContent()} + + ) } diff --git a/packages/components/modules/messages/graphql/mutations/MessageUpdate.ts b/packages/components/modules/messages/graphql/mutations/MessageUpdate.ts new file mode 100644 index 00000000..2aebc8cb --- /dev/null +++ b/packages/components/modules/messages/graphql/mutations/MessageUpdate.ts @@ -0,0 +1,51 @@ +import { useNotification } from '@baseapp-frontend/utils' + +import { Disposable, UseMutationConfig, graphql, useMutation } from 'react-relay' + +import { MessageUpdateMutation } from '../../../../__generated__/MessageUpdateMutation.graphql' + +export const MessageUpdateMutationQuery = graphql` + mutation MessageUpdateMutation($input: ChatRoomEditMessageInput!) { + chatRoomEditMessage(input: $input) { + message { + node { + id + content + ...MessageItemFragment + } + } + errors { + field + messages + } + } + } +` + +export const useMessageUpdateMutation = (): [ + (config: UseMutationConfig) => Disposable, + boolean, +] => { + const { sendToast } = useNotification() + const [commitMutation, isMutationInFlight] = useMutation( + MessageUpdateMutationQuery, + ) + + const commit = (config: UseMutationConfig) => + commitMutation({ + ...config, + onCompleted: (response, errors) => { + errors?.forEach((error) => { + sendToast(error.message, { type: 'error' }) + }) + + config?.onCompleted?.(response, errors) + }, + onError: (error) => { + sendToast(error.message, { type: 'error' }) + config?.onError?.(error) + }, + }) + + return [commit, isMutationInFlight] +} diff --git a/packages/components/package.json b/packages/components/package.json index e4716d12..afbb9fa6 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,7 +1,7 @@ { "name": "@baseapp-frontend/components", "description": "BaseApp components modules such as comments, notifications, messages, and more.", - "version": "0.0.51", + "version": "0.0.52", "main": "./index.ts", "types": "dist/index.d.ts", "sideEffects": false, diff --git a/packages/components/schema.graphql b/packages/components/schema.graphql index 4f1fbbf4..31ed127f 100644 --- a/packages/components/schema.graphql +++ b/packages/components/schema.graphql @@ -1,78 +1,23 @@ -type Query { - activityLogs(visibility: VisibilityTypes, first: Int = 10, offset: Int, before: String, after: String, last: Int, createdFrom: Date, createdTo: Date, userPk: Decimal, profilePk: Decimal): ActivityLogConnection - organization( - """The ID of the object""" - id: ID! - ): Organization - chatRoom( - """The ID of the object""" - id: ID! - ): ChatRoom - rate( - """The ID of the object""" - id: ID! - ): Rate - report( - """The ID of the object""" - id: ID! - ): Report - comment( - """The ID of the object""" - id: ID! - ): Comment - allComments( - offset: Int - before: String - after: String - first: Int - last: Int - q: String - - """Ordering""" - orderBy: String - ): CommentConnection - urlPath(path: String!): URLPath - allPages(offset: Int, before: String, after: String, first: Int, last: Int, status: PageStatus): PageConnection - page( - """The ID of the object""" - id: ID! - ): Page - allProfiles( - offset: Int - before: String - after: String - first: Int - last: Int - q: String - - """Ordering""" - orderBy: String - ): ProfileConnection - profile( - """The ID of the object""" - id: ID! - ): Profile - users( - offset: Int - before: String - after: String - first: Int - last: Int - q: String +"""Exposes a URL that specifies the behaviour of this scalar.""" +directive @specifiedBy( + """The URL that specifies the behaviour of this scalar.""" + url: String! +) on SCALAR - """Ordering""" - orderBy: String - ): UserConnection - user( - """The ID of the object""" - id: ID! - ): User - me: User - node( - """The ID of the object""" - id: ID! - ): Node - _debug: DjangoDebug +type ActivityLog implements Node { + """The ID of the object""" + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + pk: Int! + metadata: GenericScalar + events(offset: Int, before: String, after: String, first: Int, last: Int, createdFrom: Date, createdTo: Date, userPk: Decimal): NodeLogEventConnection + user: User + profile: Profile + visibility: VisibilityTypes + verb: String + ipAddress: String + url: String } type ActivityLogConnection { @@ -85,23 +30,6 @@ type ActivityLogConnection { edgeCount: Int } -""" -The Relay compliant `PageInfo` type, containing data necessary to paginate this connection. -""" -type PageInfo { - """When paginating forwards, are there more items?""" - hasNextPage: Boolean! - - """When paginating backwards, are there more items?""" - hasPreviousPage: Boolean! - - """When paginating backwards, the cursor to continue.""" - startCursor: String - - """When paginating forwards, the cursor to continue.""" - endCursor: String -} - """A Relay edge containing a `ActivityLog` and its cursor.""" type ActivityLogEdge { """The item at the end of the edge""" @@ -111,383 +39,298 @@ type ActivityLogEdge { cursor: String! } -type ActivityLog implements Node { +type Block implements Node { """The ID of the object""" id: ID! - createdAt: DateTime! - updatedAt: DateTime! - pk: Int! - metadata: GenericScalar - events(offset: Int, before: String, after: String, first: Int, last: Int, createdFrom: Date, createdTo: Date, userPk: Decimal): NodeLogEventConnection + created: DateTime! + modified: DateTime! + actor: Profile + target: Profile user: User - profile: Profile - visibility: VisibilityTypes - verb: String - ipAddress: String - url: String -} - -"""An object with an ID""" -interface Node { - """The ID of the object""" - id: ID! + pk: Int! } -""" -The `DateTime` scalar type represents a DateTime -value as specified by -[iso8601](https://en.wikipedia.org/wiki/ISO_8601). -""" -scalar DateTime - -""" -The `GenericScalar` scalar type represents a generic -GraphQL scalar value that could be: -String, Boolean, Int, Float, List or Object. -""" -scalar GenericScalar - -type NodeLogEventConnection { +type BlockConnection { """Pagination data for this connection.""" pageInfo: PageInfo! """Contains the nodes in this connection.""" - edges: [NodeLogEventEdge]! + edges: [BlockEdge]! totalCount: Int edgeCount: Int } -"""A Relay edge containing a `NodeLogEvent` and its cursor.""" -type NodeLogEventEdge { +"""A Relay edge containing a `Block` and its cursor.""" +type BlockEdge { """The item at the end of the edge""" - node: NodeLogEvent + node: Block """A cursor for use in pagination""" cursor: String! } -type NodeLogEvent implements Node { - """The user associated with the event.""" - user: User +interface BlocksInterface { + """The ID of the object""" + id: ID! + blockers(offset: Int, before: String, after: String, first: Int, last: Int): BlockConnection + blocking(offset: Int, before: String, after: String, first: Int, last: Int): BlockConnection + blockersCount: Int + blockingCount: Int + isBlockedByMe(profileId: ID): Boolean +} + +input BlockToggleInput { + actorObjectId: ID! + targetObjectId: ID! + clientMutationId: String +} + +type BlockTogglePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + block: BlockEdge + target: BlocksInterface + actor: BlocksInterface + blockDeletedId: ID + clientMutationId: String +} +type ChatRoom implements Node { """The ID of the object""" id: ID! + title: String + image(width: Int!, height: Int!): File + lastMessage: Message + lastMessageTime: DateTime + participantsCount: Int! + isGroup: Boolean! + participants(offset: Int, before: String, after: String, first: Int, last: Int): ChatRoomParticipantConnection pk: Int! + allMessages(offset: Int, before: String, after: String, first: Int, last: Int, verb: Verbs): MessageConnection + unreadMessages(profileId: ID): UnreadMessageCount + isArchived(profileId: ID): Boolean +} - """The object of the event.""" - obj: Node +input ChatRoomArchiveInput { + roomId: ID! + profileId: ID! + archive: Boolean! + clientMutationId: String +} - """The event label.""" - label: String +type ChatRoomArchivePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + room: ChatRoom + clientMutationId: String +} - """The raw data of the event.""" - data: GenericScalar +type ChatRoomConnection { + """Pagination data for this connection.""" + pageInfo: PageInfo! - """The diff between the previous event of the same label.""" - diff: GenericScalar + """Contains the nodes in this connection.""" + edges: [ChatRoomEdge]! + totalCount: Int + edgeCount: Int +} - """When the event was created.""" - createdAt: DateTime +input ChatRoomCreateInput { + profileId: ID! + participants: [ID]! + isGroup: Boolean = false + title: String + clientMutationId: String } -type User implements Node & PermissionsInterface & NotificationsInterface & PageInterface & RatingsInterface & ProfilesInterface & ProfileInterface { - lastLogin: DateTime - isSuperuser: Boolean - email: String - isEmailVerified: Boolean - dateJoined: DateTime! - passwordChangedDate: DateTime - newEmail: String - isNewEmailConfirmed: Boolean - firstName: String @deprecated(reason: "Deprecated in favor of fullName and shortName") - lastName: String @deprecated(reason: "Deprecated in favor of fullName and shortName") - phoneNumber: String +type ChatRoomCreatePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + room: ChatRoomEdge + profile: Profile + clientMutationId: String +} - """ - Designates whether this user should be treated as active. Unselect this instead of deleting accounts. - """ - isActive: Boolean! - isStaff: Boolean - reactions(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ReactionConnection! - comments( - offset: Int - before: String - after: String - first: Int - last: Int - q: String +"""A Relay edge containing a `ChatRoom` and its cursor.""" +type ChatRoomEdge { + """The item at the end of the edge""" + node: ChatRoom - """Ordering""" - orderBy: String - ): CommentConnection! - pages(offset: Int, before: String, after: String, first: Int, last: Int, status: PageStatus): PageConnection! + """A cursor for use in pagination""" + cursor: String! +} - """The ID of the object""" +input ChatRoomEditMessageInput { id: ID! + content: String! + clientMutationId: String +} - """ - Determine if the logged in user has a specific permission for this object. - """ - hasPerm(perm: String!): Boolean - notificationsUnreadCount: Int - notifications(offset: Int, before: String, after: String, first: Int, last: Int, level: NotificationsNotificationLevelChoices, unread: Boolean, verbs: String): NotificationConnection - notificationSettings(offset: Int, before: String, after: String, first: Int, last: Int): NotificationSettingConnection - isNotificationSettingActive(verb: String!, channel: NotificationChannelTypes!): Boolean - urlPath: URLPath - urlPaths: [URLPath] - metadata: Metadata - ratingsCount: Int - ratingsSum: Int - ratingsAverage: Float - ratings(offset: Int, before: String, after: String, first: Int, last: Int): RateConnection - isRatingsEnabled: Boolean! - myRating(profileId: ID): Rate - profiles( - offset: Int - before: String - after: String - first: Int - last: Int - q: String +type ChatRoomEditMessagePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + message: MessageEdge + clientMutationId: String +} - """Ordering""" - orderBy: String - ): ProfileConnection +type ChatRoomOnMessagesCountUpdate { profile: Profile - pk: Int! - activityLogs(visibility: VisibilityTypes, first: Int = 10, offset: Int, before: String, after: String, last: Int, createdFrom: Date, createdTo: Date, userPk: Decimal, profilePk: Decimal): ActivityLogConnection - isAuthenticated: Boolean - fullName: String - shortName: String - avatar(width: Int!, height: Int!): File } -interface PermissionsInterface { - """The ID of the object""" - id: ID! +type ChatRoomOnNewMessage { + message: MessageEdge +} - """ - Determine if the logged in user has a specific permission for this object. - """ - hasPerm(perm: String!): Boolean +type ChatRoomOnRoomUpdate { + room: ChatRoomEdge + removedParticipants: [ChatRoomParticipant] } -interface NotificationsInterface { +type ChatRoomParticipant implements Node { """The ID of the object""" id: ID! - notificationsUnreadCount: Int - notifications(offset: Int, before: String, after: String, first: Int, last: Int, level: NotificationsNotificationLevelChoices, unread: Boolean, verbs: String): NotificationConnection - notificationSettings(offset: Int, before: String, after: String, first: Int, last: Int): NotificationSettingConnection - isNotificationSettingActive(verb: String!, channel: NotificationChannelTypes!): Boolean + profile: Profile + role: ChatRoomParticipantRoles + hasArchivedRoom: Boolean! + pk: Int! } -type NotificationConnection { +type ChatRoomParticipantConnection { """Pagination data for this connection.""" pageInfo: PageInfo! """Contains the nodes in this connection.""" - edges: [NotificationEdge]! + edges: [ChatRoomParticipantEdge]! totalCount: Int edgeCount: Int } -"""A Relay edge containing a `Notification` and its cursor.""" -type NotificationEdge { +"""A Relay edge containing a `ChatRoomParticipant` and its cursor.""" +type ChatRoomParticipantEdge { """The item at the end of the edge""" - node: Notification + node: ChatRoomParticipant """A cursor for use in pagination""" cursor: String! } -type Notification implements Node { - """The ID of the object""" - id: ID! - level: NotificationsNotificationLevelChoices! - recipient: User! - unread: Boolean! - actorObjectId: String! - verb: String! - description: String - targetObjectId: String - actionObjectObjectId: String - timestamp: DateTime! - public: Boolean! - deleted: Boolean! - emailed: Boolean! - data: GenericScalar - pk: Int! - actor: Node - target: Node - actionObject: Node -} - """An enumeration.""" -enum NotificationsNotificationLevelChoices { - """success""" - SUCCESS - - """info""" - INFO - - """warning""" - WARNING +enum ChatRoomParticipantRoles { + """member""" + MEMBER - """error""" - ERROR + """admin""" + ADMIN } -type NotificationSettingConnection { - """Pagination data for this connection.""" - pageInfo: PageInfo! +input ChatRoomReadMessagesInput { + roomId: ID! + profileId: ID! + messageIds: [ID] + clientMutationId: String +} - """Contains the nodes in this connection.""" - edges: [NotificationSettingEdge]! - totalCount: Int - edgeCount: Int +type ChatRoomReadMessagesPayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + room: ChatRoom + profile: Profile + messages: [Message] + clientMutationId: String } -"""A Relay edge containing a `NotificationSetting` and its cursor.""" -type NotificationSettingEdge { - """The item at the end of the edge""" - node: NotificationSetting +input ChatRoomSendMessageInput { + roomId: ID! + profileId: ID! + content: String! + inReplyToId: ID + clientMutationId: String +} - """A cursor for use in pagination""" - cursor: String! +type ChatRoomSendMessagePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + message: MessageEdge + clientMutationId: String } -type NotificationSetting implements Node { +interface ChatRoomsInterface { """The ID of the object""" id: ID! - created: DateTime! - modified: DateTime! - user: User! - channel: NotificationChannelTypes - verb: String! - isActive: Boolean! - pk: Int! -} + chatRooms( + offset: Int + before: String + after: String + first: Int + last: Int + q: String -"""An enumeration.""" -enum NotificationChannelTypes { - """All""" - ALL + """Ordering""" + orderBy: String + profileId: String + unreadMessages: Boolean + archived: Boolean + ): ChatRoomConnection + unreadMessagesCount: Int +} - """Email""" - EMAIL +input ChatRoomUnreadInput { + roomId: ID! + profileId: ID! + clientMutationId: String +} - """Push""" - PUSH +type ChatRoomUnreadPayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + room: ChatRoom + profile: Profile + clientMutationId: String +} - """In-App""" - IN_APP +input ChatRoomUpdateInput { + roomId: ID! + profileId: ID! + title: String + deleteImage: Boolean = false + addParticipants: [ID] = [] + removeParticipants: [ID] = [] + clientMutationId: String } -interface PageInterface { - """The ID of the object""" - id: ID! - urlPath: URLPath - urlPaths: [URLPath] - metadata: Metadata +type ChatRoomUpdatePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + room: ChatRoomEdge + removedParticipants: [ChatRoomParticipant] + clientMutationId: String } -type URLPath implements Node { - """The ID of the object""" - id: ID! +type Comment implements Node & CommentsInterface & ReactionsInterface & PermissionsInterface & NodeActivityLogInterface { created: DateTime! modified: DateTime! - path: String! - language: Languages - isActive: Boolean! - pk: Int! - target: PageInterface -} + user: User + profile: Profile + body: String -"""Languages available""" -enum Languages { - en - es - pt -} - -type Metadata { - created: DateTime! - modified: DateTime! - targetObjectId: Int - language: Languages - metaTitle: String - metaDescription: String - metaRobots: String - metaOgType: String - metaOgImage(width: Int!, height: Int!): File - pk: Int! -} - -type File { - url: String! -} - -interface RatingsInterface { - """The ID of the object""" - id: ID! - ratingsCount: Int - ratingsSum: Int - ratingsAverage: Float - ratings(offset: Int, before: String, after: String, first: Int, last: Int): RateConnection - isRatingsEnabled: Boolean! - myRating(profileId: ID): Rate -} - -type RateConnection { - """Pagination data for this connection.""" - pageInfo: PageInfo! - - """Contains the nodes in this connection.""" - edges: [RateEdge]! - totalCount: Int - edgeCount: Int -} - -"""A Relay edge containing a `Rate` and its cursor.""" -type RateEdge { - """The item at the end of the edge""" - node: Rate - - """A cursor for use in pagination""" - cursor: String! -} - -type Rate implements Node { - """The ID of the object""" - id: ID! - created: DateTime! - modified: DateTime! - user: User! - profile: Profile - value: Int! - pk: Int! - target: Node -} + """languaged used in the comment""" + language: String + isEdited: Boolean! + isPinned: Boolean! + inReplyTo: Comment + status: CommentStatus -type Profile implements Node & PermissionsInterface & PageInterface & FollowsInterface & BlocksInterface & ChatRoomsInterface { """The ID of the object""" id: ID! - created: DateTime! - modified: DateTime! - blockersCount: Int - blockingCount: Int - followersCount: Int - followingCount: Int - reportsCount: JSONString! - commentsCount: JSONString! - isCommentsEnabled: Boolean! - name: String - image(width: Int!, height: Int!): File - bannerImage(width: Int!, height: Int!): File - biography: String - targetObjectId: Int - status: ProfilesProfileStatusChoices! - owner: User! + commentsCount: CommentsCount! comments( offset: Int before: String @@ -498,145 +341,98 @@ type Profile implements Node & PermissionsInterface & PageInterface & FollowsInt """Ordering""" orderBy: String - ): CommentConnection! - reactions(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ReactionConnection! - ratings(offset: Int, before: String, after: String, first: Int, last: Int): RateConnection! - user: User - members( - offset: Int - before: String - after: String - first: Int - last: Int - role: ProfileRoles - - """Ordering""" - orderBy: String - q: String - ): ProfileUserRoleConnection - chatroomparticipantSet(offset: Int, before: String, after: String, first: Int, last: Int, profile_TargetContentType: ID): ChatRoomParticipantConnection! - unreadmessagecountSet(offset: Int, before: String, after: String, first: Int, last: Int): UnreadMessageCountConnection! - messageSet(offset: Int, before: String, after: String, first: Int, last: Int, verb: Verbs): MessageConnection! - following(offset: Int, before: String, after: String, first: Int, last: Int, targetIsFollowingBack: Boolean): FollowConnection - followers(offset: Int, before: String, after: String, first: Int, last: Int, targetIsFollowingBack: Boolean): FollowConnection - blocking(offset: Int, before: String, after: String, first: Int, last: Int): BlockConnection - blockers(offset: Int, before: String, after: String, first: Int, last: Int): BlockConnection - organization: Organization + ): CommentConnection + isCommentsEnabled: Boolean! + reactionsCount: ReactionsCount + reactions(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ReactionConnection + isReactionsEnabled: Boolean! + myReaction(profileId: ID): Reaction """ Determine if the logged in user has a specific permission for this object. """ hasPerm(perm: String!): Boolean - urlPath: URLPath - urlPaths: [URLPath] - metadata: Metadata - isFollowedByMe(profileId: ID): Boolean - isBlockedByMe(profileId: ID): Boolean - chatRooms( - offset: Int - before: String - after: String - first: Int - last: Int - q: String - - """Ordering""" - orderBy: String - profileId: String - unreadMessages: Boolean - archived: Boolean - ): ChatRoomConnection - unreadMessagesCount: Int + nodeActivityLogs(visibility: VisibilityTypes, first: Int = 10, offset: Int, before: String, after: String, last: Int, createdFrom: Date, createdTo: Date, userPk: Decimal, profilePk: Decimal): ActivityLogConnection pk: Int! - activityLogs(visibility: VisibilityTypes, first: Int = 10, offset: Int, before: String, after: String, last: Int, createdFrom: Date, createdTo: Date, userPk: Decimal, profilePk: Decimal): ActivityLogConnection - target: ProfileInterface -} - -interface FollowsInterface { - """The ID of the object""" - id: ID! - followers(offset: Int, before: String, after: String, first: Int, last: Int, targetIsFollowingBack: Boolean): FollowConnection - following(offset: Int, before: String, after: String, first: Int, last: Int, targetIsFollowingBack: Boolean): FollowConnection - followersCount: Int - followingCount: Int - isFollowedByMe(profileId: ID): Boolean + target: CommentsInterface } -type FollowConnection { +type CommentConnection { """Pagination data for this connection.""" pageInfo: PageInfo! """Contains the nodes in this connection.""" - edges: [FollowEdge]! + edges: [CommentEdge]! totalCount: Int edgeCount: Int } -"""A Relay edge containing a `Follow` and its cursor.""" -type FollowEdge { - """The item at the end of the edge""" - node: Follow - - """A cursor for use in pagination""" - cursor: String! +input CommentCreateInput { + targetObjectId: ID! + inReplyToId: ID + profileId: ID + body: String! + clientMutationId: String } -type Follow implements Node { - """The ID of the object""" - id: ID! - created: DateTime! - modified: DateTime! - user: User - actor: Profile! - targetIsFollowingBack: Boolean! - target: Profile! - pk: Int! +type CommentCreatePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + comment: CommentEdge + clientMutationId: String } -interface BlocksInterface { - """The ID of the object""" +input CommentDeleteInput { id: ID! - blockers(offset: Int, before: String, after: String, first: Int, last: Int): BlockConnection - blocking(offset: Int, before: String, after: String, first: Int, last: Int): BlockConnection - blockersCount: Int - blockingCount: Int - isBlockedByMe(profileId: ID): Boolean + clientMutationId: String } -type BlockConnection { - """Pagination data for this connection.""" - pageInfo: PageInfo! - - """Contains the nodes in this connection.""" - edges: [BlockEdge]! - totalCount: Int - edgeCount: Int +type CommentDeletePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + deletedId: ID + target: CommentsInterface + inReplyTo: Comment + clientMutationId: String } -"""A Relay edge containing a `Block` and its cursor.""" -type BlockEdge { +"""A Relay edge containing a `Comment` and its cursor.""" +type CommentEdge { """The item at the end of the edge""" - node: Block + node: Comment """A cursor for use in pagination""" cursor: String! } -type Block implements Node { - """The ID of the object""" +input CommentPinInput { id: ID! - created: DateTime! - modified: DateTime! - actor: Profile - target: Profile - user: User - pk: Int! + clientMutationId: String } -interface ChatRoomsInterface { +type CommentPinPayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + comment: Comment + clientMutationId: String +} + +type CommentsCount { + total: Int + main: Int + replies: Int + pinned: Int + reported: Int +} + +interface CommentsInterface { """The ID of the object""" id: ID! - chatRooms( + commentsCount: CommentsCount! + comments( offset: Int before: String after: String @@ -646,69 +442,187 @@ interface ChatRoomsInterface { """Ordering""" orderBy: String - profileId: String - unreadMessages: Boolean - archived: Boolean - ): ChatRoomConnection - unreadMessagesCount: Int + ): CommentConnection + isCommentsEnabled: Boolean! } -type ChatRoomConnection { +"""An enumeration.""" +enum CommentStatus { + DELETED + PUBLISHED +} + +input CommentUpdateInput { + id: ID! + body: String! + clientMutationId: String +} + +type CommentUpdatePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + comment: Comment + clientMutationId: String +} + +""" +The `Date` scalar type represents a Date +value as specified by +[iso8601](https://en.wikipedia.org/wiki/ISO_8601). +""" +scalar Date + +""" +The `DateTime` scalar type represents a DateTime +value as specified by +[iso8601](https://en.wikipedia.org/wiki/ISO_8601). +""" +scalar DateTime + +"""The `Decimal` scalar type represents a python Decimal.""" +scalar Decimal + +"""Debugging information for the current query.""" +type DjangoDebug { + """Executed SQL queries for this API query.""" + sql: [DjangoDebugSQL] + + """Raise exceptions for this API query.""" + exceptions: [DjangoDebugException] +} + +"""Represents a single exception raised.""" +type DjangoDebugException { + """The class of the exception""" + excType: String! + + """The message of the exception""" + message: String! + + """The stack trace""" + stack: String! +} + +"""Represents a single database query made to a Django managed DB.""" +type DjangoDebugSQL { + """The type of database being used (e.g. postrgesql, mysql, sqlite).""" + vendor: String! + + """The Django database alias (e.g. 'default').""" + alias: String! + + """The actual SQL sent to this database.""" + sql: String + + """Duration of this database query in seconds.""" + duration: Float! + + """The raw SQL of this query, without params.""" + rawSql: String! + + """JSON encoded database query parameters.""" + params: String! + + """Start time of this database query.""" + startTime: Float! + + """Stop time of this database query.""" + stopTime: Float! + + """Whether this database query took more than 10 seconds.""" + isSlow: Boolean! + + """Whether this database query was a SELECT.""" + isSelect: Boolean! + + """Postgres transaction ID if available.""" + transId: String + + """Postgres transaction status if available.""" + transStatus: String + + """Postgres isolation level if available.""" + isoLevel: String + + """Postgres connection encoding if available.""" + encoding: String +} + +type ErrorType { + field: String! + messages: [String!]! +} + +type File { + url: String! +} + +type Follow implements Node { + """The ID of the object""" + id: ID! + created: DateTime! + modified: DateTime! + user: User + actor: Profile! + targetIsFollowingBack: Boolean! + target: Profile! + pk: Int! +} + +type FollowConnection { """Pagination data for this connection.""" pageInfo: PageInfo! """Contains the nodes in this connection.""" - edges: [ChatRoomEdge]! + edges: [FollowEdge]! totalCount: Int edgeCount: Int } -"""A Relay edge containing a `ChatRoom` and its cursor.""" -type ChatRoomEdge { +"""A Relay edge containing a `Follow` and its cursor.""" +type FollowEdge { """The item at the end of the edge""" - node: ChatRoom + node: Follow """A cursor for use in pagination""" cursor: String! } -type ChatRoom implements Node { +interface FollowsInterface { """The ID of the object""" id: ID! - title: String - image(width: Int!, height: Int!): File - lastMessage: Message - lastMessageTime: DateTime - participantsCount: Int! - isGroup: Boolean! - participants(offset: Int, before: String, after: String, first: Int, last: Int): ChatRoomParticipantConnection - pk: Int! - allMessages(offset: Int, before: String, after: String, first: Int, last: Int, verb: Verbs): MessageConnection - unreadMessages(profileId: ID): UnreadMessageCount - isArchived(profileId: ID): Boolean + followers(offset: Int, before: String, after: String, first: Int, last: Int, targetIsFollowingBack: Boolean): FollowConnection + following(offset: Int, before: String, after: String, first: Int, last: Int, targetIsFollowingBack: Boolean): FollowConnection + followersCount: Int + followingCount: Int + isFollowedByMe(profileId: ID): Boolean } -type Message implements Node { - """The ID of the object""" - id: ID! - created: DateTime! - content: String - user: User - profile: Profile - verb: Verbs - room: ChatRoom - inReplyTo: Message - extraData: JSONString - pk: Int! - actionObject: Node - isRead(profileId: ID): Boolean +input FollowToggleInput { + actorObjectId: ID! + targetObjectId: ID! + clientMutationId: String } -"""An enumeration.""" -enum Verbs { - SENT_MESSAGE +type FollowTogglePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + follow: FollowEdge + target: FollowsInterface + actor: FollowsInterface + followDeletedId: ID + clientMutationId: String } +""" +The `GenericScalar` scalar type represents a generic +GraphQL scalar value that could be: +String, Boolean, Int, Float, List or Object. +""" +scalar GenericScalar + """ Allows use of a JSON String for input / output from the GraphQL schema. @@ -717,41 +631,27 @@ schema (one of the key benefits of GraphQL). """ scalar JSONString -type ChatRoomParticipantConnection { - """Pagination data for this connection.""" - pageInfo: PageInfo! - - """Contains the nodes in this connection.""" - edges: [ChatRoomParticipantEdge]! - totalCount: Int - edgeCount: Int -} - -"""A Relay edge containing a `ChatRoomParticipant` and its cursor.""" -type ChatRoomParticipantEdge { - """The item at the end of the edge""" - node: ChatRoomParticipant - - """A cursor for use in pagination""" - cursor: String! +"""Languages available""" +enum Languages { + en + es + pt } -type ChatRoomParticipant implements Node { +type Message implements Node { """The ID of the object""" id: ID! + created: DateTime! + content: String + user: User profile: Profile - role: ChatRoomParticipantRoles - hasArchivedRoom: Boolean! + verb: Verbs + room: ChatRoom + inReplyTo: Message + extraData: JSONString pk: Int! -} - -"""An enumeration.""" -enum ChatRoomParticipantRoles { - """member""" - MEMBER - - """admin""" - ADMIN + actionObject: Node + isRead(profileId: ID): Boolean } type MessageConnection { @@ -773,334 +673,300 @@ type MessageEdge { cursor: String! } -type UnreadMessageCount implements Node { - markedUnread: Boolean! - count: Int! - - """The ID of the object""" - id: ID! +type Metadata { + created: DateTime! + modified: DateTime! + targetObjectId: Int + language: Languages + metaTitle: String + metaDescription: String + metaRobots: String + metaOgType: String + metaOgImage(width: Int!, height: Int!): File pk: Int! } -"""An enumeration.""" -enum ProfilesProfileStatusChoices { - """public""" - A_1 - - """private""" - A_2 -} - -type CommentConnection { - """Pagination data for this connection.""" - pageInfo: PageInfo! - - """Contains the nodes in this connection.""" - edges: [CommentEdge]! - totalCount: Int - edgeCount: Int +type Mutation { + rateCreate(input: RateCreateInput!): RateCreatePayload + organizationCreate(input: OrganizationCreateInput!): OrganizationCreatePayload + chatRoomCreate(input: ChatRoomCreateInput!): ChatRoomCreatePayload + chatRoomUpdate(input: ChatRoomUpdateInput!): ChatRoomUpdatePayload + chatRoomSendMessage(input: ChatRoomSendMessageInput!): ChatRoomSendMessagePayload + chatRoomEditMessage(input: ChatRoomEditMessageInput!): ChatRoomEditMessagePayload + chatRoomReadMessages(input: ChatRoomReadMessagesInput!): ChatRoomReadMessagesPayload + chatRoomUnread(input: ChatRoomUnreadInput!): ChatRoomUnreadPayload + chatRoomArchive(input: ChatRoomArchiveInput!): ChatRoomArchivePayload + reportCreate(input: ReportCreateInput!): ReportCreatePayload + followToggle(input: FollowToggleInput!): FollowTogglePayload + blockToggle(input: BlockToggleInput!): BlockTogglePayload + reactionToggle(input: ReactionToggleInput!): ReactionTogglePayload + notificationsMarkAsRead(input: NotificationsMarkAsReadInput!): NotificationsMarkAsReadPayload + notificationsMarkAllAsRead(input: NotificationsMarkAllAsReadInput!): NotificationsMarkAllAsReadPayload + notificationSettingToggle(input: NotificationSettingToggleInput!): NotificationSettingTogglePayload + commentCreate(input: CommentCreateInput!): CommentCreatePayload + commentUpdate(input: CommentUpdateInput!): CommentUpdatePayload + commentPin(input: CommentPinInput!): CommentPinPayload + commentDelete(input: CommentDeleteInput!): CommentDeletePayload + pageCreate(input: PageCreateInput!): PageCreatePayload + pageEdit(input: PageEditInput!): PageEditPayload + profileCreate(input: ProfileCreateInput!): ProfileCreatePayload + profileUpdate(input: ProfileUpdateInput!): ProfileUpdatePayload + profileDelete(input: ProfileDeleteInput!): ProfileDeletePayload + profileRoleUpdate(input: RoleUpdateInput!): RoleUpdatePayload } -"""A Relay edge containing a `Comment` and its cursor.""" -type CommentEdge { - """The item at the end of the edge""" - node: Comment +"""An object with an ID""" +interface Node { + """The ID of the object""" + id: ID! +} - """A cursor for use in pagination""" - cursor: String! +interface NodeActivityLogInterface { + nodeActivityLogs(visibility: VisibilityTypes, first: Int = 10, offset: Int, before: String, after: String, last: Int, createdFrom: Date, createdTo: Date, userPk: Decimal, profilePk: Decimal): ActivityLogConnection } -type Comment implements Node & CommentsInterface & ReactionsInterface & PermissionsInterface & NodeActivityLogInterface { - created: DateTime! - modified: DateTime! +type NodeLogEvent implements Node { + """The user associated with the event.""" user: User - profile: Profile - body: String - - """languaged used in the comment""" - language: String - isEdited: Boolean! - isPinned: Boolean! - inReplyTo: Comment - status: CommentStatus """The ID of the object""" id: ID! - commentsCount: CommentsCount! - comments( - offset: Int - before: String - after: String - first: Int - last: Int - q: String - - """Ordering""" - orderBy: String - ): CommentConnection - isCommentsEnabled: Boolean! - reactionsCount: ReactionsCount - reactions(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ReactionConnection - isReactionsEnabled: Boolean! - myReaction(profileId: ID): Reaction - - """ - Determine if the logged in user has a specific permission for this object. - """ - hasPerm(perm: String!): Boolean - nodeActivityLogs(visibility: VisibilityTypes, first: Int = 10, offset: Int, before: String, after: String, last: Int, createdFrom: Date, createdTo: Date, userPk: Decimal, profilePk: Decimal): ActivityLogConnection pk: Int! - target: CommentsInterface -} -interface CommentsInterface { - """The ID of the object""" - id: ID! - commentsCount: CommentsCount! - comments( - offset: Int - before: String - after: String - first: Int - last: Int - q: String + """The object of the event.""" + obj: Node - """Ordering""" - orderBy: String - ): CommentConnection - isCommentsEnabled: Boolean! -} + """The event label.""" + label: String -type CommentsCount { - total: Int - main: Int - replies: Int - pinned: Int - reported: Int -} + """The raw data of the event.""" + data: GenericScalar -interface ReactionsInterface { - """The ID of the object""" - id: ID! - reactionsCount: ReactionsCount - reactions(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ReactionConnection - isReactionsEnabled: Boolean! - myReaction(profileId: ID): Reaction -} + """The diff between the previous event of the same label.""" + diff: GenericScalar -type ReactionsCount { - LIKE: Int - DISLIKE: Int - total: Int + """When the event was created.""" + createdAt: DateTime } -type ReactionConnection { +type NodeLogEventConnection { """Pagination data for this connection.""" pageInfo: PageInfo! """Contains the nodes in this connection.""" - edges: [ReactionEdge]! + edges: [NodeLogEventEdge]! totalCount: Int edgeCount: Int } -"""A Relay edge containing a `Reaction` and its cursor.""" -type ReactionEdge { +"""A Relay edge containing a `NodeLogEvent` and its cursor.""" +type NodeLogEventEdge { """The item at the end of the edge""" - node: Reaction + node: NodeLogEvent """A cursor for use in pagination""" cursor: String! } -type Reaction implements Node { +type Notification implements Node { """The ID of the object""" id: ID! - created: DateTime! - modified: DateTime! - user: User! - reactionType: ReactionTypes + level: NotificationsNotificationLevelChoices! + recipient: User! + unread: Boolean! + actorObjectId: String! + verb: String! + description: String + targetObjectId: String + actionObjectObjectId: String + timestamp: DateTime! + public: Boolean! + deleted: Boolean! + emailed: Boolean! + data: GenericScalar pk: Int! + actor: Node target: Node + actionObject: Node } """An enumeration.""" -enum ReactionTypes { - """like""" - LIKE - - """dislike""" - DISLIKE -} - -interface NodeActivityLogInterface { - nodeActivityLogs(visibility: VisibilityTypes, first: Int = 10, offset: Int, before: String, after: String, last: Int, createdFrom: Date, createdTo: Date, userPk: Decimal, profilePk: Decimal): ActivityLogConnection -} - -"""An enumeration.""" -enum VisibilityTypes { - """public""" - PUBLIC - - """private""" - PRIVATE - - """internal""" - INTERNAL -} +enum NotificationChannelTypes { + """All""" + ALL -""" -The `Date` scalar type represents a Date -value as specified by -[iso8601](https://en.wikipedia.org/wiki/ISO_8601). -""" -scalar Date + """Email""" + EMAIL -"""The `Decimal` scalar type represents a python Decimal.""" -scalar Decimal + """Push""" + PUSH -"""An enumeration.""" -enum CommentStatus { - DELETED - PUBLISHED + """In-App""" + IN_APP } -type ProfileUserRoleConnection { +type NotificationConnection { """Pagination data for this connection.""" pageInfo: PageInfo! """Contains the nodes in this connection.""" - edges: [ProfileUserRoleEdge]! + edges: [NotificationEdge]! totalCount: Int edgeCount: Int } -"""A Relay edge containing a `ProfileUserRole` and its cursor.""" -type ProfileUserRoleEdge { +"""A Relay edge containing a `Notification` and its cursor.""" +type NotificationEdge { """The item at the end of the edge""" - node: ProfileUserRole + node: Notification """A cursor for use in pagination""" cursor: String! } -type ProfileUserRole implements Node { +type NotificationSetting implements Node { """The ID of the object""" id: ID! + created: DateTime! + modified: DateTime! user: User! - role: ProfileRoles - status: ProfileRoleStatus + channel: NotificationChannelTypes + verb: String! + isActive: Boolean! pk: Int! } -"""An enumeration.""" -enum ProfileRoles { - """admin""" - ADMIN - - """manager""" - MANAGER -} - -"""An enumeration.""" -enum ProfileRoleStatus { - """active""" - ACTIVE - - """pending""" - PENDING - - """inactive""" - INACTIVE -} - -type UnreadMessageCountConnection { +type NotificationSettingConnection { """Pagination data for this connection.""" pageInfo: PageInfo! """Contains the nodes in this connection.""" - edges: [UnreadMessageCountEdge]! + edges: [NotificationSettingEdge]! totalCount: Int edgeCount: Int } -"""A Relay edge containing a `UnreadMessageCount` and its cursor.""" -type UnreadMessageCountEdge { +"""A Relay edge containing a `NotificationSetting` and its cursor.""" +type NotificationSettingEdge { """The item at the end of the edge""" - node: UnreadMessageCount + node: NotificationSetting """A cursor for use in pagination""" cursor: String! } -type Organization implements Node & PermissionsInterface { - profile: Profile +input NotificationSettingToggleInput { + verb: String! + channel: NotificationChannelTypes! + clientMutationId: String +} + +type NotificationSettingTogglePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + notificationSetting: NotificationSetting + clientMutationId: String +} +interface NotificationsInterface { """The ID of the object""" id: ID! + notificationsUnreadCount: Int + notifications(offset: Int, before: String, after: String, first: Int, last: Int, level: NotificationsNotificationLevelChoices, unread: Boolean, verbs: String): NotificationConnection + notificationSettings(offset: Int, before: String, after: String, first: Int, last: Int): NotificationSettingConnection + isNotificationSettingActive(verb: String!, channel: NotificationChannelTypes!): Boolean +} - """ - Determine if the logged in user has a specific permission for this object. - """ - hasPerm(perm: String!): Boolean - pk: Int! +input NotificationsMarkAllAsReadInput { + """Mark as read or unread""" + read: Boolean! + clientMutationId: String } -interface ProfileInterface { - """The ID of the object""" - id: ID! - profile: Profile +type NotificationsMarkAllAsReadPayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + recipient: NotificationsInterface + clientMutationId: String } -interface ProfilesInterface { - """The ID of the object""" - id: ID! - profiles( - offset: Int - before: String - after: String - first: Int - last: Int - q: String +input NotificationsMarkAsReadInput { + """Mark as read or unread""" + read: Boolean! + notificationIds: [ID!] + clientMutationId: String +} - """Ordering""" - orderBy: String - ): ProfileConnection +type NotificationsMarkAsReadPayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + recipient: NotificationsInterface + notifications: [Notification] + clientMutationId: String } -type ProfileConnection { - """Pagination data for this connection.""" - pageInfo: PageInfo! +"""An enumeration.""" +enum NotificationsNotificationLevelChoices { + """success""" + SUCCESS - """Contains the nodes in this connection.""" - edges: [ProfileEdge]! - totalCount: Int - edgeCount: Int + """info""" + INFO + + """warning""" + WARNING + + """error""" + ERROR } -"""A Relay edge containing a `Profile` and its cursor.""" -type ProfileEdge { - """The item at the end of the edge""" - node: Profile +type OnCommentChange { + createdComment: CommentEdge + updatedComment: Comment + deletedCommentId: ID +} - """A cursor for use in pagination""" - cursor: String! +type OnNotificationChange { + createdNotification: NotificationEdge + updatedNotification: Notification + deletedNotificationId: ID } -type PageConnection { - """Pagination data for this connection.""" - pageInfo: PageInfo! +type Organization implements Node & PermissionsInterface { + profile: Profile - """Contains the nodes in this connection.""" - edges: [PageEdge]! - totalCount: Int - edgeCount: Int + """The ID of the object""" + id: ID! + + """ + Determine if the logged in user has a specific permission for this object. + """ + hasPerm(perm: String!): Boolean + pk: Int! } -"""A Relay edge containing a `Page` and its cursor.""" -type PageEdge { +input OrganizationCreateInput { + name: String! + urlPath: String + clientMutationId: String +} + +type OrganizationCreatePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + organization: OrganizationEdge + profile: ProfileEdge + clientMutationId: String +} + +"""A Relay edge containing a `Organization` and its cursor.""" +type OrganizationEdge { """The item at the end of the edge""" - node: Page + node: Organization """A cursor for use in pagination""" cursor: String! @@ -1140,381 +1006,522 @@ type Page implements Node & PageInterface & PermissionsInterface & CommentsInter body: String } -"""An enumeration.""" -enum PageStatus { - """Draft""" - DRAFT - - """Published""" - PUBLISHED -} - -type Report implements Node { - """The ID of the object""" - id: ID! - created: DateTime! - modified: DateTime! - user: User! - reportType: ReportTypes - reportSubject: String - pk: Int! - target: Node -} - -"""An enumeration.""" -enum ReportTypes { - """Spam""" - SPAM - - """Inappropriate""" - INAPPROPRIATE - - """Fake""" - FAKE - - """Other""" - OTHER -} - -type UserConnection { +type PageConnection { """Pagination data for this connection.""" pageInfo: PageInfo! """Contains the nodes in this connection.""" - edges: [UserEdge]! + edges: [PageEdge]! totalCount: Int edgeCount: Int } -"""A Relay edge containing a `User` and its cursor.""" -type UserEdge { +input PageCreateInput { + user: String + title: String + body: String + urlPath: String + clientMutationId: String +} + +type PageCreatePayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + page: PageEdge + clientMutationId: String +} + +"""A Relay edge containing a `Page` and its cursor.""" +type PageEdge { """The item at the end of the edge""" - node: User + node: Page """A cursor for use in pagination""" cursor: String! } -"""Debugging information for the current query.""" -type DjangoDebug { - """Executed SQL queries for this API query.""" - sql: [DjangoDebugSQL] - - """Raise exceptions for this API query.""" - exceptions: [DjangoDebugException] +input PageEditInput { + id: ID! + user: String + title: String + body: String + urlPath: String + clientMutationId: String } -"""Represents a single database query made to a Django managed DB.""" -type DjangoDebugSQL { - """The type of database being used (e.g. postrgesql, mysql, sqlite).""" - vendor: String! +type PageEditPayload { + """May contain more than one error for same field.""" + errors: [ErrorType] + _debug: DjangoDebug + page: Page + clientMutationId: String +} - """The Django database alias (e.g. 'default').""" - alias: String! +""" +The Relay compliant `PageInfo` type, containing data necessary to paginate this connection. +""" +type PageInfo { + """When paginating forwards, are there more items?""" + hasNextPage: Boolean! - """The actual SQL sent to this database.""" - sql: String + """When paginating backwards, are there more items?""" + hasPreviousPage: Boolean! - """Duration of this database query in seconds.""" - duration: Float! + """When paginating backwards, the cursor to continue.""" + startCursor: String - """The raw SQL of this query, without params.""" - rawSql: String! + """When paginating forwards, the cursor to continue.""" + endCursor: String +} - """JSON encoded database query parameters.""" - params: String! +interface PageInterface { + """The ID of the object""" + id: ID! + urlPath: URLPath + urlPaths: [URLPath] + metadata: Metadata +} - """Start time of this database query.""" - startTime: Float! +"""An enumeration.""" +enum PageStatus { + """Draft""" + DRAFT - """Stop time of this database query.""" - stopTime: Float! + """Published""" + PUBLISHED +} - """Whether this database query took more than 10 seconds.""" - isSlow: Boolean! +interface PermissionsInterface { + """The ID of the object""" + id: ID! - """Whether this database query was a SELECT.""" - isSelect: Boolean! + """ + Determine if the logged in user has a specific permission for this object. + """ + hasPerm(perm: String!): Boolean +} - """Postgres transaction ID if available.""" - transId: String +type Profile implements Node & PermissionsInterface & PageInterface & FollowsInterface & BlocksInterface & ChatRoomsInterface { + """The ID of the object""" + id: ID! + created: DateTime! + modified: DateTime! + blockersCount: Int + blockingCount: Int + followersCount: Int + followingCount: Int + reportsCount: JSONString! + commentsCount: JSONString! + isCommentsEnabled: Boolean! + name: String + image(width: Int!, height: Int!): File + bannerImage(width: Int!, height: Int!): File + biography: String + targetObjectId: Int + status: ProfilesProfileStatusChoices! + owner: User! + comments( + offset: Int + before: String + after: String + first: Int + last: Int + q: String - """Postgres transaction status if available.""" - transStatus: String + """Ordering""" + orderBy: String + ): CommentConnection! + reactions(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ReactionConnection! + ratings(offset: Int, before: String, after: String, first: Int, last: Int): RateConnection! + user: User + members( + offset: Int + before: String + after: String + first: Int + last: Int + role: ProfileRoles - """Postgres isolation level if available.""" - isoLevel: String + """Ordering""" + orderBy: String + q: String + ): ProfileUserRoleConnection + chatroomparticipantSet(offset: Int, before: String, after: String, first: Int, last: Int, profile_TargetContentType: ID): ChatRoomParticipantConnection! + unreadmessagecountSet(offset: Int, before: String, after: String, first: Int, last: Int): UnreadMessageCountConnection! + messageSet(offset: Int, before: String, after: String, first: Int, last: Int, verb: Verbs): MessageConnection! + following(offset: Int, before: String, after: String, first: Int, last: Int, targetIsFollowingBack: Boolean): FollowConnection + followers(offset: Int, before: String, after: String, first: Int, last: Int, targetIsFollowingBack: Boolean): FollowConnection + blocking(offset: Int, before: String, after: String, first: Int, last: Int): BlockConnection + blockers(offset: Int, before: String, after: String, first: Int, last: Int): BlockConnection + organization: Organization - """Postgres connection encoding if available.""" - encoding: String -} + """ + Determine if the logged in user has a specific permission for this object. + """ + hasPerm(perm: String!): Boolean + urlPath: URLPath + urlPaths: [URLPath] + metadata: Metadata + isFollowedByMe(profileId: ID): Boolean + isBlockedByMe(profileId: ID): Boolean + chatRooms( + offset: Int + before: String + after: String + first: Int + last: Int + q: String -"""Represents a single exception raised.""" -type DjangoDebugException { - """The class of the exception""" - excType: String! + """Ordering""" + orderBy: String + profileId: String + unreadMessages: Boolean + archived: Boolean + ): ChatRoomConnection + unreadMessagesCount: Int + pk: Int! + activityLogs(visibility: VisibilityTypes, first: Int = 10, offset: Int, before: String, after: String, last: Int, createdFrom: Date, createdTo: Date, userPk: Decimal, profilePk: Decimal): ActivityLogConnection + target: ProfileInterface +} - """The message of the exception""" - message: String! +type ProfileConnection { + """Pagination data for this connection.""" + pageInfo: PageInfo! - """The stack trace""" - stack: String! + """Contains the nodes in this connection.""" + edges: [ProfileEdge]! + totalCount: Int + edgeCount: Int } -type Mutation { - rateCreate(input: RateCreateInput!): RateCreatePayload - organizationCreate(input: OrganizationCreateInput!): OrganizationCreatePayload - chatRoomCreate(input: ChatRoomCreateInput!): ChatRoomCreatePayload - chatRoomUpdate(input: ChatRoomUpdateInput!): ChatRoomUpdatePayload - chatRoomSendMessage(input: ChatRoomSendMessageInput!): ChatRoomSendMessagePayload - chatRoomReadMessages(input: ChatRoomReadMessagesInput!): ChatRoomReadMessagesPayload - chatRoomUnread(input: ChatRoomUnreadInput!): ChatRoomUnreadPayload - chatRoomArchive(input: ChatRoomArchiveInput!): ChatRoomArchivePayload - reportCreate(input: ReportCreateInput!): ReportCreatePayload - followToggle(input: FollowToggleInput!): FollowTogglePayload - blockToggle(input: BlockToggleInput!): BlockTogglePayload - reactionToggle(input: ReactionToggleInput!): ReactionTogglePayload - notificationsMarkAsRead(input: NotificationsMarkAsReadInput!): NotificationsMarkAsReadPayload - notificationsMarkAllAsRead(input: NotificationsMarkAllAsReadInput!): NotificationsMarkAllAsReadPayload - notificationSettingToggle(input: NotificationSettingToggleInput!): NotificationSettingTogglePayload - commentCreate(input: CommentCreateInput!): CommentCreatePayload - commentUpdate(input: CommentUpdateInput!): CommentUpdatePayload - commentPin(input: CommentPinInput!): CommentPinPayload - commentDelete(input: CommentDeleteInput!): CommentDeletePayload - pageCreate(input: PageCreateInput!): PageCreatePayload - pageEdit(input: PageEditInput!): PageEditPayload - profileCreate(input: ProfileCreateInput!): ProfileCreatePayload - profileUpdate(input: ProfileUpdateInput!): ProfileUpdatePayload - profileDelete(input: ProfileDeleteInput!): ProfileDeletePayload - profileRoleUpdate(input: RoleUpdateInput!): RoleUpdatePayload +input ProfileCreateInput { + owner: String + name: String! + image: String + bannerImage: String + biography: String + urlPath: String + target: String + targetContentType: String! + targetObjectId: Int! + clientMutationId: String } -type RateCreatePayload { +type ProfileCreatePayload { """May contain more than one error for same field.""" errors: [ErrorType] _debug: DjangoDebug - rate: RateEdge - target: RatingsInterface + profile: ProfileEdge clientMutationId: String } -type ErrorType { - field: String! - messages: [String!]! -} - -input RateCreateInput { - targetObjectId: ID! - profileId: ID - value: Int! +input ProfileDeleteInput { + id: ID! clientMutationId: String } -type OrganizationCreatePayload { +type ProfileDeletePayload { """May contain more than one error for same field.""" errors: [ErrorType] _debug: DjangoDebug - organization: OrganizationEdge - profile: ProfileEdge + deletedId: ID clientMutationId: String } -"""A Relay edge containing a `Organization` and its cursor.""" -type OrganizationEdge { +"""A Relay edge containing a `Profile` and its cursor.""" +type ProfileEdge { """The item at the end of the edge""" - node: Organization + node: Profile """A cursor for use in pagination""" cursor: String! } -input OrganizationCreateInput { - name: String! - urlPath: String - clientMutationId: String -} - -type ChatRoomCreatePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - room: ChatRoomEdge +interface ProfileInterface { + """The ID of the object""" + id: ID! profile: Profile - clientMutationId: String } -input ChatRoomCreateInput { - profileId: ID! - participants: [ID]! - isGroup: Boolean = false - title: String - clientMutationId: String +"""An enumeration.""" +enum ProfileRoles { + """admin""" + ADMIN + + """manager""" + MANAGER } -type ChatRoomUpdatePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - room: ChatRoomEdge - removedParticipants: [ChatRoomParticipant] - clientMutationId: String +"""An enumeration.""" +enum ProfileRoleStatus { + """active""" + ACTIVE + + """pending""" + PENDING + + """inactive""" + INACTIVE } -input ChatRoomUpdateInput { - roomId: ID! - profileId: ID! - title: String - deleteImage: Boolean = false - addParticipants: [ID] = [] - removeParticipants: [ID] = [] - clientMutationId: String +interface ProfilesInterface { + """The ID of the object""" + id: ID! + profiles( + offset: Int + before: String + after: String + first: Int + last: Int + q: String + + """Ordering""" + orderBy: String + ): ProfileConnection } -type ChatRoomSendMessagePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - message: MessageEdge - clientMutationId: String +"""An enumeration.""" +enum ProfilesProfileStatusChoices { + """public""" + A_1 + + """private""" + A_2 } -input ChatRoomSendMessageInput { - roomId: ID! - profileId: ID! - content: String! - inReplyToId: ID +input ProfileUpdateInput { + id: ID! + owner: String + name: String + image: String + bannerImage: String + biography: String + urlPath: String + phoneNumber: String clientMutationId: String } -type ChatRoomReadMessagesPayload { +type ProfileUpdatePayload { """May contain more than one error for same field.""" errors: [ErrorType] _debug: DjangoDebug - room: ChatRoom profile: Profile - messages: [Message] clientMutationId: String } -input ChatRoomReadMessagesInput { - roomId: ID! - profileId: ID! - messageIds: [ID] - clientMutationId: String +type ProfileUserRole implements Node { + """The ID of the object""" + id: ID! + user: User! + role: ProfileRoles + status: ProfileRoleStatus + pk: Int! } -type ChatRoomUnreadPayload { - """May contain more than one error for same field.""" - errors: [ErrorType] +type ProfileUserRoleConnection { + """Pagination data for this connection.""" + pageInfo: PageInfo! + + """Contains the nodes in this connection.""" + edges: [ProfileUserRoleEdge]! + totalCount: Int + edgeCount: Int +} + +"""A Relay edge containing a `ProfileUserRole` and its cursor.""" +type ProfileUserRoleEdge { + """The item at the end of the edge""" + node: ProfileUserRole + + """A cursor for use in pagination""" + cursor: String! +} + +type Query { + activityLogs(visibility: VisibilityTypes, first: Int = 10, offset: Int, before: String, after: String, last: Int, createdFrom: Date, createdTo: Date, userPk: Decimal, profilePk: Decimal): ActivityLogConnection + organization( + """The ID of the object""" + id: ID! + ): Organization + chatRoom( + """The ID of the object""" + id: ID! + ): ChatRoom + rate( + """The ID of the object""" + id: ID! + ): Rate + report( + """The ID of the object""" + id: ID! + ): Report + comment( + """The ID of the object""" + id: ID! + ): Comment + allComments( + offset: Int + before: String + after: String + first: Int + last: Int + q: String + + """Ordering""" + orderBy: String + ): CommentConnection + urlPath(path: String!): URLPath + allPages(offset: Int, before: String, after: String, first: Int, last: Int, status: PageStatus): PageConnection + page( + """The ID of the object""" + id: ID! + ): Page + allProfiles( + offset: Int + before: String + after: String + first: Int + last: Int + q: String + + """Ordering""" + orderBy: String + ): ProfileConnection + profile( + """The ID of the object""" + id: ID! + ): Profile + users( + offset: Int + before: String + after: String + first: Int + last: Int + q: String + + """Ordering""" + orderBy: String + ): UserConnection + user( + """The ID of the object""" + id: ID! + ): User + me: User + node( + """The ID of the object""" + id: ID! + ): Node _debug: DjangoDebug - room: ChatRoom +} + +type Rate implements Node { + """The ID of the object""" + id: ID! + created: DateTime! + modified: DateTime! + user: User! profile: Profile - clientMutationId: String + value: Int! + pk: Int! + target: Node } -input ChatRoomUnreadInput { - roomId: ID! - profileId: ID! - clientMutationId: String -} +type RateConnection { + """Pagination data for this connection.""" + pageInfo: PageInfo! -type ChatRoomArchivePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - room: ChatRoom - clientMutationId: String + """Contains the nodes in this connection.""" + edges: [RateEdge]! + totalCount: Int + edgeCount: Int } -input ChatRoomArchiveInput { - roomId: ID! - profileId: ID! - archive: Boolean! +input RateCreateInput { + targetObjectId: ID! + profileId: ID + value: Int! clientMutationId: String } -type ReportCreatePayload { +type RateCreatePayload { """May contain more than one error for same field.""" errors: [ErrorType] _debug: DjangoDebug - report: ReportEdge - target: ReportsInterface + rate: RateEdge + target: RatingsInterface clientMutationId: String } -"""A Relay edge containing a `Report` and its cursor.""" -type ReportEdge { +"""A Relay edge containing a `Rate` and its cursor.""" +type RateEdge { """The item at the end of the edge""" - node: Report + node: Rate """A cursor for use in pagination""" cursor: String! } -interface ReportsInterface { +interface RatingsInterface { """The ID of the object""" id: ID! - reportsCount: ReportsCount - reports(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ReportConnection - myReports: Report + ratingsCount: Int + ratingsSum: Int + ratingsAverage: Float + ratings(offset: Int, before: String, after: String, first: Int, last: Int): RateConnection + isRatingsEnabled: Boolean! + myRating(profileId: ID): Rate } -type ReportsCount { - SPAM: Int - INAPPROPRIATE: Int - FAKE: Int - OTHER: Int - total: Int +type Reaction implements Node { + """The ID of the object""" + id: ID! + created: DateTime! + modified: DateTime! + user: User! + reactionType: ReactionTypes + pk: Int! + target: Node } -type ReportConnection { +type ReactionConnection { """Pagination data for this connection.""" pageInfo: PageInfo! """Contains the nodes in this connection.""" - edges: [ReportEdge]! + edges: [ReactionEdge]! totalCount: Int edgeCount: Int } -input ReportCreateInput { - targetObjectId: ID! - reportType: ReportTypes = null - reportSubject: String - clientMutationId: String -} +"""A Relay edge containing a `Reaction` and its cursor.""" +type ReactionEdge { + """The item at the end of the edge""" + node: Reaction -type FollowTogglePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - follow: FollowEdge - target: FollowsInterface - actor: FollowsInterface - followDeletedId: ID - clientMutationId: String + """A cursor for use in pagination""" + cursor: String! } -input FollowToggleInput { - actorObjectId: ID! - targetObjectId: ID! - clientMutationId: String +type ReactionsCount { + LIKE: Int + DISLIKE: Int + total: Int } -type BlockTogglePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - block: BlockEdge - target: BlocksInterface - actor: BlocksInterface - blockDeletedId: ID - clientMutationId: String +interface ReactionsInterface { + """The ID of the object""" + id: ID! + reactionsCount: ReactionsCount + reactions(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ReactionConnection + isReactionsEnabled: Boolean! + myReaction(profileId: ID): Reaction } -input BlockToggleInput { - actorObjectId: ID! +input ReactionToggleInput { targetObjectId: ID! + profileObjectId: ID + reactionType: ReactionTypes! clientMutationId: String } @@ -1528,199 +1535,97 @@ type ReactionTogglePayload { clientMutationId: String } -input ReactionToggleInput { - targetObjectId: ID! - profileObjectId: ID - reactionType: ReactionTypes! - clientMutationId: String -} - -type NotificationsMarkAsReadPayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - recipient: NotificationsInterface - notifications: [Notification] - clientMutationId: String -} - -input NotificationsMarkAsReadInput { - """Mark as read or unread""" - read: Boolean! - notificationIds: [ID!] - clientMutationId: String -} - -type NotificationsMarkAllAsReadPayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - recipient: NotificationsInterface - clientMutationId: String -} - -input NotificationsMarkAllAsReadInput { - """Mark as read or unread""" - read: Boolean! - clientMutationId: String -} - -type NotificationSettingTogglePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - notificationSetting: NotificationSetting - clientMutationId: String -} - -input NotificationSettingToggleInput { - verb: String! - channel: NotificationChannelTypes! - clientMutationId: String -} - -type CommentCreatePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - comment: CommentEdge - clientMutationId: String -} - -input CommentCreateInput { - targetObjectId: ID! - inReplyToId: ID - profileId: ID - body: String! - clientMutationId: String -} - -type CommentUpdatePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - comment: Comment - clientMutationId: String -} - -input CommentUpdateInput { - id: ID! - body: String! - clientMutationId: String -} - -type CommentPinPayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - comment: Comment - clientMutationId: String -} - -input CommentPinInput { - id: ID! - clientMutationId: String -} +"""An enumeration.""" +enum ReactionTypes { + """like""" + LIKE -type CommentDeletePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - deletedId: ID - target: CommentsInterface - inReplyTo: Comment - clientMutationId: String + """dislike""" + DISLIKE } -input CommentDeleteInput { +type Report implements Node { + """The ID of the object""" id: ID! - clientMutationId: String -} - -type PageCreatePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - page: PageEdge - clientMutationId: String + created: DateTime! + modified: DateTime! + user: User! + reportType: ReportTypes + reportSubject: String + pk: Int! + target: Node } -input PageCreateInput { - user: String - title: String - body: String - urlPath: String - clientMutationId: String -} +type ReportConnection { + """Pagination data for this connection.""" + pageInfo: PageInfo! -type PageEditPayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - page: Page - clientMutationId: String + """Contains the nodes in this connection.""" + edges: [ReportEdge]! + totalCount: Int + edgeCount: Int } -input PageEditInput { - id: ID! - user: String - title: String - body: String - urlPath: String +input ReportCreateInput { + targetObjectId: ID! + reportType: ReportTypes = null + reportSubject: String clientMutationId: String } -type ProfileCreatePayload { +type ReportCreatePayload { """May contain more than one error for same field.""" errors: [ErrorType] _debug: DjangoDebug - profile: ProfileEdge + report: ReportEdge + target: ReportsInterface clientMutationId: String } -input ProfileCreateInput { - owner: String - name: String! - image: String - bannerImage: String - biography: String - urlPath: String - target: String - targetContentType: String! - targetObjectId: Int! - clientMutationId: String +"""A Relay edge containing a `Report` and its cursor.""" +type ReportEdge { + """The item at the end of the edge""" + node: Report + + """A cursor for use in pagination""" + cursor: String! } -type ProfileUpdatePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - profile: Profile - clientMutationId: String +type ReportsCount { + SPAM: Int + INAPPROPRIATE: Int + FAKE: Int + OTHER: Int + total: Int } -input ProfileUpdateInput { +interface ReportsInterface { + """The ID of the object""" id: ID! - owner: String - name: String - image: String - bannerImage: String - biography: String - urlPath: String - phoneNumber: String - clientMutationId: String + reportsCount: ReportsCount + reports(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ReportConnection + myReports: Report } -type ProfileDeletePayload { - """May contain more than one error for same field.""" - errors: [ErrorType] - _debug: DjangoDebug - deletedId: ID - clientMutationId: String +"""An enumeration.""" +enum ReportTypes { + """Spam""" + SPAM + + """Inappropriate""" + INAPPROPRIATE + + """Fake""" + FAKE + + """Other""" + OTHER } -input ProfileDeleteInput { - id: ID! +input RoleUpdateInput { + profileId: ID! + userId: ID! + roleType: ProfileRoles = null clientMutationId: String } @@ -1732,13 +1637,6 @@ type RoleUpdatePayload { clientMutationId: String } -input RoleUpdateInput { - profileId: ID! - userId: ID! - roleType: ProfileRoles = null - clientMutationId: String -} - type Subscription { chatRoomOnNewMessage(roomId: ID!): ChatRoomOnNewMessage chatRoomOnRoomUpdate(profileId: ID!): ChatRoomOnRoomUpdate @@ -1747,27 +1645,151 @@ type Subscription { onCommentChange(targetObjectId: ID): OnCommentChange } -type ChatRoomOnNewMessage { - message: MessageEdge +type UnreadMessageCount implements Node { + markedUnread: Boolean! + count: Int! + + """The ID of the object""" + id: ID! + pk: Int! } -type ChatRoomOnRoomUpdate { - room: ChatRoomEdge - removedParticipants: [ChatRoomParticipant] +type UnreadMessageCountConnection { + """Pagination data for this connection.""" + pageInfo: PageInfo! + + """Contains the nodes in this connection.""" + edges: [UnreadMessageCountEdge]! + totalCount: Int + edgeCount: Int } -type ChatRoomOnMessagesCountUpdate { +"""A Relay edge containing a `UnreadMessageCount` and its cursor.""" +type UnreadMessageCountEdge { + """The item at the end of the edge""" + node: UnreadMessageCount + + """A cursor for use in pagination""" + cursor: String! +} + +type URLPath implements Node { + """The ID of the object""" + id: ID! + created: DateTime! + modified: DateTime! + path: String! + language: Languages + isActive: Boolean! + pk: Int! + target: PageInterface +} + +type User implements Node & PermissionsInterface & NotificationsInterface & PageInterface & RatingsInterface & ProfilesInterface & ProfileInterface { + lastLogin: DateTime + isSuperuser: Boolean + email: String + isEmailVerified: Boolean + dateJoined: DateTime! + passwordChangedDate: DateTime + newEmail: String + isNewEmailConfirmed: Boolean + firstName: String @deprecated(reason: "Deprecated in favor of fullName and shortName") + lastName: String @deprecated(reason: "Deprecated in favor of fullName and shortName") + phoneNumber: String + + """ + Designates whether this user should be treated as active. Unselect this instead of deleting accounts. + """ + isActive: Boolean! + isStaff: Boolean + reactions(offset: Int, before: String, after: String, first: Int, last: Int, id: ID): ReactionConnection! + comments( + offset: Int + before: String + after: String + first: Int + last: Int + q: String + + """Ordering""" + orderBy: String + ): CommentConnection! + pages(offset: Int, before: String, after: String, first: Int, last: Int, status: PageStatus): PageConnection! + + """The ID of the object""" + id: ID! + + """ + Determine if the logged in user has a specific permission for this object. + """ + hasPerm(perm: String!): Boolean + notificationsUnreadCount: Int + notifications(offset: Int, before: String, after: String, first: Int, last: Int, level: NotificationsNotificationLevelChoices, unread: Boolean, verbs: String): NotificationConnection + notificationSettings(offset: Int, before: String, after: String, first: Int, last: Int): NotificationSettingConnection + isNotificationSettingActive(verb: String!, channel: NotificationChannelTypes!): Boolean + urlPath: URLPath + urlPaths: [URLPath] + metadata: Metadata + ratingsCount: Int + ratingsSum: Int + ratingsAverage: Float + ratings(offset: Int, before: String, after: String, first: Int, last: Int): RateConnection + isRatingsEnabled: Boolean! + myRating(profileId: ID): Rate + profiles( + offset: Int + before: String + after: String + first: Int + last: Int + q: String + + """Ordering""" + orderBy: String + ): ProfileConnection profile: Profile + pk: Int! + activityLogs(visibility: VisibilityTypes, first: Int = 10, offset: Int, before: String, after: String, last: Int, createdFrom: Date, createdTo: Date, userPk: Decimal, profilePk: Decimal): ActivityLogConnection + isAuthenticated: Boolean + fullName: String + shortName: String + avatar(width: Int!, height: Int!): File } -type OnNotificationChange { - createdNotification: NotificationEdge - updatedNotification: Notification - deletedNotificationId: ID +type UserConnection { + """Pagination data for this connection.""" + pageInfo: PageInfo! + + """Contains the nodes in this connection.""" + edges: [UserEdge]! + totalCount: Int + edgeCount: Int +} + +"""A Relay edge containing a `User` and its cursor.""" +type UserEdge { + """The item at the end of the edge""" + node: User + + """A cursor for use in pagination""" + cursor: String! +} + +"""An enumeration.""" +enum Verbs { + SENT_MESSAGE +} + +"""An enumeration.""" +enum VisibilityTypes { + """public""" + PUBLIC + + """private""" + PRIVATE + + """internal""" + INTERNAL } -type OnCommentChange { - createdComment: CommentEdge - updatedComment: Comment - deletedCommentId: ID -} \ No newline at end of file diff --git a/packages/design-system/CHANGELOG.md b/packages/design-system/CHANGELOG.md index 04000a41..01d935a5 100644 --- a/packages/design-system/CHANGELOG.md +++ b/packages/design-system/CHANGELOG.md @@ -1,5 +1,11 @@ # @baseapp-frontend/design-system +## 0.0.32 + +### Patch Changes + +- Added new icons: Copy and Download. + ## 0.0.31 ### Patch Changes diff --git a/packages/design-system/components/icons/CopyIcon/index.tsx b/packages/design-system/components/icons/CopyIcon/index.tsx new file mode 100644 index 00000000..43798156 --- /dev/null +++ b/packages/design-system/components/icons/CopyIcon/index.tsx @@ -0,0 +1,48 @@ +import { FC } from 'react' + +import { SvgIcon, SvgIconProps } from '@mui/material' + +const CopyIcon: FC = ({ sx, ...props }) => ( + + + + + + + + + + +) + +export default CopyIcon diff --git a/packages/design-system/components/icons/DownloadIcon/index.tsx b/packages/design-system/components/icons/DownloadIcon/index.tsx new file mode 100644 index 00000000..17aa4f7c --- /dev/null +++ b/packages/design-system/components/icons/DownloadIcon/index.tsx @@ -0,0 +1,30 @@ +import { FC } from 'react' + +import { SvgIcon, SvgIconProps } from '@mui/material' + +const DownloadIcon: FC = ({ sx, ...props }) => ( + + + + + + + +) + +export default DownloadIcon diff --git a/packages/design-system/components/icons/index.ts b/packages/design-system/components/icons/index.ts index 22499dca..e4eb91a2 100644 --- a/packages/design-system/components/icons/index.ts +++ b/packages/design-system/components/icons/index.ts @@ -7,6 +7,8 @@ export { default as CheckMarkIcon } from './CheckMarkIcon' export { default as ChevronIcon } from './ChevronIcon' export { default as CloseIcon } from './CloseIcon' export { default as CommentReplyIcon } from './CommentReplyIcon' +export { default as CopyIcon } from './CopyIcon' +export { default as DownloadIcon } from './DownloadIcon' export { default as FavoriteIcon } from './FavoriteIcon' export { default as FavoriteSelectedIcon } from './FavoriteSelectedIcon' export { default as LinkIcon } from './LinkIcon' diff --git a/packages/design-system/package.json b/packages/design-system/package.json index 424ac575..88778a7b 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -1,7 +1,7 @@ { "name": "@baseapp-frontend/design-system", "description": "Design System components and configurations.", - "version": "0.0.31", + "version": "0.0.32", "main": "./index.ts", "types": "dist/index.d.ts", "sideEffects": false,