@tekkare/auriga 0.2.231 → 0.2.233

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/dist/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "0.2.231"
7
+ "version": "0.2.233"
8
8
  }
@@ -51,6 +51,14 @@ export default defineComponent({
51
51
  type: Boolean,
52
52
  default: false
53
53
  },
54
+ // A stepline holds its value until the next change, so a series repeats
55
+ // the same number at every category in between and the chart ends up
56
+ // paved with identical labels. With this on, a point keeps its label only
57
+ // when it is the first, the last, or an actual change.
58
+ hideUnchangedLabels: {
59
+ type: Boolean,
60
+ default: false
61
+ },
54
62
  height: {
55
63
  type: String,
56
64
  default: "auto"
@@ -260,6 +268,20 @@ export default defineComponent({
260
268
  },
261
269
  formatter: function(value, opt) {
262
270
  const seriesData = props.series;
271
+ if (props.hideUnchangedLabels && value !== null) {
272
+ const data = seriesData[opt.seriesIndex]?.data ?? [];
273
+ const isLast = opt.dataPointIndex === data.length - 1;
274
+ let previous = null;
275
+ for (let i = opt.dataPointIndex - 1; i >= 0; i -= 1) {
276
+ if (data[i] !== null && data[i] !== void 0) {
277
+ previous = data[i];
278
+ break;
279
+ }
280
+ }
281
+ if (previous !== null && !isLast && previous === value) {
282
+ return "";
283
+ }
284
+ }
263
285
  if (props.hasPercentages && value !== null) {
264
286
  const formatValue = new Intl.NumberFormat("fr-FR", {
265
287
  maximumFractionDigits: 1
@@ -16,6 +16,10 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
16
16
  type: BooleanConstructor;
17
17
  default: boolean;
18
18
  };
19
+ hideUnchangedLabels: {
20
+ type: BooleanConstructor;
21
+ default: boolean;
22
+ };
19
23
  height: {
20
24
  type: StringConstructor;
21
25
  default: string;
@@ -82,6 +86,10 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
82
86
  type: BooleanConstructor;
83
87
  default: boolean;
84
88
  };
89
+ hideUnchangedLabels: {
90
+ type: BooleanConstructor;
91
+ default: boolean;
92
+ };
85
93
  height: {
86
94
  type: StringConstructor;
87
95
  default: string;
@@ -134,6 +142,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
134
142
  hideLegend: boolean;
135
143
  tooltipOptions: Record<string, any>;
136
144
  formatDate: boolean;
145
+ hideUnchangedLabels: boolean;
137
146
  dataType: string;
138
147
  hasPercentages: boolean;
139
148
  currency: string;
@@ -0,0 +1,176 @@
1
+ <template>
2
+ <div v-if="open" class="arg_terms_gate">
3
+ <div class="arg_terms_gate_card">
4
+ <span class="arg_terms_gate_eyebrow">
5
+ {{ updated ? "Terms of use updated" : "Terms of use" }}
6
+ </span>
7
+
8
+ <h2 class="arg_terms_gate_title">
9
+ {{ updated ? "Please review the new terms" : "Before you continue" }}
10
+ </h2>
11
+
12
+ <p class="arg_terms_gate_intro">
13
+ {{
14
+ updated
15
+ ? "Our terms of use have changed. Please read and accept the new version to keep using the platform."
16
+ : "Please read and accept our terms of use to start using the platform."
17
+ }}
18
+ </p>
19
+
20
+ <div class="arg_terms_gate_doc">
21
+ <div class="arg_terms_gate_doc_head">
22
+ <strong>{{ terms?.title || "Terms of use" }}</strong>
23
+ <span class="arg_terms_gate_version">
24
+ Version {{ terms?.version }}<template v-if="publishedLabel">, {{ publishedLabel }}</template>
25
+ </span>
26
+ </div>
27
+ <p v-if="terms?.summary" class="arg_terms_gate_summary">{{ terms.summary }}</p>
28
+ <a
29
+ v-if="terms?.url"
30
+ :href="terms.url"
31
+ target="_blank"
32
+ rel="noopener"
33
+ class="arg_terms_gate_link"
34
+ >
35
+ Read the full document
36
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" stroke-linecap="round" />
38
+ <path d="M15 3h6v6M10 14 21 3" stroke-linecap="round" />
39
+ </svg>
40
+ </a>
41
+ </div>
42
+
43
+ <label class="arg_terms_gate_check">
44
+ <input v-model="checked" type="checkbox" :disabled="saving" />
45
+ <span>I have read and I accept the terms of use.</span>
46
+ </label>
47
+
48
+ <p v-if="error" class="arg_terms_gate_error">{{ error }}</p>
49
+
50
+ <button
51
+ type="button"
52
+ class="arg_terms_gate_button"
53
+ :disabled="!checked || saving"
54
+ @click="accept"
55
+ >
56
+ {{ saving ? "Saving..." : "Accept and continue" }}
57
+ </button>
58
+
59
+ <button type="button" class="arg_terms_gate_decline" :disabled="saving" @click="decline">
60
+ Decline and sign out
61
+ </button>
62
+ </div>
63
+ </div>
64
+ </template>
65
+
66
+ <script>
67
+ import { computed, defineComponent, onBeforeUnmount, onMounted, ref, watch } from "vue";
68
+ import { useRoute } from "#app";
69
+ const CACHE_KEY = "terms_accepted";
70
+ const IGNORED_PREFIX = "/auth";
71
+ export default defineComponent({
72
+ name: "argTermsGate",
73
+ props: {
74
+ api: {
75
+ type: Object,
76
+ required: true
77
+ },
78
+ /** Only check once the user is authenticated. */
79
+ enabled: {
80
+ type: Boolean,
81
+ default: true
82
+ }
83
+ },
84
+ emits: ["accepted", "declined"],
85
+ setup(props, { emit }) {
86
+ const route = useRoute();
87
+ const open = ref(false);
88
+ const terms = ref(null);
89
+ const updated = ref(false);
90
+ const checked = ref(false);
91
+ const saving = ref(false);
92
+ const error = ref("");
93
+ const publishedLabel = computed(() => {
94
+ if (!terms.value?.publishedAt) return "";
95
+ const date = new Date(terms.value.publishedAt);
96
+ return Number.isNaN(date.getTime()) ? "" : date.toLocaleDateString();
97
+ });
98
+ function cache(value) {
99
+ try {
100
+ if (value) sessionStorage.setItem(CACHE_KEY, "1");
101
+ else sessionStorage.removeItem(CACHE_KEY);
102
+ } catch {
103
+ }
104
+ }
105
+ function cached() {
106
+ try {
107
+ return !!sessionStorage.getItem(CACHE_KEY);
108
+ } catch {
109
+ return false;
110
+ }
111
+ }
112
+ async function check() {
113
+ if (!import.meta.client || !props.enabled || open.value || cached()) return;
114
+ if (route.path.startsWith(IGNORED_PREFIX)) return;
115
+ try {
116
+ const status = await props.api.get();
117
+ if (status?.accepted) {
118
+ cache(true);
119
+ return;
120
+ }
121
+ if (!status?.current) return;
122
+ terms.value = status.current;
123
+ updated.value = !!status.previouslyAccepted;
124
+ open.value = true;
125
+ } catch {
126
+ }
127
+ }
128
+ async function accept() {
129
+ if (!terms.value || !checked.value) return;
130
+ error.value = "";
131
+ saving.value = true;
132
+ try {
133
+ await props.api.accept(terms.value.id);
134
+ cache(true);
135
+ open.value = false;
136
+ emit("accepted", { version: terms.value.version });
137
+ } catch (e) {
138
+ const code = e?.statusCode || e?.response?.status;
139
+ if (code === 409) {
140
+ error.value = "A newer version was just published. Reloading...";
141
+ setTimeout(() => window.location.reload(), 1200);
142
+ } else {
143
+ error.value = e?.data?.message || "Could not record your acceptance. Please try again.";
144
+ }
145
+ } finally {
146
+ saving.value = false;
147
+ }
148
+ }
149
+ function decline() {
150
+ cache(false);
151
+ emit("declined");
152
+ }
153
+ let previousOverflow = "";
154
+ watch(open, (value) => {
155
+ if (!import.meta.client) return;
156
+ if (value) {
157
+ previousOverflow = document.body.style.overflow;
158
+ document.body.style.overflow = "hidden";
159
+ } else {
160
+ document.body.style.overflow = previousOverflow;
161
+ }
162
+ });
163
+ watch(() => props.enabled, check);
164
+ watch(() => route.path, check);
165
+ onMounted(check);
166
+ onBeforeUnmount(() => {
167
+ if (import.meta.client && open.value) document.body.style.overflow = previousOverflow;
168
+ });
169
+ return { open, terms, updated, checked, saving, error, publishedLabel, accept, decline };
170
+ }
171
+ });
172
+ </script>
173
+
174
+ <style scoped>
175
+ .arg_terms_gate{align-items:center;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);background-color:rgba(0,0,0,.6);display:flex;inset:0;justify-content:center;padding:24px;position:fixed;z-index:1000}.arg_terms_gate_card{background-color:var(--white,#fff);border-radius:16px;box-shadow:0 24px 60px rgba(0,0,0,.25);font-family:inherit;max-height:calc(100vh - 48px);max-width:480px;overflow-y:auto;padding:28px;width:100%}.arg_terms_gate_eyebrow{color:var(--primary);display:inline-block;font-size:13px;font-weight:600;margin-bottom:10px}.arg_terms_gate_title{color:var(--darkest);font-size:22px;font-weight:700;margin:0 0 8px}.arg_terms_gate_intro{color:var(--dark);font-size:14px;line-height:1.6;margin:0 0 20px}.arg_terms_gate_doc{border:1px solid var(--light);border-radius:10px;display:flex;flex-direction:column;gap:10px;margin-bottom:18px;padding:16px}.arg_terms_gate_doc_head{color:var(--darkest);display:flex;flex-direction:column;font-size:15px;gap:2px}.arg_terms_gate_version{color:var(--dark);font-size:12px;font-weight:400}.arg_terms_gate_summary{color:var(--dark);font-size:14px;line-height:1.6;margin:0;white-space:pre-line}.arg_terms_gate_link{align-items:center;color:var(--primary);display:inline-flex;font-size:14px;font-weight:600;gap:6px;text-decoration:none}.arg_terms_gate_link:hover{text-decoration:underline}.arg_terms_gate_check{align-items:flex-start;color:var(--darkest);cursor:pointer;display:flex;font-size:14px;gap:10px;line-height:1.5;margin-bottom:16px}.arg_terms_gate_check input{accent-color:var(--primary);cursor:pointer;flex-shrink:0;height:18px;margin:2px 0 0;width:18px}.arg_terms_gate_error{color:#d92d20;font-size:13px;margin:0 0 12px}.arg_terms_gate_button{background-color:var(--primary);border:none;border-radius:8px;color:var(--white,#fff);cursor:pointer;font-family:inherit;font-size:15px;font-weight:600;height:48px;transition:filter .15s,transform .1s;width:100%}.arg_terms_gate_button:hover:not(:disabled){filter:brightness(1.08);transform:translateY(-1px)}.arg_terms_gate_button:disabled{cursor:not-allowed;opacity:.5}.arg_terms_gate_decline{background-color:transparent;border:none;color:var(--dark);cursor:pointer;font-family:inherit;font-size:13px;height:40px;margin-top:10px;width:100%}.arg_terms_gate_decline:hover:not(:disabled){color:var(--darkest);text-decoration:underline}
176
+ </style>
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Blocking terms of use gate.
3
+ *
4
+ * Shows a modal the user cannot dismiss until they accept the latest published
5
+ * terms of use, on first login and again after each new version. Presentation
6
+ * and flow live here; the host app only supplies its own API client, so this
7
+ * component stays free of any auth or fetch wiring.
8
+ *
9
+ * Usage (in the app, once, next to <arg-style />):
10
+ * <arg-terms-gate
11
+ * :api="termsApi"
12
+ * :enabled="status === 'authenticated'"
13
+ * @accepted="onTermsAccepted"
14
+ * @declined="signOut({ callbackUrl: '/' })"
15
+ * />
16
+ *
17
+ * The /auth pages are skipped on purpose: the first-login onboarding lives
18
+ * there, and two blocking screens at once would stack on top of each other.
19
+ *
20
+ * `api` must expose:
21
+ * get(): Promise<{ current: TermsVersion | null, accepted: boolean, previouslyAccepted: boolean }>
22
+ * accept(termsVersionId: string): Promise<unknown>
23
+ */
24
+ interface TermsVersion {
25
+ id: string;
26
+ version: string;
27
+ title: string | null;
28
+ url: string;
29
+ summary: string | null;
30
+ publishedAt: string;
31
+ }
32
+ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
33
+ api: {
34
+ type: ObjectConstructor;
35
+ required: true;
36
+ };
37
+ /** Only check once the user is authenticated. */
38
+ enabled: {
39
+ type: BooleanConstructor;
40
+ default: boolean;
41
+ };
42
+ }>, {
43
+ open: import("vue").Ref<boolean, boolean>;
44
+ terms: import("vue").Ref<{
45
+ id: string;
46
+ version: string;
47
+ title: string | null;
48
+ url: string;
49
+ summary: string | null;
50
+ publishedAt: string;
51
+ } | null, TermsVersion | {
52
+ id: string;
53
+ version: string;
54
+ title: string | null;
55
+ url: string;
56
+ summary: string | null;
57
+ publishedAt: string;
58
+ } | null>;
59
+ updated: import("vue").Ref<boolean, boolean>;
60
+ checked: import("vue").Ref<boolean, boolean>;
61
+ saving: import("vue").Ref<boolean, boolean>;
62
+ error: import("vue").Ref<string, string>;
63
+ publishedLabel: import("vue").ComputedRef<string>;
64
+ accept: () => Promise<void>;
65
+ decline: () => void;
66
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("accepted" | "declined")[], "accepted" | "declined", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
67
+ api: {
68
+ type: ObjectConstructor;
69
+ required: true;
70
+ };
71
+ /** Only check once the user is authenticated. */
72
+ enabled: {
73
+ type: BooleanConstructor;
74
+ default: boolean;
75
+ };
76
+ }>> & Readonly<{
77
+ onAccepted?: ((...args: any[]) => any) | undefined;
78
+ onDeclined?: ((...args: any[]) => any) | undefined;
79
+ }>, {
80
+ enabled: boolean;
81
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
82
+ export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/auriga",
3
- "version": "0.2.231",
3
+ "version": "0.2.233",
4
4
  "description": "Aurgia is a UI kit developped by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",