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}
/>