cic-kit 0.0.30 → 0.0.32
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 +1 -0
- package/dist/cic-kit.css +1 -1
- package/dist/cmp/ModalDev/ModalDev.vue.d.ts +1 -0
- package/dist/cmp/ModalDev/view/AppConfigViewDev.vue.d.ts +1 -0
- package/dist/index.cjs +43 -43
- package/dist/index.mjs +9063 -9050
- package/docs/ContainerSideTabs.md +131 -0
- package/docs/FieldColorTag.md +122 -0
- package/docs/FieldTiptap.md +59 -0
- package/docs/pushMsg.md +200 -0
- package/docs/useStoreWatch.md +275 -0
- package/package.json +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#### ContainerSideTabs
|
|
2
|
+
|
|
3
|
+
Layout con sidebar tab desktop + menu offcanvas mobile, pensato per cambiare contenuto per sezione.
|
|
4
|
+
|
|
5
|
+
Componenti collegati:
|
|
6
|
+
- `ContainerSideTabs.vue`: orchestratore (stato tab selezionato, sync route, contenuto)
|
|
7
|
+
- `SideTabs.ts`: tipi (`SideTabComponent`, `SideTabGroup`, `SideTabs`)
|
|
8
|
+
- `SideTabsList.vue`: render lista tab e gruppi
|
|
9
|
+
- `SideTabCmp.vue`: singolo item tab cliccabile
|
|
10
|
+
|
|
11
|
+
| prop | default | type | utilizzo |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| `tabs` | `-` | `SideTabs` | lista tab o gruppi (obbligatoria) |
|
|
14
|
+
| `modelValue` | `""` | `string` | nome tab attivo (`v-model`) |
|
|
15
|
+
| `color` | `-` | `string` | colore icona tab attivo |
|
|
16
|
+
| `hoverBgOpacity` | `5` | `number` | opacita hover/active background con `color-mix` |
|
|
17
|
+
| `contentClass` | `"px-3"` | `string` | classi area contenuto |
|
|
18
|
+
| `scrollToTopOnSelect` | `true` | `boolean` | scroll top area contenuto al cambio tab |
|
|
19
|
+
| `scrollToTopSmooth` | `true` | `boolean` | scroll top smooth o instant |
|
|
20
|
+
| `trackRoute` | `true` | `boolean` | sincronizza tab con query route |
|
|
21
|
+
| `routeQueryKey` | `"tab"` | `string` | chiave query usata per tab (`?tab=...`) |
|
|
22
|
+
| `routeUseReplace` | `false` | `boolean` | usa `router.replace` invece di `push` |
|
|
23
|
+
| `sidebarMinWidth` | `-` | `number` | larghezza minima sidebar desktop (px) |
|
|
24
|
+
|
|
25
|
+
| evento | payload | quando |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `update:modelValue` | `string` | quando selezioni tab o cambia query route |
|
|
28
|
+
| `select` | `SideTabComponent` | quando utente seleziona un tab |
|
|
29
|
+
|
|
30
|
+
### Tipi (`SideTabs.ts`)
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
export interface SideTabComponent {
|
|
34
|
+
name: string;
|
|
35
|
+
icon?: string;
|
|
36
|
+
label?: string | Component;
|
|
37
|
+
component?: Component;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SideTabGroup {
|
|
41
|
+
icon?: string;
|
|
42
|
+
label: string | Component;
|
|
43
|
+
subTabs: SideTabComponent[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type SideTabs = (SideTabGroup | SideTabComponent)[];
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Regole pratiche:
|
|
50
|
+
- `name` deve essere univoco in tutta la struttura
|
|
51
|
+
- se un tab non ha `component`, il container mostra fallback "Nessun componente disponibile"
|
|
52
|
+
- puoi usare `label` stringa o componente Vue custom
|
|
53
|
+
|
|
54
|
+
### Come funziona internamente
|
|
55
|
+
|
|
56
|
+
- Desktop (`lg+`): sidebar fissa con `SideTabsList`
|
|
57
|
+
- Mobile (`<lg`): bottone menu + `Offcanvas` con la stessa lista tab
|
|
58
|
+
- Se `trackRoute = true`, legge `route.query[routeQueryKey]` e aggiorna `modelValue`
|
|
59
|
+
- Quando selezioni tab aggiorna query con `push` o `replace`
|
|
60
|
+
- Dopo selezione, se attivo, fa scroll top del contenuto interno
|
|
61
|
+
|
|
62
|
+
### Uso consigliato con file dedicato (tabs config)
|
|
63
|
+
|
|
64
|
+
#### 1) Crea file config tab
|
|
65
|
+
|
|
66
|
+
`src/defaultViews/settings/settingsTabs.ts`
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import type { SideTabs } from "cic-kit";
|
|
70
|
+
import GeneralTab from "./tabs/GeneralTab.vue";
|
|
71
|
+
import SecurityTab from "./tabs/SecurityTab.vue";
|
|
72
|
+
import ProfileTab from "./tabs/ProfileTab.vue";
|
|
73
|
+
|
|
74
|
+
export const settingsTabs: SideTabs = [
|
|
75
|
+
{
|
|
76
|
+
icon: "settings",
|
|
77
|
+
label: "Impostazioni",
|
|
78
|
+
subTabs: [
|
|
79
|
+
{ name: "general", label: "Generali", icon: "tune", component: GeneralTab },
|
|
80
|
+
{ name: "security", label: "Sicurezza", icon: "lock", component: SecurityTab },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
{ name: "profile", label: "Profilo", icon: "person", component: ProfileTab },
|
|
84
|
+
];
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### 2) Usalo nella view
|
|
88
|
+
|
|
89
|
+
`src/defaultViews/settings/SettingsView.vue`
|
|
90
|
+
|
|
91
|
+
```vue
|
|
92
|
+
<script setup lang="ts">
|
|
93
|
+
import { ref } from "vue";
|
|
94
|
+
import { ContainerSideTabs } from "cic-kit";
|
|
95
|
+
import { settingsTabs } from "./settingsTabs";
|
|
96
|
+
|
|
97
|
+
const activeTab = ref("general");
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<template>
|
|
101
|
+
<ContainerSideTabs
|
|
102
|
+
v-model="activeTab"
|
|
103
|
+
:tabs="settingsTabs"
|
|
104
|
+
color="#0ea5e9"
|
|
105
|
+
:sidebar-min-width="260"
|
|
106
|
+
route-query-key="section"
|
|
107
|
+
/>
|
|
108
|
+
</template>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Con `trackRoute=true` (default) l'URL diventa, ad esempio:
|
|
112
|
+
- `...?section=general`
|
|
113
|
+
- `...?section=security`
|
|
114
|
+
|
|
115
|
+
### Uso con slot (contenuto custom)
|
|
116
|
+
|
|
117
|
+
Se passi lo slot di default, gestisci tu il contenuto e il render automatico `selectedTab.component` non viene usato.
|
|
118
|
+
|
|
119
|
+
```vue
|
|
120
|
+
<ContainerSideTabs v-model="activeTab" :tabs="settingsTabs">
|
|
121
|
+
<div v-if="activeTab === 'general'">...</div>
|
|
122
|
+
<div v-else-if="activeTab === 'security'">...</div>
|
|
123
|
+
</ContainerSideTabs>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Note utili
|
|
127
|
+
|
|
128
|
+
- Se `modelValue` e vuoto e non c'e query valida, nessun tab e selezionato.
|
|
129
|
+
- Per apertura diretta da URL, usa query coerente col `name` del tab.
|
|
130
|
+
- Se non vuoi sporcare history browser ad ogni click tab, imposta `routeUseReplace`.
|
|
131
|
+
- Per disattivare del tutto sync route, imposta `trackRoute=false`.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#### FieldColorTag
|
|
2
|
+
|
|
3
|
+
Campo tag colorati integrato con `vee-validate` (`useField`) e output array via `v-model`.
|
|
4
|
+
|
|
5
|
+
Tipi principali:
|
|
6
|
+
- `ColorTag`: `{ label: string; color: string }`
|
|
7
|
+
- `OnChangeColorTag`: `"add" | "edit" | "delete"`
|
|
8
|
+
|
|
9
|
+
| prop | default | type | utilizzo |
|
|
10
|
+
| --- | --- | --- | --- |
|
|
11
|
+
| `name` | `-` | `string` | nome campo `vee-validate` |
|
|
12
|
+
| `modelValue` | `[]` | `ColorTag[]` | valore controllato |
|
|
13
|
+
| `suggestions` | `[]` | `ColorTag[]` | lista suggerimenti filtrabile |
|
|
14
|
+
| `label` | `false` | `string \| boolean \| Component` | etichetta (`true` usa `name`) o componente custom |
|
|
15
|
+
| `placeholder` | `"Aggiungi tag..."` | `string` | placeholder input |
|
|
16
|
+
| `showErrors` | `true` | `boolean` | mostra errore validazione |
|
|
17
|
+
| `allowDuplicates` | `false` | `boolean` | consente tag con stesso `label` |
|
|
18
|
+
| `caseSensitive` | `false` | `boolean` | confronto duplicati/suggerimenti case-sensitive |
|
|
19
|
+
| `maxTags` | `null` | `number \| null` | massimo numero tag inseribili |
|
|
20
|
+
| `defaultColor` | `"#1985a1"` | `string` | colore default per nuovi tag |
|
|
21
|
+
| `class` | `-` | `any` | classi extra wrapper input |
|
|
22
|
+
| `style` | `-` | `any` | style inline wrapper input |
|
|
23
|
+
| `classLabel` | `-` | `any` | classi extra label |
|
|
24
|
+
| `required` | `false` | `boolean` | mostra asterisco nel label |
|
|
25
|
+
|
|
26
|
+
| evento | payload | quando |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `update:modelValue` | `ColorTag[]` | a ogni modifica valore |
|
|
29
|
+
| `change` | `(type: OnChangeColorTag, changedTag: ColorTag, value: ColorTag[])` | add/edit/delete |
|
|
30
|
+
| `add` | `ColorTag` | quando aggiungi un tag |
|
|
31
|
+
| `remove` | `ColorTag` | quando rimuovi un tag |
|
|
32
|
+
| `newSuggestion` | `ColorTag` | quando aggiungi un tag non presente in `suggestions` |
|
|
33
|
+
| `deleteSuggestion` | `ColorTag` | click su elimina suggerimento |
|
|
34
|
+
|
|
35
|
+
Comportamento valore:
|
|
36
|
+
- normalizza sempre con `toColorTagArray`
|
|
37
|
+
- ogni colore viene validato con `normalizeColorTagColor`
|
|
38
|
+
- se il colore non e valido, usa `COLOR_TAG_DEFAULT_COLOR` (`#1985a1`)
|
|
39
|
+
- confronto duplicati su `label` (trim), con o senza case-sensitive in base a `caseSensitive`
|
|
40
|
+
|
|
41
|
+
Comportamento input/tastiera:
|
|
42
|
+
- `Enter`, `,`, `Tab`: aggiunge tag corrente (o suggestion evidenziata)
|
|
43
|
+
- `ArrowDown` / `ArrowUp`: naviga suggerimenti
|
|
44
|
+
- `Escape`: chiude dropdown
|
|
45
|
+
- `Backspace` su input vuoto: rimuove ultimo tag
|
|
46
|
+
- `blur`: se c'e testo residuo, prova ad aggiungerlo automaticamente
|
|
47
|
+
|
|
48
|
+
Suggerimenti:
|
|
49
|
+
- dropdown visibile solo se input in focus, query non vuota e risultati presenti
|
|
50
|
+
- filtro per `includes` sul `label`
|
|
51
|
+
- se `allowDuplicates = false`, esclude suggestion gia presenti
|
|
52
|
+
- il bottone `x` per eliminare suggestion appare solo se ascolti `@deleteSuggestion`
|
|
53
|
+
|
|
54
|
+
Limite tag:
|
|
55
|
+
- con `maxTags`, oltre soglia blocca nuove aggiunte
|
|
56
|
+
- mostra messaggio: `Hai raggiunto il numero massimo di tag (...)`
|
|
57
|
+
|
|
58
|
+
```vue
|
|
59
|
+
<script setup lang="ts">
|
|
60
|
+
import { ref } from "vue";
|
|
61
|
+
import { FieldColorTag, type ColorTag } from "cic-kit";
|
|
62
|
+
|
|
63
|
+
const tags = ref<ColorTag[]>([
|
|
64
|
+
{ label: "Urgente", color: "#ef4444" },
|
|
65
|
+
]);
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<template>
|
|
69
|
+
<FieldColorTag
|
|
70
|
+
name="tags"
|
|
71
|
+
v-model="tags"
|
|
72
|
+
label="Tag"
|
|
73
|
+
placeholder="Scrivi e premi invio..."
|
|
74
|
+
/>
|
|
75
|
+
</template>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Esempio con suggerimenti, deduplica e limite:
|
|
79
|
+
|
|
80
|
+
```vue
|
|
81
|
+
<script setup lang="ts">
|
|
82
|
+
import { ref } from "vue";
|
|
83
|
+
import { FieldColorTag, type ColorTag } from "cic-kit";
|
|
84
|
+
|
|
85
|
+
const tags = ref<ColorTag[]>([]);
|
|
86
|
+
const suggestions = ref<ColorTag[]>([
|
|
87
|
+
{ label: "Backend", color: "#0ea5e9" },
|
|
88
|
+
{ label: "Frontend", color: "#8b5cf6" },
|
|
89
|
+
{ label: "Bug", color: "#ef4444" },
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
function onNewSuggestion(tag: ColorTag) {
|
|
93
|
+
// opzionale: persisti subito il nuovo suggerimento
|
|
94
|
+
suggestions.value.push(tag);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function onDeleteSuggestion(tag: ColorTag) {
|
|
98
|
+
suggestions.value = suggestions.value.filter((s) => s.label !== tag.label);
|
|
99
|
+
}
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<template>
|
|
103
|
+
<FieldColorTag
|
|
104
|
+
name="tags"
|
|
105
|
+
v-model="tags"
|
|
106
|
+
:suggestions="suggestions"
|
|
107
|
+
:allow-duplicates="false"
|
|
108
|
+
:case-sensitive="false"
|
|
109
|
+
:max-tags="8"
|
|
110
|
+
@newSuggestion="onNewSuggestion"
|
|
111
|
+
@deleteSuggestion="onDeleteSuggestion"
|
|
112
|
+
/>
|
|
113
|
+
</template>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Utility collegate (export da `cic-kit`):
|
|
117
|
+
- `toColorTag(value)`
|
|
118
|
+
- `toColorTagArray(value)`
|
|
119
|
+
- `normalizeColorTagColor(value)`
|
|
120
|
+
- `normalizeColorTagLabel(value)`
|
|
121
|
+
- `isSameColorTagArray(a, b)`
|
|
122
|
+
- `COLOR_TAG_DEFAULT_COLOR`
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#### FieldTiptap
|
|
2
|
+
|
|
3
|
+
Editor rich-text Tiptap integrato con `vee-validate` (`useField`) e output HTML via `v-model`.
|
|
4
|
+
|
|
5
|
+
| prop | default | type | utilizzo |
|
|
6
|
+
| --- | --- | --- | --- |
|
|
7
|
+
| `name` | `-` | `string` | nome campo `vee-validate` |
|
|
8
|
+
| `modelValue` | `""` | `string` | contenuto HTML controllato |
|
|
9
|
+
| `label` | `false` | `string \| boolean \| Component` | etichetta (`true` usa `name`) o componente custom |
|
|
10
|
+
| `placeholder` | `"Scrivi qui..."` | `string` | placeholder dell'editor |
|
|
11
|
+
| `toolbarStickyOn` | `false` | `FieldTiptapToolbarSticky` | toolbar sticky: `"top"`, `"bottom"` o disattivata |
|
|
12
|
+
| `showErrors` | `true` | `boolean` | mostra errore validazione |
|
|
13
|
+
| `required` | `false` | `boolean` | mostra asterisco nel label |
|
|
14
|
+
| `class` | `-` | `any` | classi extra sul wrapper |
|
|
15
|
+
| `style` | `-` | `any` | style inline sul wrapper |
|
|
16
|
+
| `classLabel` | `-` | `any` | classi extra sul label |
|
|
17
|
+
|
|
18
|
+
`FieldTiptapToolbarSticky`:
|
|
19
|
+
- `"top" \| "bottom" \| false`
|
|
20
|
+
|
|
21
|
+
| evento | payload | quando |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| `update:modelValue` | `string` | a ogni update dell'editor |
|
|
24
|
+
|
|
25
|
+
Comportamento valore:
|
|
26
|
+
- il valore e normalizzato con `normalizeTiptapHtml`
|
|
27
|
+
- se vuoto/null, viene convertito in `"<p></p>"`
|
|
28
|
+
|
|
29
|
+
Toolbar inclusa:
|
|
30
|
+
- blocco: paragrafo, `h1`, `h2`, `h3`
|
|
31
|
+
- stile: bold, italic, inline code
|
|
32
|
+
- liste: puntata, numerata
|
|
33
|
+
- extra: code block, link, undo, redo
|
|
34
|
+
|
|
35
|
+
```vue
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
import { ref } from "vue";
|
|
38
|
+
import { Form } from "vee-validate";
|
|
39
|
+
import { FieldTiptap } from "cic-kit";
|
|
40
|
+
|
|
41
|
+
const bio = ref("<p>Ciao!</p>");
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<Form>
|
|
46
|
+
<FieldTiptap
|
|
47
|
+
name="bio"
|
|
48
|
+
v-model="bio"
|
|
49
|
+
label="Bio"
|
|
50
|
+
placeholder="Scrivi una breve presentazione..."
|
|
51
|
+
:toolbar-sticky-on="'top'"
|
|
52
|
+
required
|
|
53
|
+
/>
|
|
54
|
+
</Form>
|
|
55
|
+
</template>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Utility collegata:
|
|
59
|
+
- `normalizeTiptapHtml(value)` da `cic-kit`
|
package/docs/pushMsg.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#### pushMsg (Notifiche Push)
|
|
2
|
+
|
|
3
|
+
Guida completa per usare le notifiche push nel progetto con `cic-kit`.
|
|
4
|
+
|
|
5
|
+
`pushMsg` espone tutto il necessario per:
|
|
6
|
+
- chiedere i permessi browser
|
|
7
|
+
- leggere/registrare il token FCM del device
|
|
8
|
+
- inviare notifiche locali
|
|
9
|
+
- inviare notifiche remote tramite Cloud Function
|
|
10
|
+
|
|
11
|
+
### Prerequisiti obbligatori
|
|
12
|
+
|
|
13
|
+
1. Firebase inizializzato con Messaging:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { setupFirebase } from "cic-kit";
|
|
17
|
+
import { firebaseConfig, VAPID_PUBLIC_KEY } from "./firebase-config";
|
|
18
|
+
|
|
19
|
+
setupFirebase(firebaseConfig, VAPID_PUBLIC_KEY);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Nota: in alternativa puoi assegnare manualmente:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { pushMsg } from "cic-kit";
|
|
26
|
+
pushMsg.vapidPublicKey = VAPID_PUBLIC_KEY;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
2. Auth inizializzata (serve per salvare il token su `_Auth.user`):
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { initAuth, _CurrentUser } from "cic-kit";
|
|
33
|
+
|
|
34
|
+
const Auth = initAuth(_CurrentUser);
|
|
35
|
+
Auth.checkAuth();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
3. Service Worker registrato (fondamentale per FCM token e notifiche background):
|
|
39
|
+
|
|
40
|
+
```vue
|
|
41
|
+
<script setup lang="ts">
|
|
42
|
+
import { RegisterSW } from "cic-kit";
|
|
43
|
+
import { registerSW } from "virtual:pwa-register";
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<RegisterSW :registerSW="registerSW" />
|
|
48
|
+
</template>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
4. Per invio remoto: Cloud Function `sendUserPush` deployata (chiamata da `pushMsg.sendTo`).
|
|
52
|
+
|
|
53
|
+
### API disponibile
|
|
54
|
+
|
|
55
|
+
| elemento | type | cosa fa |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `pushMsg.permission` | `NotificationPermission` | stato permesso (`granted`, `default`, `denied`) |
|
|
58
|
+
| `pushMsg.needToAskPermission` | `boolean` | `true` se devi ancora chiedere permesso |
|
|
59
|
+
| `pushMsg.askPermission()` | `() => Promise<NotificationPermission>` | chiede permesso; se granted prova anche a registrare il token |
|
|
60
|
+
| `pushMsg.send(content)` | `(string \| PushMsgContent) => Promise<void>` | invia notifica locale sul device corrente |
|
|
61
|
+
| `pushMsg.sendTo(uid, content)` | `(uid: string, content: string \| PushMsgContent) => Promise<boolean>` | invia notifica remota a un utente (Cloud Function) |
|
|
62
|
+
| `pushMsg.getCurrentFcmToken()` | `() => Promise<string \| null>` | prende token FCM del device |
|
|
63
|
+
| `pushMsg.registerFcmToken()` | `() => Promise<string \| null>` | registra token su utente corrente |
|
|
64
|
+
| `pushMsg.isThisDeviceTokenRegistered()` | `() => Promise<boolean>` | verifica se il token del device e gia su `_Auth.user.fcmTokens` |
|
|
65
|
+
| `pushMsg.removeThisDeviceToken()` | `() => Promise<boolean>` | rimuove token da browser e utente |
|
|
66
|
+
|
|
67
|
+
### Payload notifica (`PushMsgContent`)
|
|
68
|
+
|
|
69
|
+
`PushMsgContent` estende `NotificationOptions` e richiede `title`.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
type PushMsgContent = NotificationOptions & {
|
|
73
|
+
title: string;
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Default automatici applicati internamente:
|
|
78
|
+
- `icon`: `"/img/logo/pwa.png"`
|
|
79
|
+
- `tag`: `default-YYYY-MM-DD`
|
|
80
|
+
- `data.url`: `"/"`
|
|
81
|
+
|
|
82
|
+
Esempio:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
{
|
|
86
|
+
title: "Nuovo ordine",
|
|
87
|
+
body: "Hai ricevuto un nuovo ordine",
|
|
88
|
+
icon: "/img/logo/pwa.png",
|
|
89
|
+
data: { url: "/orders/123" },
|
|
90
|
+
silent: false
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Flusso consigliato (device corrente)
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { pushMsg } from "cic-kit";
|
|
98
|
+
|
|
99
|
+
await pushMsg.askPermission();
|
|
100
|
+
const token = await pushMsg.registerFcmToken();
|
|
101
|
+
const registered = await pushMsg.isThisDeviceTokenRegistered();
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Disattivazione:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
await pushMsg.removeThisDeviceToken();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Inviare una notifica locale
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
await pushMsg.send({
|
|
114
|
+
title: "Notifica locale",
|
|
115
|
+
body: "Questo e un test sul device corrente",
|
|
116
|
+
data: { url: "/demo/push" },
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Comportamento:
|
|
121
|
+
- se permesso non concesso, `send()` chiama prima `askPermission()`
|
|
122
|
+
- se esiste `ServiceWorkerRegistration`, usa `showNotification(...)`
|
|
123
|
+
- altrimenti fallback a `new Notification(...)`
|
|
124
|
+
|
|
125
|
+
### Inviare una notifica remota (a un altro utente)
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const ok = await pushMsg.sendTo("UID_DESTINATARIO", {
|
|
129
|
+
title: "Notifica remota",
|
|
130
|
+
body: "Messaggio inviato dal pannello admin",
|
|
131
|
+
data: { url: "/orders" },
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`sendTo` ritorna:
|
|
136
|
+
- `true`: invio richiesto con successo alla Cloud Function
|
|
137
|
+
- `false`: errore (Firebase non init, funzione assente/errore, ecc.)
|
|
138
|
+
|
|
139
|
+
### Esempio componente completo
|
|
140
|
+
|
|
141
|
+
```vue
|
|
142
|
+
<script setup lang="ts">
|
|
143
|
+
import { computed, ref } from "vue";
|
|
144
|
+
import { _Auth, pushMsg } from "cic-kit";
|
|
145
|
+
|
|
146
|
+
const token = ref<string | null>(null);
|
|
147
|
+
const registered = ref(false);
|
|
148
|
+
const toUid = ref(_Auth?.uid ?? "");
|
|
149
|
+
|
|
150
|
+
const permission = computed(() => pushMsg.permission);
|
|
151
|
+
|
|
152
|
+
async function enableDevice() {
|
|
153
|
+
await pushMsg.askPermission();
|
|
154
|
+
token.value = await pushMsg.registerFcmToken();
|
|
155
|
+
registered.value = await pushMsg.isThisDeviceTokenRegistered();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function disableDevice() {
|
|
159
|
+
await pushMsg.removeThisDeviceToken();
|
|
160
|
+
token.value = await pushMsg.getCurrentFcmToken();
|
|
161
|
+
registered.value = await pushMsg.isThisDeviceTokenRegistered();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function sendRemote() {
|
|
165
|
+
if (!toUid.value) return;
|
|
166
|
+
await pushMsg.sendTo(toUid.value, {
|
|
167
|
+
title: "Test push",
|
|
168
|
+
body: "Ciao da cic-kit",
|
|
169
|
+
data: { url: "/" },
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
</script>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Errori comuni e soluzione
|
|
176
|
+
|
|
177
|
+
- `Firebase non inizializzato (...)`
|
|
178
|
+
- chiama `setupFirebase(...)` prima di usare `pushMsg`
|
|
179
|
+
|
|
180
|
+
- `ServiceWorker non registrato`
|
|
181
|
+
- monta `<RegisterSW :registerSW="registerSW" />`
|
|
182
|
+
- verifica che la registrazione avvenga davvero in produzione/dev
|
|
183
|
+
|
|
184
|
+
- `vapidPublicKey mancante`
|
|
185
|
+
- passa la VAPID key a `setupFirebase(config, vapidKey)` o `pushMsg.vapidPublicKey = ...`
|
|
186
|
+
|
|
187
|
+
- permesso `denied`
|
|
188
|
+
- il browser non mostrera piu il popup: devi riabilitare dalle impostazioni sito/browser
|
|
189
|
+
|
|
190
|
+
- `Devi essere loggato per attivare le notifiche su questo dispositivo`
|
|
191
|
+
- `registerFcmToken()` ha ottenuto il token ma non puo salvarlo in `fcmTokens` senza utente loggato
|
|
192
|
+
|
|
193
|
+
- `Errore invio notifica` in `sendTo`
|
|
194
|
+
- controlla Cloud Function `sendUserPush`, permessi IAM/rules e token destinatario presente
|
|
195
|
+
|
|
196
|
+
### Note importanti
|
|
197
|
+
|
|
198
|
+
- I metodi con parametro `_legacyCurrentUser` sono mantenuti per compatibilita ma oggi ignorati.
|
|
199
|
+
- Per ricezione in background e click su notifica, il Service Worker deve gestire eventi `push` e `notificationclick`.
|
|
200
|
+
- La persistenza token usa i metodi utente `addFcmToken`, `removeFcmToken`, `hasFcmToken` (gia presenti in `_CurrentUser`).
|