@live-change/frontend-template 0.9.198 → 0.9.200

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.
Files changed (71) hide show
  1. package/.claude/rules/live-change-backend-actions-views-triggers.md +246 -0
  2. package/.claude/rules/live-change-backend-architecture.md +126 -0
  3. package/.claude/rules/live-change-backend-event-sourcing.md +186 -0
  4. package/.claude/rules/live-change-backend-models-and-relations.md +260 -0
  5. package/.claude/rules/live-change-frontend-vue-primevue.md +317 -0
  6. package/.claude/rules/live-change-service-structure.md +89 -0
  7. package/.claude/settings.json +32 -0
  8. package/.claude/skills/create-skills-and-rules/SKILL.md +248 -0
  9. package/.claude/skills/create-skills-and-rules.md +196 -0
  10. package/.claude/skills/live-change-backend-change-triggers/SKILL.md +186 -0
  11. package/.claude/skills/live-change-design-actions-views-triggers/SKILL.md +462 -0
  12. package/.claude/skills/live-change-design-actions-views-triggers.md +190 -0
  13. package/.claude/skills/live-change-design-models-relations/SKILL.md +230 -0
  14. package/.claude/skills/live-change-design-models-relations.md +173 -0
  15. package/.claude/skills/live-change-design-service/SKILL.md +133 -0
  16. package/.claude/skills/live-change-design-service.md +132 -0
  17. package/.claude/skills/live-change-frontend-accessible-objects/SKILL.md +384 -0
  18. package/.claude/skills/live-change-frontend-accessible-objects.md +383 -0
  19. package/.claude/skills/live-change-frontend-action-buttons/SKILL.md +129 -0
  20. package/.claude/skills/live-change-frontend-action-buttons.md +128 -0
  21. package/.claude/skills/live-change-frontend-action-form/SKILL.md +149 -0
  22. package/.claude/skills/live-change-frontend-action-form.md +143 -0
  23. package/.claude/skills/live-change-frontend-analytics/SKILL.md +147 -0
  24. package/.claude/skills/live-change-frontend-analytics.md +146 -0
  25. package/.claude/skills/live-change-frontend-command-forms/SKILL.md +216 -0
  26. package/.claude/skills/live-change-frontend-command-forms.md +215 -0
  27. package/.claude/skills/live-change-frontend-data-views/SKILL.md +183 -0
  28. package/.claude/skills/live-change-frontend-data-views.md +182 -0
  29. package/.claude/skills/live-change-frontend-editor-form/SKILL.md +240 -0
  30. package/.claude/skills/live-change-frontend-editor-form.md +177 -0
  31. package/.claude/skills/live-change-frontend-locale-time/SKILL.md +172 -0
  32. package/.claude/skills/live-change-frontend-locale-time.md +171 -0
  33. package/.claude/skills/live-change-frontend-page-list-detail/SKILL.md +201 -0
  34. package/.claude/skills/live-change-frontend-page-list-detail.md +200 -0
  35. package/.claude/skills/live-change-frontend-range-list/SKILL.md +129 -0
  36. package/.claude/skills/live-change-frontend-range-list.md +128 -0
  37. package/.claude/skills/live-change-frontend-ssr-setup/SKILL.md +119 -0
  38. package/.claude/skills/live-change-frontend-ssr-setup.md +118 -0
  39. package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +290 -0
  40. package/.cursor/rules/live-change-backend-architecture.mdc +131 -0
  41. package/.cursor/rules/live-change-backend-event-sourcing.mdc +185 -0
  42. package/.cursor/rules/live-change-backend-models-and-relations.mdc +256 -0
  43. package/.cursor/rules/live-change-frontend-vue-primevue.mdc +290 -0
  44. package/.cursor/rules/live-change-service-structure.mdc +107 -0
  45. package/.cursor/skills/create-skills-and-rules.md +248 -0
  46. package/.cursor/skills/live-change-backend-change-triggers.md +186 -0
  47. package/.cursor/skills/live-change-design-actions-views-triggers.md +296 -0
  48. package/.cursor/skills/live-change-design-models-relations.md +230 -0
  49. package/.cursor/skills/live-change-design-service.md +76 -0
  50. package/.cursor/skills/live-change-frontend-accessible-objects.md +384 -0
  51. package/.cursor/skills/live-change-frontend-action-buttons.md +129 -0
  52. package/.cursor/skills/live-change-frontend-action-form.md +149 -0
  53. package/.cursor/skills/live-change-frontend-analytics.md +147 -0
  54. package/.cursor/skills/live-change-frontend-command-forms.md +216 -0
  55. package/.cursor/skills/live-change-frontend-data-views.md +183 -0
  56. package/.cursor/skills/live-change-frontend-editor-form.md +240 -0
  57. package/.cursor/skills/live-change-frontend-locale-time.md +172 -0
  58. package/.cursor/skills/live-change-frontend-page-list-detail.md +201 -0
  59. package/.cursor/skills/live-change-frontend-range-list.md +129 -0
  60. package/.cursor/skills/live-change-frontend-ssr-setup.md +120 -0
  61. package/README.md +71 -0
  62. package/front/src/router.js +2 -1
  63. package/opencode.json +10 -0
  64. package/package.json +52 -50
  65. package/server/app.config.js +35 -0
  66. package/server/services.list.js +2 -0
  67. package/.nx/workspace-data/file-map.json +0 -195
  68. package/.nx/workspace-data/nx_files.nxt +0 -0
  69. package/.nx/workspace-data/project-graph.json +0 -8
  70. package/.nx/workspace-data/project-graph.lock +0 -0
  71. package/.nx/workspace-data/source-maps.json +0 -1
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: live-change-frontend-action-form
3
+ description: Build one-shot action forms with actionData, AutoField and ActionButtons
4
+ ---
5
+
6
+ # Skill: live-change-frontend-action-form (Claude Code)
7
+
8
+ Use this skill when you build **one-shot action forms** with `actionData` and `AutoField` in a LiveChange frontend.
9
+
10
+ ## When to use
11
+
12
+ - You need a form for a command/action (not CRUD model editing).
13
+ - The form submits once, then shows a "done" state.
14
+ - You want draft auto-save while the user fills in the form.
15
+
16
+ **Editing a model record instead?** Use `editorData` (see `live-change-frontend-editor-form` skill).
17
+ **No form fields, just a button?** Use `api.command` (see `live-change-frontend-command-forms` skill).
18
+
19
+ ## Step 1 – Set up actionData
20
+
21
+ ```javascript
22
+ import { AutoField, actionData, ActionButtons } from '@live-change/frontend-auto-form'
23
+
24
+ const formData = await actionData({
25
+ service: 'blog',
26
+ action: 'publishArticle',
27
+ parameters: { article: props.articleId }, // fixed params (not shown as fields)
28
+ initialValue: { scheduleTime: null }, // initial values for editable fields
29
+ draft: true,
30
+ doneToast: 'Article published!',
31
+ onDone: (result) => {
32
+ router.push({ name: 'articles' })
33
+ },
34
+ })
35
+ ```
36
+
37
+ For reactive parameters, use `computedAsync`:
38
+
39
+ ```javascript
40
+ import { computedAsync } from '@vueuse/core'
41
+
42
+ const formData = computedAsync(() =>
43
+ actionData({
44
+ service: 'blog',
45
+ action: 'publishArticle',
46
+ parameters: { article: props.articleId },
47
+ })
48
+ )
49
+ ```
50
+
51
+ ## Step 2 – Build the template with AutoField
52
+
53
+ **Every field must show validation errors.** Always pass `:error="formData.propertiesErrors?.fieldName"` to AutoField — or use a manual `Message` if not using AutoField (see `live-change-frontend-editor-form` skill for all 3 approaches).
54
+
55
+ **Always wrap in `<form>`** with `@submit.prevent` and `@reset.prevent` handlers. `ActionButtons` uses `type="submit"` / `type="reset"` internally — without a parent `<form>`, the buttons do nothing.
56
+
57
+ Use `formData.action.properties.*` as definitions and `formData.value.*` as v-model:
58
+
59
+ ```vue
60
+ <template>
61
+ <form @submit.prevent="formData.submit()" @reset.prevent="formData.reset()">
62
+ <AutoField
63
+ :definition="formData.action.properties.scheduleTime"
64
+ v-model="formData.data.value.scheduleTime"
65
+ :error="formData.propertiesErrors?.scheduleTime"
66
+ label="Schedule time"
67
+ />
68
+ <AutoField
69
+ :definition="formData.action.properties.message"
70
+ v-model="formData.data.value.message"
71
+ :error="formData.propertiesErrors?.message"
72
+ label="Message"
73
+ />
74
+
75
+ <ActionButtons :actionFormData="formData" :resetButton="true" />
76
+ </form>
77
+ </template>
78
+ ```
79
+
80
+ ## Step 3 – Handle done state
81
+
82
+ After successful submission, `formData.done` becomes `true`:
83
+
84
+ ```vue
85
+ <template>
86
+ <div v-if="formData.done" class="text-center">
87
+ <i class="pi pi-check-circle text-4xl text-green-500 mb-2"></i>
88
+ <p>Article published successfully!</p>
89
+ </div>
90
+ <form v-else @submit.prevent="formData.submit()" @reset.prevent="formData.reset()">
91
+ <!-- fields + ActionButtons -->
92
+ </form>
93
+ </template>
94
+ ```
95
+
96
+ ## Step 4 – Manual buttons (alternative to ActionButtons)
97
+
98
+ ```vue
99
+ <div class="flex gap-2 justify-end">
100
+ <Button
101
+ type="submit"
102
+ :label="formData.submitting ? 'Executing...' : 'Execute'"
103
+ :icon="formData.submitting ? 'pi pi-spin pi-spinner' : 'pi pi-play'"
104
+ :disabled="formData.submitting"
105
+ />
106
+ <Button type="reset" label="Reset" :disabled="!formData.changed" icon="pi pi-eraser" />
107
+ </div>
108
+ ```
109
+
110
+ ## editorData vs actionData
111
+
112
+ | Aspect | `editorData` | `actionData` |
113
+ |---|---|---|
114
+ | Purpose | CRUD model editing | One-shot command form |
115
+ | Identifier | `model` + `identifiers` | `action` + `parameters` |
116
+ | Create/update | Detects automatically | Always "execute" |
117
+ | After submit | Record is saved | `done` becomes `true` |
118
+ | Definition source | `editor.model` | `formData.action` |
119
+ | `parameters` | Extra params on save | Fixed fields excluded from form |
120
+
121
+ ## Key options
122
+
123
+ | Option | Default | Description |
124
+ |---|---|---|
125
+ | `service` | required | Service name |
126
+ | `action` | required | Action name |
127
+ | `parameters` | `{}` | Fixed identifier fields (not editable) |
128
+ | `initialValue` | `{}` | Initial values for editable fields |
129
+ | `draft` | `true` | Auto-save draft while filling |
130
+ | `debounce` | `600` | Debounce delay in ms |
131
+ | `doneToast` | `"Action done"` | Toast after success |
132
+ | `onDone` | – | Callback after success |
133
+
134
+ ## Key returned properties
135
+
136
+ | Property | Description |
137
+ |---|---|
138
+ | `data` | Editable form data (recommended), it is a ref, it should be used with value - editor.data.value |
139
+ | `value` | Editable form data (obsolete), it is a ref, it should be used with value - editor.value.value |
140
+ | `action` | Action definition (`.properties.*` for `AutoField`) |
141
+ | `editableProperties` | Properties not fixed by `parameters` |
142
+ | `changed` | Form differs from initial value |
143
+ | `submit()` | Execute the action |
144
+ | `submitting` | Action call in progress |
145
+ | `done` | `true` after success |
146
+ | `reset()` | Discard draft, restore initial value |
147
+ | `propertiesErrors` | Server validation errors per property |
148
+ | `draftChanged` | Draft auto-saved but not submitted |
149
+ | `savingDraft` | Draft auto-save in progress |
@@ -0,0 +1,143 @@
1
+ ---
2
+ description: Build one-shot action forms with actionData, AutoField and ActionButtons
3
+ ---
4
+
5
+ # Skill: live-change-frontend-action-form (Claude Code)
6
+
7
+ Use this skill when you build **one-shot action forms** with `actionData` and `AutoField` in a LiveChange frontend.
8
+
9
+ ## When to use
10
+
11
+ - You need a form for a command/action (not CRUD model editing).
12
+ - The form submits once, then shows a "done" state.
13
+ - You want draft auto-save while the user fills in the form.
14
+
15
+ **Editing a model record instead?** Use `editorData` (see `live-change-frontend-editor-form` skill).
16
+ **No form fields, just a button?** Use `api.command` (see `live-change-frontend-command-forms` skill).
17
+
18
+ ## Step 1 – Set up actionData
19
+
20
+ ```javascript
21
+ import { AutoField, actionData, ActionButtons } from '@live-change/frontend-auto-form'
22
+
23
+ const formData = await actionData({
24
+ service: 'blog',
25
+ action: 'publishArticle',
26
+ parameters: { article: props.articleId }, // fixed params (not shown as fields)
27
+ initialValue: { scheduleTime: null }, // initial values for editable fields
28
+ draft: true,
29
+ doneToast: 'Article published!',
30
+ onDone: (result) => {
31
+ router.push({ name: 'articles' })
32
+ },
33
+ })
34
+ ```
35
+
36
+ For reactive parameters, use `computedAsync`:
37
+
38
+ ```javascript
39
+ import { computedAsync } from '@vueuse/core'
40
+
41
+ const formData = computedAsync(() =>
42
+ actionData({
43
+ service: 'blog',
44
+ action: 'publishArticle',
45
+ parameters: { article: props.articleId },
46
+ })
47
+ )
48
+ ```
49
+
50
+ ## Step 2 – Build the template with AutoField
51
+
52
+ Use `formData.action.properties.*` as definitions and `formData.value.*` as v-model:
53
+
54
+ ```vue
55
+ <template>
56
+ <form @submit.prevent="formData.submit()" @reset.prevent="formData.reset()">
57
+ <AutoField
58
+ :definition="formData.action.properties.scheduleTime"
59
+ v-model="formData.value.scheduleTime"
60
+ :error="formData.propertiesErrors?.scheduleTime"
61
+ label="Schedule time"
62
+ />
63
+ <AutoField
64
+ :definition="formData.action.properties.message"
65
+ v-model="formData.value.message"
66
+ :error="formData.propertiesErrors?.message"
67
+ label="Message"
68
+ />
69
+
70
+ <ActionButtons :actionFormData="formData" :resetButton="true" />
71
+ </form>
72
+ </template>
73
+ ```
74
+
75
+ ## Step 3 – Handle done state
76
+
77
+ After successful submission, `formData.done` becomes `true`:
78
+
79
+ ```vue
80
+ <template>
81
+ <div v-if="formData.done" class="text-center">
82
+ <i class="pi pi-check-circle text-4xl text-green-500 mb-2"></i>
83
+ <p>Article published successfully!</p>
84
+ </div>
85
+ <form v-else @submit.prevent="formData.submit()" @reset.prevent="formData.reset()">
86
+ <!-- fields + ActionButtons -->
87
+ </form>
88
+ </template>
89
+ ```
90
+
91
+ ## Step 4 – Manual buttons (alternative to ActionButtons)
92
+
93
+ ```vue
94
+ <div class="flex gap-2 justify-end">
95
+ <Button
96
+ type="submit"
97
+ :label="formData.submitting ? 'Executing...' : 'Execute'"
98
+ :icon="formData.submitting ? 'pi pi-spin pi-spinner' : 'pi pi-play'"
99
+ :disabled="formData.submitting"
100
+ />
101
+ <Button type="reset" label="Reset" :disabled="!formData.changed" icon="pi pi-eraser" />
102
+ </div>
103
+ ```
104
+
105
+ ## editorData vs actionData
106
+
107
+ | Aspect | `editorData` | `actionData` |
108
+ |---|---|---|
109
+ | Purpose | CRUD model editing | One-shot command form |
110
+ | Identifier | `model` + `identifiers` | `action` + `parameters` |
111
+ | Create/update | Detects automatically | Always "execute" |
112
+ | After submit | Record is saved | `done` becomes `true` |
113
+ | Definition source | `editor.model` | `formData.action` |
114
+ | `parameters` | Extra params on save | Fixed fields excluded from form |
115
+
116
+ ## Key options
117
+
118
+ | Option | Default | Description |
119
+ |---|---|---|
120
+ | `service` | required | Service name |
121
+ | `action` | required | Action name |
122
+ | `parameters` | `{}` | Fixed identifier fields (not editable) |
123
+ | `initialValue` | `{}` | Initial values for editable fields |
124
+ | `draft` | `true` | Auto-save draft while filling |
125
+ | `debounce` | `600` | Debounce delay in ms |
126
+ | `doneToast` | `"Action done"` | Toast after success |
127
+ | `onDone` | – | Callback after success |
128
+
129
+ ## Key returned properties
130
+
131
+ | Property | Description |
132
+ |---|---|
133
+ | `value` | Editable form data |
134
+ | `action` | Action definition (`.properties.*` for `AutoField`) |
135
+ | `editableProperties` | Properties not fixed by `parameters` |
136
+ | `changed` | Form differs from initial value |
137
+ | `submit()` | Execute the action |
138
+ | `submitting` | Action call in progress |
139
+ | `done` | `true` after success |
140
+ | `reset()` | Discard draft, restore initial value |
141
+ | `propertiesErrors` | Server validation errors per property |
142
+ | `draftChanged` | Draft auto-saved but not submitted |
143
+ | `savingDraft` | Draft auto-save in progress |
@@ -0,0 +1,147 @@
1
+ ---
2
+ name: live-change-frontend-analytics
3
+ description: Integrate analytics tracking with analytics.emit and provider wiring
4
+ ---
5
+
6
+ # Skill: live-change-frontend-analytics (Claude Code)
7
+
8
+ Use this skill when you add **analytics tracking** to a LiveChange frontend.
9
+
10
+ ## When to use
11
+
12
+ - You need to track user actions, page views, or custom events.
13
+ - You are integrating PostHog, GA4, or another analytics provider.
14
+ - You need consent-aware analytics.
15
+
16
+ ## Step 1 – Emit events with `analytics`
17
+
18
+ Import `analytics` from `@live-change/vue3-components` and emit events:
19
+
20
+ ```javascript
21
+ import { analytics } from '@live-change/vue3-components'
22
+
23
+ // In a component or handler:
24
+ analytics.emit('article:published', { articleId: article.id })
25
+ analytics.emit('button:clicked', { action: 'delete', target: 'article' })
26
+ ```
27
+
28
+ Standard built-in events:
29
+
30
+ | Event | Payload | When emitted |
31
+ |---|---|---|
32
+ | `pageView` | route `to` object | On route change (automatic in App.vue) |
33
+ | `user:identification` | `{ user, session, identification, contacts }` | When user identity is known |
34
+ | `consent` | `{ analytics: boolean }` | When user grants/denies tracking consent |
35
+ | `locale:change` | locale settings object | When language/locale changes |
36
+
37
+ ## Step 2 – Wire user identification in App.vue
38
+
39
+ Emit `user:identification` when the user profile is loaded:
40
+
41
+ ```javascript
42
+ import { analytics } from '@live-change/vue3-components'
43
+ import { usePath, live, useClient } from '@live-change/vue3-ssr'
44
+ import { computed, watch } from 'vue'
45
+
46
+ const client = useClient()
47
+ const path = usePath()
48
+
49
+ if(typeof window !== 'undefined') {
50
+ Promise.all([
51
+ live(path.userIdentification.myIdentification()),
52
+ ]).then(([identification]) => {
53
+ const fullIdentification = computed(() => ({
54
+ user: client.value.user,
55
+ session: client.value.session,
56
+ identification: identification.value,
57
+ }))
58
+ watch(fullIdentification, (newId) => {
59
+ analytics.emit('user:identification', newId)
60
+ }, { immediate: true })
61
+ })
62
+ }
63
+ ```
64
+
65
+ ## Step 3 – Create a provider file (e.g. PostHog)
66
+
67
+ Create a file like `src/analytics/posthog.js`:
68
+
69
+ ```javascript
70
+ import { analytics } from '@live-change/vue3-components'
71
+ import posthog from 'posthog-js'
72
+
73
+ posthog.init('phc_YOUR_KEY', {
74
+ api_host: 'https://eu.i.posthog.com',
75
+ person_profiles: 'always'
76
+ })
77
+
78
+ // Page views
79
+ analytics.on('pageView', (to) => {
80
+ posthog.register({
81
+ route: { name: to.name, params: to.params }
82
+ })
83
+ })
84
+
85
+ // User identification
86
+ analytics.on('user:identification', (identification) => {
87
+ if (!identification.user) {
88
+ posthog.reset()
89
+ return
90
+ }
91
+ posthog.identify(identification.user, {
92
+ firstName: identification.identification?.firstName,
93
+ lastName: identification.identification?.lastName,
94
+ })
95
+ })
96
+
97
+ // Consent
98
+ analytics.on('consent', (payload) => {
99
+ if (payload?.analytics === true) {
100
+ posthog.opt_in_capturing()
101
+ } else {
102
+ posthog.opt_out_capturing()
103
+ }
104
+ })
105
+
106
+ // Catch-all for custom events
107
+ const ignored = ['user:identification', 'pageView', 'consent']
108
+ analytics.on('*', (type, event) => {
109
+ if (ignored.includes(type)) return
110
+ posthog.capture(type, event)
111
+ })
112
+ ```
113
+
114
+ Import it in `App.vue`:
115
+
116
+ ```javascript
117
+ import './analytics/posthog'
118
+ ```
119
+
120
+ ## Step 4 – Emit custom events in components
121
+
122
+ ```javascript
123
+ // Track a form submission
124
+ analytics.emit('form:submitted', { formName: 'contactForm' })
125
+
126
+ // Track a feature usage
127
+ analytics.emit('feature:used', { feature: 'darkMode', enabled: true })
128
+
129
+ // Track navigation
130
+ analytics.emit('navigation:click', { target: 'pricing', source: 'navbar' })
131
+ ```
132
+
133
+ ## Step 5 – Consent handling
134
+
135
+ Emit consent events from your consent banner:
136
+
137
+ ```javascript
138
+ function acceptAnalytics() {
139
+ analytics.emit('consent', { analytics: true })
140
+ }
141
+
142
+ function rejectAnalytics() {
143
+ analytics.emit('consent', { analytics: false })
144
+ }
145
+ ```
146
+
147
+ Provider files listen for `consent` and enable/disable tracking accordingly.
@@ -0,0 +1,146 @@
1
+ ---
2
+ description: Integrate analytics tracking with analytics.emit and provider wiring
3
+ ---
4
+
5
+ # Skill: live-change-frontend-analytics (Claude Code)
6
+
7
+ Use this skill when you add **analytics tracking** to a LiveChange frontend.
8
+
9
+ ## When to use
10
+
11
+ - You need to track user actions, page views, or custom events.
12
+ - You are integrating PostHog, GA4, or another analytics provider.
13
+ - You need consent-aware analytics.
14
+
15
+ ## Step 1 – Emit events with `analytics`
16
+
17
+ Import `analytics` from `@live-change/vue3-components` and emit events:
18
+
19
+ ```javascript
20
+ import { analytics } from '@live-change/vue3-components'
21
+
22
+ // In a component or handler:
23
+ analytics.emit('article:published', { articleId: article.id })
24
+ analytics.emit('button:clicked', { action: 'delete', target: 'article' })
25
+ ```
26
+
27
+ Standard built-in events:
28
+
29
+ | Event | Payload | When emitted |
30
+ |---|---|---|
31
+ | `pageView` | route `to` object | On route change (automatic in App.vue) |
32
+ | `user:identification` | `{ user, session, identification, contacts }` | When user identity is known |
33
+ | `consent` | `{ analytics: boolean }` | When user grants/denies tracking consent |
34
+ | `locale:change` | locale settings object | When language/locale changes |
35
+
36
+ ## Step 2 – Wire user identification in App.vue
37
+
38
+ Emit `user:identification` when the user profile is loaded:
39
+
40
+ ```javascript
41
+ import { analytics } from '@live-change/vue3-components'
42
+ import { usePath, live, useClient } from '@live-change/vue3-ssr'
43
+ import { computed, watch } from 'vue'
44
+
45
+ const client = useClient()
46
+ const path = usePath()
47
+
48
+ if(typeof window !== 'undefined') {
49
+ Promise.all([
50
+ live(path.userIdentification.myIdentification()),
51
+ ]).then(([identification]) => {
52
+ const fullIdentification = computed(() => ({
53
+ user: client.value.user,
54
+ session: client.value.session,
55
+ identification: identification.value,
56
+ }))
57
+ watch(fullIdentification, (newId) => {
58
+ analytics.emit('user:identification', newId)
59
+ }, { immediate: true })
60
+ })
61
+ }
62
+ ```
63
+
64
+ ## Step 3 – Create a provider file (e.g. PostHog)
65
+
66
+ Create a file like `src/analytics/posthog.js`:
67
+
68
+ ```javascript
69
+ import { analytics } from '@live-change/vue3-components'
70
+ import posthog from 'posthog-js'
71
+
72
+ posthog.init('phc_YOUR_KEY', {
73
+ api_host: 'https://eu.i.posthog.com',
74
+ person_profiles: 'always'
75
+ })
76
+
77
+ // Page views
78
+ analytics.on('pageView', (to) => {
79
+ posthog.register({
80
+ route: { name: to.name, params: to.params }
81
+ })
82
+ })
83
+
84
+ // User identification
85
+ analytics.on('user:identification', (identification) => {
86
+ if (!identification.user) {
87
+ posthog.reset()
88
+ return
89
+ }
90
+ posthog.identify(identification.user, {
91
+ firstName: identification.identification?.firstName,
92
+ lastName: identification.identification?.lastName,
93
+ })
94
+ })
95
+
96
+ // Consent
97
+ analytics.on('consent', (payload) => {
98
+ if (payload?.analytics === true) {
99
+ posthog.opt_in_capturing()
100
+ } else {
101
+ posthog.opt_out_capturing()
102
+ }
103
+ })
104
+
105
+ // Catch-all for custom events
106
+ const ignored = ['user:identification', 'pageView', 'consent']
107
+ analytics.on('*', (type, event) => {
108
+ if (ignored.includes(type)) return
109
+ posthog.capture(type, event)
110
+ })
111
+ ```
112
+
113
+ Import it in `App.vue`:
114
+
115
+ ```javascript
116
+ import './analytics/posthog'
117
+ ```
118
+
119
+ ## Step 4 – Emit custom events in components
120
+
121
+ ```javascript
122
+ // Track a form submission
123
+ analytics.emit('form:submitted', { formName: 'contactForm' })
124
+
125
+ // Track a feature usage
126
+ analytics.emit('feature:used', { feature: 'darkMode', enabled: true })
127
+
128
+ // Track navigation
129
+ analytics.emit('navigation:click', { target: 'pricing', source: 'navbar' })
130
+ ```
131
+
132
+ ## Step 5 – Consent handling
133
+
134
+ Emit consent events from your consent banner:
135
+
136
+ ```javascript
137
+ function acceptAnalytics() {
138
+ analytics.emit('consent', { analytics: true })
139
+ }
140
+
141
+ function rejectAnalytics() {
142
+ analytics.emit('consent', { analytics: false })
143
+ }
144
+ ```
145
+
146
+ Provider files listen for `consent` and enable/disable tracking accordingly.