Compare commits

..

1 Commits

Author SHA1 Message Date
renovate[bot] 49b57aefea ⬆️(dependencies) update GitHub Actions dependencies 2026-10-26 00:58:06 +00:00
28 changed files with 174 additions and 388 deletions
+13 -13
View File
@@ -87,7 +87,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
@@ -117,11 +117,11 @@ jobs:
timeout 30 sh -c 'until docker exec garage /garage health; do sleep 1; done'
- name: Install Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
python-version: "3.15"
- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
- name: Install the dependencies
run: uv sync --locked --all-extras
@@ -160,7 +160,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
@@ -170,12 +170,12 @@ jobs:
sudo apt-get install -y ffmpeg
- name: Install Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
python-version: "3.15"
- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
- name: Install the project
run: uv sync --locked --all-extras
@@ -189,7 +189,7 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
@@ -208,12 +208,12 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "24.22.1"
@@ -232,7 +232,7 @@ jobs:
working-directory: src/sdk/library
steps:
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
@@ -255,7 +255,7 @@ jobs:
working-directory: src/sdk/library
steps:
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
+1 -6
View File
@@ -8,11 +8,6 @@ and this project adheres to
## [Unreleased]
### Changed
- ⬆️(summary) update ffmpeg to 8.1.3-r0
- ✨(frontend) replace the Crisp chat with the La Suite support form
## [1.36.3] - 2026-10-23
### Fixed
@@ -183,7 +178,7 @@ and this project adheres to
### Fixed
- 🐛(helm) probe liveness on `__lbheartbeat__` and readiness on `__heartbeat__`
- 🐛(helm) probe liveness on __lbheartbeat__ and readiness on __heartbeat__
- 🐛(helm) render periodSeconds and failureThreshold on probes
- 🐛(backend) report the app release to Sentry instead of "NA"
- 🐛(frontend) play the waiting room notification sound on every arrival
-7
View File
@@ -16,13 +16,6 @@ the following command inside your docker container:
## [Unreleased]
### Crisp support chat replaced by the La Suite support form
The Crisp chat is gone, the support button, now open the La Suite support form. They are hidden unless these `FRONTEND_SUPPORT` keys are all set.
The former help links for recording/transcription are now redirecting to the adhoc page in the official documentation
## [v1.35.0]
### Purging deleted rooms
Deleting a room now only soft deletes it: it is hidden but keeps its slug and PIN code. The `purge_inactive_rooms` command also permanently deletes soft-deleted rooms. See [the room purge documentation](docs/features/room-purge.md).
+1 -1
View File
@@ -37,7 +37,7 @@ $ make help
---
## Need Help?
If you need any assistance or have questions while getting started, feel free to reach out to @lebaudantoine anytime! Antoine is available to help you onboard and guide you through the process. Chat with him @antoine.lebaud:matrix.org, or by email at [support-visio@numerique.gouv.fr](mailto:support-visio@numerique.gouv.fr).
If you need any assistance or have questions while getting started, feel free to reach out to @lebaudantoine anytime! Antoine is available to help you onboard and guide you through the process. Chat with him @antoine.lebaud:matrix.org, or from the [support hotline](https://go.crisp.chat/chat/embed/?website_id=58ea6697-8eba-4492-bc59-ad6562585041).
---
+1 -1
View File
@@ -348,7 +348,7 @@ These are the environmental options available on meet backend.
| CSRF_TRUSTED_ORIGINS | CSRF trusted origins list | [] |
| FRONTEND_CUSTOM_CSS_URL | URL of an additional CSS file to load in the frontend app. If set, a `<link>` tag with this URL as href is added to the `<head>` of the frontend app | |
| FRONTEND_ANALYTICS | Analytics information | {} |
| FRONTEND_SUPPORT | La Suite support form (`widget_script_url`, `widget_api_url`, `widget_channel`) and help articles (`help_article_*`) | {} |
| FRONTEND_SUPPORT | Crisp frontend support configuration, also you can pass help articles, with `help_article_transcript`, `help_article_recording`, `help_article_more_tools` | {} |
| FRONTEND_MANIFEST_LINK | Link to the "Learn more" button on the homepage | {} |
| FRONTEND_SILENCE_LIVEKIT_DEBUG | Silence LiveKit debug logs | false |
| FRONTEND_IS_SILENT_LOGIN_ENABLED | Enable silent login feature | true |
+7
View File
@@ -21,6 +21,7 @@
"@remixicon/react": "4.9.0",
"@tanstack/react-query": "5.101.4",
"@timephy/rnnoise-wasm": "1.0.0",
"crisp-sdk-web": "1.1.2",
"hoofd": "1.7.3",
"humanize-duration": "3.34.1",
"i18next": "26.3.6",
@@ -4562,6 +4563,12 @@
}
}
},
"node_modules/crisp-sdk-web": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/crisp-sdk-web/-/crisp-sdk-web-1.1.2.tgz",
"integrity": "sha512-qxSpiT4EGHnVEO6J/t1UURY6fg1ojgquLU0IIr7RNjZxXZs1R2e6AhAiFqxiGZ13Vb4o5VoUpATFU2TzX/ppOQ==",
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+1
View File
@@ -29,6 +29,7 @@
"@remixicon/react": "4.9.0",
"@tanstack/react-query": "5.101.4",
"@timephy/rnnoise-wasm": "1.0.0",
"crisp-sdk-web": "1.1.2",
"hoofd": "1.7.3",
"humanize-duration": "3.34.1",
"i18next": "26.3.6",
+1 -3
View File
@@ -13,9 +13,7 @@ export interface ApiConfig {
flags_api_host?: string
}
support?: {
widget_script_url?: string
widget_api_url?: string
widget_channel?: string
id: string
help_article_transcript: string
help_article_recording: string
help_article_more_tools: string
@@ -1,5 +1,6 @@
import { useConfig } from '@/api/useConfig'
import { useAnalytics } from '@/features/analytics/hooks/useAnalytics'
import { useSupport } from '@/features/support/hooks/useSupport'
import { useSyncUserPreferencesWithBackend } from '@/features/auth/api/useSyncUserPreferencesWithBackend'
import { useEffect } from 'react'
@@ -7,9 +8,10 @@ export const AppInitialization = () => {
const { data } = useConfig()
useSyncUserPreferencesWithBackend()
const { analytics = {}, custom_css_url = '' } = data ?? {}
const { analytics = {}, support = {}, custom_css_url = '' } = data ?? {}
useAnalytics(analytics)
useSupport(support)
useEffect(() => {
if (custom_css_url) {
@@ -32,7 +32,6 @@ export type LogCode =
| 'fullscreen_failure'
| 'publish_sources_failure'
| 'disconnect_failure'
| 'support_widget_failure'
| 'generic_failure'
export const reportError = (
@@ -1,4 +1,5 @@
import { apiUrl } from '@/api/apiUrl'
import { terminateSupportSession } from '@/features/support/hooks/useSupport'
import { terminateAnalyticsSession } from '@/features/analytics/hooks/useAnalytics'
const logoutUrl = () => {
@@ -7,5 +8,6 @@ const logoutUrl = () => {
export const logout = async () => {
await terminateAnalyticsSession()
terminateSupportSession()
window.location.href = logoutUrl()
}
@@ -2,18 +2,23 @@ import { RiQuestionLine } from '@remixicon/react'
import { MenuItem } from 'react-aria-components'
import { useTranslation } from 'react-i18next'
import { menuRecipe } from '@/primitives/menuRecipe'
import { useSupportWidget } from '@/features/support/hooks/useSupportWidget'
import {
useIsSupportEnabled,
openSupportChat,
} from '@/features/support/hooks/useSupport'
export const SupportMenuItem = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'options.items' })
const { isEnabled, open } = useSupportWidget()
const isSupportEnabled = useIsSupportEnabled()
if (!isEnabled) return
if (!isSupportEnabled) {
return
}
return (
<MenuItem
className={menuRecipe({ icon: true, variant: 'dark' }).item}
onAction={open}
onAction={openSupportChat}
>
<RiQuestionLine size={20} />
{t('support')}
@@ -1,25 +0,0 @@
import { useTranslation } from 'react-i18next'
import { RiQuestionLine } from '@remixicon/react'
import { Button } from '@/primitives'
import { Spinner } from '@/primitives/Spinner'
import { useSupportWidget } from '../hooks/useSupportWidget'
export const SupportButton = () => {
const { t } = useTranslation('global', { keyPrefix: 'support' })
const { isEnabled, isPending, open } = useSupportWidget()
if (!isEnabled) return null
return (
<Button
square
variant="secondaryText"
aria-label={t('buttonLabel')}
tooltip={t('buttonLabel')}
isPending={isPending}
onPress={open}
>
{isPending ? <Spinner size={24} /> : <RiQuestionLine />}
</Button>
)
}
@@ -1,73 +0,0 @@
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useSnapshot } from 'valtio'
import { useLocation } from 'wouter'
import { RiCheckboxCircleFill, RiCloseLine } from '@remixicon/react'
import { css } from '@/styled-system/css'
import { Button, Text } from '@/primitives'
import { announceToScreenReader } from '@/stores/screenReaderAnnouncer'
import { supportStore } from '@/stores/support'
import { subscribeToSupportWidget } from '../utils/supportWidget'
import { ToastDuration } from '@/features/notifications/NotificationDuration'
import { StyledToast } from '@/features/notifications/components/StyledToast'
import { StyledToastContainer } from '@/features/notifications/components/StyledToastContainer'
const hide = () => {
supportStore.showSentConfirmation = false
}
export const SupportWidget = () => {
const { t } = useTranslation('global', { keyPrefix: 'support.sent' })
const { showSentConfirmation } = useSnapshot(supportStore)
const [location] = useLocation()
useEffect(() => subscribeToSupportWidget(), [])
useEffect(hide, [location])
useEffect(() => {
if (!showSentConfirmation) return
announceToScreenReader(`${t('title')}. ${t('body')}`)
const timeout = window.setTimeout(hide, ToastDuration.EXTRA_LONG)
return () => window.clearTimeout(timeout)
}, [showSentConfirmation, t])
if (!showSentConfirmation) return null
return (
<div
className={css({
position: 'fixed',
top: '5rem',
left: '50%',
transform: 'translateX(-50%)',
zIndex: 1000,
})}
>
<StyledToastContainer>
<StyledToast>
<RiCheckboxCircleFill
size={24}
aria-hidden="true"
className={css({ color: 'success.300', flexShrink: 0 })}
/>
<div>
<Text margin={false} bold>
{t('title')}
</Text>
<Text margin={false}>{t('body')}</Text>
</div>
<Button
square
size="sm"
invisible
aria-label={t('close')}
onPress={hide}
>
<RiCloseLine color="white" />
</Button>
</StyledToast>
</StyledToastContainer>
</div>
)
}
@@ -0,0 +1,125 @@
import { useEffect, useState } from 'react'
import { type ApiUser } from '@/features/auth/api/ApiUser'
import { useUser } from '@/features/auth/api/useUser'
import { useConfig } from '@/api/useConfig'
type CrispSdk = (typeof import('crisp-sdk-web'))['Crisp']
let crisp: CrispSdk | undefined
let crispPromise: Promise<CrispSdk> | undefined
const loadCrisp = (): Promise<CrispSdk> => {
crispPromise ??= import('crisp-sdk-web')
.then((module) => {
crisp = module.Crisp
return module.Crisp
})
.catch((error) => {
crispPromise = undefined
throw error
})
return crispPromise
}
export const openSupportChat = () => {
if (!crisp?.isCrispInjected()) return
crisp.chat.open()
}
export const initializeSupportSession = (user: ApiUser) => {
if (!crisp?.isCrispInjected()) return
const { id, email } = user
crisp.setTokenId(`meet-${id}`)
if (email) crisp.user.setEmail(email)
}
export const terminateSupportSession = () => {
if (!crisp?.isCrispInjected()) return
crisp.setTokenId()
crisp.session.reset()
}
export type useSupportProps = {
id?: string
isDisabled?: boolean
}
const IDLE_TIMEOUT_MS = 10_000
const scheduleWhenIdle = (callback: () => void): (() => void) => {
if (typeof window.requestIdleCallback === 'function') {
const handle = window.requestIdleCallback(callback, {
timeout: IDLE_TIMEOUT_MS,
})
return () => window.cancelIdleCallback(handle)
}
const handle = window.setTimeout(callback, 1)
return () => window.clearTimeout(handle)
}
export const useSupport = ({ id, isDisabled }: useSupportProps) => {
const { user } = useUser()
const [isInjected, setIsInjected] = useState(
() => crisp?.isCrispInjected() ?? false
)
useEffect(() => {
if (!id || isDisabled) return
if (crisp?.isCrispInjected()) {
setIsInjected(true)
return
}
let cancelled = false
const cancelIdle = scheduleWhenIdle(() => {
void loadCrisp()
.then((sdk) => {
if (cancelled) return
if (!sdk.isCrispInjected()) {
sdk.configure(id)
sdk.setHideOnMobile(true)
}
setIsInjected(true)
})
.catch((error) => {
if (!cancelled) {
console.error('Failed to initialize support chat', error)
}
})
})
return () => {
cancelled = true
cancelIdle()
}
}, [id, isDisabled])
useEffect(() => {
if (!user || !isInjected || isDisabled) return
initializeSupportSession(user)
}, [user, isInjected, isDisabled])
return null
}
// Some users block the chat widget, so check its availability safely.
const isCrispAvailable = () => {
try {
return !!window?.$crisp?.is
} catch {
return false
}
}
export const useIsSupportEnabled = () => {
const { data } = useConfig()
return !!data?.support?.id && isCrispAvailable()
}
@@ -1,42 +0,0 @@
import { useTranslation } from 'react-i18next'
import { useSnapshot } from 'valtio'
import { useConfig } from '@/api/useConfig'
import { useUser } from '@/features/auth/api/useUser'
import { reportError } from '@/features/analytics/telemetry'
import { announceToScreenReader } from '@/stores/screenReaderAnnouncer'
import { supportStore } from '@/stores/support'
import { openSupportWidget } from '../utils/supportWidget'
export const useSupportWidget = () => {
const { t, i18n } = useTranslation('global', { keyPrefix: 'support' })
const { data } = useConfig()
const { user } = useUser()
const { status } = useSnapshot(supportStore)
const {
widget_script_url: scriptUrl,
widget_api_url: api,
widget_channel: channel,
} = data?.support ?? {}
const isEnabled = !!(scriptUrl && api && channel)
const open = () => {
if (!isEnabled) return
openSupportWidget(scriptUrl, {
api,
channel,
locale: i18n.language,
email: user?.email,
}).catch((error) => {
reportError('support_widget_failure', error)
announceToScreenReader(t('openError'), 'assertive')
})
}
return {
isEnabled,
isPending: status === 'pending',
open,
}
}
@@ -1,147 +0,0 @@
import { supportStore } from '@/stores/support'
declare global {
interface Window {
_lasuite_widget?: unknown[]
}
}
type WidgetOptions = {
api: string
channel: string
locale: string
email?: string
}
const ROOT_ID = 'lasuite-widget-support-root'
const OPENED_EVENT = 'lasuite-widget-support-opened'
const SUCCESS_EVENT = 'lasuite-widget-support-success'
const CLOSED_EVENT = 'lasuite-widget-support-closed'
const TIMEOUT_MS = 10_000
const WIDGET_CSS = `@media (min-width: 48em) {
[role='dialog'] {
top: 50%;
right: auto;
bottom: auto;
left: 50%;
transform: translate(-50%, -50%);
}
}
[role='status'] {
display: none;
}`
const styleWidget = () => {
const shadowRoot = document.getElementById(ROOT_ID)?.shadowRoot
if (!shadowRoot) return
const style = document.createElement('style')
style.textContent = WIDGET_CSS
shadowRoot.appendChild(style)
}
let returnFocusTo: HTMLElement | null = null
// The widget removes the form without moving focus, which drops it on <body>.
const restoreFocus = () => {
const activeEl = document.activeElement
const isFocusLost = activeEl === document.body || activeEl?.id === ROOT_ID
if (isFocusLost && returnFocusTo?.isConnected) {
returnFocusTo.focus({ preventScroll: true })
}
}
export const subscribeToSupportWidget = () => {
const onOpened = () => {
styleWidget()
supportStore.status = 'open'
}
const onSuccess = () => {
supportStore.showSentConfirmation = true
restoreFocus()
}
const onClosed = () => {
supportStore.status = 'closed'
restoreFocus()
returnFocusTo = null
}
document.addEventListener(OPENED_EVENT, onOpened)
document.addEventListener(SUCCESS_EVENT, onSuccess)
document.addEventListener(CLOSED_EVENT, onClosed)
return () => {
document.removeEventListener(OPENED_EVENT, onOpened)
document.removeEventListener(SUCCESS_EVENT, onSuccess)
document.removeEventListener(CLOSED_EVENT, onClosed)
}
}
const resolveTrigger = () => {
const activeEl = document.activeElement as HTMLElement | null
if (activeEl?.getAttribute('role') === 'menuitem') {
return document.querySelector<HTMLElement>('#room-options-trigger')
}
return activeEl
}
let scriptPromise: Promise<void> | undefined
const loadScript = (src: string): Promise<void> => {
scriptPromise ??= new Promise<void>((resolve, reject) => {
const script = document.createElement('script')
const timeout = window.setTimeout(
() => fail(new Error(`Timed out loading ${src}`)),
TIMEOUT_MS
)
const fail = (error: Error) => {
window.clearTimeout(timeout)
script.remove()
scriptPromise = undefined
reject(error)
}
script.src = src
script.async = true
script.onload = () => {
window.clearTimeout(timeout)
resolve()
}
script.onerror = () => fail(new Error(`Failed to load ${src}`))
document.body.appendChild(script)
})
return scriptPromise
}
const initWidget = (options: WidgetOptions): Promise<void> =>
new Promise<void>((resolve, reject) => {
const onOpened = () => {
window.clearTimeout(timeout)
resolve()
}
const timeout = window.setTimeout(() => {
document.removeEventListener(OPENED_EVENT, onOpened)
reject(new Error('Support widget did not open'))
}, TIMEOUT_MS)
document.addEventListener(OPENED_EVENT, onOpened, { once: true })
window._lasuite_widget?.push(['support', 'init', options])
})
export const openSupportWidget = async (
scriptUrl: string,
options: WidgetOptions
) => {
if (supportStore.status !== 'closed') return
supportStore.status = 'pending'
returnFocusTo = resolveTrigger()
try {
await loadScript(scriptUrl)
await initWidget(options)
} catch (error) {
supportStore.status = 'closed'
returnFocusTo = null
throw error
}
}
-2
View File
@@ -4,7 +4,6 @@ import { HStack, Stack } from '@/styled-system/jsx'
import { useTranslation } from 'react-i18next'
import { Button, Text } from '@/primitives'
import { SettingsButton } from '@/features/settings'
import { SupportButton } from '@/features/support/components/SupportButton'
import { useUser } from '@/features/auth/api/useUser'
import { useMatchesRoute } from '@/navigation/useMatchesRoute'
import { FeedbackBanner } from '@/components/FeedbackBanner'
@@ -154,7 +153,6 @@ export const Header = () => {
</header>
<nav>
<Stack gap={1} direction="row" align="center">
<SupportButton />
{isLoggedIn === false &&
!isHome &&
!isLegalTerms &&
-2
View File
@@ -6,7 +6,6 @@ import { useSnapshot } from 'valtio'
import { Footer } from '@/layout/Footer'
import { ScreenReaderAnnouncer } from '@/primitives'
import { SkipLink, MAIN_CONTENT_ID } from './SkipLink'
import { SupportWidget } from '@/features/support/components/SupportWidget'
export type Layout = 'fullpage' | 'centered'
@@ -47,7 +46,6 @@ export const Layout = ({ children }: { children: ReactNode }) => {
})}
>
<ScreenReaderAnnouncer />
<SupportWidget />
{children}
</main>
</div>
-9
View File
@@ -27,15 +27,6 @@
},
"selected": "ausgewählt",
"submit": "OK",
"support": {
"buttonLabel": "Support kontaktieren",
"openError": "Das Kontaktformular konnte nicht geöffnet werden. Bitte versuchen Sie es später erneut.",
"sent": {
"title": "Nachricht gesendet",
"body": "Das Support-Team hat Ihre Nachricht erhalten.",
"close": "Schließen"
}
},
"footer": {
"links": {
"legifrance": "legifrance.gouv.fr",
-9
View File
@@ -27,15 +27,6 @@
},
"selected": "selected",
"submit": "OK",
"support": {
"buttonLabel": "Contact support",
"openError": "The contact form could not be opened. Please try again later.",
"sent": {
"title": "Message sent",
"body": "The support team has received your message.",
"close": "Close"
}
},
"footer": {
"links": {
"legifrance": "legifrance.gouv.fr",
-9
View File
@@ -27,15 +27,6 @@
},
"selected": "seleccionado",
"submit": "Aceptar",
"support": {
"buttonLabel": "Contactar con el soporte",
"openError": "No se ha podido abrir el formulario de contacto. Inténtelo de nuevo más tarde.",
"sent": {
"title": "Mensaje enviado",
"body": "El equipo de soporte ha recibido su mensaje.",
"close": "Cerrar"
}
},
"footer": {
"links": {
"legifrance": "legifrance.gouv.fr",
-9
View File
@@ -27,15 +27,6 @@
},
"selected": "sélectionné",
"submit": "OK",
"support": {
"buttonLabel": "Contacter l'aide",
"openError": "Impossible d'ouvrir le formulaire de contact. Réessayez plus tard.",
"sent": {
"title": "Message envoyé",
"body": "L'équipe support a bien reçu votre message.",
"close": "Fermer"
}
},
"footer": {
"links": {
"legifrance": "legifrance.gouv.fr",
-9
View File
@@ -26,15 +26,6 @@
},
"selected": "geselecteerd",
"submit": "OK",
"support": {
"buttonLabel": "Contact ondersteuning",
"openError": "Het contactformulier kon niet worden geopend. Probeer het later opnieuw.",
"sent": {
"title": "Bericht verzonden",
"body": "Het supportteam heeft je bericht ontvangen.",
"close": "Sluiten"
}
},
"footer": {
"links": {
"legifrance": "legifrance.gouv.fr",
-13
View File
@@ -1,13 +0,0 @@
import { proxy } from 'valtio'
export type SupportWidgetStatus = 'closed' | 'pending' | 'open'
type State = {
status: SupportWidgetStatus
showSentConfirmation: boolean
}
export const supportStore = proxy<State>({
status: 'closed',
showSentConfirmation: false,
})
+8
View File
@@ -72,6 +72,14 @@ main#main-content :is(h1, h2, h3)[tabindex='-1']:focus {
}
}
body:has(.lk-video-conference) #crisp-chatbox-button {
display: none !important;
}
body:has(.lk-video-conference) #crisp-chatbox > * > div[role='button'] {
display: none !important;
}
html:has(.lk-video-conference) {
scrollbar-gutter: auto !important;
}
+1 -1
View File
@@ -163,7 +163,7 @@ backend:
LIVEKIT_DEFAULT_VIDEO_CODEC: vp9
FRONTEND_IDLE_DISCONNECT_WARNING_DELAY: 9000
FRONTEND_SILENCE_LIVEKIT_DEBUG: False
FRONTEND_SUPPORT: "{'help_article_transcript': 'https://docs.numerique.gouv.fr/docs/ba213749-2379-421c-b2ea-e4bd0840ea9b', 'help_article_recording': 'https://docs.numerique.gouv.fr/docs/a4e12e41-2974-472c-84a9-c00d12c10769', 'help_article_more_tools': 'https://docs.numerique.gouv.fr/docs/75fa98c7-5af1-433e-a611-e38143db26d0'}"
FRONTEND_SUPPORT: "{'id': '58ea6697-8eba-4492-bc59-ad6562585041', 'help_article_transcript': 'https://lasuite.crisp.help/fr/article/visio-transcript-1sjq43x', 'help_article_recording': 'https://lasuite.crisp.help/fr/article/visio-enregistrement-wgc8o0', 'help_article_more_tools': 'https://lasuite.crisp.help/fr/article/visio-tools-bvxj23'}"
FRONTEND_FEEDBACK: "{'url': 'https://grist.numerique.gouv.fr/o/docs/cbMv4G7pLY3Z/USER-RESEARCH-or-LA-SUITE/f/26'}"
FRONTEND_DOCUMENTATION_URL: "https://docs.numerique.gouv.fr/docs/7c5bd65d-3c21-486f-bce1-26e0a921d642/"
FRONTEND_TECHNICAL_DOCUMENTATION_URL: "https://docs.numerique.gouv.fr/docs/f2baa1b9-f29e-4d58-959d-65d4376fc6b8/"
+1 -1
View File
@@ -5,7 +5,7 @@ RUN apk add --no-cache --upgrade 'ca-certificates>=20261020-r0'
# Install ffmpeg for audio/video processing (format conversion, extraction, compression)
# See summary/core/file_service.py for usage.
RUN apk add --no-cache "ffmpeg=8.1.3-r0" 'brotli-libs>=1.2.1-r0'
RUN apk add --no-cache "ffmpeg=8.1.2-r0" 'brotli-libs>=1.2.1-r0'
FROM base AS builder