Skip to content

Commit

Permalink
Merge pull request #1015 from cozy/feat/ver-772_2
Browse files Browse the repository at this point in the history
feat: Add favorite button
  • Loading branch information
Merkur39 authored Oct 29, 2024
2 parents 69580bd + 07f08a7 commit 46c3400
Show file tree
Hide file tree
Showing 20 changed files with 214 additions and 91 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
"cozy-minilog": "^3.3.1",
"cozy-realtime": "^5.0.0",
"cozy-sharing": "^16.0.0",
"cozy-ui": "^111.12.0",
"cozy-ui": "^112.0.0",
"cozy-vcard": "^0.2.18",
"final-form": "4.20.9",
"final-form-arrays": "3.1.0",
Expand Down
50 changes: 50 additions & 0 deletions src/components/Actions/favorite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import minilog from 'cozy-minilog'

import { updateContact } from '../../connections/allContacts'

const log = minilog('connections/allContacts')

/**
* Action to favorite a contact
* @param {object} options.client - CozyClient instance
* @param {array} options.selection - Array of selected contacts
* @param {object} options.t - Translation function
*/
export const favorite = ({ client, selection, clearSelection, t }) => {
const noFavoriteSelected = selection.filter(
contact => !contact.cozyMetadata?.favorite
)
const isAllFavorite = noFavoriteSelected.length === 0
const icon = isAllFavorite ? 'star' : 'star-outline'
const label = isAllFavorite
? t('SelectionBar.remove_favorite')
: t('SelectionBar.add_favorite')

return {
name: 'favorite',
label,
icon,
action: async () => {
const contactToUpdate = isAllFavorite ? selection : noFavoriteSelected
const favorite = isAllFavorite ? false : true

try {
await Promise.all(
contactToUpdate.map(contact =>
updateContact({
client,
contact,
attributes: {
cozyMetadata: { favorite }
}
})
)
)
} catch (error) {
log.error('Error updating contact', error)
} finally {
clearSelection()
}
}
}
}
85 changes: 85 additions & 0 deletions src/components/Actions/favorite.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { favorite } from './favorite'
import { updateContact } from '../../connections/allContacts'

jest.mock('../../connections/allContacts', () => ({
updateContact: jest.fn()
}))

describe('favorite', () => {
const clearSelection = jest.fn()

const favoriteSelection = [
{
_id: '000',
cozyMetadata: {
favorite: true
}
},
{
_id: '001',
cozyMetadata: {
favorite: true
}
}
]
const unfavoriteSelection = [
{
_id: '100',
cozyMetadata: {
favorite: false
}
},
{
_id: '101',
cozyMetadata: {
favorite: false
}
}
]
const mixedSelection = [favoriteSelection[0], unfavoriteSelection[0]]

it('should return action with "add_favorite" label & "star-outline" icon', async () => {
const action = favorite({
selection: unfavoriteSelection,
clearSelection,
t: jest.fn(k => k)
})

expect(action.label).toBe('SelectionBar.add_favorite')
expect(action.icon).toBe('star-outline')

const action2 = favorite({
selection: mixedSelection,
clearSelection,
t: jest.fn(k => k)
})

expect(action2.label).toBe('SelectionBar.add_favorite')
expect(action2.icon).toBe('star-outline')
})

it('should return action with "remove_favorite" label & "star" icon', async () => {
const action = favorite({
selection: favoriteSelection,
clearSelection,
t: jest.fn(k => k)
})

expect(action.label).toBe('SelectionBar.remove_favorite')
expect(action.icon).toBe('star')
})

it('Should only update not favorite contacts', async () => {
const action = favorite({
selection: mixedSelection,
clearSelection,
t: jest.fn(k => k)
})

expect(action.label).toBe('SelectionBar.add_favorite')
expect(action.icon).toBe('star-outline')
await action.action()
expect(updateContact).toHaveBeenCalledTimes(1)
expect(clearSelection).toHaveBeenCalledTimes(1)
})
})
15 changes: 0 additions & 15 deletions src/components/Actions/helpers.js

This file was deleted.

34 changes: 0 additions & 34 deletions src/components/Actions/helpers.spec.js

This file was deleted.

2 changes: 0 additions & 2 deletions src/components/Actions/index.js

This file was deleted.

7 changes: 5 additions & 2 deletions src/components/Actions/selectAll.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
* @param {Function} options.selectAll - Function to select all contacts
* @returns {Object} - Object with action
*/
export const selectAll = ({ contactsDisplayed, selection, selectAll }) => {
export const selectAll = ({ contactsDisplayed, selection, selectAll, t }) => {
const label = t('SelectionBar.select_all_action')

return {
name: 'select_all_action',
name: 'selectAll',
label,
icon: 'select-all',
action: () => {
const isAllContactsSelected =
Expand Down
5 changes: 4 additions & 1 deletion src/components/Actions/trash.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ export const trash = ({
client,
t
}) => {
const label = t('SelectionBar.trash_action')

return {
name: 'trash_action',
name: 'trash',
label,
icon: 'trash',
action: () => {
const hasConnectedAccounts = contact =>
Expand Down
8 changes: 4 additions & 4 deletions src/components/GroupsSelect/GroupsSelect.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('GroupsSelect', () => {
setup()

act(() => {
fireEvent.click(screen.getByText('Manage groups'))
fireEvent.click(screen.getByText('Groups'))
})

for (const group of groups) {
Expand All @@ -47,7 +47,7 @@ describe('GroupsSelect', () => {
setup()

act(() => {
fireEvent.click(screen.getByText('Manage groups'))
fireEvent.click(screen.getByText('Groups'))
})

act(() => {
Expand Down Expand Up @@ -98,7 +98,7 @@ describe('GroupsSelect', () => {
setup()

act(() => {
fireEvent.click(screen.getByText('Manage groups'))
fireEvent.click(screen.getByText('Groups'))
})

act(() => {
Expand Down Expand Up @@ -150,7 +150,7 @@ describe('GroupsSelect', () => {

act(() => {
// it should replace the field by input with group name as value
fireEvent.click(screen.getByText('Manage groups'))
fireEvent.click(screen.getByText('Groups'))
})

act(() => {
Expand Down
4 changes: 2 additions & 2 deletions src/components/Intents/CreateContact.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import flow from 'lodash/flow'
import PropTypes from 'prop-types'
import React from 'react'
import { DOCTYPE_CONTACTS } from 'src/helpers/doctypes'

import { withClient } from 'cozy-client'
import IntentHeader from 'cozy-ui/transpiled/react/IntentHeader'
import { translate } from 'cozy-ui/transpiled/react/providers/I18n'

import IntentMain from './IntentMain'
import { createContact as createContactWithClient } from '../../connections/allContacts'
import ContactForm from '../ContactCard/ContactForm'

const CreateContact = ({ client, data, onTerminate, onError, onCancel }) => {
const createContact = async contact => {
try {
const me = !!data.me
if (me) contact.metadata.me = true
const resp = await createContactWithClient(client, contact)
const resp = await client.create(DOCTYPE_CONTACTS, contact)
onTerminate(resp.data)
} catch (e) {
onError('Could not create contact')
Expand Down
2 changes: 0 additions & 2 deletions src/components/Modals/ContactFormModal.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import { createOrUpdateContact } from '../../connections/allContacts'
import AppLike from '../../tests/Applike'

jest.mock('../../connections/allContacts', () => ({
createContact: jest.fn().mockResolvedValue({ data: 'created' }),
updateContact: jest.fn().mockResolvedValue({ data: 'updated' }),
createOrUpdateContact: jest.fn()
}))
jest.mock('cozy-client/dist/hooks', () => ({
Expand Down
35 changes: 34 additions & 1 deletion src/components/Modals/ContactInfoTitle.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import get from 'lodash/get'
import throttle from 'lodash/throttle'
import PropTypes from 'prop-types'
import React from 'react'
import React, { useCallback } from 'react'
import { useNavigate } from 'react-router-dom'

import { useClient } from 'cozy-client'
import minilog from 'cozy-minilog'
import Button from 'cozy-ui/transpiled/react/Buttons'
import Grid from 'cozy-ui/transpiled/react/Grid'
import Icon from 'cozy-ui/transpiled/react/Icon'
import RenameIcon from 'cozy-ui/transpiled/react/Icons/Rename'
import StarIcon from 'cozy-ui/transpiled/react/Icons/Star'
import StarOutlineIcon from 'cozy-ui/transpiled/react/Icons/StarOutline'
import TrashIcon from 'cozy-ui/transpiled/react/Icons/Trash'
import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n'

import { updateContact } from '../../connections/allContacts'
import { updateContactGroups } from '../../helpers/groups'
import ContactIdentity from '../ContactCard/ContactIdentity'
import { fullContactPropTypes } from '../ContactPropTypes'
Expand Down Expand Up @@ -39,6 +44,7 @@ const ContactInfoTitle = ({ contact, allGroups }) => {
const navigate = useNavigate()
const { t } = useI18n()
const { showAlert } = useAlert()
const client = useClient()

const handleChange = async nextGroups => {
try {
Expand All @@ -53,6 +59,23 @@ const ContactInfoTitle = ({ contact, allGroups }) => {
await contact.groups.addById(createdGroup._id)
}

// eslint-disable-next-line react-hooks/exhaustive-deps
const throttledUpdateContact = useCallback(throttle(updateContact, 500), [])

const handleFavorite = () => {
throttledUpdateContact({
client,
contact,
attributes: {
cozyMetadata: {
favorite: !contact.cozyMetadata?.favorite
}
}
})
}
const isFavorite = contact.cozyMetadata?.favorite ?? false
const favoriteIcon = isFavorite ? StarIcon : StarOutlineIcon

const handleValue = get(contact, 'relationships.groups.data', [])

return (
Expand All @@ -74,6 +97,16 @@ const ContactInfoTitle = ({ contact, allGroups }) => {
menuPosition="fixed"
/>
</Grid>
<Grid item>
<Button
className="u-miw-auto"
variant="secondary"
label={<Icon icon={favoriteIcon} size={12} />}
size="small"
onClick={handleFavorite}
aria-label={t('edit')}
/>
</Grid>
<Grid item>
<Button
className="u-miw-auto"
Expand Down
Loading

0 comments on commit 46c3400

Please sign in to comment.