Skip to content

Commit

Permalink
feat: add logic that lets users favorite a template (#9713)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickoferrall committed May 20, 2024
1 parent 28c7432 commit 4558e14
Show file tree
Hide file tree
Showing 16 changed files with 250 additions and 17 deletions.
5 changes: 3 additions & 2 deletions codegen.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@
"ActionMeeting": "../../database/types/MeetingAction#default",
"ActionMeetingMember": "../../database/types/ActionMeetingMember#default as ActionMeetingMemberDB",
"AddApprovedOrganizationDomainsSuccess": "./types/AddApprovedOrganizationDomainsSuccess#AddApprovedOrganizationDomainsSuccessSource",
"AddPokerTemplateSuccess": "./types/AddPokerTemplateSuccess#AddPokerTemplateSuccessSource",
"AddReactjiToReactableSuccess": "./types/AddReactjiToReactableSuccess#AddReactjiToReactableSuccessSource",
"AddReflectTemplateSuccess": "./types/AddReflectTemplateSuccess#AddReflectTemplateSuccessSource",
"AddPokerTemplateSuccess": "./types/AddPokerTemplateSuccess#AddPokerTemplateSuccessSource",
"AddTranscriptionBotSuccess": "./types/AddTranscriptionBotSuccess#AddTranscriptionBotSuccessSource",
"AddedNotification": "./types/AddedNotification#AddedNotificationSource",
"AgendaItem": "../../database/types/AgendaItem#default as AgendaItemDB",
Expand Down Expand Up @@ -132,6 +132,7 @@
"TeamPromptResponse": "../../postgres/queries/getTeamPromptResponsesByIds#TeamPromptResponse",
"TemplateDimension": "../../database/types/TemplateDimension#default",
"TimelineEventTeamPromptComplete": "./types/TimelineEventTeamPromptComplete#TimelineEventTeamPromptCompleteSource",
"ToggleFavoriteTemplateSuccess": "./types/ToggleFavoriteTemplateSuccess#ToggleFavoriteTemplateSuccessSource",
"ToggleSummaryEmailSuccess": "./types/ToggleSummaryEmailSuccess#ToggleSummaryEmailSuccessSource",
"TopRetroTemplate": "./types/TopRetroTemplate#TopRetroTemplateSource",
"UpdateAutoJoinSuccess": "./types/UpdateAutoJoinSuccess#UpdateAutoJoinSuccessSource",
Expand All @@ -140,9 +141,9 @@
"UpdateFeatureFlagPayload": "./types/UpdateFeatureFlagPayload#UpdateFeatureFlagPayloadSource",
"UpdateGitLabDimensionFieldSuccess": "./types/UpdateGitLabDimensionFieldSuccess#UpdateGitLabDimensionFieldSuccessSource",
"UpdateMeetingPromptSuccess": "./types/UpdateMeetingPromptSuccess#UpdateMeetingPromptSuccessSource",
"UpdateMeetingTemplateSuccess": "./types/UpdateMeetingTemplateSuccess#UpdateMeetingTemplateSuccessSource",
"UpdateOrgPayload": "./types/UpdateOrgPayload#UpdateOrgPayloadSource",
"UpdateRecurrenceSettingsSuccess": "./types/UpdateRecurrenceSettingsSuccess#UpdateRecurrenceSettingsSuccessSource",
"UpdateMeetingTemplateSuccess": "./types/UpdateMeetingTemplateSuccess#UpdateMeetingTemplateSuccessSource",
"UpdateTaskPayload": "./types/UpdateTaskPayload#UpdateTaskPayloadSource",
"UpdateTemplateCategorySuccess": "./types/UpdateTemplateCategorySuccess#UpdateTemplateCategorySuccessSource",
"UpdateUserProfilePayload": "./types/UpdateUserProfilePayload#UpdateUserProfilePayloadSource",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
import {Favorite} from '@mui/icons-material'
import graphql from 'babel-plugin-relay/macro'
import clsx from 'clsx'
import React, {useState} from 'react'
import React from 'react'
import {useFragment} from 'react-relay'
import {ActivityCardFavorite_user$key} from '../../__generated__/ActivityCardFavorite_user.graphql'
import useAtmosphere from '../../hooks/useAtmosphere'
import useMutationProps from '../../hooks/useMutationProps'
import ToggleFavoriteTemplateMutation from '../../mutations/ToggleFavoriteTemplateMutation'
import {Tooltip} from '../../ui/Tooltip/Tooltip'
import {TooltipContent} from '../../ui/Tooltip/TooltipContent'
import {TooltipTrigger} from '../../ui/Tooltip/TooltipTrigger'

type Props = {
className?: string
templateId: string
viewerRef: ActivityCardFavorite_user$key
}

const ActivityCardFavorite = (props: Props) => {
const {className} = props
const [isSelected, setIsSelected] = useState(false)
const tooltipCopy = isSelected ? 'Remove from favorites' : 'Add to favorites'
const {className, templateId, viewerRef} = props
const atmosphere = useAtmosphere()
const {onError, onCompleted} = useMutationProps()

const viewer = useFragment(
graphql`
fragment ActivityCardFavorite_user on User {
favoriteTemplates {
id
}
}
`,
viewerRef
)
const favoriteTemplateIds = viewer.favoriteTemplates.map((template) => template.id)
const isFavorite = favoriteTemplateIds.includes(templateId)
const tooltipCopy = isFavorite ? 'Remove from favorites' : 'Add to favorites'

const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
setIsSelected((prev) => !prev)
ToggleFavoriteTemplateMutation(atmosphere, {templateId}, {onError, onCompleted})
}

return (
Expand All @@ -32,7 +54,7 @@ const ActivityCardFavorite = (props: Props) => {
onClick={handleClick}
className='flex h-full w-full cursor-pointer items-center justify-center bg-transparent'
>
<Favorite className={isSelected ? 'text-rose-600' : 'text-slate-600'} />
<Favorite className={isFavorite ? 'text-rose-600' : 'text-slate-600'} />
</button>
</TooltipTrigger>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export const TemplateDetails = (props: Props) => {
const viewer = useFragment(
graphql`
fragment TemplateDetails_user on User {
...ActivityCardFavorite_user
preferredTeamId
teams {
...TeamPickerModal_teams
Expand Down Expand Up @@ -260,7 +261,11 @@ export const TemplateDetails = (props: Props) => {
<div className='flex items-center justify-between'>
<div className='py-2 text-sm font-semibold text-slate-600'>{description}</div>
<div className='flex items-center gap-2'>
<ActivityCardFavorite className='rounded-full border border-solid border-slate-400 hover:bg-slate-200' />
<ActivityCardFavorite
templateId={activityId}
viewerRef={viewer}
className='rounded-full border border-solid border-slate-400 hover:bg-slate-200'
/>
<div className='rounded-full border border-solid border-slate-400'>
<FlatButton
style={{padding: '8px 12px', border: '0'}}
Expand Down
23 changes: 21 additions & 2 deletions packages/client/components/ActivityLibrary/ActivityGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import graphql from 'babel-plugin-relay/macro'
import React from 'react'
import {useFragment} from 'react-relay'
import {Link} from 'react-router-dom'
import {ActivityGrid_user$key} from '../../__generated__/ActivityGrid_user.graphql'
import {ActivityBadge} from './ActivityBadge'
import {ActivityCard, ActivityCardImage} from './ActivityCard'
import ActivityCardFavorite from './ActivityCardFavorite'
Expand All @@ -10,9 +13,19 @@ import {CATEGORY_THEMES, CategoryID} from './Categories'
interface ActivityGridProps {
templates: Template[]
selectedCategory: string
viewerRef?: ActivityGrid_user$key
}

const ActivityGrid = ({templates, selectedCategory}: ActivityGridProps) => {
const ActivityGrid = (props: ActivityGridProps) => {
const {templates, selectedCategory, viewerRef} = props
const viewer = useFragment(
graphql`
fragment ActivityGrid_user on User {
...ActivityCardFavorite_user
}
`,
viewerRef ?? null
)
return (
<>
{templates.map((template) => {
Expand Down Expand Up @@ -43,7 +56,13 @@ const ActivityGrid = ({templates, selectedCategory}: ActivityGridProps) => {
src={template.illustrationUrl}
category={template.category as CategoryID}
/>
<ActivityCardFavorite className='absolute bottom-3 right-3' />
{viewer && (
<ActivityCardFavorite
templateId={template.id}
className='absolute bottom-2 right-2'
viewerRef={viewer}
/>
)}
<ActivityLibraryCardDescription
className='hidden group-hover/card:flex'
templateRef={template}
Expand Down
11 changes: 10 additions & 1 deletion packages/client/components/ActivityLibrary/ActivityLibrary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ graphql`
const query = graphql`
query ActivityLibraryQuery {
viewer {
...ActivityGrid_user
favoriteTemplates {
...ActivityLibrary_template @relay(mask: false)
}
availableTemplates(first: 2000) @connection(key: "ActivityLibrary_availableTemplates") {
edges {
node {
Expand Down Expand Up @@ -240,6 +244,9 @@ export const ActivityLibrary = (props: Props) => {
// If there's a search query, just use the search filter results
return filteredTemplates
}
if (categoryId === 'favorite') {
return viewer.favoriteTemplates
}

return filteredTemplates.filter((template) =>
categoryId === QUICK_START_CATEGORY_ID
Expand Down Expand Up @@ -344,7 +351,7 @@ export const ActivityLibrary = (props: Props) => {
style={{
color:
category === 'favorite'
? category === categoryId
? category === categoryId && searchQuery.length === 0
? 'white'
: 'red'
: undefined
Expand Down Expand Up @@ -388,6 +395,7 @@ export const ActivityLibrary = (props: Props) => {
<ActivityGrid
templates={subCategoryTemplates}
selectedCategory={categoryId}
viewerRef={viewer}
/>
</div>
</Fragment>
Expand All @@ -400,6 +408,7 @@ export const ActivityLibrary = (props: Props) => {
<ActivityGrid
templates={templatesToRender as Template[]}
selectedCategory={categoryId}
viewerRef={viewer}
/>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ const ActivityLibraryEmptyState = (props: Props) => {

if (categoryId === 'favorite') {
return (
<div className='relative mx-auto flex p-2 text-slate-700'>
<div className='md:p-18 p-4 xl:p-24'>
<img className='w-82' src={favoriteImg} alt='Favorite placeholder' />
<div className='relative mx-auto flex justify-center p-2 align-middle text-slate-700'>
<div className='p-4 md:p-0 '>
<img
className='w-[500px] md:w-[700px] lg:w-[900px]'
src={favoriteImg}
alt='Favorite placeholder'
/>
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform'>
<div className='flex flex-col items-center'>
<FavoriteIcon
Expand Down
3 changes: 1 addition & 2 deletions packages/client/components/ActivityLibrary/Categories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ export const CATEGORY_ID_TO_NAME: Record<AllCategoryID, string | JSX.Element> =
style={{
color: 'inherit',
display: 'flex',
alignItems: 'center',
fontSize: '18px'
fontSize: '22px'
}}
/>
),
Expand Down
41 changes: 41 additions & 0 deletions packages/client/mutations/ToggleFavoriteTemplateMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import graphql from 'babel-plugin-relay/macro'
import {commitMutation} from 'react-relay'
import {ToggleFavoriteTemplateMutation as TToggleFavoriteTemplateMutation} from '../__generated__/ToggleFavoriteTemplateMutation.graphql'
import {StandardMutation} from '../types/relayMutations'

graphql`
fragment ToggleFavoriteTemplateMutation_viewer on ToggleFavoriteTemplateSuccess {
user {
id
...ActivityCardFavorite_user
}
}
`

const mutation = graphql`
mutation ToggleFavoriteTemplateMutation($templateId: ID!) {
toggleFavoriteTemplate(templateId: $templateId) {
... on ErrorPayload {
error {
message
}
}
...ToggleFavoriteTemplateMutation_viewer @relay(mask: false)
}
}
`

const ToggleFavoriteTemplateMutation: StandardMutation<TToggleFavoriteTemplateMutation> = (
atmosphere,
variables,
{onError, onCompleted}
) => {
return commitMutation<TToggleFavoriteTemplateMutation>(atmosphere, {
mutation,
variables,
onCompleted,
onError
})
}

export default ToggleFavoriteTemplateMutation
4 changes: 4 additions & 0 deletions packages/server/database/types/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface Input {
id?: string
preferredName: string
email: string
favoriteTemplateIds?: string[]
featureFlags?: string[]
lastSeenAt?: Date
lastSeenAtURLs?: string[]
Expand All @@ -28,6 +29,7 @@ export default class User {
id: string
preferredName: string
email: string
favoriteTemplateIds: string[]
featureFlags: string[]
lastSeenAt: Date
lastSeenAtURLs: string[] | null
Expand Down Expand Up @@ -55,6 +57,7 @@ export default class User {
createdAt,
picture,
updatedAt,
favoriteTemplateIds,
featureFlags,
lastSeenAt,
lastSeenAtURLs,
Expand All @@ -73,6 +76,7 @@ export default class User {
this.createdAt = createdAt || now
this.picture = picture
this.updatedAt = updatedAt || now
this.favoriteTemplateIds = favoriteTemplateIds || []
this.featureFlags = featureFlags || []
this.identities = identities || []
this.inactive = inactive || false
Expand Down
21 changes: 21 additions & 0 deletions packages/server/dataloader/customLoaderMakers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,27 @@ export const isCompanyDomain = (parent: RootDataLoader) => {
)
}

export const favoriteTemplateIds = (parent: RootDataLoader) => {
return new DataLoader<string, string[], string>(
async (userIds) => {
const pg = getKysely()
const users = await pg
.selectFrom('User')
.select(['id', 'favoriteTemplateIds'])
.where('id', 'in', userIds)
.execute()

const userIdToFavoriteTemplateIds = new Map(
users.map((user) => [user.id, user.favoriteTemplateIds])
)
return userIds.map((userId) => userIdToFavoriteTemplateIds.get(userId) || [])
},
{
...parent.dataLoaderOptions
}
)
}

export const fileStoreAsset = (parent: RootDataLoader) => {
return new DataLoader<string, string, string>(
async (urls) => {
Expand Down
37 changes: 37 additions & 0 deletions packages/server/graphql/public/mutations/toggleFavoriteTemplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import getKysely from '../../../postgres/getKysely'
import {getUserId} from '../../../utils/authorization'
import {MutationResolvers} from '../resolverTypes'

const toggleFavoriteTemplate: MutationResolvers['toggleFavoriteTemplate'] = async (
_source,
{templateId},
{authToken, dataLoader}
) => {
const viewerId = getUserId(authToken)
const pg = getKysely()
const userId = getUserId(authToken)

const favoriteTemplateIds = await dataLoader.get('favoriteTemplateIds').load(viewerId)

let updatedFavoriteTemplateIds

const isCurrentlyFavorite = favoriteTemplateIds.includes(templateId)

if (isCurrentlyFavorite) {
updatedFavoriteTemplateIds = favoriteTemplateIds.filter((id) => id !== templateId)
} else {
updatedFavoriteTemplateIds = [...favoriteTemplateIds, templateId]
}

await pg
.updateTable('User')
.set({
favoriteTemplateIds: updatedFavoriteTemplateIds
})
.where('id', '=', userId)
.execute()

return true
}

export default toggleFavoriteTemplate
5 changes: 5 additions & 0 deletions packages/server/graphql/public/typeDefs/User.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ type User {
"""
email: Email!

"""
The user's favorite meeting templates
"""
favoriteTemplates: [MeetingTemplate!]!

"""
Any super power given to the user via a super user
"""
Expand Down
Loading

0 comments on commit 4558e14

Please sign in to comment.