webapp: harden mapbox/chatwoot runtime config
Some checks failed
Build Docker Image / build (push) Failing after 13m29s
Some checks failed
Build Docker Image / build (push) Failing after 13m29s
This commit is contained in:
@@ -21,16 +21,21 @@
|
||||
<label class="label">
|
||||
<span class="label-text">{{ t('profileAddresses.form.address.label') }}</span>
|
||||
</label>
|
||||
<div ref="searchBoxContainer" class="w-full" />
|
||||
<input
|
||||
v-if="addressData.address"
|
||||
type="hidden"
|
||||
:value="addressData.address"
|
||||
<div
|
||||
v-if="hasMapboxToken"
|
||||
ref="searchBoxContainer"
|
||||
class="w-full"
|
||||
/>
|
||||
<Input
|
||||
v-else
|
||||
v-model="addressData.address"
|
||||
:placeholder="t('profileAddresses.form.address.placeholder')"
|
||||
/>
|
||||
<input v-if="addressData.address && hasMapboxToken" type="hidden" :value="addressData.address" />
|
||||
</div>
|
||||
|
||||
<!-- Mapbox map for selecting coordinates -->
|
||||
<div class="form-control">
|
||||
<div v-if="hasMapboxToken" class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">{{ t('profileAddresses.form.mapLabel') }}</span>
|
||||
</label>
|
||||
@@ -61,9 +66,12 @@
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div v-else class="alert alert-warning text-sm">
|
||||
Mapbox is not configured. Enter address manually.
|
||||
</div>
|
||||
|
||||
<Stack direction="row" gap="3">
|
||||
<Button @click="updateAddress" :disabled="isSaving || !addressData.latitude">
|
||||
<Button @click="updateAddress" :disabled="isSaving || (hasMapboxToken && !addressData.latitude)">
|
||||
{{ isSaving ? t('profileAddresses.form.updating') : t('profileAddresses.form.update') }}
|
||||
</Button>
|
||||
<Button variant="outline" :as="NuxtLink" :to="localePath('/clientarea/addresses')">
|
||||
@@ -108,6 +116,8 @@ const { execute, mutate } = useGraphQL()
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
const config = useRuntimeConfig()
|
||||
const mapboxAccessToken = computed(() => String(config.public.mapboxAccessToken || '').trim())
|
||||
const hasMapboxToken = computed(() => mapboxAccessToken.value.length > 0)
|
||||
|
||||
const uuid = computed(() => route.params.uuid as string)
|
||||
|
||||
@@ -161,10 +171,13 @@ const onMapCreated = (map: MapboxMapType) => {
|
||||
|
||||
// Reverse geocode: get address by coordinates (local language)
|
||||
const reverseGeocode = async (lat: number, lng: number): Promise<{ address: string | null; countryCode: string | null }> => {
|
||||
if (!hasMapboxToken.value) {
|
||||
return { address: null, countryCode: null }
|
||||
}
|
||||
|
||||
try {
|
||||
const token = config.public.mapboxAccessToken
|
||||
const response = await fetch(
|
||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${lng},${lat}.json?access_token=${token}`
|
||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${lng},${lat}.json?access_token=${mapboxAccessToken.value}`
|
||||
)
|
||||
const data = await response.json()
|
||||
const feature = data.features?.[0]
|
||||
@@ -203,12 +216,12 @@ const onMapClick = async (event: MapMouseEvent) => {
|
||||
|
||||
// Initialize Mapbox SearchBox
|
||||
onMounted(async () => {
|
||||
if (!searchBoxContainer.value) return
|
||||
if (!hasMapboxToken.value || !searchBoxContainer.value) return
|
||||
|
||||
const { MapboxSearchBox } = await import('@mapbox/search-js-web')
|
||||
|
||||
const searchBox = new MapboxSearchBox()
|
||||
searchBox.accessToken = config.public.mapboxAccessToken as string
|
||||
searchBox.accessToken = mapboxAccessToken.value
|
||||
searchBox.options = {
|
||||
// Without language: uses local country language
|
||||
}
|
||||
@@ -248,7 +261,7 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
const updateAddress = async () => {
|
||||
if (!addressData.value || !addressData.value.name || !addressData.value.address || !addressData.value.latitude) return
|
||||
if (!addressData.value || !addressData.value.name || !addressData.value.address || (hasMapboxToken.value && !addressData.value.latitude)) return
|
||||
|
||||
isSaving.value = true
|
||||
try {
|
||||
|
||||
@@ -14,16 +14,21 @@
|
||||
<label class="label">
|
||||
<span class="label-text">{{ t('profileAddresses.form.address.label') }}</span>
|
||||
</label>
|
||||
<div ref="searchBoxContainer" class="w-full" />
|
||||
<input
|
||||
v-if="newAddress.address"
|
||||
type="hidden"
|
||||
:value="newAddress.address"
|
||||
<div
|
||||
v-if="hasMapboxToken"
|
||||
ref="searchBoxContainer"
|
||||
class="w-full"
|
||||
/>
|
||||
<Input
|
||||
v-else
|
||||
v-model="newAddress.address"
|
||||
:placeholder="t('profileAddresses.form.address.placeholder')"
|
||||
/>
|
||||
<input v-if="newAddress.address && hasMapboxToken" type="hidden" :value="newAddress.address" />
|
||||
</div>
|
||||
|
||||
<!-- Mapbox map for selecting coordinates -->
|
||||
<div class="form-control">
|
||||
<div v-if="hasMapboxToken" class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">{{ t('profileAddresses.form.mapLabel') }}</span>
|
||||
</label>
|
||||
@@ -54,9 +59,12 @@
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div v-else class="alert alert-warning text-sm">
|
||||
Mapbox is not configured. Enter address manually.
|
||||
</div>
|
||||
|
||||
<Stack direction="row" gap="3">
|
||||
<Button @click="createAddress" :disabled="isCreating || !newAddress.latitude">
|
||||
<Button @click="createAddress" :disabled="isCreating || (hasMapboxToken && !newAddress.latitude)">
|
||||
{{ isCreating ? t('profileAddresses.form.saving') : t('profileAddresses.form.save') }}
|
||||
</Button>
|
||||
<Button variant="outline" :as="NuxtLink" :to="localePath('/clientarea/addresses')">
|
||||
@@ -84,6 +92,8 @@ const { mutate } = useGraphQL()
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
const config = useRuntimeConfig()
|
||||
const mapboxAccessToken = computed(() => String(config.public.mapboxAccessToken || '').trim())
|
||||
const hasMapboxToken = computed(() => mapboxAccessToken.value.length > 0)
|
||||
|
||||
const isCreating = ref(false)
|
||||
const searchBoxContainer = ref<HTMLElement | null>(null)
|
||||
@@ -104,10 +114,13 @@ const onMapCreated = (map: MapboxMapType) => {
|
||||
|
||||
// Reverse geocode: get address by coordinates (local language)
|
||||
const reverseGeocode = async (lat: number, lng: number): Promise<{ address: string | null; countryCode: string | null }> => {
|
||||
if (!hasMapboxToken.value) {
|
||||
return { address: null, countryCode: null }
|
||||
}
|
||||
|
||||
try {
|
||||
const token = config.public.mapboxAccessToken
|
||||
const response = await fetch(
|
||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${lng},${lat}.json?access_token=${token}`
|
||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${lng},${lat}.json?access_token=${mapboxAccessToken.value}`
|
||||
)
|
||||
const data = await response.json()
|
||||
const feature = data.features?.[0]
|
||||
@@ -144,12 +157,12 @@ const onMapClick = async (event: MapMouseEvent) => {
|
||||
|
||||
// Initialize Mapbox SearchBox
|
||||
onMounted(async () => {
|
||||
if (!searchBoxContainer.value) return
|
||||
if (!hasMapboxToken.value || !searchBoxContainer.value) return
|
||||
|
||||
const { MapboxSearchBox } = await import('@mapbox/search-js-web')
|
||||
|
||||
const searchBox = new MapboxSearchBox()
|
||||
searchBox.accessToken = config.public.mapboxAccessToken as string
|
||||
searchBox.accessToken = mapboxAccessToken.value
|
||||
searchBox.options = {
|
||||
// Without language: uses local country language
|
||||
}
|
||||
@@ -182,7 +195,7 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
const createAddress = async () => {
|
||||
if (!newAddress.name || !newAddress.address || !newAddress.latitude) return
|
||||
if (!newAddress.name || !newAddress.address || (hasMapboxToken.value && !newAddress.latitude)) return
|
||||
|
||||
isCreating.value = true
|
||||
try {
|
||||
|
||||
20
app/plugins/00-apollo-console-filter.client.ts
Normal file
20
app/plugins/00-apollo-console-filter.client.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export default defineNuxtPlugin(() => {
|
||||
const originalConsoleError = console.error
|
||||
|
||||
console.error = (...args: unknown[]) => {
|
||||
const hasApolloDevtoolsWarning = args.some((arg) => {
|
||||
if (typeof arg !== 'string') return false
|
||||
|
||||
return (
|
||||
arg.includes('connectToDevTools') &&
|
||||
arg.includes('devtools.enabled')
|
||||
)
|
||||
})
|
||||
|
||||
if (hasApolloDevtoolsWarning) {
|
||||
return
|
||||
}
|
||||
|
||||
originalConsoleError(...args)
|
||||
}
|
||||
})
|
||||
@@ -1,8 +1,15 @@
|
||||
export default defineNuxtPlugin(() => {
|
||||
const config = useRuntimeConfig()
|
||||
const baseUrl = String(config.public.chatwootBaseUrl || '').trim()
|
||||
const websiteToken = String(config.public.chatwootWebsiteToken || '').trim()
|
||||
|
||||
if (!baseUrl || !websiteToken) {
|
||||
return
|
||||
}
|
||||
|
||||
const loadChatwoot = () => {
|
||||
if (document.getElementById('chatwoot-sdk')) return
|
||||
|
||||
const baseUrl = 'https://chatwoot.optovia.ru'
|
||||
const script = document.createElement('script')
|
||||
script.id = 'chatwoot-sdk'
|
||||
script.src = `${baseUrl}/packs/js/sdk.js`
|
||||
@@ -10,7 +17,7 @@ export default defineNuxtPlugin(() => {
|
||||
script.defer = true
|
||||
script.onload = () => {
|
||||
window.chatwootSDK?.run({
|
||||
websiteToken: 'bc668ge3hM5ZpPeUgGEV1ZU9',
|
||||
websiteToken,
|
||||
baseUrl
|
||||
})
|
||||
}
|
||||
|
||||
@@ -200,7 +200,9 @@ export default defineNuxtConfig({
|
||||
novuAppId: process.env.NUXT_PUBLIC_NOVU_APP_ID,
|
||||
novuBackendUrl: process.env.NUXT_PUBLIC_NOVU_BACKEND_URL,
|
||||
novuSocketUrl: process.env.NUXT_PUBLIC_NOVU_SOCKET_URL,
|
||||
mapboxAccessToken: process.env.NUXT_PUBLIC_MAPBOX_ACCESS_TOKEN || ''
|
||||
mapboxAccessToken: process.env.NUXT_PUBLIC_MAPBOX_ACCESS_TOKEN || '',
|
||||
chatwootBaseUrl: process.env.NUXT_PUBLIC_CHATWOOT_BASE_URL || '',
|
||||
chatwootWebsiteToken: process.env.NUXT_PUBLIC_CHATWOOT_WEBSITE_TOKEN || ''
|
||||
}
|
||||
},
|
||||
mapbox: {
|
||||
|
||||
Reference in New Issue
Block a user