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">
|
<label class="label">
|
||||||
<span class="label-text">{{ t('profileAddresses.form.address.label') }}</span>
|
<span class="label-text">{{ t('profileAddresses.form.address.label') }}</span>
|
||||||
</label>
|
</label>
|
||||||
<div ref="searchBoxContainer" class="w-full" />
|
<div
|
||||||
<input
|
v-if="hasMapboxToken"
|
||||||
v-if="addressData.address"
|
ref="searchBoxContainer"
|
||||||
type="hidden"
|
class="w-full"
|
||||||
:value="addressData.address"
|
|
||||||
/>
|
/>
|
||||||
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Mapbox map for selecting coordinates -->
|
<!-- Mapbox map for selecting coordinates -->
|
||||||
<div class="form-control">
|
<div v-if="hasMapboxToken" class="form-control">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text">{{ t('profileAddresses.form.mapLabel') }}</span>
|
<span class="label-text">{{ t('profileAddresses.form.mapLabel') }}</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -61,9 +66,12 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="alert alert-warning text-sm">
|
||||||
|
Mapbox is not configured. Enter address manually.
|
||||||
|
</div>
|
||||||
|
|
||||||
<Stack direction="row" gap="3">
|
<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') }}
|
{{ isSaving ? t('profileAddresses.form.updating') : t('profileAddresses.form.update') }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" :as="NuxtLink" :to="localePath('/clientarea/addresses')">
|
<Button variant="outline" :as="NuxtLink" :to="localePath('/clientarea/addresses')">
|
||||||
@@ -108,6 +116,8 @@ const { execute, mutate } = useGraphQL()
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const localePath = useLocalePath()
|
const localePath = useLocalePath()
|
||||||
const config = useRuntimeConfig()
|
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)
|
const uuid = computed(() => route.params.uuid as string)
|
||||||
|
|
||||||
@@ -161,10 +171,13 @@ const onMapCreated = (map: MapboxMapType) => {
|
|||||||
|
|
||||||
// Reverse geocode: get address by coordinates (local language)
|
// Reverse geocode: get address by coordinates (local language)
|
||||||
const reverseGeocode = async (lat: number, lng: number): Promise<{ address: string | null; countryCode: string | null }> => {
|
const reverseGeocode = async (lat: number, lng: number): Promise<{ address: string | null; countryCode: string | null }> => {
|
||||||
|
if (!hasMapboxToken.value) {
|
||||||
|
return { address: null, countryCode: null }
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = config.public.mapboxAccessToken
|
|
||||||
const response = await fetch(
|
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 data = await response.json()
|
||||||
const feature = data.features?.[0]
|
const feature = data.features?.[0]
|
||||||
@@ -203,12 +216,12 @@ const onMapClick = async (event: MapMouseEvent) => {
|
|||||||
|
|
||||||
// Initialize Mapbox SearchBox
|
// Initialize Mapbox SearchBox
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!searchBoxContainer.value) return
|
if (!hasMapboxToken.value || !searchBoxContainer.value) return
|
||||||
|
|
||||||
const { MapboxSearchBox } = await import('@mapbox/search-js-web')
|
const { MapboxSearchBox } = await import('@mapbox/search-js-web')
|
||||||
|
|
||||||
const searchBox = new MapboxSearchBox()
|
const searchBox = new MapboxSearchBox()
|
||||||
searchBox.accessToken = config.public.mapboxAccessToken as string
|
searchBox.accessToken = mapboxAccessToken.value
|
||||||
searchBox.options = {
|
searchBox.options = {
|
||||||
// Without language: uses local country language
|
// Without language: uses local country language
|
||||||
}
|
}
|
||||||
@@ -248,7 +261,7 @@ onMounted(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const updateAddress = 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
|
isSaving.value = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -14,16 +14,21 @@
|
|||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text">{{ t('profileAddresses.form.address.label') }}</span>
|
<span class="label-text">{{ t('profileAddresses.form.address.label') }}</span>
|
||||||
</label>
|
</label>
|
||||||
<div ref="searchBoxContainer" class="w-full" />
|
<div
|
||||||
<input
|
v-if="hasMapboxToken"
|
||||||
v-if="newAddress.address"
|
ref="searchBoxContainer"
|
||||||
type="hidden"
|
class="w-full"
|
||||||
:value="newAddress.address"
|
|
||||||
/>
|
/>
|
||||||
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Mapbox map for selecting coordinates -->
|
<!-- Mapbox map for selecting coordinates -->
|
||||||
<div class="form-control">
|
<div v-if="hasMapboxToken" class="form-control">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text">{{ t('profileAddresses.form.mapLabel') }}</span>
|
<span class="label-text">{{ t('profileAddresses.form.mapLabel') }}</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -54,9 +59,12 @@
|
|||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="alert alert-warning text-sm">
|
||||||
|
Mapbox is not configured. Enter address manually.
|
||||||
|
</div>
|
||||||
|
|
||||||
<Stack direction="row" gap="3">
|
<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') }}
|
{{ isCreating ? t('profileAddresses.form.saving') : t('profileAddresses.form.save') }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" :as="NuxtLink" :to="localePath('/clientarea/addresses')">
|
<Button variant="outline" :as="NuxtLink" :to="localePath('/clientarea/addresses')">
|
||||||
@@ -84,6 +92,8 @@ const { mutate } = useGraphQL()
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const localePath = useLocalePath()
|
const localePath = useLocalePath()
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
|
const mapboxAccessToken = computed(() => String(config.public.mapboxAccessToken || '').trim())
|
||||||
|
const hasMapboxToken = computed(() => mapboxAccessToken.value.length > 0)
|
||||||
|
|
||||||
const isCreating = ref(false)
|
const isCreating = ref(false)
|
||||||
const searchBoxContainer = ref<HTMLElement | null>(null)
|
const searchBoxContainer = ref<HTMLElement | null>(null)
|
||||||
@@ -104,10 +114,13 @@ const onMapCreated = (map: MapboxMapType) => {
|
|||||||
|
|
||||||
// Reverse geocode: get address by coordinates (local language)
|
// Reverse geocode: get address by coordinates (local language)
|
||||||
const reverseGeocode = async (lat: number, lng: number): Promise<{ address: string | null; countryCode: string | null }> => {
|
const reverseGeocode = async (lat: number, lng: number): Promise<{ address: string | null; countryCode: string | null }> => {
|
||||||
|
if (!hasMapboxToken.value) {
|
||||||
|
return { address: null, countryCode: null }
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = config.public.mapboxAccessToken
|
|
||||||
const response = await fetch(
|
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 data = await response.json()
|
||||||
const feature = data.features?.[0]
|
const feature = data.features?.[0]
|
||||||
@@ -144,12 +157,12 @@ const onMapClick = async (event: MapMouseEvent) => {
|
|||||||
|
|
||||||
// Initialize Mapbox SearchBox
|
// Initialize Mapbox SearchBox
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!searchBoxContainer.value) return
|
if (!hasMapboxToken.value || !searchBoxContainer.value) return
|
||||||
|
|
||||||
const { MapboxSearchBox } = await import('@mapbox/search-js-web')
|
const { MapboxSearchBox } = await import('@mapbox/search-js-web')
|
||||||
|
|
||||||
const searchBox = new MapboxSearchBox()
|
const searchBox = new MapboxSearchBox()
|
||||||
searchBox.accessToken = config.public.mapboxAccessToken as string
|
searchBox.accessToken = mapboxAccessToken.value
|
||||||
searchBox.options = {
|
searchBox.options = {
|
||||||
// Without language: uses local country language
|
// Without language: uses local country language
|
||||||
}
|
}
|
||||||
@@ -182,7 +195,7 @@ onMounted(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const createAddress = 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
|
isCreating.value = true
|
||||||
try {
|
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(() => {
|
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 = () => {
|
const loadChatwoot = () => {
|
||||||
if (document.getElementById('chatwoot-sdk')) return
|
if (document.getElementById('chatwoot-sdk')) return
|
||||||
|
|
||||||
const baseUrl = 'https://chatwoot.optovia.ru'
|
|
||||||
const script = document.createElement('script')
|
const script = document.createElement('script')
|
||||||
script.id = 'chatwoot-sdk'
|
script.id = 'chatwoot-sdk'
|
||||||
script.src = `${baseUrl}/packs/js/sdk.js`
|
script.src = `${baseUrl}/packs/js/sdk.js`
|
||||||
@@ -10,7 +17,7 @@ export default defineNuxtPlugin(() => {
|
|||||||
script.defer = true
|
script.defer = true
|
||||||
script.onload = () => {
|
script.onload = () => {
|
||||||
window.chatwootSDK?.run({
|
window.chatwootSDK?.run({
|
||||||
websiteToken: 'bc668ge3hM5ZpPeUgGEV1ZU9',
|
websiteToken,
|
||||||
baseUrl
|
baseUrl
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,7 +200,9 @@ export default defineNuxtConfig({
|
|||||||
novuAppId: process.env.NUXT_PUBLIC_NOVU_APP_ID,
|
novuAppId: process.env.NUXT_PUBLIC_NOVU_APP_ID,
|
||||||
novuBackendUrl: process.env.NUXT_PUBLIC_NOVU_BACKEND_URL,
|
novuBackendUrl: process.env.NUXT_PUBLIC_NOVU_BACKEND_URL,
|
||||||
novuSocketUrl: process.env.NUXT_PUBLIC_NOVU_SOCKET_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: {
|
mapbox: {
|
||||||
|
|||||||
Reference in New Issue
Block a user