Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
56066fb
wip: initial ivr emulator loading
eselimsen Jul 22, 2026
b37e8ba
imp: add the initial interface for the emulator
eselimsen Jul 24, 2026
4e67601
imp: enable config generation and emulator lifecycle management
eselimsen Jul 27, 2026
6eabada
imp: allow setting ivr emulator tracing log levels
eselimsen Jul 27, 2026
41d6637
imp: apply filters locally
eselimsen Jul 27, 2026
2b9daf6
imp: pretty prompt lines
eselimsen Jul 27, 2026
503f5d8
imp: stable order and keys for prompt lines
eselimsen Jul 27, 2026
b7ecf18
imp: clear the input field after sending it
eselimsen Jul 27, 2026
0f6ec96
imp: translate and add hints
eselimsen Jul 27, 2026
8f679d3
fix: actually translate the disconnect msg
eselimsen Jul 27, 2026
957092d
imp: better "disconnected" notification
eselimsen Jul 27, 2026
6d5d125
imp: use id instead of reserved "key" as prop name
eselimsen Jul 27, 2026
8a2a2a8
imp: better manage the lifecycle of wasm ivr emulators
eselimsen Jul 27, 2026
bd05745
imp: load shim dynamically too
eselimsen Jul 28, 2026
c6f5c16
imp: allow setting the emulator wasm url from deployment
eselimsen Jul 28, 2026
9f0588b
imp: improve fetch error handling
eselimsen Jul 28, 2026
aa457ff
chore: slightly more understandable comment about emulator cleanup logic
eselimsen Jul 28, 2026
1d8332b
chore: fix licenses for the new files
eselimsen Jul 28, 2026
16752dc
chore: ignore format of the generated type file
eselimsen Jul 28, 2026
674d3c9
Merge branch 'release/10.0' of github.com:sequentech/step into feat/m…
eselimsen Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,9 @@ path = "packages/admin-portal/public/sql-wasm.wasm"
precedence = "aggregate"
SPDX-FileCopyrightText = ["", "2025 Sequent Tech <legal@sequentech.io>"]
SPDX-License-Identifier = "MIT"

[[annotations]]
path = "packages/admin-portal/src/services/generated/ivr_emulator_wasm.d.ts"
precedence = "aggregate"
SPDX-FileCopyrightText = ["", "2026 Sequent Tech Inc <legal@sequentech.io>"]
SPDX-License-Identifier = "AGPL-3.0-only"
3 changes: 2 additions & 1 deletion packages/admin-portal/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ src/gql/
.github
public/tinymce/
public/intl-tel-input/
public/sql-wasm.js
public/sql-wasm.js
src/services/generated/ivr_emulator_wasm.d.ts
1 change: 1 addition & 0 deletions packages/admin-portal/public/global-settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"ONLINE_VOTING_CLIENT_ID": "admin-portal",
"KEYCLOAK_URL": "http://localhost:8090/",
"HASURA_URL": "http://localhost:8080/v1/graphql",
"IVR_EMULATOR_BASE_URL": "/wasm/ivr_emulator_wasm",
"APP_VERSION": "dev",
"APP_HASH": "dev",
"DEFAULT_EMAIL_SUBJECT": {"en": "Participate in {{election_event.name}}"},
Expand Down
54 changes: 54 additions & 0 deletions packages/admin-portal/src/providers/IvrEmulatorContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2026 Sequent Tech Inc <legal@sequentech.io>
//
// SPDX-License-Identifier: AGPL-3.0-only
import React, {createContext, useContext, useEffect, useState} from "react"
import {IvrEmulatorApi, IvrEmulatorError, loadIvrEmulator} from "@/services/IvrEmulator"
import {SettingsContext} from "@/providers/SettingsContextProvider"

export enum IvrApiStatus {
UNAVAILABLE = "unavailable",
LOADING = "loading",
READY = "ready",
ERROR = "error",
}

export interface IvrEmulatorContextType {
status: IvrApiStatus
api?: IvrEmulatorApi
}

export const IvrEmulatorContext = createContext<IvrEmulatorContextType | undefined>(undefined)
export const IvrEmulatorContextProvider: React.FC<{children: React.ReactNode}> = ({children}) => {
const [status, setStatus] = useState<IvrApiStatus>(IvrApiStatus.LOADING)
const [api, setApi] = useState<IvrEmulatorApi | undefined>(undefined)
const {globalSettings} = useContext(SettingsContext)
const url = globalSettings.IVR_EMULATOR_BASE_URL
? globalSettings.IVR_EMULATOR_BASE_URL
: "/wasm/ivr_emulator_wasm"

useEffect(() => {
loadIvrEmulator(url)
?.then((resolvedApi) => {
setStatus(IvrApiStatus.READY)
setApi(resolvedApi)
})
.catch((e) => {
if (e instanceof IvrEmulatorError && e.operation === "fetch") {
setStatus(IvrApiStatus.UNAVAILABLE)
} else {
setStatus(IvrApiStatus.ERROR)
}
})
}, [])
const value = {status, api}

return <IvrEmulatorContext.Provider value={value}>{children}</IvrEmulatorContext.Provider>
}

export const useIvrEmulator = (): IvrEmulatorContextType => {
const emulator = useContext(IvrEmulatorContext)
if (!emulator) {
throw new Error("useIvrEmulator can't be used outside IvrEmulatorContext")
}
return emulator
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface GlobalSettings {
PUBLIC_BUCKET_URL: string
VOTING_PORTAL_URL: string
RESULTS_PORTAL_URL: string
IVR_EMULATOR_BASE_URL: string
ACTIVATE_MIRU_EXPORT: boolean
CUSTOM_URLS_DOMAIN_NAME: string
}
Expand Down Expand Up @@ -87,6 +88,7 @@ const defaultSettingsValues: SettingsContextValues = {
PUBLIC_BUCKET_URL: "http://127.0.0.1:9002/public/",
VOTING_PORTAL_URL: "http://localhost:3000",
RESULTS_PORTAL_URL: "http://localhost:3004",
IVR_EMULATOR_BASE_URL: "/wasm/ivr_emulator_wasm",
ACTIVATE_MIRU_EXPORT: false,
CUSTOM_URLS_DOMAIN_NAME: "google.com",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {IPermissions} from "@/types/keycloak"
import {PhoneBlacklist} from "./PhoneBlacklist"
import {IvrConfig} from "./IvrConfig"
import {IvrPrompts} from "./IvrPrompts"
import {IvrEmulator} from "./IvrEmulator"
import {Box} from "@mui/material"
import {IvrEmulatorContextProvider} from "@/providers/IvrEmulatorContextProvider"

const ConfigTab: React.FC = () => (
<Suspense fallback={<div>Loading...</div>}>
Expand All @@ -29,6 +31,14 @@ const PromptsTab: React.FC = () => (
</Suspense>
)

const EmulatorTab: React.FC = () => (
<Suspense fallback={<div>Loading...</div>}>
<IvrEmulatorContextProvider>
<IvrEmulator />
</IvrEmulatorContextProvider>
</Suspense>
)

export const EditElectionEventIvr: React.FC = () => {
const {t} = useTranslation()
const authContext = useContext(AuthContext)
Expand All @@ -52,6 +62,11 @@ export const EditElectionEventIvr: React.FC = () => {
})
}

tabs.push({
label: "Emulator",
component: EmulatorTab,
})

return (
<Box sx={{margin: "-1.5rem 0 0 0"}}>
<Tabs elements={tabs} />
Expand Down
Loading
Loading