@tvsgroup/appointment-form 0.1.1
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.
- package/README.md +121 -0
- package/dist/AppointmentForm.d.ts +78 -0
- package/dist/appointment-form.es.js +13878 -0
- package/dist/appointment-form.es.js.map +1 -0
- package/dist/chunks/registerFn-DsKKhIA8.js +9053 -0
- package/dist/chunks/registerFn-DsKKhIA8.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +8 -0
- package/dist/register.js.map +1 -0
- package/dist/registerFn.d.ts +7 -0
- package/package.json +92 -0
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# @tvsgroup/appointment-form
|
|
2
|
+
|
|
3
|
+
Multi-step appointment booking form for TVS Engineering, shipped as the `<tvs-appointment-form>` web component (Vue 3 inside a shadow root) with an optional typed Vue wrapper. Consumable three ways:
|
|
4
|
+
|
|
5
|
+
1. **Web component** from any framework or plain HTML (Astro, WordPress, …)
|
|
6
|
+
2. **Vue wrapper component** (`AppointmentForm`) for Vue apps / Astro with `@astrojs/vue`
|
|
7
|
+
3. **Standalone bundle** (`dist/appointment-form.es.js`) served from S3 — the legacy embed, unchanged
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install @tvsgroup/appointment-form
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Vue 3 is a peer dependency (npm ≥7 installs it automatically). All other runtime dependencies (Pinia, intl-tel-input, js-datepicker, …) are bundled.
|
|
16
|
+
|
|
17
|
+
## Attributes / Props
|
|
18
|
+
|
|
19
|
+
| Custom-element attribute | Vue wrapper prop | Type (wrapper) | Default | Description |
|
|
20
|
+
|---|---|---|---|---|
|
|
21
|
+
| `lang` | `lang` | `string` | `'nl'` | UI language (`nl` or `en`) |
|
|
22
|
+
| `aws_url` | `awsUrl` | `string` | — (required) | Base URL of the appointment API |
|
|
23
|
+
| `vacation_mode` | `vacationMode` | `boolean` | `false` | Holiday closure: disables queue/direct lanes, shows closure banner. Attribute form accepts `"true"` / `"1"` / `"yes"` / bare attribute |
|
|
24
|
+
| `vacation_start` | `vacationStart` | `string` | `''` | First closed day, ISO `YYYY-MM-DD` |
|
|
25
|
+
| `vacation_end` | `vacationEnd` | `string` | `''` | Last closed day, ISO `YYYY-MM-DD` |
|
|
26
|
+
| `vacation_reopen` | `vacationReopen` | `string` | `''` | First day back at work, ISO `YYYY-MM-DD` |
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### Astro — web component (no framework integration needed)
|
|
31
|
+
|
|
32
|
+
```astro
|
|
33
|
+
---
|
|
34
|
+
// src/pages/afspraak.astro
|
|
35
|
+
---
|
|
36
|
+
<tvs-appointment-form
|
|
37
|
+
lang="nl"
|
|
38
|
+
aws_url="https://<api-id>.execute-api.us-east-1.amazonaws.com"
|
|
39
|
+
></tvs-appointment-form>
|
|
40
|
+
|
|
41
|
+
<script>
|
|
42
|
+
import '@tvsgroup/appointment-form/register';
|
|
43
|
+
</script>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The `/register` import defines the element on the client (idempotent, SSR-safe).
|
|
47
|
+
|
|
48
|
+
### Astro + `@astrojs/vue` — Vue wrapper
|
|
49
|
+
|
|
50
|
+
```astro
|
|
51
|
+
---
|
|
52
|
+
import { AppointmentForm } from '@tvsgroup/appointment-form';
|
|
53
|
+
---
|
|
54
|
+
<AppointmentForm
|
|
55
|
+
client:only="vue"
|
|
56
|
+
lang="nl"
|
|
57
|
+
awsUrl="https://<api-id>.execute-api.us-east-1.amazonaws.com"
|
|
58
|
+
vacationMode={false}
|
|
59
|
+
/>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`client:only="vue"` is recommended — the form is fully interactive and gains nothing from SSR.
|
|
63
|
+
|
|
64
|
+
### Plain HTML (standalone S3 bundle — legacy embed)
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<script type="module" src="https://<bucket>/appointment-form.es.js"></script>
|
|
68
|
+
|
|
69
|
+
<tvs-appointment-form
|
|
70
|
+
lang="nl"
|
|
71
|
+
aws_url="https://<api-id>.execute-api.us-east-1.amazonaws.com"
|
|
72
|
+
></tvs-appointment-form>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Host-page notes
|
|
76
|
+
|
|
77
|
+
- **Multiple instances supported.** Each `<tvs-appointment-form>` element gets its own Pinia instance (installed per element via `configureApp`), so several forms on one page keep fully independent state.
|
|
78
|
+
- **Fonts**: the component inherits the host page's font stack; the TVS look expects [Inter](https://fonts.google.com/specimen/Inter) to be loaded by the host.
|
|
79
|
+
- **Styles** are encapsulated in a shadow root — the host page's CSS (and Tailwind config) won't clash with the form's.
|
|
80
|
+
- **Runtime network**: the phone input loads the intl-tel-input utils script from jsDelivr and does a geo-IP lookup (ipapi.co) for the default country flag.
|
|
81
|
+
|
|
82
|
+
## Package layout
|
|
83
|
+
|
|
84
|
+
| Entry | File | Vue |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `@tvsgroup/appointment-form` | `dist/index.js` — `AppointmentForm`, `register()`, `TAG_NAME`, types | external (peer) |
|
|
87
|
+
| `@tvsgroup/appointment-form/register` | `dist/register.js` — side-effect: defines the element | external (peer) |
|
|
88
|
+
| standalone | `dist/appointment-form.es.js` — self-contained, self-registering | bundled |
|
|
89
|
+
|
|
90
|
+
## Development
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
npm install
|
|
94
|
+
npm run dev # dev harness (src/main.ts + App.vue)
|
|
95
|
+
npm run build # standalone bundle + npm library (dist/)
|
|
96
|
+
npm run test:unit # vitest
|
|
97
|
+
npm run e2e:dry # Playwright suite in dry-run mode
|
|
98
|
+
npm run type-check
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Deployment of the standalone bundle to S3 is handled by `.github/workflows/deploy-*.yml` + `s3-sync.sh`. Publishing to a registry is not wired up yet; `npm run build` runs automatically via `prepublishOnly`.
|
|
102
|
+
|
|
103
|
+
## Known Issues / Pending Fixes
|
|
104
|
+
|
|
105
|
+
These items are tracked here so the team has a single source of truth for outstanding tech debt. Update this list as items are resolved or new issues are discovered.
|
|
106
|
+
|
|
107
|
+
### Cross-repo: Lambda `createAppointment` (`tvs-cloud-services/services/rest/src/createAppointment.ts`)
|
|
108
|
+
|
|
109
|
+
1. **`loanCar` contract mismatch (worked around in this repo).** The Lambda types `loanCar` as `string` (scalar) and forwards it directly to Frappe's scalar fields `loan_car` and `custom_loan_car`. This webcomponent originally sent `loanCar` as `string[]` (multiselect), which caused Frappe to throw `frappe.exceptions.ValidationError: Waar voor Loan car kan geen lijst worden` (HTTP 417).
|
|
110
|
+
- **Current workaround**: `buildSubmitPayload` in `src/stores/appointmentForm.ts` serializes the array to a CSV string before fetch.
|
|
111
|
+
- **Proper fix**: keep `loanCar: string[]` end-to-end and let the Lambda do the join. The transformation belongs in the anti-corruption layer (Lambda), not the UI.
|
|
112
|
+
|
|
113
|
+
2. **Bug at `createAppointment.ts:152` (latent).** In the PUT branch (when a project already exists), the field assignment is `custom_is_loan_car: data.loanCar` — it should be `data.needsLoanCar` (boolean). Today it never fires because the flow always goes through POST, but it will break the moment a customer updates an existing appointment.
|
|
114
|
+
|
|
115
|
+
### Internal: this repo
|
|
116
|
+
|
|
117
|
+
3. **Duplicate `LOAN_CAR_LABELS` mapping.** The map `golf6 → "GOLF 6 DSG"`, `up → "VW UP!"`, `caddy → "VW CADDY"` lives in **two** places:
|
|
118
|
+
- `src/stores/appointmentForm.ts` — used by `buildSubmitPayload` for the wire payload.
|
|
119
|
+
- `src/views/steps/ConfirmView.vue` — used by `formatLoanCars` for the confirmation summary shown to the customer.
|
|
120
|
+
|
|
121
|
+
If a label changes in only one place, the customer will see one value on Confirm but a different one will land in ERPNext. Candidate to dedupe into a shared util / constants module.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export interface AppointmentFormProps {
|
|
2
|
+
/** UI language, e.g. "nl" or "en". Defaults to "nl". */
|
|
3
|
+
lang?: string;
|
|
4
|
+
/** Base URL of the appointment API (required). */
|
|
5
|
+
awsUrl: string;
|
|
6
|
+
/** Holiday closure switch. Defaults to false. */
|
|
7
|
+
vacationMode?: boolean;
|
|
8
|
+
/** First closed day (ISO `YYYY-MM-DD`). */
|
|
9
|
+
vacationStart?: string;
|
|
10
|
+
/** Last closed day (ISO `YYYY-MM-DD`). */
|
|
11
|
+
vacationEnd?: string;
|
|
12
|
+
/** First day back at work (ISO `YYYY-MM-DD`). */
|
|
13
|
+
vacationReopen?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Vue wrapper around the <tvs-appointment-form> custom element with typed,
|
|
17
|
+
* camelCase props. Rendered via a render function so consumers don't need
|
|
18
|
+
* `isCustomElement` compiler configuration.
|
|
19
|
+
*/
|
|
20
|
+
export declare const AppointmentForm: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
|
|
21
|
+
lang: {
|
|
22
|
+
type: StringConstructor;
|
|
23
|
+
default: string;
|
|
24
|
+
};
|
|
25
|
+
awsUrl: {
|
|
26
|
+
type: StringConstructor;
|
|
27
|
+
required: true;
|
|
28
|
+
};
|
|
29
|
+
vacationMode: {
|
|
30
|
+
type: BooleanConstructor;
|
|
31
|
+
default: boolean;
|
|
32
|
+
};
|
|
33
|
+
vacationStart: {
|
|
34
|
+
type: StringConstructor;
|
|
35
|
+
default: string;
|
|
36
|
+
};
|
|
37
|
+
vacationEnd: {
|
|
38
|
+
type: StringConstructor;
|
|
39
|
+
default: string;
|
|
40
|
+
};
|
|
41
|
+
vacationReopen: {
|
|
42
|
+
type: StringConstructor;
|
|
43
|
+
default: string;
|
|
44
|
+
};
|
|
45
|
+
}>, () => import('vue').VNode<import('vue').RendererNode, import('vue').RendererElement, {
|
|
46
|
+
[key: string]: any;
|
|
47
|
+
}>, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
|
|
48
|
+
lang: {
|
|
49
|
+
type: StringConstructor;
|
|
50
|
+
default: string;
|
|
51
|
+
};
|
|
52
|
+
awsUrl: {
|
|
53
|
+
type: StringConstructor;
|
|
54
|
+
required: true;
|
|
55
|
+
};
|
|
56
|
+
vacationMode: {
|
|
57
|
+
type: BooleanConstructor;
|
|
58
|
+
default: boolean;
|
|
59
|
+
};
|
|
60
|
+
vacationStart: {
|
|
61
|
+
type: StringConstructor;
|
|
62
|
+
default: string;
|
|
63
|
+
};
|
|
64
|
+
vacationEnd: {
|
|
65
|
+
type: StringConstructor;
|
|
66
|
+
default: string;
|
|
67
|
+
};
|
|
68
|
+
vacationReopen: {
|
|
69
|
+
type: StringConstructor;
|
|
70
|
+
default: string;
|
|
71
|
+
};
|
|
72
|
+
}>> & Readonly<{}>, {
|
|
73
|
+
lang: string;
|
|
74
|
+
vacationMode: boolean;
|
|
75
|
+
vacationStart: string;
|
|
76
|
+
vacationEnd: string;
|
|
77
|
+
vacationReopen: string;
|
|
78
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|