- TRA-76 - Send Button color changes implemented

- TRA-72 - Translation of privacy statement and T&C
- TRA-73 - Strange characters in Tenant Make Name
- Addition of meta information in Specialist Form Fields
This commit is contained in:
Josako
2025-09-15 17:57:13 +02:00
parent 43fd4ce9c1
commit 2b04692fab
8 changed files with 280 additions and 108 deletions

View File

@@ -0,0 +1,94 @@
<template>
<span class="consent-rich-text">
<template v-for="(node, idx) in nodes" :key="idx">
<component
v-if="node.type !== 'text'"
:is="linkTag"
:href="linkTag === 'a' ? '#' : undefined"
class="consent-link"
:aria-label="node.aria"
role="button"
tabindex="0"
@click.prevent="emitClick(node.type)"
@keydown.enter.prevent="emitClick(node.type)"
@keydown.space.prevent="emitClick(node.type)"
>{{ node.label }}</component>
<span v-else>{{ node.text }}</span>
</template>
</span>
</template>
<script>
export default {
name: 'ConsentRichText',
props: {
template: { type: String, required: true },
asButton: { type: Boolean, default: false },
ariaPrivacy: { type: String, default: 'Open privacy statement in a dialog' },
ariaTerms: { type: String, default: 'Open terms and conditions in a dialog' }
},
emits: ['open-privacy', 'open-terms'],
computed: {
linkTag() {
return this.asButton ? 'button' : 'a';
},
nodes() {
// Parse only allowed tags <privacy>...</privacy> and <terms>...</terms>
const source = (this.template || '');
// 2) parse only allowed tags <privacy>...</privacy> and <terms>...</terms>
const pattern = /<(privacy|terms)>([\s\S]*?)<\/\1>/gi;
const out = [];
let lastIndex = 0;
let match;
while ((match = pattern.exec(source)) !== null) {
const [full, tag, label] = match;
const start = match.index;
if (start > lastIndex) {
out.push({ type: 'text', text: source.slice(lastIndex, start) });
}
out.push({
type: tag, // 'privacy' | 'terms'
label: (label || '').trim(),
aria: tag === 'privacy' ? this.ariaPrivacy : this.ariaTerms
});
lastIndex = start + full.length;
}
if (lastIndex < source.length) {
out.push({ type: 'text', text: source.slice(lastIndex) });
}
return out;
}
},
methods: {
emitClick(kind) {
if (kind === 'privacy') this.$emit('open-privacy');
if (kind === 'terms') this.$emit('open-terms');
}
}
};
</script>
<style scoped>
.consent-link {
color: #007bff;
text-decoration: underline;
cursor: pointer;
transition: color 0.2s ease;
}
.consent-link:hover {
color: #0056b3;
}
.consent-link:focus {
outline: 2px solid #007bff;
outline-offset: 2px;
border-radius: 2px;
}
button.consent-link {
background: none;
border: none;
padding: 0;
color: #007bff;
text-decoration: underline;
}
</style>

View File

@@ -104,14 +104,16 @@
>
<!-- Regular checkbox label -->
<span v-if="!isConsentField" class="checkbox-text">{{ field.name }}</span>
<!-- Consent field with privacy and terms links -->
<span v-else class="checkbox-text consent-text">
{{ texts.consentPrefix }}
<a href="#" @click="openPrivacyModal" class="consent-link">{{ texts.privacyLink }}</a>
{{ texts.consentMiddle }}
<a href="#" @click="openTermsModal" class="consent-link">{{ texts.termsLink }}</a>
{{ texts.consentSuffix }}
</span>
<!-- Consent field with privacy and terms links (rich, multilingual) -->
<ConsentRichText
v-else
class="checkbox-text consent-text"
:template="texts.consentRich"
:aria-privacy="texts.ariaPrivacy || 'Open privacy statement in a dialog'"
:aria-terms="texts.ariaTerms || 'Open terms and conditions in a dialog'"
@open-privacy="openPrivacyModal"
@open-terms="openTermsModal"
/>
<span v-if="field.required" class="required" style="color: #d93025; margin-left: 2px;">*</span>
</label>
</div>
@@ -180,9 +182,11 @@
<script>
import { useComponentTranslations } from '../js/services/LanguageProvider.js';
import ConsentRichText from './ConsentRichText.vue';
export default {
name: 'FormField',
components: { ConsentRichText },
props: {
field: {
type: Object,
@@ -201,13 +205,11 @@ export default {
},
emits: ['update:modelValue', 'open-privacy-modal', 'open-terms-modal', 'keydown-enter'],
setup() {
// Consent text constants (English base)
// Consent text constants (English base) - rich template
const consentTexts = {
consentPrefix: "I agree with the",
consentMiddle: "and",
consentSuffix: "of AskEveAI",
privacyLink: "privacy statement",
termsLink: "terms and conditions"
consentRich: "I agree with the <privacy>privacy statement</privacy> and the <terms>terms and conditions</terms>",
ariaPrivacy: 'Open privacy statement in a dialog',
ariaTerms: 'Open terms and conditions in a dialog'
};
try {
@@ -230,24 +232,36 @@ export default {
},
computed: {
texts() {
// Robust consent texts that always return valid values
// Use translated texts if available and valid, otherwise use fallback
if (this.translatedTexts && typeof this.translatedTexts === 'object') {
const translated = this.translatedTexts;
// Check if translated texts have all required properties
if (translated.consentPrefix && translated.consentMiddle && translated.consentSuffix &&
translated.privacyLink && translated.termsLink) {
return translated;
}
// Validate that consentRich exists and includes both required tags; otherwise fallback to English base
const hasValidRich = (t) => t && typeof t.consentRich === 'string'
&& /<privacy>[\s\S]*?<\/privacy>/.test(t.consentRich)
&& /<terms>[\s\S]*?<\/terms>/.test(t.consentRich);
// 1) Prefer backend-provided rich string on the field's meta (already localized)
const meta = this.field && this.field.meta ? this.field.meta : (this.field.i18n || null);
if (meta && typeof meta.consentRich === 'string' && hasValidRich(meta)) {
return {
consentRich: meta.consentRich,
ariaPrivacy: meta.ariaPrivacy || this.fallbackTexts.ariaPrivacy,
ariaTerms: meta.ariaTerms || this.fallbackTexts.ariaTerms
};
}
// 2) Otherwise, use client-side translated texts if available and valid
if (this.translatedTexts && typeof this.translatedTexts === 'object' && hasValidRich(this.translatedTexts)) {
return this.translatedTexts;
}
// Fallback to English texts
return this.fallbackTexts || {
consentPrefix: "I agree with the",
consentMiddle: "and",
consentSuffix: "of AskEveAI",
privacyLink: "privacy statement",
termsLink: "terms and conditions"
// 3) Fallback to English texts (rich template)
if (this.fallbackTexts && hasValidRich(this.fallbackTexts)) {
return this.fallbackTexts;
}
// 4) Ultimate fallback (should not happen): provide a safe default
return {
consentRich: "I agree with the <privacy>privacy statement</privacy> and the <terms>terms and conditions</terms>",
ariaPrivacy: 'Open privacy statement in a dialog',
ariaTerms: 'Open terms and conditions in a dialog'
};
},
value: {
@@ -317,12 +331,10 @@ export default {
this.value = file;
}
},
openPrivacyModal(event) {
event.preventDefault();
openPrivacyModal() {
this.$emit('open-privacy-modal');
},
openTermsModal(event) {
event.preventDefault();
openTermsModal() {
this.$emit('open-terms-modal');
},

View File

@@ -9,10 +9,10 @@
<script>
// Definieer chatConfig voordat componenten worden geladen
window.chatConfig = {
explanation: `{{ customisation.sidebar_markdown|default('') }}`,
progress_tracker_insights: `{{ customisation.progress_tracker_insights|default('No Information') }}`,
form_title_display: `{{ customisation.form_title_display|default('Full Title') }}`,
conversationId: '{{ conversation_id|default("default") }}',
explanation: {{ customisation.sidebar_markdown|default('')|tojson }},
progress_tracker_insights: {{ customisation.progress_tracker_insights|default('No Information')|tojson }},
form_title_display: {{ customisation.form_title_display|default('Full Title')|tojson }},
conversationId: {{ conversation_id|default('default')|tojson }},
messages: {{ messages|tojson|safe }},
settings: {
maxMessageLength: {{ settings.max_message_length|default(2000) }},
@@ -22,15 +22,15 @@
allowReactions: {{ settings.allow_reactions|default('true')|lower }}
},
apiPrefix: '/chat-client/chat',
language: '{{ session.magic_link.specialist_args.language|default("en") }}',
language: {{ session.magic_link.specialist_args.language|default('en')|tojson }},
supportedLanguageDetails: {{ config.SUPPORTED_LANGUAGE_DETAILS|tojson|safe }},
allowedLanguages: {{ tenant_make.allowed_languages|tojson|safe }},
tenantMake: {
name: "{{ tenant_make.name|default('EveAI') }}",
logo_url: "{{ tenant_make.logo_url|default('') }}"
},
tenantMake: {{ {
'name': tenant_make.name or 'EveAI',
'logo_url': tenant_make.logo_url or ''
}|tojson|safe }},
// Environment-aware static base provided by Flask's overridden url_for
staticBase: '{{ static_url }}'
staticBase: {{ static_url|tojson }}
};
// Debug info om te controleren of chatConfig correct is ingesteld

View File

@@ -10,8 +10,8 @@
try { globalThis.staticUrl = window.staticUrl; } catch (e) {}
} else {
// Prefer runtime chatConfig.staticBase; else fallback to server-provided base or default
var serverStaticBase = '{{ static_url|default("") }}' || '';
if (!serverStaticBase) { serverStaticBase = '{{ url_for("static", filename="") }}'; }
var serverStaticBase = {{ static_url|default('')|tojson }} || '';
if (!serverStaticBase) { serverStaticBase = {{ url_for('static', filename='')|tojson }}; }
var base = (window.chatConfig && window.chatConfig.staticBase) ? window.chatConfig.staticBase : (serverStaticBase || '/static/');
var normalizedBase = String(base).replace(/\/+$/, '/');
window.staticUrl = function(path) {
@@ -34,19 +34,19 @@
window.chatConfig.supportedLanguages = [
{% for lang_code in config.SUPPORTED_LANGUAGES %}
{
code: "{{ lang_code }}",
name: "{{ config.SUPPORTED_LANGUAGE_DETAILS[config.SUPPORTED_LANGUAGES_FULL[loop.index0]]['iso 639-1'] }}",
flag: "{{ config.SUPPORTED_LANGUAGE_DETAILS[config.SUPPORTED_LANGUAGES_FULL[loop.index0]]['flag'] }}"
code: {{ lang_code|tojson }},
name: {{ config.SUPPORTED_LANGUAGE_DETAILS[config.SUPPORTED_LANGUAGES_FULL[loop.index0]]['iso 639-1']|tojson }},
flag: {{ config.SUPPORTED_LANGUAGE_DETAILS[config.SUPPORTED_LANGUAGES_FULL[loop.index0]]['flag']|tojson }}
}{% if not loop.last %},{% endif %}
{% endfor %}
];
// Voeg tenantMake toe aan chatConfig als die nog niet bestaat
if (!window.chatConfig.tenantMake) {
window.chatConfig.tenantMake = {
name: "{{ tenant_make.name|default('EveAI') }}",
logo_url: "{{ tenant_make.logo_url|default('') }}"
};
window.chatConfig.tenantMake = {{ {
'name': tenant_make.name or 'EveAI',
'logo_url': tenant_make.logo_url or ''
}|tojson|safe }};
}
console.log('Taalinstellingen toegevoegd aan chatConfig');