@arquimedes.co/eureka-forms 3.0.72 → 3.0.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -100,7 +100,6 @@ describe('BookingManagePage', () => {
100
100
  renderPage();
101
101
  await user.click(await screen.findByRole('button', { name: 'Elegir otra hora' }));
102
102
  await user.click(await screen.findByRole('button', { name: format(TOMORROW, "EEEE d 'de' MMMM 'de' yyyy", { locale: es }) }));
103
- await user.click(screen.getByRole('button', { name: 'Siguiente' }));
104
103
  await user.click(await screen.findByRole('button', { name: /^10:00/ }));
105
104
  await user.click(screen.getByRole('button', { name: 'Siguiente' }));
106
105
  expect(await screen.findByRole('heading', { name: 'Revisa la nueva hora' })).toBeInTheDocument();
@@ -67,6 +67,8 @@ function MaterialBookingStep({ step, editable, fetchSlots: customFetchSlots }) {
67
67
  '--eureka-text': formStyle.textColor,
68
68
  '--eureka-bg': formStyle.standAloneBackgroundColor ?? '#fff',
69
69
  '--booking-accent': formStyle.secondaryContrastColor,
70
+ '--eureka-description': formStyle.descriptionTextColor,
71
+ '--eureka-error': formStyle.errorColor,
70
72
  };
71
73
  const { ref, value, onChange, error } = useFormStep(step, {
72
74
  defaultValue: null,
@@ -284,6 +286,6 @@ function MaterialBookingStep({ step, editable, fetchSlots: customFetchSlots }) {
284
286
  const personSelectionMode = usesEdge
285
287
  ? (offer?.personSelection ?? PersonSelectionMode.NONE)
286
288
  : step.personSelectionMode;
287
- return (_jsx("div", { ref: ref, style: cssVars, children: _jsx(ErkBookingPicker, { value: value, onChange: handleChange, label: step.label, description: step.description, durationMinutes: offer?.durationMinutes ?? step.durationMinutes, bookingMode: step.bookingMode, temporaryLockMinutes: step.temporaryLockMinutes, personSelectionMode: personSelectionMode, persons: usesEdge ? persons : step.persons, eventTemplates: usesEdge ? eventTemplates : undefined, timeZone: offer?.timeZone, knownContact: knownContact, askEmail: askEmail, lapsed: lapsed, fetchSlots: usesEdge ? fetchSlots : customFetchSlots, fetchDays: usesEdge ? fetchDays : undefined, holdSlot: usesEdge ? holdSlot : undefined, disabled: usesEdge && !offer, readOnly: isReadOnly, error: error?.message, loadError: usesEdge ? offerError : undefined, onRetryLoad: retryOffer }) }));
289
+ return (_jsx("div", { ref: ref, style: cssVars, children: _jsx(ErkBookingPicker, { value: value, onChange: handleChange, label: step.label, description: step.description, required: step.required, durationMinutes: offer?.durationMinutes ?? step.durationMinutes, bookingMode: step.bookingMode, temporaryLockMinutes: step.temporaryLockMinutes, personSelectionMode: personSelectionMode, persons: usesEdge ? persons : step.persons, eventTemplates: usesEdge ? eventTemplates : undefined, timeZone: offer?.timeZone, knownContact: knownContact, askEmail: askEmail, lapsed: lapsed, fetchSlots: usesEdge ? fetchSlots : customFetchSlots, fetchDays: usesEdge ? fetchDays : undefined, holdSlot: usesEdge ? holdSlot : undefined, disabled: usesEdge && !offer, readOnly: isReadOnly, error: error?.message, loadError: usesEdge ? offerError : undefined, onRetryLoad: retryOffer }) }));
288
290
  }
289
291
  export default MaterialBookingStep;
@@ -98,12 +98,11 @@ function today() {
98
98
  return screen.getByRole('button', { name: format(new Date(), "EEEE d 'de' MMMM 'de' yyyy", { locale: es }) });
99
99
  }
100
100
  async function reserveFirstHour(user) {
101
- const open = await screen.findByRole('button', { name: 'Agendar cita' });
101
+ const open = await screen.findByRole('button', { name: 'Elegir horario' });
102
102
  await waitFor(() => expect(open).toBeEnabled());
103
103
  await user.click(open);
104
104
  await waitFor(() => expect(today()).toBeEnabled());
105
105
  await user.click(today());
106
- await user.click(screen.getByRole('button', { name: 'Siguiente' }));
107
106
  await user.click(await screen.findByRole('button', { name: /^09:00/ }));
108
107
  await user.click(screen.getByRole('button', { name: 'Siguiente' }));
109
108
  await user.click(await screen.findByRole('button', { name: 'Reservar' }));
@@ -127,8 +126,9 @@ describe('MaterialBookingStep', () => {
127
126
  contact: { name: 'Ana Gómez', email: 'ana@correo.com' },
128
127
  });
129
128
  // Hours and person come from the hold, not from the button that was clicked.
130
- expect(await screen.findByText(/09:15–09:45 · con Bruno Pérez/)).toBeInTheDocument();
131
- expect(screen.getByText(/Envía el formulario para confirmar la cita/)).toBeInTheDocument();
129
+ expect(await screen.findByText(/ · 09:15 – 09:45$/)).toBeInTheDocument();
130
+ expect(screen.getByText('Bruno Pérez · Reservado hasta las 08:00')).toBeInTheDocument();
131
+ expect(screen.getByText(/Te confirmaremos a/)).toHaveTextContent('Te confirmaremos a ana@correo.com cuando envíes el formulario.');
132
132
  });
133
133
  it('asks for the email in the picker when the step is set to FIELD', async () => {
134
134
  const user = userEvent.setup();
@@ -136,12 +136,11 @@ describe('MaterialBookingStep', () => {
136
136
  'email-step': 'ana@correo.com',
137
137
  'name-step': 'Ana Gómez',
138
138
  });
139
- const open = await screen.findByRole('button', { name: 'Agendar cita' });
139
+ const open = await screen.findByRole('button', { name: 'Elegir horario' });
140
140
  await waitFor(() => expect(open).toBeEnabled());
141
141
  await user.click(open);
142
142
  await waitFor(() => expect(today()).toBeEnabled());
143
143
  await user.click(today());
144
- await user.click(screen.getByRole('button', { name: 'Siguiente' }));
145
144
  await user.click(await screen.findByRole('button', { name: /^09:00/ }));
146
145
  await user.click(screen.getByRole('button', { name: 'Siguiente' }));
147
146
  expect(await screen.findByLabelText(/Correo/)).toBeInTheDocument();
@@ -171,7 +170,7 @@ describe('MaterialBookingStep', () => {
171
170
  });
172
171
  expect(renewBookingHold).toHaveBeenLastCalledWith(expect.anything(), { renew: 'renew-2', hold: 'secret-1' });
173
172
  expect(await screen.findByRole('alert')).toHaveTextContent('Tu reserva venció; elige la hora de nuevo.');
174
- expect(screen.getByRole('button', { name: 'Agendar cita' })).toBeInTheDocument();
173
+ expect(screen.getByRole('button', { name: 'Elegir horario' })).toBeInTheDocument();
175
174
  });
176
175
  it('reloads the offer and retries once when a person token went stale', async () => {
177
176
  const user = userEvent.setup();
@@ -196,14 +195,13 @@ describe('MaterialBookingStep', () => {
196
195
  'email-step': 'ana@correo.com',
197
196
  'name-step': 'Ana Gómez',
198
197
  });
199
- const open = await screen.findByRole('button', { name: 'Agendar cita' });
198
+ const open = await screen.findByRole('button', { name: 'Elegir horario' });
200
199
  await waitFor(() => expect(open).toBeEnabled());
201
200
  await user.click(open);
202
201
  await user.click(await screen.findByText('Dra. Ruiz'));
203
202
  await user.click(screen.getByRole('button', { name: 'Siguiente' }));
204
203
  await waitFor(() => expect(today()).toBeEnabled());
205
204
  await user.click(today());
206
- await user.click(screen.getByRole('button', { name: 'Siguiente' }));
207
205
  expect(await screen.findByRole('button', { name: /^09:00/ })).toBeInTheDocument();
208
206
  expect(vi.mocked(fetchBookingSlots).mock.calls.map((call) => call[2]?.person)).toEqual(['p-old', 'p-new']);
209
207
  });
@@ -222,14 +220,13 @@ describe('MaterialBookingStep', () => {
222
220
  eventTemplate: { name: 'Asesoría de crédito', durationMinutes: 45 },
223
221
  });
224
222
  renderStep(bookingStep(), { 'email-step': 'ana@correo.com', 'name-step': 'Ana Gómez' });
225
- const open = await screen.findByRole('button', { name: 'Agendar cita' });
223
+ const open = await screen.findByRole('button', { name: 'Elegir horario' });
226
224
  await waitFor(() => expect(open).toBeEnabled());
227
225
  await user.click(open);
228
226
  await user.click(await screen.findByRole('button', { name: /Asesoría de crédito/ }));
229
227
  await user.click(screen.getByRole('button', { name: 'Siguiente' }));
230
228
  await waitFor(() => expect(today()).toBeEnabled());
231
229
  await user.click(today());
232
- await user.click(screen.getByRole('button', { name: 'Siguiente' }));
233
230
  await user.click(await screen.findByRole('button', { name: /^09:00/ }));
234
231
  await user.click(screen.getByRole('button', { name: 'Siguiente' }));
235
232
  await user.click(await screen.findByRole('button', { name: 'Reservar' }));
@@ -240,7 +237,7 @@ describe('MaterialBookingStep', () => {
240
237
  contact: { name: 'Ana Gómez', email: 'ana@correo.com' },
241
238
  eventTemplate: 'tt-2',
242
239
  });
243
- expect(await screen.findByText('Asesoría de crédito · 45 min')).toBeInTheDocument();
240
+ expect(await screen.findByText(/^Bruno Pérez · Asesoría de crédito · 45 min · Reservado hasta/)).toBeInTheDocument();
244
241
  });
245
242
  it('asks for no type when the step does not let the requester pick one', async () => {
246
243
  const user = userEvent.setup();
@@ -266,10 +263,19 @@ describe('MaterialBookingStep', () => {
266
263
  .mockResolvedValue(OFFER);
267
264
  renderStep(bookingStep(), { 'email-step': 'ana@correo.com', 'name-step': 'Ana Gómez' });
268
265
  expect(await screen.findByRole('alert')).toHaveTextContent('Algo falló');
269
- expect(screen.getByRole('button', { name: 'Agendar cita' })).toBeDisabled();
266
+ expect(screen.getByRole('button', { name: 'Elegir horario' })).toBeDisabled();
270
267
  await user.click(screen.getByRole('button', { name: 'Reintentar' }));
271
- await waitFor(() => expect(screen.getByRole('button', { name: 'Agendar cita' })).toBeEnabled());
268
+ await waitFor(() => expect(screen.getByRole('button', { name: 'Elegir horario' })).toBeEnabled());
272
269
  expect(screen.queryByRole('alert')).not.toBeInTheDocument();
273
270
  expect(fetchBookingOffer).toHaveBeenCalledTimes(2);
274
271
  });
272
+ it('tells the requester before opening where the confirmation will go', async () => {
273
+ renderStep(bookingStep({ required: true }), { 'email-step': 'ana@correo.com', 'name-step': 'Ana Gómez' });
274
+ expect(await screen.findByText(/Te confirmaremos a/)).toHaveTextContent('Te confirmaremos a ana@correo.com cuando envíes el formulario.');
275
+ expect(screen.getByText('Agendar cita *')).toBeInTheDocument();
276
+ });
277
+ it('promises to ask for name and email when no earlier step gives them', async () => {
278
+ renderStep(bookingStep({ contactEmail: { source: BookingContactEmailSourceTypes.FIELD } }), {});
279
+ expect(await screen.findByText('Te pediremos nombre y correo al reservar.')).toBeInTheDocument();
280
+ });
275
281
  });
@@ -0,0 +1,3 @@
1
+ import { IconProps } from './@ErkIcon';
2
+ declare function CheckIcon(props: IconProps): JSX.Element;
3
+ export default CheckIcon;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useIconProps } from './@ErkIcon';
3
+ function CheckIcon(props) {
4
+ const iconProps = useIconProps(props);
5
+ return (_jsx("svg", { ...iconProps, version: "1.1", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M9 16.17 5.53 12.7a.9959.9959 0 0 0-1.41 0c-.39.39-.39 1.02 0 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71c.39-.39.39-1.02 0-1.41a.9959.9959 0 0 0-1.41 0L9 16.17z" }) }));
6
+ }
7
+ export default CheckIcon;
@@ -0,0 +1,3 @@
1
+ import { IconProps } from './@ErkIcon';
2
+ declare function VideoIcon(props: IconProps): JSX.Element;
3
+ export default VideoIcon;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useIconProps } from './@ErkIcon';
3
+ function VideoIcon(props) {
4
+ const iconProps = useIconProps(props);
5
+ return (_jsx("svg", { ...iconProps, version: "1.1", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l2.29 2.29c.63.63 1.71.18 1.71-.71V8.91c0-.89-1.08-1.34-1.71-.71L17 10.5z" }) }));
6
+ }
7
+ export default VideoIcon;
@@ -4,15 +4,15 @@ import { BookablePerson, BookingEventTemplateOption, BookingMode, BookingSlot, B
4
4
  * (la página de "Elegir otra hora" del enlace del correo).
5
5
  */
6
6
  export interface BookingPickerCopy {
7
- /** Botón de la tarjeta de entrada. */
7
+ /** Botón que abre el selector. */
8
8
  open: string;
9
- /** Paso final, su botón y su título. */
9
+ /** Paso final y su botón. */
10
10
  confirm: string;
11
11
  /** El botón mientras se confirma. */
12
12
  confirming: string;
13
- /** El título del paso final. */
13
+ /** El título del paso final cuando no se piden datos (`withoutContact`). */
14
14
  title: string;
15
- /** La explicación bajo el título y junto al resumen. */
15
+ /** La explicación del paso final: cuándo queda hecha la cita. */
16
16
  note: string;
17
17
  }
18
18
  export interface ErkBookingPickerProps {
@@ -20,6 +20,8 @@ export interface ErkBookingPickerProps {
20
20
  onChange: (value: BookingValue | null) => void;
21
21
  label: string;
22
22
  description?: string | null;
23
+ /** Marca la etiqueta con " *". */
24
+ required?: boolean;
23
25
  durationMinutes: number;
24
26
  bookingMode?: BookingMode;
25
27
  temporaryLockMinutes?: number;
@@ -32,8 +34,8 @@ export interface ErkBookingPickerProps {
32
34
  */
33
35
  eventTemplates?: BookingEventTemplateOption[];
34
36
  /**
35
- * Zona horaria de la organización (IANA). Con ella el paso de hora dice en qué reloj están las horas
36
- * y, si el navegador está en otra zona, la equivalencia en la hora de quien agenda.
37
+ * Zona horaria de la organización (IANA). Con ella el paso de día y hora dice en qué reloj están las
38
+ * horas y, si el navegador está en otra zona, la equivalencia en la hora de quien agenda.
37
39
  */
38
40
  timeZone?: string;
39
41
  /**
@@ -48,7 +50,7 @@ export interface ErkBookingPickerProps {
48
50
  askEmail?: boolean;
49
51
  /**
50
52
  * Mensaje de una reserva que se perdió después de hecha (la renovación falló). Al cambiar, el
51
- * wizard vuelve al paso de hora con el mensaje y horas frescas.
53
+ * wizard vuelve al paso de día y hora con el mensaje y horas frescas.
52
54
  */
53
55
  lapsed?: string;
54
56
  /** Provee los slots disponibles para una persona + día (+ el tipo de atención elegido). */
@@ -78,7 +80,7 @@ export interface ErkBookingPickerProps {
78
80
  email?: string;
79
81
  };
80
82
  }) => Promise<Partial<BookingValue>>;
81
- /** Deshabilita la tarjeta de entrada mientras el host termina de cargar la oferta. */
83
+ /** Deshabilita el botón de abrir mientras el host termina de cargar la oferta. */
82
84
  disabled?: boolean;
83
85
  /** No pide nombre ni correo: la cita que se está moviendo ya los tiene. */
84
86
  withoutContact?: boolean;
@@ -87,19 +89,20 @@ export interface ErkBookingPickerProps {
87
89
  readOnly?: boolean;
88
90
  error?: string;
89
91
  /**
90
- * La oferta no cargó: el mensaje se muestra bajo la tarjeta de entrada con "Reintentar", que llama a
92
+ * La oferta no cargó: el mensaje se muestra bajo el botón de abrir con "Reintentar", que llama a
91
93
  * `onRetryLoad`.
92
94
  */
93
95
  loadError?: string;
94
96
  onRetryLoad?: () => void;
95
97
  }
96
98
  /**
97
- * Selector de cita: una tarjeta de entrada ("Agendar cita") abre el wizard en un modal —
98
- * una decisión por pantalla (persona → día → hora → confirmar), con panel izquierdo de
99
- * resumen que se va llenando, breadcrumb navegable arriba y barra inferior Atrás / Siguiente.
100
- * En el paso final el panel derecho desaparece y el resumen se expande en una columna
101
- * centrada (cross-fade corto). "Reservar" retira el horario de la oferta por unos minutos, cierra el
102
- * modal y deja el resumen compacto; la cita se confirma cuando se envía el formulario.
99
+ * Selector de cita. En el formulario es un control suelto como el de archivos: etiqueta, descripción y
100
+ * el botón "Elegir horario"; con un horario elegido, una fila con el resumen y "Cambiar".
101
+ *
102
+ * El botón abre el wizard en un modal de tamaño fijo — una decisión por pantalla ([tipo] → [persona] →
103
+ * día y hora → reservar), con stepper navegable arriba y barra inferior con lo elegido y Atrás /
104
+ * Siguiente. "Reservar" retira el horario de la oferta por unos minutos, cierra el modal y deja el
105
+ * resumen; la cita se confirma cuando se envía el formulario.
103
106
  */
104
- declare function ErkBookingPicker({ value, onChange, label, description, durationMinutes, bookingMode, temporaryLockMinutes, personSelectionMode, persons, eventTemplates, timeZone, knownContact, askEmail, lapsed, fetchSlots, fetchDays, holdSlot, disabled, withoutContact, copy: copyOverrides, readOnly, error, loadError, onRetryLoad, }: ErkBookingPickerProps): JSX.Element;
107
+ declare function ErkBookingPicker({ value, onChange, label, description, required, durationMinutes, bookingMode, temporaryLockMinutes, personSelectionMode, persons, eventTemplates, timeZone, knownContact, askEmail, lapsed, fetchSlots, fetchDays, holdSlot, disabled, withoutContact, copy: copyOverrides, readOnly, error, loadError, onRetryLoad, }: ErkBookingPickerProps): JSX.Element;
105
108
  export default ErkBookingPicker;