Skip to content

Commit

Permalink
feat: adds image url to avatar
Browse files Browse the repository at this point in the history
  • Loading branch information
seanes committed Aug 19, 2024
1 parent 68bbd9a commit 0c88edb
Show file tree
Hide file tree
Showing 13 changed files with 96 additions and 51 deletions.
23 changes: 12 additions & 11 deletions packages/frontend/src/api/useDialogById.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import { type FormatFunction, useFormat } from '../i18n/useDateFnsLocale.tsx';
import { getOrganisation } from './organisations.ts';
import { graphQLSDK } from './queries.ts';

interface Participant {
label: string;
icon?: JSX.Element;
export interface Participant {
name: string;
isCompany: boolean;
imageURL?: string;
}

interface InboxItemTag {
Expand Down Expand Up @@ -112,22 +113,22 @@ export function mapDialogDtoToInboxItem(
const additionalInfoObj = item?.content?.additionalInfo?.value;
const summaryObj = item?.content?.summary?.value;
const mainContentReference = item?.content?.mainContentReference;
const nameOfParty = parties?.find((party) => party.party === item.party)?.name ?? '';
const endUserParty = parties?.find((party) => party.isCurrentEndUser);
const dialogReceiverParty = parties?.find((party) => party.party === item.party);
const actualReceiverParty = dialogReceiverParty ?? endUserParty;
const serviceOwner = getOrganisation(item.org, 'nb');
return {
title: getPropertyByCultureCode(titleObj),
description: getPropertyByCultureCode(summaryObj),
toLabel: i18n.t('word.to'), // TODO: Remove this
sender: {
label: serviceOwner?.name ?? item.org,
...(serviceOwner?.logo
? {
icon: <img src={serviceOwner?.logo} alt={`logo of ${serviceOwner?.name ?? item.org}`} />,
}
: {}),
name: serviceOwner?.name ?? '',
isCompany: true,
imageURL: serviceOwner?.logo,
},
receiver: {
label: nameOfParty,
name: actualReceiverParty?.name ?? '',
isCompany: actualReceiverParty?.partyType === 'Organisation',
},
tags: getTags(item, format),
additionalInfo: getPropertyByCultureCode(additionalInfoObj),
Expand Down
16 changes: 8 additions & 8 deletions packages/frontend/src/api/useDialogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ export function mapDialogDtoToInboxItem(
return input.map((item) => {
const titleObj = item.content.title.value;
const summaryObj = item.content.summary.value;
const nameOfParty = parties.find((party) => party.party === item.party)?.name ?? '';
const endUserParty = parties?.find((party) => party.isCurrentEndUser);
const dialogReceiverParty = parties?.find((party) => party.party === item.party);
const actualReceiverParty = dialogReceiverParty ?? endUserParty;
const serviceOwner = getOrganisation(item.org, 'nb');
const isSeenByEndUser =
item.seenSinceLastUpdate.find((seenLogEntry) => seenLogEntry.isCurrentEndUser) !== undefined;
Expand All @@ -71,15 +73,13 @@ export function mapDialogDtoToInboxItem(
title: getPropertyByCultureCode(titleObj),
description: getPropertyByCultureCode(summaryObj),
sender: {
label: serviceOwner?.name ?? item.org,
...(serviceOwner?.logo
? {
icon: <img src={serviceOwner?.logo} alt={`logo of ${serviceOwner?.name ?? item.org}`} />,
}
: {}),
name: serviceOwner?.name ?? '',
isCompany: true,
imageURL: serviceOwner?.logo,
},
receiver: {
label: nameOfParty,
name: actualReceiverParty?.name ?? '',
isCompany: actualReceiverParty?.partyType === 'Organisation',
},
tags: getTags(item, isSeenByEndUser, format),
linkTo: `/inbox/${item.id}`,
Expand Down
29 changes: 26 additions & 3 deletions packages/frontend/src/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,53 @@
import cx from 'classnames';
import { useState } from 'react';
import { fromStringToColor } from '../../profile';
import styles from './avatar.module.css';

interface AvatarProps {
name: string;
companyName?: string;
className?: string;
imageUrl?: string;
imageUrlAlt?: string;
size?: 'small' | 'medium';
}

export const Avatar = ({ name, companyName, className, size = 'medium' }: AvatarProps) => {
export const Avatar = ({ name, companyName, className, size = 'medium', imageUrl, imageUrlAlt }: AvatarProps) => {
const [hasImageError, setHasImageError] = useState<boolean>(false);
const appliedName = companyName || name || '';
const isOrganization = !!companyName;
const colorType = isOrganization ? 'dark' : 'light';
const { backgroundColor, foregroundColor } = fromStringToColor(appliedName, colorType);
const initials = (appliedName[0] ?? '').toUpperCase();
const usingImageUrl = imageUrl && !hasImageError;
const initialStyles = !usingImageUrl
? {
backgroundColor,
color: foregroundColor,
}
: {};

return (
<div
className={cx(styles.initialsCircle, className, {
[styles.isOrganization]: isOrganization,
[styles.small]: size === 'small',
})}
style={{ backgroundColor, color: foregroundColor }}
style={initialStyles}
aria-hidden="true"
>
{initials}
{usingImageUrl ? (
<img
src={imageUrl}
className={styles.image}
alt={imageUrlAlt || imageUrl}
onError={() => {
setHasImageError(true);
}}
/>
) : (
<span>{initials}</span>
)}
</div>
);
};
9 changes: 7 additions & 2 deletions packages/frontend/src/components/Avatar/avatar.module.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
.initialsCircle {
min-width: 36px;
min-height: 36px;
max-height: 36px;
max-width: 36px;
max-height: 36px;
display: flex;
justify-content: center;
align-items: center;
Expand All @@ -13,8 +13,8 @@
.small {
min-width: 24px;
min-height: 24px;
max-height: 24px;
max-width: 24px;
max-height: 24px;
color: #fff;
font-size: 0.75rem;
font-style: normal;
Expand All @@ -25,3 +25,8 @@
.initialsCircle.isOrganization {
border-radius: 0.25em;
}

.image {
width: 100%;
height: 100%;
}
7 changes: 6 additions & 1 deletion packages/frontend/src/components/Header/SearchDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ export const SearchDropdown: React.FC<SearchDropdownProps> = ({ showDropdownMenu
/>
<div className={cx(styles.rightContent)}>
<span className={styles.timeSince}>{autoFormatRelativeTime(new Date(item.date), formatDistance)}</span>
<Avatar name={item.sender.label} size="small" />
<Avatar
name={item.sender.name}
companyName={item.sender.isCompany ? item.sender.name : ''}
imageUrl={item.sender.imageURL}
size="small"
/>
</div>
</SearchDropdownItem>
))
Expand Down
19 changes: 10 additions & 9 deletions packages/frontend/src/components/InboxItem/InboxItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ import type { JSX } from 'react';
import { Link } from 'react-router-dom';
import { useSelectedDialogs } from '../PageLayout';

import type { Participant } from '../../api/useDialogById.tsx';
import { Avatar } from '../Avatar';
import { ProfileCheckbox } from '../ProfileCheckbox';
import styles from './inboxItem.module.css';

export interface Participant {
label: string;
icon?: JSX.Element;
}

export interface InboxItemTag {
label: string;
icon?: JSX.Element;
Expand Down Expand Up @@ -199,13 +196,17 @@ export const InboxItem = ({
</header>
<div className={styles.participants}>
<div className={styles.sender}>
{sender?.icon && <div className={styles.senderIcon}>{sender.icon}</div>}
<span className={styles.participantLabel}>{sender?.label}</span>
<Avatar
name={sender?.name}
companyName={sender?.isCompany ? sender?.name : ''}
imageUrl={sender.imageURL}
size="small"
/>
<span className={styles.participantLabel}>{sender?.name}</span>
</div>
<span>{toLabel}</span>
<div className={styles.receiver}>
{receiver?.icon && <div className={styles.icon}>{receiver.icon}</div>}
<span className={styles.participantLabel}>{receiver?.label}</span>
<span className={styles.participantLabel}>{receiver?.name}</span>
</div>
</div>
<p className={styles.description}>{description}</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Markdown } from 'embeddable-markdown-html';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { type DialogByIdDetails, getPropertyByCultureCode } from '../../api/useDialogById.tsx';
import { Avatar } from '../Avatar';
import { GuiActions } from './GuiActions.tsx';
import styles from './inboxItemDetail.module.css';

Expand Down Expand Up @@ -81,13 +82,12 @@ export const InboxItemDetail = ({
</header>
<div className={styles.participants}>
<div className={styles.sender}>
{sender?.icon && <div className={styles.participantIcon}>{sender.icon}</div>}
<span className={styles.participantLabel}>{sender?.label}</span>
<Avatar name={sender?.name} imageUrl={sender?.imageURL} />
<span className={styles.participantLabel}>{sender?.name}</span>
</div>
<span>{toLabel}</span>
<div className={styles.receiver}>
{receiver?.icon && <div className={styles.participantIcon}>{receiver.icon}</div>}
<span className={styles.participantLabel}>{receiver?.label}</span>
<span className={styles.participantLabel}>{receiver?.name}</span>
</div>
</div>
<section className={styles.descriptionContainer}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
display: flex;
align-items: center;
flex-wrap: wrap;
column-gap: 0.5rem;
}

.participantLabel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
font-size: 1rem;
display: flex;
align-items: center;
column-gap: 0.5rem;
}

.sender {
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/pages/Inbox/Inbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation, useSearchParams } from 'react-router-dom';
import { createSavedSearch } from '../../api/queries.ts';
import type { Participant } from '../../api/useDialogById.tsx';
import { type InboxViewType, getViewType, useDialogs, useSearchDialogs } from '../../api/useDialogs.tsx';
import { useParties } from '../../api/useParties.ts';
import {
ActionPanel,
InboxItem,
type InboxItemTag,
InboxItems,
type Participant,
SortOrderDropdown,
useSearchString,
} from '../../components';
Expand Down
16 changes: 8 additions & 8 deletions packages/frontend/src/pages/Inbox/filters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,26 @@ describe('filterDialogs', () => {
const dialogs = [
{
id: 1,
sender: { label: 'Ole' },
receiver: { label: 'Kari' },
sender: { name: 'Ole' },
receiver: { name: 'Kari' },
createdAt: '2024-01-01T10:00:00Z',
},
{
id: 2,
sender: { label: 'Nils' },
receiver: { label: 'Liv' },
sender: { name: 'Nils' },
receiver: { name: 'Liv' },
createdAt: '2024-02-01T10:00:00Z',
},
{
id: 3,
sender: { label: 'Ole' },
receiver: { label: 'Eva' },
sender: { name: 'Ole' },
receiver: { name: 'Eva' },
createdAt: '2024-03-01T10:00:00Z',
},
{
id: 4,
sender: { label: 'Per' },
receiver: { label: 'Kari' },
sender: { name: 'Per' },
receiver: { name: 'Kari' },
createdAt: '2024-01-15T10:00:00Z',
},
] as unknown as InboxItemInput[];
Expand Down
9 changes: 5 additions & 4 deletions packages/frontend/src/pages/Inbox/filters.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { t } from 'i18next';
import type { Participant } from '../../components';
import type { Participant } from '../../api/useDialogById.tsx';
import type { Filter, FilterSetting } from '../../components/FilterBar/FilterBar.tsx';
import {
countOccurrences,
Expand All @@ -15,6 +15,7 @@ import type { InboxItemInput } from './Inbox.tsx';
*
* @param {InboxItemInput[]} dialogs - The array of dialogs to filter.
* @param {Array} activeFilters - The array of active filter objects, where each filter has an 'id' and a 'value'.
* @param format
* @returns {InboxItemInput[]} - The filtered array of dialogs.
*/

Expand Down Expand Up @@ -48,7 +49,7 @@ export const filterDialogs = (
return filters.some((filter) => {
if (filter.id === 'sender' || filter.id === 'receiver') {
const participant = item[filter.id as keyof InboxItemInput] as Participant;
return filter.value === participant.label;
return filter.value === participant.name;
}
if (filter.id === 'created') {
const rangeProperties = getPredefinedRange().find((range) => range.value === filter.value);
Expand Down Expand Up @@ -83,7 +84,7 @@ export const getFilterBarSettings = (dialogs: InboxItemInput[], format: FormatFu
mobileNavLabel: t('filter_bar.label.choose_sender'),
operation: 'includes',
options: (() => {
const senders = dialogs.map((p) => p.sender.label);
const senders = dialogs.map((p) => p.sender.name);
const senderCounts = countOccurrences(senders);
return Array.from(new Set(senders)).map((sender) => ({
displayLabel: `${t('filter_bar_fields.from')} ${sender}`,
Expand All @@ -99,7 +100,7 @@ export const getFilterBarSettings = (dialogs: InboxItemInput[], format: FormatFu
mobileNavLabel: t('filter_bar.label.choose_recipient'),
operation: 'includes',
options: (() => {
const receivers = dialogs.map((p) => p.receiver.label);
const receivers = dialogs.map((p) => p.receiver.name);
const receiversCount = countOccurrences(receivers);
return Array.from(new Set(receivers)).map((receiver) => ({
displayLabel: `${t('filter_bar_fields.to')} ${receiver}`,
Expand Down
7 changes: 7 additions & 0 deletions packages/storybook/src/stories/Avatar/avatar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ const meta = {
export default meta;
type Story = StoryObj<typeof Avatar>;

export const ImageURL: Story = {
args: {
companyName: 'NAV',
imageUrl: 'https://altinncdn.no/orgs/nav/nav.png',
},
};

export const ColorPlayground: StoryObj<typeof Avatar> = {
render: (args) => {
const [name, setName] = useState<string>(args.name);
Expand Down

0 comments on commit 0c88edb

Please sign in to comment.