@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
|
@@ -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;
|