Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7cceffc
Add tests for DynamicWorkspaceInvitePage keyboard dismiss behavior
MelvinBot May 14, 2026
a2d9f11
Remove DynamicWorkspaceInvitePage unit test file
MelvinBot May 14, 2026
aaf7a7e
feat: Add avatar editing functionality for agents, including new rout…
NicolasBonet May 12, 2026
a7255a5
feat: Add update avatar error messages and edit avatar page for multi…
NicolasBonet May 12, 2026
eb7de5a
feat: Enhance agent avatar management by introducing isAgentEmail and…
NicolasBonet May 12, 2026
993a527
feat: Integrate OfflineWithFeedback component for agent avatar update…
NicolasBonet May 12, 2026
420b8b0
feat: Implement updateAgentAvatar functionality with optimistic updat…
NicolasBonet May 12, 2026
45ef5ec
feat: Extend agent error handling to include avatarErrors in settings…
NicolasBonet May 13, 2026
6a156a7
feat: Add onSave callback to EditAgentAvatarContent for improved avat…
NicolasBonet May 13, 2026
f9da59b
refactor: remove unused isAgentAccount hook from ProfilePage to strea…
NicolasBonet May 14, 2026
58c22ce
fix: Update error handling in EditAgentAvatarPage to include avatarEr…
NicolasBonet May 14, 2026
1e4b88b
feat: Update language files for agent management, enhancing error mes…
NicolasBonet May 14, 2026
e383b3a
fix: Standardize error messages in French and Italian language files …
NicolasBonet May 14, 2026
92a9974
fix: Simplify error handling in AgentsPage by consolidating error che…
NicolasBonet May 14, 2026
fb5f126
refactor: Remove unused theme imports and simplify avatar selection s…
NicolasBonet May 14, 2026
b51fc2e
fix: Update avatar URL generation in ProfileAvatar to ensure correct …
NicolasBonet May 14, 2026
631e486
fix: Enhance avatar update logic in ProfileAvatar to correctly handle…
NicolasBonet May 14, 2026
d808f3f
fix: Improve type handling in mock for parseStyleFromFunction in Edit…
NicolasBonet May 14, 2026
3bce1f5
feat: Introduce new bot avatars and update avatar handling in PresetA…
NicolasBonet May 14, 2026
280e89f
feat: Add avatar upload functionality for agents, including new route…
NicolasBonet May 14, 2026
4ff67e9
feat: Enhance createAgent function to support optimistic avatar URI h…
NicolasBonet May 14, 2026
bdb294e
feat: Add unit tests for AddAgentAvatarPage and enhance AddAgentPage …
NicolasBonet May 14, 2026
805514c
refactor: Simplify avatar URI assignment logic in createAgent functio…
NicolasBonet May 14, 2026
d12b01d
feat: Update createAgent function to accept file parameter and enhanc…
NicolasBonet May 15, 2026
94aa6c6
test: Add unit test to verify file upload behavior in createAgent fun…
NicolasBonet May 15, 2026
1644fb2
feat: Introduce resolveAvatarURI function to streamline avatar URI re…
NicolasBonet May 15, 2026
e61227f
feat: Add initialization check in AddAgentAvatarPage to prevent redun…
NicolasBonet May 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9693,6 +9693,9 @@ const CONST = {
ADD_AGENT_PAGE: {
AVATAR: 'AddAgentPage-Avatar',
},
EDIT_AGENT_PAGE: {
AVATAR: 'EditAgentPage-Avatar',
},
SETTINGS_PROFILE: {
AVATAR: 'SettingsProfile-Avatar',
DISPLAY_NAME: 'SettingsProfile-DisplayName',
Expand Down
5 changes: 5 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,7 @@ const ROUTES = {
SETTINGS_WALLET_TRAVEL_CVV_VERIFY_ACCOUNT: `settings/wallet/travel-cvv/${VERIFY_ACCOUNT}`,
SETTINGS_AGENTS: 'settings/agents',
SETTINGS_AGENTS_ADD: 'settings/agents/new',
SETTINGS_AGENTS_ADD_AVATAR: 'settings/agents/new/avatar',
SETTINGS_AGENTS_EDIT: {
route: 'settings/agents/:accountID/edit',
getRoute: (accountID: number) => `settings/agents/${accountID}/edit` as const,
Expand All @@ -1075,6 +1076,10 @@ const ROUTES = {
route: 'settings/agents/:accountID/edit/prompt',
getRoute: (accountID: number) => `settings/agents/${accountID}/edit/prompt` as const,
},
SETTINGS_AGENTS_EDIT_AVATAR: {
route: 'settings/agents/:accountID/edit/avatar',
getRoute: (accountID: number) => `settings/agents/${accountID}/edit/avatar` as const,
},
SETTINGS_RULES: 'settings/rules',
SETTINGS_RULES_ADD: {
route: 'settings/rules/new/:field?/:index?',
Expand Down
2 changes: 2 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,11 @@ const SCREENS = {
AGENTS: {
ROOT: 'Settings_Agents',
ADD: 'Settings_Agents_Add',
ADD_AVATAR: 'Settings_Agents_Add_Avatar',
EDIT: 'Settings_Agents_Edit',
EDIT_NAME: 'Settings_Agents_Edit_Name',
EDIT_PROMPT: 'Settings_Agents_Edit_Prompt',
EDIT_AVATAR: 'Settings_Agents_Edit_Avatar',
},

RULES: {
Expand Down
4 changes: 1 addition & 3 deletions src/components/AvatarSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {View} from 'react-native';
import useLetterAvatars from '@hooks/useLetterAvatars';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {PRESET_AVATAR_CATALOG_ORDERED} from '@libs/Avatars/PresetAvatarCatalog';
import type {AvatarSizeName} from '@styles/utils';
Expand Down Expand Up @@ -37,7 +36,6 @@ const SPACER_SIZE = 10;
function AvatarSelector({selectedID, onSelect, label, name, size = CONST.AVATAR_SIZE.MEDIUM}: AvatarSelectorProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const theme = useTheme();
const StyleUtils = useStyleUtils();
const {avatarList} = useLetterAvatars(name, size);

Expand All @@ -59,7 +57,7 @@ function AvatarSelector({selectedID, onSelect, label, name, size = CONST.AVATAR_
accessibilityRole="button"
accessibilityLabel={translate('avatarPage.selectAvatar')}
onPress={() => onSelect(id)}
style={[styles.avatarSelectorWrapper, isSelected && {borderColor: theme.success, borderWidth: 2}]}
style={[styles.avatarSelectorWrapper, isSelected && styles.avatarSelected]}
>
<Avatar
type={CONST.ICON_TYPE_AVATAR}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Icon/DefaultBotAvatars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ const botAvatarIDs = new Map<BotAvatar, string>([
[BotAvatarYellow, 'bot-avatar--yellow'],
]);

export {botAvatars, botAvatarIDs};
export {botAvatars, botAvatarIDs, BotAvatarBlue, BotAvatarGreen, BotAvatarIce, BotAvatarPink, BotAvatarTangerine, BotAvatarYellow};
export type {BotAvatar};
16 changes: 9 additions & 7 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2745,31 +2745,33 @@ ${amount} für ${merchant} – ${date}`,
emptyAgents: {title: 'Keine Agenten erstellt', subtitle: 'Hör auf, Dinge manuell zu erledigen. Weise stattdessen eine:n Agent:in an und spare dir eine Menge Zeit.'},
error: {
genericAdd: 'Beim Hinzufügen dieses Agenten ist ein Problem aufgetreten',
genericUpdate: 'Beim Aktualisieren dieses Agents ist ein Problem aufgetreten',
updateName: 'Beim Aktualisieren des Namens dieses Agents ist ein Problem aufgetreten',
genericUpdate: 'Beim Aktualisieren dieses Agenten ist ein Problem aufgetreten',
updateName: 'Beim Aktualisieren des Namens dieser Vertretung ist ein Problem aufgetreten',
updatePrompt: 'Beim Aktualisieren der Anweisungen dieses Agenten ist ein Problem aufgetreten',
updateAvatar: 'Beim Aktualisieren des Avatars dieser Vertretung ist ein Problem aufgetreten',
},
},
addAgentPage: {
title: 'Neue Kontaktperson',
agentName: 'Name der Ansprechperson',
instructions: 'Eigene Anweisungen schreiben',
createAgent: 'Agent erstellen',
switchAvatar: 'Profilbild wechseln',
editAvatar: 'Profilbild wechseln',
defaultAgentName: (displayName: string) => `Agent*in von ${displayName}`,
defaultPrompt:
'Lehne Ausgaben ab, die für Glücksspiele, Kinobesuche oder andere offensichtlich nicht geschäftliche Zwecke sind.\n\nErinnere den:die Nutzer:in daran, immer ein Belegfoto beizufügen, auf dem das Trinkgeld klar erkennbar ist.\n\nGenehmige den Bericht, wenn er früheren Berichten derselben Person sehr ähnlich ist.\n\nLehne Berichte mit mehr als 500 $ an Reisekosten ab.',
},
editAgentPage: {
title: 'Agent bearbeiten',
agentName: 'Agentenname',
agentName: 'Name der Agentin/des Agenten',
instructions: 'Eigene Anweisungen schreiben',
deleteAgent: 'Agent löschen',
deleteAgentTitle: 'Agent löschen?',
deleteAgentMessage: 'Sind Sie sicher, dass Sie diese Ansprechperson löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.',
deleteAgentMessage: 'Sind Sie sicher, dass Sie diese Vermittlerin/diesen Vermittler löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.',
},
editAgentNamePage: {title: 'Agentenname'},
editAgentPromptPage: {title: 'Eigene Anweisungen schreiben', error: {emptyPrompt: 'Bitte gib Anweisungen für deine Agentin/deinen Agenten ein.'}},
editAgentAvatarPage: {title: 'Avatar bearbeiten'},
editAgentNamePage: {title: 'Name der Agentin/des Agenten'},
editAgentPromptPage: {title: 'Eigene Anweisungen schreiben', error: {emptyPrompt: 'Bitte geben Sie Anweisungen für Ihre:n Agent:in ein.'}},
expenseRulesPage: {
title: 'Ausgabenregeln',
findRule: 'Regel finden',
Expand Down
6 changes: 5 additions & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2804,14 +2804,15 @@ const translations = {
genericUpdate: 'There was a problem updating this agent',
updateName: "There was a problem updating this agent's name",
updatePrompt: "There was a problem updating this agent's instructions",
updateAvatar: "There was a problem updating this agent's avatar",
},
},
addAgentPage: {
title: 'New agent',
agentName: 'Agent name',
instructions: 'Write custom instructions',
createAgent: 'Create agent',
switchAvatar: 'Switch avatar',
editAvatar: 'Edit avatar',
defaultAgentName: (displayName: string) => `${displayName}'s Agent`,
defaultPrompt:
"Reject expenses that are for gambling, movies, or other obvious non-business reasons.\n\nRemind the user to always include a receipt image that makes the tip clear.\n\nApprove the report if it's very similar to previous reports from the same user.\n\nReject reports with more than $500 in travel expenses.",
Expand All @@ -2824,6 +2825,9 @@ const translations = {
deleteAgentTitle: 'Delete agent?',
deleteAgentMessage: 'Are you sure you want to delete this agent? This action cannot be undone.',
},
editAgentAvatarPage: {
title: 'Edit avatar',
},
editAgentNamePage: {
title: 'Agent name',
},
Expand Down
35 changes: 7 additions & 28 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1692,10 +1692,8 @@ const translations: TranslationDeepObject<typeof en> = {
backdropLabel: 'Fondo del Modal',
},
nextStep: {
/* eslint-disable @typescript-eslint/no-unused-vars */
message: {
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_ADD_TRANSACTIONS]: (actor, actorType, _eta, _etaType) => {
// eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> añadas gastos.`;
Expand All @@ -1706,7 +1704,6 @@ const translations: TranslationDeepObject<typeof en> = {
}
},
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_SUBMIT]: (actor, actorType, _eta, _etaType) => {
// eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> envíes los gastos.`;
Expand All @@ -1718,7 +1715,6 @@ const translations: TranslationDeepObject<typeof en> = {
},
[CONST.NEXT_STEP.MESSAGE_KEY.NO_FURTHER_ACTION]: (_actor, _actorType, _eta, _etaType) => `¡No se requiere ninguna acción adicional!`,
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_FOR_SUBMITTER_ACCOUNT]: (actor, actorType, _eta, _etaType) => {
// eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> añadas una cuenta bancaria.`;
Expand All @@ -1733,7 +1729,6 @@ const translations: TranslationDeepObject<typeof en> = {
if (eta) {
formattedETA = etaType === CONST.NEXT_STEP.ETA_TYPE.DATE_TIME ? ` el ${eta} de cada mes` : ` ${eta}`;
}
// eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que tus gastos se envíen automáticamente${formattedETA}.`;
Expand All @@ -1744,7 +1739,6 @@ const translations: TranslationDeepObject<typeof en> = {
}
},
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_FIX_ISSUES]: (actor, actorType, _eta, _etaType) => {
// eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> soluciones ellos problemas.`;
Expand All @@ -1755,7 +1749,6 @@ const translations: TranslationDeepObject<typeof en> = {
}
},
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_APPROVE]: (actor, actorType, _eta, _etaType) => {
// eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> apruebes los gastos.`;
Expand All @@ -1766,7 +1759,6 @@ const translations: TranslationDeepObject<typeof en> = {
}
},
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_EXPORT]: (actor, actorType, _eta, _etaType) => {
// eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> exportes este informe.`;
Expand All @@ -1777,7 +1769,6 @@ const translations: TranslationDeepObject<typeof en> = {
}
},
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_PAY]: (actor, actorType, _eta, _etaType) => {
// eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> pagues los gastos.`;
Expand All @@ -1788,7 +1779,6 @@ const translations: TranslationDeepObject<typeof en> = {
}
},
[CONST.NEXT_STEP.MESSAGE_KEY.WAITING_FOR_POLICY_BANK_ACCOUNT]: (actor, actorType, _eta, _etaType) => {
// eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Esperando a que <strong>tú</strong> termines de configurar una cuenta bancaria de empresa.`;
Expand All @@ -1808,7 +1798,6 @@ const translations: TranslationDeepObject<typeof en> = {
[CONST.NEXT_STEP.MESSAGE_KEY.SUBMITTING_TO_SELF]: (_actor, _actorType, _eta, _etaType) =>
`¡Ups! Parece que estás enviando el informe a <strong>ti mismo</strong>. Aprobar tus propios informes está <strong>prohibido</strong> por tu espacio de trabajo. Por favor, envía este informe a otra persona o contacta a tu administrador para cambiar la persona a la que lo envías.`,
[CONST.NEXT_STEP.MESSAGE_KEY.REJECTED_REPORT]: (actor, actorType) => {
// eslint-disable-next-line default-case
switch (actorType) {
case CONST.NEXT_STEP.ACTOR_TYPE.CURRENT_USER:
return `Este informe fue rechazado. Esperando a que <strong>tú</strong> corrijas los problemas y lo vuelvas a enviar manualmente.`;
Expand Down Expand Up @@ -2464,7 +2453,6 @@ ${amount} para ${merchant} - ${date}`,
two: 'º',
few: 'º',
other: 'º',
/* eslint-disable @typescript-eslint/naming-convention */
'1': 'Primero',
'2': 'Segundo',
'3': 'Tercero',
Expand All @@ -2475,7 +2463,6 @@ ${amount} para ${merchant} - ${date}`,
'8': 'Octavo',
'9': 'Noveno',
'10': 'Décimo',
/* eslint-enable @typescript-eslint/naming-convention */
},
},
approverInMultipleWorkflows: 'Este miembro ya pertenece a otro flujo de aprobación. Cualquier actualización aquí se reflejará allí también.',
Expand Down Expand Up @@ -2633,35 +2620,30 @@ ${amount} para ${merchant} - ${date}`,
genericUpdate: 'Hubo un problema al actualizar este agente',
updateName: 'Hubo un problema al actualizar el nombre de este agente',
updatePrompt: 'Hubo un problema al actualizar las instrucciones de este agente',
updateAvatar: 'Hubo un problema al actualizar el avatar de este agente',
},
},
addAgentPage: {
title: 'Nuevo agente',
agentName: 'Nombre del agente',
instructions: 'Escribe instrucciones personalizadas',
createAgent: 'Crear agente',
switchAvatar: 'Cambiar avatar',
editAvatar: 'Cambiar avatar',
defaultAgentName: (displayName: string) => `Agente de ${displayName}`,
defaultPrompt:
'Rechazar gastos por juegos de azar, películas u otras razones claramente no comerciales.\n\nRecordar al usuario que siempre incluya una imagen del recibo que muestre claramente la propina.\n\nAprobar el informe si es muy similar a informes anteriores del mismo usuario.\n\nRechazar informes con más de $500 en gastos de viaje.',
},
editAgentPage: {
title: 'Editar agente',
agentName: 'Nombre del agente',
instructions: 'Escribe instrucciones personalizadas',
instructions: 'Escribir instrucciones personalizadas',
deleteAgent: 'Eliminar agente',
deleteAgentTitle: '¿Eliminar agente?',
deleteAgentMessage: '¿Estás seguro de que quieres eliminar este agente? Esta acción no se puede deshacer.',
},
editAgentNamePage: {
title: 'Nombre del agente',
},
editAgentPromptPage: {
title: 'Escribe instrucciones personalizadas',
error: {
emptyPrompt: 'Por favor, introduce instrucciones para tu agente.',
},
deleteAgentMessage: '¿Seguro que quieres eliminar a este agente? Esta acción no se puede deshacer.',
},
editAgentAvatarPage: {title: 'Editar avatar'},
editAgentNamePage: {title: 'Nombre del agente'},
editAgentPromptPage: {title: 'Escribir instrucciones personalizadas', error: {emptyPrompt: 'Por favor, introduce instrucciones para tu agente.'}},
expenseRulesPage: {
title: 'Reglas de gastos',
subtitle: 'Estas reglas se aplicarán a tus gastos.',
Expand Down Expand Up @@ -6961,7 +6943,6 @@ ${amount} para ${merchant} - ${date}`,
restrictedDescription: 'Sólo las personas en tu espacio de trabajo pueden encontrar esta sala',
privateDescription: 'Sólo las personas que están invitadas a esta sala pueden encontrarla',
publicDescription: 'Cualquier persona puede unirse a esta sala',
// eslint-disable-next-line @typescript-eslint/naming-convention
public_announceDescription: 'Cualquier persona puede unirse a esta sala',
createRoom: 'Crea una sala de chat',
roomAlreadyExistsError: 'Ya existe una sala con este nombre',
Expand All @@ -6981,7 +6962,6 @@ ${amount} para ${merchant} - ${date}`,
restricted: 'Espacio de trabajo',
private: 'Privada',
public: 'Público',
// eslint-disable-next-line @typescript-eslint/naming-convention
public_announce: 'Anuncio Público',
},
},
Expand Down Expand Up @@ -7296,7 +7276,6 @@ ${amount} para ${merchant} - ${date}`,
updatedDefaultTitle: (newDefaultTitle, oldDefaultTitle) => `cambió la fórmula personalizada del nombre del informe a "${newDefaultTitle}" (previamente "${oldDefaultTitle}")`,
updatedOwnership: (oldOwnerEmail, oldOwnerName, policyName) => `asumió la propiedad del espacio de trabajo ${policyName} de ${oldOwnerName} (${oldOwnerEmail})`,
updatedAutoHarvesting: (enabled) => `${enabled ? 'habilitó' : 'deshabilitó'} el envío programado`,
// eslint-disable-next-line @typescript-eslint/max-params
updatedIndividualBudgetNotification: (
budgetAmount,
budgetFrequency,
Expand Down
Loading
Loading