remove the feedback widget

- delete the bundle loader composable and the unused slot component
- strip the site-wide embed from app.vue, leaving the layout shell
- drop the runtime config keys, custom element tag and env entries
- remove the login opt-in and the preconnect to the feedback host
This commit is contained in:
corsacca 2026-09-22 10:27:08 +01:00
parent 0262bb4219
commit 87b835e312
8 changed files with 5 additions and 111 deletions

View file

@ -35,10 +35,6 @@ NUXT_PUBLIC_MAPBOX_TOKEN=your-mapbox-public-token
NUXT_TURNSTILE_SITE_KEY=your-turnstile-site-key
NUXT_TURNSTILE_SECRET_KEY=your-turnstile-secret-key
# Feedback widget (site-wide chat bubble). No PROJECT_ID = widget not rendered.
NUXT_PUBLIC_FEEDBACK_API_BASE=https://support.example.org
NUXT_PUBLIC_FEEDBACK_PROJECT_ID=your-feedback-project-id
# Statinator analytics (self-hosted tracking-server)
# Project must exist on the tracking-server with this site's hostname in allowed_domains.
# Set NUXT_PUBLIC_STATINATOR_ENABLED=false (or unset) to disable tracking entirely.

View file

@ -1,45 +1,5 @@
<script setup lang="ts">
// Site-wide feedback widget. It lives here (outside NuxtLayout) so it renders
// on every layout — public, admin and auth. It stays opted out by default and
// surfaces only when the visitor opts in: via ?feedback=true or by logging in.
// Both set the component's `show-feedback-widget` localStorage flag (login is
// wired in useAuth). This is the only feedback widget on the site.
// Destination comes from NUXT_PUBLIC_FEEDBACK_API_BASE / _PROJECT_ID. With no
// project id there is nowhere to post, so the widget and its bundle are skipped.
const { public: pub } = useRuntimeConfig()
const feedbackEnabled = Boolean(pub.feedbackProjectId)
if (feedbackEnabled) useFeedbackScript()
const siteFeedbackConfig = JSON.stringify({
profile: 'chat-bubble',
apiBase: pub.feedbackApiBase,
enabled: true,
showByDefault: false,
instanceId: 'fb-site',
projectId: pub.feedbackProjectId
})
</script>
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
<ClientOnly>
<div
v-if="feedbackEnabled"
class="site-feedback-widget"
>
<feedback-web-component :profile-config="siteFeedbackConfig" />
</div>
</ClientOnly>
</template>
<style scoped>
.site-feedback-widget {
position: fixed;
right: 16px;
bottom: 16px;
z-index: 2147483647;
}
</style>

View file

@ -1,22 +0,0 @@
<script setup lang="ts">
interface Props {
profileConfig: string
}
defineProps<Props>()
</script>
<template>
<div class="feedback-widget-slot">
<feedback-web-component :profile-config="profileConfig" />
</div>
</template>
<style scoped>
.feedback-widget-slot {
position: absolute;
right: 16px;
bottom: 16px;
z-index: 2147483647;
}
</style>

View file

@ -26,17 +26,6 @@ const clearCache = () => {
}
}
// Opt this browser into the feedback widget — the same `show-feedback-widget`
// flag that ?feedback=true sets. Lets logged-in users see the widget on public,
// edge-cached pages without a /api/auth/me check (we don't run one there).
const enableFeedbackWidget = () => {
if (import.meta.client) {
try {
localStorage.setItem('show-feedback-widget', 'true')
} catch {}
}
}
export const useAuth = () => {
const user = useState('auth.user', () => null as any)
const authReady = useState('auth.ready', () => false)
@ -58,7 +47,6 @@ export const useAuth = () => {
if (response.success) {
user.value = response.user
saveToCache(response.user)
enableFeedbackWidget()
const route = useRoute()
const redirectTo = route.query.redirect as string
@ -124,7 +112,6 @@ export const useAuth = () => {
const response = await $fetch('/api/auth/me') as { user: any }
user.value = response.user
saveToCache(response.user)
enableFeedbackWidget()
return response.user
} catch {
user.value = null

View file

@ -4,7 +4,6 @@
// window.mapboxgl / window.MapboxGeocoder BEFORE map-app.iife.js runs
// customElements.define('doxa-map', …) — otherwise the custom-element upgrade's
// connectedCallback calls `new mapboxgl.Map(…)` before the global exists.
// The <feedback-widget> bundle is loaded globally via nuxt.config.ts.
const MAPBOX_JS = 'https://api.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.js'
const MAPBOX_GEOCODER_JS = 'https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.0/mapbox-gl-geocoder.min.js'

View file

@ -1,24 +0,0 @@
// Idle-loads the third-party feedback web-component bundle. It's a ~95 KB
// script not needed for first paint, so we defer it until the browser is idle
// to keep it off the initial load critical path. Call this from any layout
// that renders a <feedback-web-component>; the in-DOM guard means multiple
// callers still load the bundle only once.
const FEEDBACK_SCRIPT = 'https://support.gospelambition.org/js/feedback-web-component.iife.js'
function loadFeedbackScript() {
if (document.querySelector(`script[src="${FEEDBACK_SCRIPT}"]`)) return
const script = document.createElement('script')
script.src = FEEDBACK_SCRIPT
script.async = true
document.head.appendChild(script)
}
export function useFeedbackScript() {
onMounted(() => {
if ('requestIdleCallback' in window) {
requestIdleCallback(loadFeedbackScript, { timeout: 3000 })
} else {
setTimeout(loadFeedbackScript, 2000)
}
})
}

View file

@ -8,11 +8,11 @@ const { locale, locales } = useI18n()
const { public: pub } = useRuntimeConfig()
// Warm DNS+TCP+TLS for the cross-origin services these pages hit: the prayer
// data API (people-group components) and the idle-loaded analytics + feedback
// widget scripts. Deriving from runtime config keeps these correct per env.
// data API (people-group components) and the idle-loaded analytics script.
// Deriving from runtime config keeps these correct per env.
const preconnectOrigins = computed(() => {
const origins = new Set<string>()
for (const url of [pub.prayBaseUrl, pub.statinatorUrl, pub.feedbackApiBase]) {
for (const url of [pub.prayBaseUrl, pub.statinatorUrl]) {
if (!url) continue
try {
origins.add(new URL(String(url)).origin)

View file

@ -277,8 +277,6 @@ export default defineNuxtConfig({
siteUrl: process.env.NUXT_PUBLIC_SITE_URL || '',
prayBaseUrl: process.env.NUXT_PUBLIC_PRAY_BASE_URL || 'https://pray.doxa.life',
mapboxToken: process.env.NUXT_PUBLIC_MAPBOX_TOKEN || '',
feedbackApiBase: process.env.NUXT_PUBLIC_FEEDBACK_API_BASE || 'https://support.gospelambition.org',
feedbackProjectId: process.env.NUXT_PUBLIC_FEEDBACK_PROJECT_ID || '',
statinatorUrl: process.env.NUXT_PUBLIC_STATINATOR_URL || 'https://statinator.doxa.life',
statinatorProjectId: process.env.NUXT_PUBLIC_STATINATOR_PROJECT_ID || 'doxa',
statinatorEnabled: process.env.NUXT_PUBLIC_STATINATOR_ENABLED === 'true',
@ -299,7 +297,7 @@ export default defineNuxtConfig({
vue: {
compilerOptions: {
isCustomElement: (tag: string) => ['doxa-map', 'doxa-research-map', 'feedback-web-component'].includes(tag)
isCustomElement: (tag: string) => ['doxa-map', 'doxa-research-map'].includes(tag)
}
},
@ -313,7 +311,7 @@ export default defineNuxtConfig({
vue: {
template: {
compilerOptions: {
isCustomElement: (tag: string) => ['doxa-map', 'doxa-research-map', 'feedback-web-component'].includes(tag)
isCustomElement: (tag: string) => ['doxa-map', 'doxa-research-map'].includes(tag)
}
}
}