allow external attendees (#141)

* [#122] added external attendees

* fixup! [#122] added external attendees

* fixup! [#122] added external attendees

* Update src/components/Attendees/AttendeeSearch.tsx

---------

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
Co-authored-by: Benoit TELLIER <btellier@linagora.com>
This commit is contained in:
Camille Moussu
2025-09-25 11:25:22 +02:00
committed by GitHub
parent 2f7cf9c797
commit 2c78a037a9
5 changed files with 56 additions and 13 deletions
@@ -27,6 +27,7 @@ describe("PeopleSearch", () => {
const onChange = jest.fn();
renderWithProviders(
<PeopleSearch
objectTypes={["user"]}
selectedUsers={selectedUsers}
onChange={onChange}
{...props}
@@ -34,6 +34,7 @@ export default function UserSearch({
<>
<PeopleSearch
selectedUsers={selectedUsers}
objectTypes={["user", "contact"]}
disabled={disabled}
onChange={(event: any, value: User[]) => {
setAttendees(
@@ -48,6 +49,7 @@ export default function UserSearch({
);
setSelectedUsers(value);
}}
freeSolo
/>
</>
);
+51 -13
View File
@@ -32,24 +32,32 @@ export interface User {
export function PeopleSearch({
selectedUsers,
onChange,
objectTypes,
disabled,
freeSolo,
onToggleEventPreview,
}: {
selectedUsers: User[];
onChange: Function;
objectTypes: string[];
disabled?: boolean;
freeSolo?: boolean;
onToggleEventPreview?: Function;
}) {
const [query, setQuery] = useState("");
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState<User[]>([]);
const isValidEmail = (email: string) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
const [error, setError] = useState<string | null>(null);
const theme = useTheme();
useEffect(() => {
const delayDebounceFn = setTimeout(async () => {
if (query) {
setLoading(true);
const res = await searchUsers(query, ["user"]);
const res = await searchUsers(query, objectTypes);
setOptions(res);
setLoading(false);
}
@@ -60,20 +68,40 @@ export function PeopleSearch({
return (
<Autocomplete
freeSolo={freeSolo}
multiple
options={options}
disabled={disabled}
loading={loading}
filterOptions={(x) => x}
fullWidth
getOptionLabel={(option) => option.displayName || option.email}
getOptionLabel={(option) => {
if (typeof option === "object") {
return option.displayName || option.email;
} else {
return option;
}
}}
filterSelectedOptions
value={selectedUsers}
onInputChange={(event, value) => setQuery(value)}
onChange={(event, value) => onChange(event, value)}
onChange={(event, value) => {
const last = value[value.length - 1];
if (typeof last === "string" && !isValidEmail(last)) {
setError(`"${last}" is not a valid email address`);
return;
}
setError(null);
const mapped = value.map((v: any) =>
typeof v === "string" ? { email: v } : v
);
onChange(event, mapped);
}}
renderInput={(params) => (
<TextField
{...params}
error={!!error}
helperText={error}
placeholder="Search user"
label="Search user"
onKeyDown={(e) => {
@@ -124,16 +152,26 @@ export function PeopleSearch({
);
}}
renderValue={(value, getTagProps) =>
value.map((option, index) => (
<Chip
{...getTagProps({ index })}
sx={{
backgroundColor: option.color,
color: theme.palette.getContrastText(option.color ?? "#ffffffff"),
}}
label={option.displayName}
/>
))
value.map((option, index) => {
const isString = typeof option === "string";
const label = isString ? option : option.displayName || option.email;
const chipColor = isString
? theme.palette.grey[300]
: (option.color ?? theme.palette.grey[300]);
const textColor = theme.palette.getContrastText(chipColor);
return (
<Chip
{...getTagProps({ index })}
key={label}
sx={{
backgroundColor: chipColor,
color: textColor,
}}
label={label}
/>
);
})
}
/>
);
@@ -259,6 +259,7 @@ export default function CalendarSearch({
<CardHeader title="Browse other calendars" sx={{ pb: 0 }} />
<CardContent sx={{ flex: 1, overflow: "auto" }}>
<PeopleSearch
objectTypes={["user"]}
selectedUsers={selectedUsers}
onChange={async (event: any, value: User[]) => {
setSelectedUsers(value);
@@ -105,6 +105,7 @@ export function TempCalendarsInput({
return (
<PeopleSearch
objectTypes={["user"]}
selectedUsers={tempUsers}
onChange={handleUserChange}
onToggleEventPreview={handleToggleEventPreview}