diff --git a/api/src/graphql/partReactions.sdl.js b/api/src/graphql/partReactions.sdl.js index 7aba11f..4eaaa26 100644 --- a/api/src/graphql/partReactions.sdl.js +++ b/api/src/graphql/partReactions.sdl.js @@ -15,7 +15,7 @@ export const schema = gql` partReaction(id: String!): PartReaction } - input CreatePartReactionInput { + input TogglePartReactionInput { emote: String! userId: String! partId: String! @@ -28,7 +28,7 @@ export const schema = gql` } type Mutation { - createPartReaction(input: CreatePartReactionInput!): PartReaction! + togglePartReaction(input: TogglePartReactionInput!): PartReaction! updatePartReaction( id: String! input: UpdatePartReactionInput! diff --git a/api/src/graphql/parts.sdl.js b/api/src/graphql/parts.sdl.js index 917ca7b..a7f42e4 100644 --- a/api/src/graphql/parts.sdl.js +++ b/api/src/graphql/parts.sdl.js @@ -10,7 +10,7 @@ export const schema = gql` user: User! userId: String! Comment: [Comment]! - Reaction: [PartReaction]! + Reaction(userId: String): [PartReaction]! } type Query { diff --git a/api/src/lib/owner.js b/api/src/lib/owner.js index e52ac90..086deeb 100644 --- a/api/src/lib/owner.js +++ b/api/src/lib/owner.js @@ -1,4 +1,4 @@ -import { AuthenticationError, ForbiddenError, parseJWT } from '@redwoodjs/api' +import { AuthenticationError, ForbiddenError } from '@redwoodjs/api' import { db } from 'src/lib/db' export const requireOwnership = async ({ userId, userName, partId } = {}) => { diff --git a/api/src/services/partReactions/partReactions.js b/api/src/services/partReactions/partReactions.js index e7b526d..4efd545 100644 --- a/api/src/services/partReactions/partReactions.js +++ b/api/src/services/partReactions/partReactions.js @@ -1,3 +1,7 @@ +import { UserInputError } from '@redwoodjs/api' + +import { requireAuth } from 'src/lib/auth' +import { requireOwnership } from 'src/lib/owner' import { db } from 'src/lib/db' import { foreignKeyReplacement } from 'src/services/helpers' @@ -11,10 +15,26 @@ export const partReaction = ({ id }) => { }) } -export const createPartReaction = ({ input }) => { - return db.partReaction.create({ - data: foreignKeyReplacement(input), - }) +export const togglePartReaction = async ({ input }) => { + // if write fails emote_userId_partId @@unique constraint, then delete it instead + requireAuth() + await requireOwnership({userId: input?.userId}) + const legalReactions = ['❤️', '👍', '😄', '🙌'] // TODO figure out a way of sharing code between FE and BE, so this is consistent with web/src/components/EmojiReaction/EmojiReaction.js + if(!legalReactions.includes(input.emote)) { + throw new UserInputError(`You can't react with '${input.emote}', only the following are allowed: ${legalReactions.join(', ')}`) + } + let dbPromise + const inputClone = {...input} // TODO foreignKeyReplacement mutates input, which I should fix but am lazy right now + try{ + dbPromise = await db.partReaction.create({ + data: foreignKeyReplacement(input), + }) + } catch(e) { + dbPromise = db.partReaction.delete({ + where: { emote_userId_partId: inputClone}, + }) + } + return dbPromise } export const updatePartReaction = ({ id, input }) => { diff --git a/api/src/services/parts/parts.js b/api/src/services/parts/parts.js index 8589c5e..017ad9c 100644 --- a/api/src/services/parts/parts.js +++ b/api/src/services/parts/parts.js @@ -57,5 +57,5 @@ export const Part = { Comment: (_obj, { root }) => db.part.findOne({ where: { id: root.id } }).Comment(), Reaction: (_obj, { root }) => - db.part.findOne({ where: { id: root.id } }).Reaction(), + db.part.findOne({ where: { id: root.id } }).Reaction({where: {userId: _obj.userId}}), } diff --git a/web/src/components/EmojiReaction/EmojiReaction.js b/web/src/components/EmojiReaction/EmojiReaction.js index b90151d..722aa06 100644 --- a/web/src/components/EmojiReaction/EmojiReaction.js +++ b/web/src/components/EmojiReaction/EmojiReaction.js @@ -13,7 +13,7 @@ const noEmotes =[{ const textShadow = {textShadow: '0 4px 6px rgba(0, 0, 0, 0.3)'} -const EmojiReaction = ({ emotes, onEmote = () => {}, className }) => { +const EmojiReaction = ({ emotes, userEmotes, onEmote = () => {}, className }) => { const [isOpen, setIsOpen] = useState(false) const [anchorEl, setAnchorEl] = useState(null) const [popoverId, setPopoverId] = useState(undefined) @@ -59,7 +59,10 @@ const EmojiReaction = ({ emotes, onEmote = () => {}, className }) => {
{(emotes.length ? emotes : noEmotes).map((emote, i) => ( handleEmojiClick(emote.emoji)} diff --git a/web/src/components/NewPartReaction/NewPartReaction.js b/web/src/components/NewPartReaction/NewPartReaction.js index 8cbcba3..07457d2 100644 --- a/web/src/components/NewPartReaction/NewPartReaction.js +++ b/web/src/components/NewPartReaction/NewPartReaction.js @@ -3,8 +3,8 @@ import { navigate, routes } from '@redwoodjs/router' import PartReactionForm from 'src/components/PartReactionForm' const CREATE_PART_REACTION_MUTATION = gql` - mutation CreatePartReactionMutation($input: CreatePartReactionInput!) { - createPartReaction(input: $input) { + mutation TogglePartReactionMutation($input: TogglePartReactionInput!) { + togglePartReaction(input: $input) { id } } @@ -12,7 +12,7 @@ const CREATE_PART_REACTION_MUTATION = gql` const NewPartReaction = () => { const { addMessage } = useFlash() - const [createPartReaction, { loading, error }] = useMutation( + const [togglePartReaction, { loading, error }] = useMutation( CREATE_PART_REACTION_MUTATION, { onCompleted: () => { @@ -23,7 +23,7 @@ const NewPartReaction = () => { ) const onSave = (input) => { - createPartReaction({ variables: { input } }) + togglePartReaction({ variables: { input } }) } return ( diff --git a/web/src/components/Part2Cell/Part2Cell.js b/web/src/components/Part2Cell/Part2Cell.js index 4dcff89..005d58e 100644 --- a/web/src/components/Part2Cell/Part2Cell.js +++ b/web/src/components/Part2Cell/Part2Cell.js @@ -1,10 +1,11 @@ import { useMutation, useFlash } from '@redwoodjs/web' import { navigate, routes } from '@redwoodjs/router' +import { useAuth } from '@redwoodjs/auth' import PartProfile from 'src/components/PartProfile' export const QUERY = gql` - query FIND_PART_BY_USERNAME_TITLE($userName: String!, $partTitle: String!) { + query FIND_PART_BY_USERNAME_TITLE($userName: String!, $partTitle: String!, $currentUserId: String!) { userPart: userName(userName: $userName) { id name @@ -20,6 +21,12 @@ export const QUERY = gql` createdAt updatedAt userId + Reaction { + emote + } + userReactions: Reaction(userId: $currentUserId) { + emote + } } } } @@ -37,6 +44,14 @@ const UPDATE_PART_MUTATION = gql` } } ` +const TOGGLE_REACTION_MUTATION = gql` + mutation ToggleReactionMutation($input: TogglePartReactionInput!) { + togglePartReaction(input: $input){ + id + emote + } + } +` export const Loading = () =>
Loading...
@@ -44,7 +59,8 @@ export const Empty = () =>
Empty
export const Failure = ({ error }) =>
Error: {error.message}
-export const Success = ({ userPart, variables: {isEditable} }) => { +export const Success = ({ userPart, variables: {isEditable}, refetch}) => { + const { currentUser } = useAuth() const { addMessage } = useFlash() const [updateUser, { loading, error }] = useMutation(UPDATE_PART_MUTATION, { onCompleted: ({updatePart}) => { @@ -52,16 +68,25 @@ export const Success = ({ userPart, variables: {isEditable} }) => { addMessage('Part updated.', { classes: 'rw-flash-success' }) }, }) - const onSave = (id, input) => { updateUser({ variables: { id, input } }) } + const [toggleReaction] = useMutation(TOGGLE_REACTION_MUTATION, { + onCompleted: (hey) => refetch() + }) + const onReaction = (emote) => toggleReaction({variables: {input: { + emote, + userId: currentUser.sub, + partId: userPart?.Part?.id, + }}}) + return } diff --git a/web/src/components/PartProfile/PartProfile.js b/web/src/components/PartProfile/PartProfile.js index f0b2390..9a43d94 100644 --- a/web/src/components/PartProfile/PartProfile.js +++ b/web/src/components/PartProfile/PartProfile.js @@ -7,11 +7,14 @@ import ImageUploader from 'src/components/ImageUploader' import Breadcrumb from 'src/components/Breadcrumb' import EmojiReaction from 'src/components/EmojiReaction' import Button from 'src/components/Button' +import { countEmotes } from 'src/helpers/emote' -const PartProfile = ({userPart, isEditable, onSave, loading, error}) => { +const PartProfile = ({userPart, isEditable, onSave, loading, error, onReaction}) => { const { currentUser } = useAuth() const canEdit = currentUser?.sub === userPart.id const part = userPart?.Part + const emotes = countEmotes(part?.Reaction) + const userEmotes = part?.userReactions.map(({emote}) => emote) useEffect(() => {isEditable && !canEdit && navigate(routes.part2({userName: userPart.userName, partTitle: part.title}))}, @@ -50,12 +53,10 @@ const PartProfile = ({userPart, isEditable, onSave, loading, error}) => { />

{userPart?.name}

- {/* TODO hook up to emoji data properly */} {}} + emotes={emotes} + userEmotes={userEmotes} + onEmote={onReaction} />