Welcome to the next generation of the TeddyCloud Administration Frontend!
If you are using this repository for the first time, please refer to the General React Information section first.
You'll need to allow CORS for your teddyCloud instance used for development. The easiest variant is to set CORS Allow-Originⓘ to *.
With web login enabled, images and audio are not loaded in the setup above: the browser requests them without the login token. Set VITE_APP_TEDDYCLOUD_DEV_PROXY=true in your .env.local to let the dev server proxy all requests except /web to the teddyCloud instance set in VITE_APP_TEDDYCLOUD_API_URL. The browser then only talks to the dev server, and no CORS configuration is needed.
The dev server connects to VITE_APP_TEDDYCLOUD_API_URL as configured. If your teddyCloud is behind a reverse proxy that routes by hostname (SNI), use the hostname there, not the IP.
If access to your teddyCloud is secured by a client certificate, put the PKCS#12 file (.p12, with certificate and private key) into the certs folder and set TEDDYCLOUD_DEV_CLIENT_P12=<file name> and TEDDYCLOUD_DEV_CLIENT_P12_PASSWORD=<password> in your .env.local.
Please place an environment file '.env.local' in the teddycloud_web directory.
VITE_APP_TEDDYCLOUD_API_URL=http://<teddycloud-ip>
VITE_APP_TEDDYCLOUD_PORT_HTTPS=3443
VITE_APP_TEDDYCLOUD_PORT_HTTP=3000
SSL_CRT_FILE=./certs/localhost.pem
SSL_KEY_FILE=./certs/localhost-key.pemVITE_APP_TEDDYCLOUD_PORT_HTTPS and VITE_APP_TEDDYCLOUD_PORT_HTTP should match the ones entered in the package.json. If you don't change them, these are the ones from the example above.
needed for ESP32 Box Flashing section
You need to provide certificates for https. Use for example mkcert. The generated certificates must be stored in the certs folder of the project (the folder is git-ignored). Certificates in the project root are still found.
mkcert -install
mkdir -p certs
mkcert -cert-file certs/localhost.pem -key-file certs/localhost-key.pem localhostYou must also allow unsecure content in chrome (HowTo) to be able to connect to teddycloud server in https context.
If you don't need the ESP32 Box flashing section working, you can adapt the package.json and change the following:
"scripts": {
"start-http": "cross-env PORT=3000 vite",
"start-https": "cross-env HTTPS=true PORT=3443 vite",
"start": "concurrently \"npm run start-http\" \"npm run start-https\"",
"build": "tsc && vite build",
"preview": "vite preview",
"format": "prettier . --write",
"format:check": "prettier . --check",
"check:translations": "node scripts/check-translations.mjs"
},to
"scripts": {
"start": "npm run start",
"build": "tsc && vite build",
"preview": "vite preview",
"format": "prettier . --write",
"format:check": "prettier . --check",
"check:translations": "node scripts/check-translations.mjs"
},Use ./start_dev.sh to start the NPM server in development mode. Be patient, it may take a while.
Be sure your teddyCloud instance is also running.
If you just need the http variant, simply call dotenv -e .env.development.local npm start-http
Unit tests use Vitest and live next to the code they cover (*.test.ts). Run them with npm test.
The TeddyCloud frontend follows a topic-based architecture.
Pages and components are organized by feature domains (e.g. tonies, tonieboxes,settings, community).
Each feature contains its own components, hooks, and modals.
This keeps all relevant code physically close, reduces duplication, and maintains clarity.
This architecture represents the intended structure of the frontend; however, due to legacy code and ongoing migration efforts, not all parts of the project fully adhere to it yet.
If you encounter areas that are not yet aligned with this architecture or appear messy, please feel free to clean them up as part of ongoing improvements.
Location:
src/pages/<topic>/...
Purpose:
- Represent application routes.
- Provide layout and structure (
StyledLayout,StyledSider, breadcrumbs). - Compose topic-specific components.
- Contain minimal logic.
Guidelines:
- Pages should stay thin.
- Do not embed business logic in a Page.
- Let the page assemble layout + feature components + navigation.
Location:
src/components/<topic>/...
Feature areas (e.g. tonies, settings) contain sub-folders for specific features:
src/components/tonies/encoder/...
src/components/settings/notificationlist/...
src/components/tonieboxes/tonieboxeslist/...
Purpose:
- Implement UI behavior for a domain-specific feature.
- Organize related components, hooks, and modals within the same feature folder.
- Keep JSX + view logic here, push non-view logic into hooks.
When to split into a hook?
- When a component grows beyond UI concerns.
- When API logic, state machines, or repeated side effects appear.
Hooks follow the same topic-based structure.
Location examples:
src/components/tonies/encoder/hooks/useEncoder.ts
src/components/settings/notificationlist/hooks/useNotificationsList.ts
Rules:
- Hooks that belong to one feature stay inside that feature’s folder.
- They encapsulate its behavior: API calls, state transitions, validation, etc.
- They are not shared across unrelated domains.
This avoids a global “hooks graveyard”.
Only domain-agnostic, multi-topic hooks belong here:
src/hooks/useDebounce.ts
src/hooks/useMediaQuery.ts
If it is not 100% generic → keep it in the feature folder.
Modals follow the feature they belong to.
Example:
src/components/tonies/filebrowser/modals/CreateDirectoryModal.tsx
Guidelines:
- State (open/close, selected path, etc.) is controlled in the parent component/hook.
- Modal components receive all required data via props.
- Modals should not contain business logic (fetch, validation) unless tightly coupled to the feature.
Generic helpers live in:
src/utils/
Allowed:
- Validators
- Formatter functions
- Encoding helpers
- URL builders
- Type utilities
Not allowed:
- React logic
- State management
- Component-specific utilities
There are no complete guidelines defined currently, only some fragments which you shall read and follow. The following rules complement the project structure described above.
All changes must be added to the central CHANGELOG.md file.
Whenever you implement a change, add a new entry under the correct version.
If the next version does not yet exist in the changelog, create a new version block and append your changes there.
Reference related GitHub issues or pull requests whenever possible.
Pull requests are checked by the GitHub Actions workflow .github/workflows/changelog-check.yml.
The workflow verifies that CHANGELOG.md was changed and that at least one new changelog entry was added.
We are using the AntD framework. If you add anything new, try to use AntD components. AntD also provides a wide range of icons.
More details can be found here:
When building components, prefer AntD components and patterns over custom HTML whenever possible, to keep the UI consistent.
As we support dark and light themes, do not use hard-coded colors like #000000.
Instead, always use the AntD theme tokens.
If not already added, extend your file like this:
import { theme } from "antd";
...
const { useToken } = theme;
...
export const TonieCard: React.FC<{
overlay: string;
}> = ({ overlay }) => {
const { token } = useToken();
...
}Then you can use:
token.*
// e.g. token.colorTextDisabledThis ensures that your component respects both light and dark themes.
Always use t("...") instead of hard-coded text.
- English (
en), German (de), French (fr) and Spanish (es) are actively maintained. New strings must be added to all four translation JSON files. - Additional languages are optional to maintain. Missing translations in these languages are allowed, but contributions are welcome.
- Use meaningful, structured keys (e.g.
settings.notifications.title,tonies.encoder.uploadHint). - Avoid inline strings in JSX, especially in pages and reusable components.
- Run
npm run translation:checkbefore opening a pull request.
Generated, bundled or minified files should not be formatted with Prettier.
Examples:
- wasm glue code
- generated API clients
- third-party vendor bundles
- minified JavaScript assets
These files should be excluded via .prettierignore.
If you need to add a new API request to the TeddyCloud API, please use one of the existing helper methods in src/api/apis/TeddyCloudApi.ts:
apiPostTeddyCloudRawapiPostTeddyCloudFormDataRawapiGetTeddyCloudApiRaw- or any other already existing method in
TeddyCloudApi.ts
If none of the existing methods meet your needs, add the new request to src/api/apis/TeddyCloudApi.ts.
We prefer to have all API requests centralized in this file.
One reason is the upcoming authentication for accessing the API and the possibility to reintroduce generated clients later.
The web UI can be served under a URL prefix that is only known at runtime (see Serving under a URL prefix). Never hardcode root-absolute backend paths like /content/..., /api/... or /web/... in src:
- API requests through
TeddyCloudApialready take the prefix into account. - For URLs handed to the browser directly (
<img src>, audio sources, downloads,EventSource, ...), usebackendUrl(path)fromsrc/utils/basePath.ts. It adds the dev-onlyVITE_APP_TEDDYCLOUD_API_URLand the runtime prefix. - For other same-origin paths (router basename, i18n, WASM scripts, iframes), use
withBase(path).
Both helpers leave absolute URLs (https:, data:, blob:, ...) unchanged and are safe to apply twice.
If you need to link to another source, element, or URL, please check if it is already defined in src/constants/urls.ts.
- If it is, use the existing variable instead of hardcoding the URL.
- If it isn’t, consider adding it as a variable in
src/constants/urls.ts.
You may need the same URL more than once; defining it in one place ensures that updates are centralized.
Some URLs already defined (partial list):
tonieboxDefaultImageUrl = "https://cdn.tonies.de/thumbnails/03-0009-i.png"telegramGroupUrl = "https://t.me/toniebox_reverse_engineering"forumUrl = "https://forum.revvox.de/"gitHubUrl = "https://github.com/toniebox-reverse-engineering"wikiUrl = "https://tonies-wiki.revvox.de/docs/tools/teddycloud/"
- Pages: routing and layout per topic, minimal logic.
- Components: topic-specific UI and behavior; move heavy logic into hooks.
- Feature hooks: live next to the components they serve (
src/components/<topic>/<feature>/hooks/). - Shared hooks: only for generic, cross-feature helpers in
src/hooks/. - Modals: in the topic’s component tree, with parent-controlled state.
- Utils: pure helpers without React or domain-specific dependencies.
- Changelog, translations, and design tokens: keep them consistent and up to date.
This combined structure and guideline set should be followed for all new code and refactorings.
The production build works unchanged behind a reverse proxy sub-path or Home Assistant ingress. There is nothing to configure: the prefix is everything in the browser URL before the first /web path segment, e.g. /teddycloud for https://proxy/teddycloud/web/.
The proxy has to strip the prefix and forward everything below it to the root of teddyCloud, not only /web (the UI also calls /api, /content, /v1, /reverse, ...). Example for nginx:
location /teddycloud/ {
proxy_pass http://teddycloud:80/; # the trailing slash strips /teddycloud
}Then open https://proxy/teddycloud/web/. The prefix itself must not contain a /web segment.
As the img_unknown.png is part of the teddycloud server, normally it's not shown in the running dev environment. To solve that you can do the following:
-
Just add
img_unknown.pngto the/publicfolder. -
Restart the dev environment.
After these steps, the img_unknown.png should be shown. The file is already part of the file .gitignore so you do not have to care about accidentally committing this file.
Sometimes it happens, that the dev environment unexpectedly crashes. Even after a new start it can crash immediately. To solve that problem, you can delete the node_modules folder and call npm install again. Then the node_modules will be reloaded and you should be able to restart the dev environment.
The GitHub Actions workflow uses npm ci instead of npm install.
This is intentional: npm ci performs a clean, reproducible install and fails if package.json and package-lock.json are not in sync.
If CI fails with an error like:
npm ci can only install packages when your package.json and package-lock.json are in sync
regenerate the lockfile locally:
rm -rf node_modules package-lock.json
npm install
npm ciIf npm ci succeeds locally, commit the regenerated package-lock.json.
Ideally, the TeddyCloudApi.ts should be generated using OpenApiTools with the swagger.yaml. However, it was actually changed manually and new API functions were added directly. This should be revised in the future. Until then, you should NOT generate the API with the OpenApiTools, as this will break the frontend.
Due to security reasons we removed "@openapitools/openapi-generator-cli" from the devDependencies completely. If you want to refactor the manually changed api functions and bring back support for OpenApiTools, add it and the following in the package.json:
"scripts": {
...
"api:generate": "rm -rf ./src/api && openapi-generator-cli generate -i ./api/swagger.yaml -g typescript-fetch -o ./src/api --additional-properties=typescriptThreePlus=true"
...
}This project was bootstrapped with Vite.
You have node.js and python installed.
This project uses Prettier to keep TypeScript, TSX, JavaScript, JSON, CSS and Markdown formatting consistent.
Before opening a pull request, run:
npm run format:checkIf formatting issues are reported, fix them with:
npm run formatThen commit the changed files.
The CI workflow runs the same formatting check (and additionally a production build) on pull requests, as well as on pushes to master and develop.
Before opening a pull request, run:
npm run translation:checkThe check uses English (en) as the reference language. English (en), German (de), French (fr) and Spanish (es) are actively maintained and must contain all required translation keys.
Additional languages are optional to maintain. Missing keys in optional languages are reported as warnings only.
The check also reports statically used translation keys that are missing from en.json. Dynamically constructed translation keys cannot be resolved reliably and are therefore ignored by this source-code check.
sudo apt install python3-dotenv-cli
pip install python-dotenv
followed by
pip install "python-dotenv[cli]"
You need to install cross-env:
npm install --save-dev cross-env
In the project directory, you can run:
Formats the project using Prettier.
npm run formatUse this before committing if files were changed.
Checks whether all files match the configured Prettier formatting rules.
npm run format:checkThis command does not modify files. It is also used by GitHub Actions to verify formatting in pull requests.
If the check fails locally, run:
npm run formatThen commit the formatted files.
Checks the translation files and statically used translation keys.
npm run translation:checkEnglish (en) is used as the reference language. German (de), French (fr) and Spanish (es) are actively maintained and must contain all required translation keys.
Additional languages are optional to maintain. Missing keys in these languages are reported as warnings only.
Runs the app in the development mode. Open http://localhost:3000 to view the http variant in the browser. Open https://localhost:3443 to view the https variant in the browser.
The page will reload if you make edits. You will also see any lint errors in the console.
If you changed the default ports, adapt the links above accordingly.
Builds the app for production to the build folder.
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes. Your app is ready to be deployed!
See the section about deployment for more information.
After building the app, you can test the optimized (minified) production build using this command. Unlike React’s development mode, which enables features such as hot reloading, detailed error overlays, and repeated API calls for strict mode checks, the preview mode runs the app exactly as it will behave in production.
This means:
- API calls are executed only once (no double-invocation from React Strict Mode)
- Performance reflects the real-world production setup
- You can verify that your build works correctly before deployment
You can learn more in the Vite documentation.
To learn React, check out the React documentation.
The Open Toniebox Guide ([teddycloud]/tonieboxes/boxsetup/openboxguide / src\pages\tonieboxes\boxsetup\OpenBoxGuidePage.tsx) is based on the following two excellent guides from iFixIt.com:
- [iFixIt[1]] Toniebox Opening Procedure
- [iFixIt[2]] Toniebox Teardown
Both guides were originally written and illustrated by Tobias Isakeit, who also created all the images used here.
Special thanks to Tobias for providing such clear and detailed instructions!
The icons used are from here:
Thanks for the original authors for these great icons.
The country flags are derived from here:
Thanks to Nikolay Kuchumov for creating this flag collection!