Skip to content

Commit

Permalink
Feat/issue 163 - Attendee Profile photo and Name to be displayed in t…
Browse files Browse the repository at this point in the history
…he suggestions (#169)

* add: initial layout for the image and name #163

* add: attendee information formation

* add: attendee dropdown shown

* feat: attendees can be choosen from dropdown #163

* fix: types and checks

* fix: color used from palette

* fix: interface renamed

* fix: dynamic width
  • Loading branch information
parvez-ahammed authored Jan 17, 2025
1 parent 5baf75b commit 6e3d282
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 28 deletions.
54 changes: 44 additions & 10 deletions client/src/components/AttendeeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { useState } from 'react';
import { useApi } from '@/context/ApiContext';
import { isEmailValid } from '@/helpers/utility';
import toast from 'react-hot-toast';
import Avatar from '@mui/material/Avatar';
import { Typography } from '@mui/material';
import type { IPeopleInformation } from '@quickmeet/shared';

interface AttendeeInputProps {
id: string;
Expand All @@ -14,7 +17,7 @@ interface AttendeeInputProps {
}

export default function AttendeeInput({ id, onChange, value, type }: AttendeeInputProps) {
const [options, setOptions] = useState<string[]>([]);
const [options, setOptions] = useState<IPeopleInformation[]>([]);
const [textInput, setTextInput] = useState('');

const api = useApi();
Expand All @@ -23,22 +26,24 @@ export default function AttendeeInput({ id, onChange, value, type }: AttendeeInp
if (newInputValue.length > 2) {
const res = await api.searchPeople(newInputValue);
if (res.status === 'success') {
setOptions(res.data || []);
setOptions((res.data as IPeopleInformation[]) || []);
}
}
};

const handleSelectionChange = (_: React.SyntheticEvent, newValue: string[]) => {
if (newValue.length > 0) {
const lastValue = newValue[newValue.length - 1].trim();
const handleSelectionChange = (_: React.SyntheticEvent, newValue: Array<string | IPeopleInformation>) => {
const emails = newValue.map((option) => (typeof option === 'object' && option.email ? option.email : (option as string)));
const filteredEmails = emails.filter((email) => emails.indexOf(email) === emails.lastIndexOf(email));
if (filteredEmails.length > 0) {
const lastValue = filteredEmails[filteredEmails.length - 1].trim();
if (isEmailValid(lastValue)) {
onChange(id, newValue);
onChange(id, filteredEmails);
setTextInput('');
} else {
toast.error('Invalid email entered');
}
} else {
onChange(id, newValue);
onChange(id, filteredEmails);
setTextInput('');
}
};
Expand Down Expand Up @@ -108,7 +113,8 @@ export default function AttendeeInput({ id, onChange, value, type }: AttendeeInp
multiple
options={options}
value={value || []}
getOptionLabel={(option) => option}
getOptionLabel={(option) => (typeof option === 'object' && option.email ? option.email : '')}
noOptionsText=""
freeSolo
inputValue={textInput}
fullWidth
Expand All @@ -132,9 +138,9 @@ export default function AttendeeInput({ id, onChange, value, type }: AttendeeInp
}}
onInputChange={debouncedInputChange}
renderTags={(value: readonly string[], getTagProps) =>
value.map((option: string, index: number) => {
value.map((email: string, index: number) => {
const { key, ...tagProps } = getTagProps({ index });
return <Chip variant="filled" label={option} key={key} {...tagProps} />;
return <Chip variant="filled" label={email} key={key} {...tagProps} />;
})
}
renderInput={(params) => (
Expand Down Expand Up @@ -173,6 +179,34 @@ export default function AttendeeInput({ id, onChange, value, type }: AttendeeInp
]}
/>
)}
renderOption={(props, option) => {
const { key, ...optionProps } = props;
const isSelected = value?.includes(option.email);
return (
<Box
key={key}
component="li"
sx={[
(theme) => ({
'& > img': { mr: 2, flexShrink: 0 },
backgroundColor: isSelected ? theme.palette.grey[100] : 'transparent',
}),
]}
{...optionProps}
gap={1}
>
<Avatar src={option.photo} alt={`Image of ${option.name}`} />
<Box sx={{ width: '80%' }}>
<Typography variant="subtitle2" noWrap={true}>
{option.name}
</Typography>
<Typography variant="subtitle2" color="text.secondary" noWrap={true}>
{option.email}
</Typography>
</Box>
</Box>
);
}}
/>
</Box>
</Box>
Expand Down
7 changes: 4 additions & 3 deletions server/src/calender/calender.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
EventResponse,
EventUpdateResponse,
IConferenceRoom,
IPeopleInformation,
} from '@quickmeet/shared';
import { createResponse } from 'src/helpers/payload.util';
import { _Request } from 'src/auth/interfaces';
Expand Down Expand Up @@ -92,10 +93,10 @@ export class CalenderController {
@UseGuards(AuthGuard)
@UseInterceptors(OauthInterceptor)
@Get('/directory/people')
async searchPeople(@_OAuth2Client() client: OAuth2Client, @Query('email') email: string): Promise<ApiResponse<string[]>> {
const emails = await this.calenderService.searchPeople(client, email);
async searchPeople(@_OAuth2Client() client: OAuth2Client, @Query('email') email: string): Promise<ApiResponse<IPeopleInformation[]>> {
const peoples = await this.calenderService.searchPeople(client, email);

return createResponse(emails);
return createResponse(peoples);
}

@UseGuards(AuthGuard)
Expand Down
27 changes: 14 additions & 13 deletions server/src/calender/calender.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BadRequestException, ConflictException, ForbiddenException, Inject, Inj
import { calendar_v3 } from 'googleapis';
import { extractRoomByEmail, isRoomAvailable, validateEmail } from './util/calender.util';
import { AuthService } from '../auth/auth.service';
import { DeleteResponse, EventResponse, EventUpdateResponse, IConferenceRoom } from '@quickmeet/shared';
import { DeleteResponse, EventResponse, EventUpdateResponse, IConferenceRoom, IPeopleInformation } from '@quickmeet/shared';
import { GoogleApiService } from 'src/google-api/google-api.service';

@Injectable()
Expand Down Expand Up @@ -417,18 +417,19 @@ export class CalenderService {
return floors;
}

async searchPeople(client: OAuth2Client, emailQuery: string): Promise<string[]> {
const people = await this.googleApiService.searchPeople(client, emailQuery);
const emails = [];
for (const p of people) {
for (const email of p.emailAddresses) {
if (email.metadata.primary && email.metadata.verified) {
emails.push(email.value);
break;
}
}
}
async searchPeople(client: OAuth2Client, emailQuery: string): Promise<IPeopleInformation[]> {
const response = await this.googleApiService.searchPeople(client, emailQuery);
const peoples: IPeopleInformation[] = response.map((people) => {
const email = people.emailAddresses.find((email) => email.metadata.primary && email.metadata.verified);
const photo = people.photos?.find((photo) => photo.metadata.primary);
const name = people.names?.find((name) => name.metadata.primary);
return {
email: email?.value,
name: name?.displayName,
photo: photo?.url,
};
});

return emails;
return peoples;
}
}
2 changes: 1 addition & 1 deletion server/src/google-api/google-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export class GoogleApiService implements IGoogleApiService {
const [err, res]: [GaxiosError, GaxiosResponse<people_v1.Schema$SearchDirectoryPeopleResponse>] = await to(
peopleService.people.searchDirectoryPeople({
query,
readMask: 'emailAddresses',
readMask: 'emailAddresses,photos,names',
pageSize: 10,
sources: ['DIRECTORY_SOURCE_TYPE_DOMAIN_PROFILE'],
}),
Expand Down
2 changes: 1 addition & 1 deletion shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ export {
StatusTypes,
} from './dto';

export { IConferenceRoom } from './interfaces';
export { IConferenceRoom, IPeopleInformation } from './interfaces';
1 change: 1 addition & 0 deletions shared/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './conference-room.interface';
export * from './people-information.interface';
5 changes: 5 additions & 0 deletions shared/interfaces/people-information.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface IPeopleInformation {
email?: string;
name?: string;
photo?: string;
}

0 comments on commit 6e3d282

Please sign in to comment.