diff --git a/.eslintignore b/.eslintignore index b6dff305c70d..d098f740f0cb 100644 --- a/.eslintignore +++ b/.eslintignore @@ -45,9 +45,6 @@ packages/manager-tools/manager-vite-config packages/manager-tools/manager-pm packages/manager-tools/manager-muk-cli packages/manager/modules/backup-agent -packages/manager/apps/backup-vaults -packages/manager/apps/backup-services -packages/manager/apps/backup-billing packages/manager/apps/hpc-backup-agent-iaas packages/manager/apps/bmc-backup-agent-baremetal packages/manager/apps/pci-instances diff --git a/packages/manager/apps/backup-billing/README.md b/packages/manager/apps/backup-billing/README.md deleted file mode 100644 index 8bfa7cc89fd1..000000000000 --- a/packages/manager/apps/backup-billing/README.md +++ /dev/null @@ -1,186 +0,0 @@ -# backup-billing — OVHcloud Manager Application - -> Universe: **HostedPrivatedCloud / HostedPrivatedCloud** - -## Overview - -This application is a single-page React app integrated into the OVHcloud Manager ecosystem. -It is fully **flavor-driven**, meaning routing, tracking, and API configuration are centralized in [`src/App.constants.ts`](src/App.constants.ts). -The same codebase supports multiple product universes (PCI, Hub, Web, Zimbra) by changing only constants. - ---- - -## ⚙ Configuration - -### `src/App.constants.ts` - -Single source of truth for app identity, API strategy, feature flags, and tracking. Tokens like `app-gen-test` and `Hub` are filled by the generator. - -```ts -export const appName = 'backup-billing'; - -export const APP_FEATURES = { - // API strategies - listingApi: 'v2' as ListingApi, // 'v6Iceberg' | 'v2' | 'v6' - dashboardApi: 'v2' as DashboardApi, // 'v6' | 'v2' - - // Flavor + routing - isPci: 'false', - routeFlavor: 'generic' as const, // 'pci' | 'generic' | 'platformParam' - basePrefix: '', // optional shell prefix - serviceParam: 'id', // service route param (no ':' in final URL) - platformParam: 'id', // platform route param - appSlug: appName, // for PCI, use short slug (e.g. "billing") - - // Tracking - tracking: { - level2ByRegion: { - EU: { level2: '120' }, - CA: { level2: '120' }, - US: { level2: '120' }, - }, - universe: 'HostedPrivatedCloud', - subUniverse: 'HostedPrivatedCloud', - appNameForTracking: appName, - }, -} as const; -``` - -Changing these values updates: -- **Root route** computation (see Routing below) -- **Tracking constants** (`LEVEL2`, `UNIVERSE`, `SUB_UNIVERSE`, `APP_NAME`) -- **API strategy** for onboarding and listing - ---- - -## 📍 Routing - -Route helpers and constants live in [`src/routes/Routes.utils.ts`](src/routes/Routes.utils.ts). -The **root path** is computed from `APP_FEATURES.routeFlavor` (optional `basePrefix` is prepended): - -- **'pci'** → `/[basePrefix]/pci/projects/:projectId/{appSlug}` -- **'generic'** (default) → `/[basePrefix]/{appSlug}` - -High-level routes (relative to the root) are exposed via `urls`: -```ts -export const urls = { - root: getRoot(), // flavor-aware root - dashboard: `dashboard/:serviceName?`, - onboarding: 'onboarding', -} as const; -``` - -**Dashboard subroutes** are centralized to avoid circular imports: -```ts -export const subRoutes = { - overview: '', // default tab - settings: 'settings', - ...(isPci ? { quota: 'quota' } : {}), -} as const; -``` - -The route tree is defined in [`src/routes/Routes.tsx`](src/routes/Routes.tsx) using `React.lazy` and integrates the Manager `ErrorBoundary` (with `redirectionApp` from `Routes.utils.ts`). - ---- - -## 📊 Tracking Constants - -Defined in [`src/Tracking.constants.ts`](src/Tracking.constants.ts) and **resolved from `App.constants.ts`**: - -```ts -export const LEVEL2 = { - EU: { config: { level2: APP_FEATURES.tracking.level2ByRegion.EU.level2 } }, - CA: { config: { level2: APP_FEATURES.tracking.level2ByRegion.CA.level2 } }, - US: { config: { level2: APP_FEATURES.tracking.level2ByRegion.US.level2 } }, -} as const; - -export const UNIVERSE = APP_FEATURES.tracking.universe; -export const SUB_UNIVERSE = APP_FEATURES.tracking.subUniverse; -export const APP_NAME = APP_FEATURES.tracking.appNameForTracking ?? appName; -``` - -These values are injected into the shell from `src/index.tsx`. - ---- - -## 🔌 API Layer (Facade) - -**Where:** `src/data/api/commons` and `src/data/api/hooks` - -- `Client.api.ts` — typed facade over `@ovh-ux/manager-core-api` (v2/v6, Iceberg helpers) -- `Client.utils.ts` — route interpolation & normalization (e.g., `resolveListingRoute()`) -- `useResources.ts` — hook facade that selects the right listing strategy based on `APP_FEATURES.listingApi` - -**Examples** - -Listing (one page via Iceberg v6 / v2 / plain v6 — chosen by `APP_FEATURES.listingApi`): -```ts -import { getListingPage } from '@/data/api/commons/Client.api'; - -const { data, totalCount, status } = await getListingPage({ - page: 1, - pageSize: 50, - sortBy: 'creationDate', - sortDesc: true, - // optional: route override, filters, cursor (for v2), etc. -}); -``` - -Onboarding (with mock fallback when `API_DATA_MODE === 'mock'`): -```ts -import { getOnboardingConfig } from '@/data/api/commons/Client.api'; - -const config = await getOnboardingConfig(); -``` - -If you need to work directly with Iceberg helpers: -```ts -import { fetchIcebergV6, fetchIcebergV2 } from '@ovh-ux/manager-core-api'; -``` - ---- - -## 🚀 Development - -From the root: - -```bash -# Install dependencies -yarn install - -# Start dev server (Vite) -yarn start - -# Build for production -yarn build - -# Lint -yarn lint:modern -yarn lint:modern:fix - -# Test -yarn test -yarn test:coverage -``` - -The app uses **hash-based routing**. Open the dev server URL printed by Vite (default `http://localhost:5173/`) and navigate under: -``` -#/[flavor-aware root from Routes.utils.ts] -``` -For example (PCI flavor): -``` -#/pci/projects/:projectId/app-gen-test -``` - ---- - -## 📚 Useful Links - -- Manager React Shell Client: https://github.com/ovh/manager -- React Router Docs: https://reactrouter.com/ -- Iceberg API Guide: https://github.com/ovh/manager-core-api -- OVHcloud Public API Explorer: https://api.ovh.com/ - ---- - -**Generated with ❤️ by OVHcloud Manager App Generator** diff --git a/packages/manager/apps/backup-billing/eslint.config.mjs b/packages/manager/apps/backup-billing/eslint.config.mjs deleted file mode 100644 index 9e851d7f3dae..000000000000 --- a/packages/manager/apps/backup-billing/eslint.config.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import { prettierEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/prettier'; - -import { javascriptEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/javascript'; -import { typescriptEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/typescript'; -import { reactEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/react'; -import { a11yEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/a11y'; -import { - complexityJsxTsxConfig, - complexityTsJsConfig, -} from '@ovh-ux/manager-static-analysis-kit/eslint/complexity'; -import { htmlEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/html'; -import { cssEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/css'; -import { tailwindJsxConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/tailwind-jsx'; -import { tanStackQueryEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/tanstack'; -import { vitestEslintConfig } from '@ovh-ux/manager-static-analysis-kit/eslint/tests'; -import { tailwindSyntax } from '@eslint/css/syntax'; - -export default [ - javascriptEslintConfig, - typescriptEslintConfig, - reactEslintConfig, - a11yEslintConfig, - htmlEslintConfig, - tailwindJsxConfig, - tanStackQueryEslintConfig, - vitestEslintConfig, - prettierEslintConfig, - complexityJsxTsxConfig, - complexityTsJsConfig, - { - ...cssEslintConfig, - files: ['**/*.css', '**/*.scss'], - languageOptions: { - ...cssEslintConfig.languageOptions, - customSyntax: tailwindSyntax, - }, - rules: { - ...cssEslintConfig.rules, - 'css/no-invalid-properties': 'off', - }, - }, -]; diff --git a/packages/manager/apps/backup-billing/index.html b/packages/manager/apps/backup-billing/index.html deleted file mode 100644 index ec4e92d05007..000000000000 --- a/packages/manager/apps/backup-billing/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - OVHcloud - - - - - -
- - - diff --git a/packages/manager/apps/backup-billing/package.json b/packages/manager/apps/backup-billing/package.json deleted file mode 100644 index 6ea26d61f12b..000000000000 --- a/packages/manager/apps/backup-billing/package.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "name": "@ovh-ux/manager-backup-billing-app", - "version": "0.0.0", - "private": true, - "description": "OVHcloud Backup Billing", - "repository": { - "type": "git", - "url": "git+https://github.com/ovh/manager.git", - "directory": "packages/manager/apps/backup-billing" - }, - "license": "BSD-3-Clause", - "author": "OVH SAS", - "scripts": { - "build": "tsc && vite build", - "lint:modern": "manager-lint --config eslint.config.mjs ./src", - "lint:modern:fix": "manager-lint --fix --config eslint.config.mjs ./src", - "start": "vite", - "test": "manager-test run", - "test:coverage": "manager-test run --coverage" - }, - "dependencies": { - "@ovh-ux/manager-common-translations": "*", - "@ovh-ux/manager-config": "*", - "@ovh-ux/manager-core-api": "*", - "@ovh-ux/manager-core-utils": "*", - "@ovh-ux/manager-pci-common": "^2.4.2", - "@ovh-ux/manager-react-components": "^2.36.0", - "@ovh-ux/manager-react-core-application": "*", - "@ovh-ux/manager-react-shell-client": "*", - "@ovh-ux/request-tagger": "*", - "@ovh-ux/shell": "^4.5.7", - "@ovhcloud/ods-components": "^18.6.2", - "@ovhcloud/ods-themes": "^18.6.2", - "@tanstack/react-query": "^5.51.21", - "axios": "^1.1.2", - "clsx": "^1.2.1", - "element-internals-polyfill": "^3.0.0", - "i18next": "^23.8.2", - "i18next-http-backend": "^2.4.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-i18next": "^14.0.5", - "react-router-dom": "^6.3.0", - "tailwindcss": "^3.4.4" - }, - "devDependencies": { - "@ovh-ux/manager-static-analysis-kit": "*", - "@ovh-ux/manager-tests-setup": "latest", - "@ovh-ux/manager-vite-config": "*", - "@tanstack/react-query-devtools": "^5.51.21" - }, - "regions": [ - "CA", - "EU", - "US" - ], - "universes": [ - "Baremetal", - "HostedPrivatedCloud" - ] -} diff --git a/packages/manager/apps/backup-billing/postcss.config.js b/packages/manager/apps/backup-billing/postcss.config.js deleted file mode 100644 index 12a703d900da..000000000000 --- a/packages/manager/apps/backup-billing/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_de_DE.json b/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_de_DE.json deleted file mode 100644 index 85b20d723b41..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_de_DE.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "home": "Start", - "order": "Bestellen", - "back": "Zurück", - "title": "Dashboard", - "email": "E-Mail", - "subject": "Betreff", - "message": "Nachricht", - "submitButton": "Absenden", - "successMessage": "Ihre Anfrage wurde erfolgreich gesendet.", - "errors": { - "emailRequired": "Die E-Mail-Adresse muss angegeben werden.", - "emailInvalid": "Bitte geben Sie eine gültige E-Mail-Adresse ein.", - "subjectRequired": "Der Betreff muss angegeben werden.", - "messageRequired": "Es muss eine Nachricht angegeben werden." - }, - "general-information": "Allgemeine Informationen", - "help": "Hilfe" -} diff --git a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_en_GB.json b/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_en_GB.json deleted file mode 100644 index 8e596f50f515..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_en_GB.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "home": "Home", - "order": "Order", - "back": "Back", - "title": "Dashboard", - "email": "Email address", - "subject": "Subject", - "message": "Message", - "submitButton": "Send", - "successMessage": "Your request has been sent.", - "errors": { - "emailRequired": "You need to enter an email address", - "emailInvalid": "Please enter a valid email address.", - "subjectRequired": "You need to enter a subject.", - "messageRequired": "You need to write a message." - }, - "general-information": "General information", - "help": "Help" -} diff --git a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_es_ES.json b/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_es_ES.json deleted file mode 100644 index 89987c9a767e..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_es_ES.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "home": "Inicio", - "order": "Contratar", - "back": "Volver", - "title": "Dashboard", - "email": "Email", - "subject": "Asunto", - "message": "Mensaje", - "submitButton": "Enviar", - "successMessage": "Su solicitud se ha enviado correctamente.", - "errors": { - "emailRequired": "La dirección de correo es obligatoria.", - "emailInvalid": "Introduzca una dirección de correo electrónico válida.", - "subjectRequired": "El asunto es obligatorio.", - "messageRequired": "El mensaje es obligatorio." - }, - "general-information": "Información general", - "help": "Ayuda" -} diff --git a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_fr_CA.json b/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_fr_CA.json deleted file mode 100644 index 4606bf80bc46..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_fr_CA.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "home": "Accueil", - "order": "Commander", - "back": "Retour", - "title": "Dashboard", - "email": "E-mail", - "subject": "Objet", - "message": "Message", - "submitButton": "Envoyer", - "successMessage": "Votre demande a été envoyée avec succès.", - "errors": { - "emailRequired": "L’e-mail est obligatoire.", - "emailInvalid": "Veuillez entrer une adresse e-mail valide.", - "subjectRequired": "L’objet est obligatoire.", - "messageRequired": "Le message est obligatoire." - }, - "general-information": "Informations générales", - "help": "Aide" -} diff --git a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_fr_FR.json b/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_fr_FR.json deleted file mode 100644 index 4606bf80bc46..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_fr_FR.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "home": "Accueil", - "order": "Commander", - "back": "Retour", - "title": "Dashboard", - "email": "E-mail", - "subject": "Objet", - "message": "Message", - "submitButton": "Envoyer", - "successMessage": "Votre demande a été envoyée avec succès.", - "errors": { - "emailRequired": "L’e-mail est obligatoire.", - "emailInvalid": "Veuillez entrer une adresse e-mail valide.", - "subjectRequired": "L’objet est obligatoire.", - "messageRequired": "Le message est obligatoire." - }, - "general-information": "Informations générales", - "help": "Aide" -} diff --git a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_it_IT.json b/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_it_IT.json deleted file mode 100644 index 2ef9e769d45f..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_it_IT.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "home": "Home page", - "order": "Ordinare", - "back": "Indietro", - "title": "Dashboard", - "email": "Email", - "subject": "Oggetto", - "message": "Messaggio", - "submitButton": "Inviare", - "successMessage": "La richiesta è stata inviata correttamente.", - "errors": { - "emailRequired": "L'email è obbligatoria.", - "emailInvalid": "Inserisci un indirizzo email valido.", - "subjectRequired": "L'oggetto è obbligatorio.", - "messageRequired": "Il messaggio è obbligatorio." - }, - "general-information": "Informazioni generali", - "help": "Aiuto" -} diff --git a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_pl_PL.json b/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_pl_PL.json deleted file mode 100644 index c84b08bd4bc0..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_pl_PL.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "home": "Strona główna", - "order": "Zamów", - "back": "Powrót", - "title": "Dashboard", - "email": "Adres e-mail", - "subject": "Temat", - "message": "Wiadomość", - "submitButton": "Wyślij", - "successMessage": "Twoja wiadomość została wysłana.", - "errors": { - "emailRequired": "Wpisz adres e-mail.", - "emailInvalid": "Wpisz poprawny adres e-mail.", - "subjectRequired": "Wpisz tytuł wiadomości.", - "messageRequired": "Wpisz wiadomość." - }, - "general-information": "Informacje ogólne", - "help": "Pomoc" -} diff --git a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_pt_PT.json b/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_pt_PT.json deleted file mode 100644 index fbe89c2b2e46..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/dashboard/Messages_pt_PT.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "home": "Página inicial", - "order": "Encomendar", - "back": "Retroceder", - "title": "Dashboard", - "email": "E-mail", - "subject": "Objeto", - "message": "Mensagem", - "submitButton": "Enviar", - "successMessage": "O seu pedido foi enviado com sucesso.", - "errors": { - "emailRequired": "O e-mail é obrigatório.", - "emailInvalid": "Introduza um endereço de correio eletrónico válido.", - "subjectRequired": "O objeto é obrigatório.", - "messageRequired": "A mensagem é obrigatória." - }, - "general-information": "Informações gerais", - "help": "Ajuda" -} diff --git a/packages/manager/apps/backup-billing/public/translations/listing/Messages_de_DE.json b/packages/manager/apps/backup-billing/public/translations/listing/Messages_de_DE.json deleted file mode 100644 index ea71b7f3beaf..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/listing/Messages_de_DE.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "home": "Start", - "order": "Bestellen", - "open": "Dashboard öffnen", - "title": "Liste" -} diff --git a/packages/manager/apps/backup-billing/public/translations/listing/Messages_en_GB.json b/packages/manager/apps/backup-billing/public/translations/listing/Messages_en_GB.json deleted file mode 100644 index 6f8b81ad0a29..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/listing/Messages_en_GB.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "home": "Home", - "order": "Order", - "open": "Open dashboard", - "title": "List" -} diff --git a/packages/manager/apps/backup-billing/public/translations/listing/Messages_es_ES.json b/packages/manager/apps/backup-billing/public/translations/listing/Messages_es_ES.json deleted file mode 100644 index b6afda5e81a1..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/listing/Messages_es_ES.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "home": "Inicio", - "order": "Contratar", - "open": "Abrir el panel de control", - "title": "Lista" -} diff --git a/packages/manager/apps/backup-billing/public/translations/listing/Messages_fr_CA.json b/packages/manager/apps/backup-billing/public/translations/listing/Messages_fr_CA.json deleted file mode 100644 index 154cec73e5b5..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/listing/Messages_fr_CA.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "home": "Accueil", - "order": "Commander", - "open": "Ouvrir le tableau de bord", - "title": "Liste" -} diff --git a/packages/manager/apps/backup-billing/public/translations/listing/Messages_fr_FR.json b/packages/manager/apps/backup-billing/public/translations/listing/Messages_fr_FR.json deleted file mode 100644 index 2d0c96578061..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/listing/Messages_fr_FR.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "home": "Accueil", - "order": "Commander", - "open": "Ouvrir le tableau de bord", - "title": "Facturation" -} diff --git a/packages/manager/apps/backup-billing/public/translations/listing/Messages_it_IT.json b/packages/manager/apps/backup-billing/public/translations/listing/Messages_it_IT.json deleted file mode 100644 index 725cc19a92a8..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/listing/Messages_it_IT.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "home": "Home page", - "order": "Ordinare", - "open": "Aprire la dashboard", - "title": "Lista" -} diff --git a/packages/manager/apps/backup-billing/public/translations/listing/Messages_pl_PL.json b/packages/manager/apps/backup-billing/public/translations/listing/Messages_pl_PL.json deleted file mode 100644 index f9bd28bf2c7e..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/listing/Messages_pl_PL.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "home": "Strona główna", - "order": "Zamów", - "open": "Otwórz dashboard", - "title": "Lista" -} diff --git a/packages/manager/apps/backup-billing/public/translations/listing/Messages_pt_PT.json b/packages/manager/apps/backup-billing/public/translations/listing/Messages_pt_PT.json deleted file mode 100644 index 51bf0357ef42..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/listing/Messages_pt_PT.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "home": "Página inicial", - "order": "Encomendar", - "open": "Abrir painel", - "title": "Lista" -} diff --git a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_de_DE.json b/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_de_DE.json deleted file mode 100644 index 66f7ad74be8f..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_de_DE.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "title_fallback": "Willkommen bei app-gen-test", - "description_part1": "Entdecken Sie, wie Sie app-gen-test, ddd optimal nutzen können.", - "description_part2": "Befolgen Sie unsere Guides für einen schnellen Einstieg.", - "guides": { - "guide1": { - "category": "Entdecken", - "title": "Erste Schritte mit app-gen-test", - "description": "Lernen Sie die Grundlagen für den Einstieg.", - "cta": "Jetzt entdecken" - }, - "guide2": { - "category": "Tutorials", - "title": "Umgebung konfigurieren", - "description": "Folgen Sie unserem Guide Schritt für Schritt.", - "cta": "Zum Tutorial" - }, - "guide3": { - "category": "FAQ", - "title": "Häufig gestellte Fragen", - "description": "Finden Sie Antworten auf häufig gestellte Fragen.", - "cta": "FAQs lesen" - } - } -} diff --git a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_en_GB.json b/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_en_GB.json deleted file mode 100644 index 174c4be2d71c..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_en_GB.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "title_fallback": "Welcome to app-gen-test", - "description_part1": "Find out how to get the most out of app-gen-test, ddd.", - "description_part2": "Follow our guides to get started quickly.", - "guides": { - "guide1": { - "category": "Discovery", - "title": "Get started with app-gen-test", - "description": "Learn the basics to get started.", - "cta": "Get started" - }, - "guide2": { - "category": "Tutorials", - "title": "Configure your environment", - "description": "Follow our step-by-step guide.", - "cta": "See tutorial" - }, - "guide3": { - "category": "FAQs", - "title": "Frequently asked questions", - "description": "Find answers to frequently asked questions.", - "cta": "Read the FAQ" - } - } -} diff --git a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_es_ES.json b/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_es_ES.json deleted file mode 100644 index c23444209c08..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_es_ES.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "title_fallback": "Bienvenido/a a app-gen-test", - "description_part1": "Descubra cómo sacar el máximo partido de app-gen-test, ddd.", - "description_part2": "Siga nuestras guías para empezar rápidamente.", - "guides": { - "guide1": { - "category": "Discovery", - "title": "Empezar con app-gen-test", - "description": "Descubra los aspectos clave para empezar.", - "cta": "Descubrir" - }, - "guide2": { - "category": "Guías", - "title": "Configurar su entorno", - "description": "Siga nuestra guía paso a paso.", - "cta": "Ver el tutorial" - }, - "guide3": { - "category": "FAQ", - "title": "Preguntas frecuentes", - "description": "Encuentre la respuesta a sus preguntas.", - "cta": "Leer las FAQ" - } - } -} diff --git a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_fr_CA.json b/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_fr_CA.json deleted file mode 100644 index 4eadd715ae59..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_fr_CA.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "title_fallback": "Bienvenue sur app-gen-test", - "description_part1": "Découvrez comment tirer le meilleur parti de app-gen-test, ddd.", - "description_part2": "Suivez nos guides pour démarrer rapidement.", - "guides": { - "guide1": { - "category": "Découverte", - "title": "Commencer avec app-gen-test", - "description": "Apprenez les bases pour démarrer.", - "cta": "Découvrir" - }, - "guide2": { - "category": "Tutoriels", - "title": "Configurer votre environnement", - "description": "Suivez notre guide pas à pas.", - "cta": "Voir le tutoriel" - }, - "guide3": { - "category": "FAQ", - "title": "Questions fréquentes", - "description": "Trouvez les réponses aux questions courantes.", - "cta": "Lire la FAQ" - } - } -} diff --git a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_fr_FR.json b/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_fr_FR.json deleted file mode 100644 index 4eadd715ae59..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_fr_FR.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "title_fallback": "Bienvenue sur app-gen-test", - "description_part1": "Découvrez comment tirer le meilleur parti de app-gen-test, ddd.", - "description_part2": "Suivez nos guides pour démarrer rapidement.", - "guides": { - "guide1": { - "category": "Découverte", - "title": "Commencer avec app-gen-test", - "description": "Apprenez les bases pour démarrer.", - "cta": "Découvrir" - }, - "guide2": { - "category": "Tutoriels", - "title": "Configurer votre environnement", - "description": "Suivez notre guide pas à pas.", - "cta": "Voir le tutoriel" - }, - "guide3": { - "category": "FAQ", - "title": "Questions fréquentes", - "description": "Trouvez les réponses aux questions courantes.", - "cta": "Lire la FAQ" - } - } -} diff --git a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_it_IT.json b/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_it_IT.json deleted file mode 100644 index 3fce8bf80970..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_it_IT.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "title_fallback": "Benvenuto su app-gen-test", - "description_part1": "Scopri come ottenere il massimo da app-gen-test, ddd.", - "description_part2": "Segui le nostre guide per iniziare rapidamente.", - "guides": { - "guide1": { - "category": "Discovery", - "title": "Iniziare a utilizzare app-gen-test", - "description": "Concetti base per iniziare.", - "cta": "Scopri" - }, - "guide2": { - "category": "Tutorial", - "title": "Configurare il tuo ambiente", - "description": "Segui la nostra guida passo per passo.", - "cta": "Guarda il tutorial" - }, - "guide3": { - "category": "FAQ", - "title": "Domande frequenti", - "description": "Trova le risposte alle domande più frequenti.", - "cta": "Leggi le FAQ" - } - } -} diff --git a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_pl_PL.json b/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_pl_PL.json deleted file mode 100644 index e7d87d266270..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_pl_PL.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "title_fallback": "Witamy w app-gen-test", - "description_part1": "Dowiedz się, jak najlepiej wykorzystać app-gen-test, ddd.", - "description_part2": "Zapoznaj się z naszymi przewodnikami, aby szybko wystartować.", - "guides": { - "guide1": { - "category": "Pierwsze kroki", - "title": "Rozpocznij pracę z app-gen-test", - "description": "Poznaj podstawy i przejdź do działania.", - "cta": "Sprawdź" - }, - "guide2": { - "category": "Tutoriale", - "title": "Konfiguracja środowiska", - "description": "Postępuj zgodnie z instrukcjami z naszego przewodnika", - "cta": "Sprawdź tutorial" - }, - "guide3": { - "category": "FAQ", - "title": "Najczęściej zadawane pytania", - "description": "Znajdź odpowiedzi na często zadawane pytania.", - "cta": "Przeczytaj FAQ" - } - } -} diff --git a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_pt_PT.json b/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_pt_PT.json deleted file mode 100644 index 07c7880ef0ee..000000000000 --- a/packages/manager/apps/backup-billing/public/translations/onboarding/Messages_pt_PT.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "title_fallback": "Bem-vindo ao app-gen-test", - "description_part1": "Saiba como tirar o máximo partido da app-gen-test, ddd.", - "description_part2": "Siga os nossos guias para começar rapidamente.", - "guides": { - "guide1": { - "category": "Descoberta", - "title": "Começar com app-gen-test", - "description": "Aprenda o básico para começar.", - "cta": "Descobrir" - }, - "guide2": { - "category": "Tutoriais", - "title": "Configurar o seu ambiente", - "description": "Siga o nosso guia passo a passo.", - "cta": "Consultar o tutorial" - }, - "guide3": { - "category": "FAQ", - "title": "Perguntas frequentes", - "description": "Encontre as respostas para as perguntas mais habituais.", - "cta": "Ler a FAQ" - } - } -} diff --git a/packages/manager/apps/backup-billing/src/App.constants.ts b/packages/manager/apps/backup-billing/src/App.constants.ts deleted file mode 100644 index 5095d096d2b1..000000000000 --- a/packages/manager/apps/backup-billing/src/App.constants.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { OnboardingConfigType } from '@/types/Onboarding.type'; - -export const appName = 'backup-billing'; -export const productName = 'OVHcloud Backup Billing'; - -export const AppConfig = { - listing: { - datagrid: { - serviceKey: 'name', - }, - }, - rootLabel: appName, -} as const; - -const docUrl = 'https://docs.ovh.com'; - -export const ONBOARDING_CONFIG: OnboardingConfigType = { - productName: 'backup-billing', - productCategory: 'Public Cloud', - brand: 'OVHcloud', - tiles: [ - { id: 1, key: 'guide1', linkKey: 'discover' }, - { id: 2, key: 'guide2', linkKey: 'tutorial' }, - { id: 3, key: 'guide3', linkKey: 'faq' }, - ], - links: { - discover: docUrl, - tutorial: docUrl, - faq: docUrl, - }, -}; - -export type ListingApi = 'v6Iceberg' | 'v6' | 'v2'; -export type DashboardApi = 'v6' | 'v2'; - -export const APP_FEATURES = { - listingApi: 'v2' as ListingApi, - dashboardApi: 'v2' as DashboardApi, - listingEndpoint: '/domain/alldom', - dashboardEndpoint: '/domain/alldom', - isPci: false, - routeFlavor: 'generic' as const, - basePrefix: 'backup', - serviceParam: 'id', - platformParam: 'id', - appSlug: 'billing', - tracking: { - level2ByRegion: { - EU: { level2: '120' }, - CA: { level2: '120' }, - US: { level2: '120' }, - } as const, - universe: 'HostedPrivatedCloud' as const, - subUniverse: 'HostedPrivatedCloud' as const, - appNameForTracking: appName, - }, -} as const; diff --git a/packages/manager/apps/backup-billing/src/App.tsx b/packages/manager/apps/backup-billing/src/App.tsx deleted file mode 100644 index 4de4224775ba..000000000000 --- a/packages/manager/apps/backup-billing/src/App.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { Suspense, useContext, useEffect } from 'react'; - -import { RouterProvider, createHashRouter, createRoutesFromElements } from 'react-router-dom'; - -import { QueryClientProvider } from '@tanstack/react-query'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; - -import { ShellContext } from '@ovh-ux/manager-react-shell-client'; - -import Routes from '@/routes/Routes'; - -import queryClient from './QueryClient'; - -function App() { - const { shell } = useContext(ShellContext); - - const router = createHashRouter(createRoutesFromElements(Routes)); - - useEffect(() => { - const hidePreloader = async () => { - try { - await shell?.ux?.hidePreloader?.(); - } catch (err) { - console.error('Failed to hide preloader:', err); - } - }; - - void hidePreloader(); - }, [shell]); - - return ( - - {/* Suspense ensures lazy-loaded route components show a fallback UI */} - Loading routes ...}> - - - - {/* React Query Devtools are included for development and debugging */} - - - ); -} - -export default App; diff --git a/packages/manager/apps/backup-billing/src/QueryClient.ts b/packages/manager/apps/backup-billing/src/QueryClient.ts deleted file mode 100644 index 94100b5328ad..000000000000 --- a/packages/manager/apps/backup-billing/src/QueryClient.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { QueryClient } from '@tanstack/react-query'; - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 300_000, - }, - }, -}); - -export default queryClient; diff --git a/packages/manager/apps/backup-billing/src/Tracking.constants.ts b/packages/manager/apps/backup-billing/src/Tracking.constants.ts deleted file mode 100644 index deea3de3ee50..000000000000 --- a/packages/manager/apps/backup-billing/src/Tracking.constants.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { APP_FEATURES, appName } from '@/App.constants'; - -export const LEVEL2 = { - EU: { config: { level2: APP_FEATURES.tracking.level2ByRegion.EU.level2 } }, - CA: { config: { level2: APP_FEATURES.tracking.level2ByRegion.CA.level2 } }, - US: { config: { level2: APP_FEATURES.tracking.level2ByRegion.US.level2 } }, -} as const; - -export const UNIVERSE = APP_FEATURES.tracking.universe; -export const SUB_UNIVERSE = APP_FEATURES.tracking.subUniverse; - -export const APP_NAME = APP_FEATURES.tracking.appNameForTracking ?? appName; diff --git a/packages/manager/apps/backup-billing/src/assets/error-banner-oops.png b/packages/manager/apps/backup-billing/src/assets/error-banner-oops.png deleted file mode 100644 index 413028afad19..000000000000 Binary files a/packages/manager/apps/backup-billing/src/assets/error-banner-oops.png and /dev/null differ diff --git a/packages/manager/apps/backup-billing/src/components/dashboard/general-information/GeneralInformationTile.component.tsx b/packages/manager/apps/backup-billing/src/components/dashboard/general-information/GeneralInformationTile.component.tsx deleted file mode 100644 index b46257a67baf..000000000000 --- a/packages/manager/apps/backup-billing/src/components/dashboard/general-information/GeneralInformationTile.component.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -import { ManagerTile } from '@ovh-ux/manager-react-components'; - -import { GeneralInformationProps } from '@/types/GeneralInfo.type'; - -export default function GeneralInformationTile({ tiles }: GeneralInformationProps) { - return ( -
- {tiles.map((tile) => ( - - {tile.title} - - {tile.items.map((item) => ( - - {item.label} - {item.value} -
-
- ))} -
- ))} -
- ); -} diff --git a/packages/manager/apps/backup-billing/src/components/dashboard/general-information/GeneralInformationTile.spec.tsx b/packages/manager/apps/backup-billing/src/components/dashboard/general-information/GeneralInformationTile.spec.tsx deleted file mode 100644 index 3bdf346140c4..000000000000 --- a/packages/manager/apps/backup-billing/src/components/dashboard/general-information/GeneralInformationTile.spec.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; - -import { render, screen } from '@testing-library/react'; - -import { GeneralInformationProps } from '@/types/GeneralInfo.type'; - -import GeneralInformationTile from './GeneralInformationTile.component'; - -describe('GeneralInformationTile', () => { - const props: GeneralInformationProps = { - tiles: [ - { - title: 'Project Information', - help: 'Extra help tooltip', - items: [ - { id: '1', label: 'Project Name', value: 'My Project' }, - { id: '2', label: 'Region', value: 'GRA' }, - ], - }, - { - title: 'Billing', - items: [{ id: '3', label: 'Plan', value: 'Premium' }], - }, - ], - }; - - it('renders all tile titles', () => { - render(); - - expect(screen.getByText('Project Information')).toBeInTheDocument(); - expect(screen.getByText('Billing')).toBeInTheDocument(); - }); - - it('renders all item labels and values', () => { - render(); - - expect(screen.getByText('Project Name')).toBeInTheDocument(); - expect(screen.getByText('My Project')).toBeInTheDocument(); - - expect(screen.getByText('Region')).toBeInTheDocument(); - expect(screen.getByText('GRA')).toBeInTheDocument(); - - expect(screen.getByText('Plan')).toBeInTheDocument(); - expect(screen.getByText('Premium')).toBeInTheDocument(); - }); - - it('renders labels even if help tooltip is provided', () => { - render(); - - // Just assert the label text shows up - expect(screen.getByText('Project Name')).toBeInTheDocument(); - }); -}); diff --git a/packages/manager/apps/backup-billing/src/data/api/Client.api.ts b/packages/manager/apps/backup-billing/src/data/api/Client.api.ts deleted file mode 100644 index 1d5adc757079..000000000000 --- a/packages/manager/apps/backup-billing/src/data/api/Client.api.ts +++ /dev/null @@ -1,140 +0,0 @@ -import type { AxiosInstance } from 'axios'; - -import { - type IcebergFetchResultV2, - type IcebergFetchResultV6, - fetchIcebergV2, - fetchIcebergV6, - v2 as httpV2, - v6 as httpV6, -} from '@ovh-ux/manager-core-api'; - -import * as AppC from '@/App.constants'; -import { ApiVersion, GetListingParams, JsonRequestOptions } from '@/types/ClientApi.type'; -import { ListingPageResult } from '@/types/Listing.type'; - -const API_CLIENTS: Record = { - v2: httpV2, - v6: httpV6, -}; - -export function getApiClient(version: ApiVersion): AxiosInstance { - return API_CLIENTS[version]; -} - -export async function getJSON( - version: ApiVersion, - route: string, - options: JsonRequestOptions = {}, -): Promise { - const client = getApiClient(version); - const headers = { - ...(options.headers ?? {}), - ...(options.disableCache ? { Pragma: 'no-cache' } : {}), - }; - const { data } = await client.get(route, { params: options.params, headers }); - return data; -} - -export async function postJSON( - version: ApiVersion, - route: string, - body?: unknown, - options: JsonRequestOptions = {}, -): Promise { - const client = getApiClient(version); - const headers = { - ...(options.headers ?? {}), - ...(options.disableCache ? { Pragma: 'no-cache' } : {}), - }; - const { data } = await client.post(route, body, { params: options.params, headers }); - return data; -} - -export async function putJSON( - version: ApiVersion, - route: string, - body?: unknown, - options: JsonRequestOptions = {}, -): Promise { - const client = getApiClient(version); - const headers = { - ...(options.headers ?? {}), - ...(options.disableCache ? { Pragma: 'no-cache' } : {}), - }; - const { data } = await client.put(route, body, { params: options.params, headers }); - return data; -} - -export async function deleteJSON( - version: ApiVersion, - route: string, - options: JsonRequestOptions = {}, -): Promise { - const client = getApiClient(version); - const headers = { - ...(options.headers ?? {}), - ...(options.disableCache ? { Pragma: 'no-cache' } : {}), - }; - const { data } = await client.delete(route, { params: options.params, headers }); - return data; -} - -async function fetchListingV6Iceberg( - route: string, - { page = 1, pageSize = 20, sortBy, sortDesc, filters = [] }: GetListingParams, -): Promise> { - const res: IcebergFetchResultV6 = await fetchIcebergV6({ - route, - page, - pageSize, - sortBy, - sortReverse: !!sortDesc, - filters, - }); - return { - data: res.data, - totalCount: res.totalCount, - status: res.status, - }; -} - -async function fetchListingV2Iceberg( - route: string, - { pageSize = 20, sortBy, sortDesc, filters = [], cursor }: GetListingParams, -): Promise> { - const res: IcebergFetchResultV2 = await fetchIcebergV2({ - route, - pageSize, - sortBy, - sortOrder: sortDesc ? 'DESC' : 'ASC', - filters, - cursor, - }); - return { - data: res.data, - totalCount: Number.POSITIVE_INFINITY, - cursorNext: res.cursorNext, - status: res.status, - }; -} - -async function fetchListingPlainV6(route: string): Promise> { - const data = await getJSON('v6', route); - return { data, totalCount: Array.isArray(data) ? data.length : 0, status: 200 }; -} - -export async function fetchListing( - route: string, - params: GetListingParams = {}, -): Promise> { - const apiChoice = AppC.APP_FEATURES?.listingApi; - - if (apiChoice === 'v6Iceberg') { - return fetchListingV6Iceberg(route, params); - } - if (apiChoice === 'v2') { - return fetchListingV2Iceberg(route, params); - } - return fetchListingPlainV6(route); -} diff --git a/packages/manager/apps/backup-billing/src/data/api/Client.spec.ts b/packages/manager/apps/backup-billing/src/data/api/Client.spec.ts deleted file mode 100644 index b35d8c7c9aed..000000000000 --- a/packages/manager/apps/backup-billing/src/data/api/Client.spec.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { AxiosHeaders, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; - -import * as coreApi from '@ovh-ux/manager-core-api'; - -import * as AppC from '@/App.constants'; - -import { deleteJSON, fetchListing, getJSON, postJSON, putJSON } from './Client.api'; - -// Define a minimal client type instead of `any` -type MockableAxios = Pick; - -// helper to fake axios responses -const makeResponse = (data: T): AxiosResponse => ({ - data, - status: 200, - statusText: 'OK', - headers: {}, - config: { - headers: new AxiosHeaders(), - } as InternalAxiosRequestConfig, -}); - -// eslint-disable-next-line max-lines-per-function -describe('API client helpers', () => { - const mockGet = vi.fn(); - const mockPost = vi.fn(); - const mockPut = vi.fn(); - const mockDelete = vi.fn(); - - beforeEach(() => { - vi.clearAllMocks(); - - const fakeClient: MockableAxios = { - get: mockGet, - post: mockPost, - put: mockPut, - delete: mockDelete, - }; - - // patch both v6 and v2 axios clients with typed cast - Object.assign(coreApi.v6 as unknown as MockableAxios, fakeClient); - Object.assign(coreApi.v2 as unknown as MockableAxios, fakeClient); - }); - - it('getJSON should call axios.get with params and headers', async () => { - mockGet.mockResolvedValue(makeResponse({ foo: 'bar' })); - - const result = await getJSON('v6', '/test', { - params: { a: 1 }, - headers: { h: 'x' }, - disableCache: true, - }); - - expect(result).toEqual({ foo: 'bar' }); - expect(mockGet).toHaveBeenCalledWith('/test', { - params: { a: 1 }, - headers: { h: 'x', Pragma: 'no-cache' }, - }); - }); - - it('postJSON should call axios.post with body and options', async () => { - mockPost.mockResolvedValue(makeResponse({ ok: true })); - - const result = await postJSON('v2', '/route', { foo: 1 }, { params: { q: 2 } }); - expect(result).toEqual({ ok: true }); - expect(mockPost).toHaveBeenCalledWith('/route', { foo: 1 }, { params: { q: 2 }, headers: {} }); - }); - - it('putJSON should call axios.put with body and options', async () => { - mockPut.mockResolvedValue(makeResponse({ updated: true })); - - const result = await putJSON('v6', '/route', { foo: 'bar' }); - expect(result).toEqual({ updated: true }); - expect(mockPut).toHaveBeenCalledWith( - '/route', - { foo: 'bar' }, - { params: undefined, headers: {} }, - ); - }); - - it('deleteJSON should call axios.delete with options', async () => { - mockDelete.mockResolvedValue(makeResponse({ deleted: true })); - - const result = await deleteJSON('v6', '/route'); - expect(result).toEqual({ deleted: true }); - expect(mockDelete).toHaveBeenCalledWith('/route', { - params: undefined, - headers: {}, - }); - }); -}); - -// eslint-disable-next-line max-lines-per-function -describe('fetchListing', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('calls fetchIcebergV6 when APP_FEATURES.listingApi = v6Iceberg', async () => { - vi.spyOn(coreApi, 'fetchIcebergV6').mockResolvedValue({ - data: [{ id: 1 }], - totalCount: 42, - status: 200, - }); - - vi.spyOn(AppC, 'APP_FEATURES', 'get').mockReturnValue({ - ...AppC.APP_FEATURES, - listingApi: 'v6Iceberg', - }); - - const result = await fetchListing('/my/route', { page: 2 }); - expect(coreApi.fetchIcebergV6).toHaveBeenCalledWith( - expect.objectContaining({ route: '/my/route', page: 2 }), - ); - expect(result.data).toEqual([{ id: 1 }]); - expect(result.totalCount).toBe(42); - }); - - it('calls fetchIcebergV2 when APP_FEATURES.listingApi = v2', async () => { - vi.spyOn(coreApi, 'fetchIcebergV2').mockResolvedValue({ - data: [{ id: 1 }], - cursorNext: 'next', - status: 200, - }); - - vi.spyOn(AppC, 'APP_FEATURES', 'get').mockReturnValue({ - ...AppC.APP_FEATURES, - listingApi: 'v2', - }); - - const result = await fetchListing('/my/route', { cursor: 'cur' }); - expect(coreApi.fetchIcebergV2).toHaveBeenCalledWith( - expect.objectContaining({ route: '/my/route', cursor: 'cur' }), - ); - expect(result.data).toEqual([{ id: 1 }]); - expect(result.cursorNext).toBe('next'); - }); - - it('falls back to plain v6 listing if APP_FEATURES.listingApi = v6', async () => { - vi.spyOn(AppC, 'APP_FEATURES', 'get').mockReturnValue({ - ...AppC.APP_FEATURES, - listingApi: 'v6', - }); - - const mockGet = vi.fn().mockResolvedValue(makeResponse([{ id: 'a' }, { id: 'b' }])); - Object.assign(coreApi.v6 as unknown as MockableAxios, { get: mockGet }); - - const result = await fetchListing('/plain'); - expect(mockGet).toHaveBeenCalledWith('/plain', { - params: undefined, - headers: {}, - }); - expect(result.data.length).toBe(2); - expect(result.totalCount).toBe(2); - }); -}); diff --git a/packages/manager/apps/backup-billing/src/data/hooks/useResources.spec.ts b/packages/manager/apps/backup-billing/src/data/hooks/useResources.spec.ts deleted file mode 100644 index fa5117abc64f..000000000000 --- a/packages/manager/apps/backup-billing/src/data/hooks/useResources.spec.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { act, renderHook } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; - -// Small helper to (re)load the module under test with a specific APP_FEATURES.listingApi -async function loadSubject(listingApi: 'v6Iceberg' | 'v2' | 'v6' | undefined) { - vi.resetModules(); - - // Mock APP_FEATURES with the desired listingApi for this test case - vi.doMock('@/App.constants', () => ({ - APP_FEATURES: { listingApi }, - })); - - // Create hook mocks we can tweak per test - const useResourcesIcebergV2 = vi.fn(); - const useResourcesIcebergV6 = vi.fn(); - const useResourcesV6 = vi.fn(); - - vi.doMock('@ovh-ux/manager-react-components', () => ({ - useResourcesIcebergV2, - useResourcesIcebergV6, - useResourcesV6, - })); - - // Import the subject AFTER mocks are set - const mod = await import('./useResources'); // <-- ← adjust to your real path - - return { - ...mod, - mocks: { useResourcesIcebergV2, useResourcesIcebergV6, useResourcesV6 }, - } as unknown as { - useResources: (typeof import('./useResources'))['useResources']; - useListingData: (typeof import('./useResources'))['useListingData']; - mocks: { - useResourcesIcebergV2: ReturnType; - useResourcesIcebergV6: ReturnType; - useResourcesV6: ReturnType; - }; - }; -} - -// eslint-disable-next-line max-lines-per-function -describe('useResources', () => { - it('uses v6Iceberg when APP_FEATURES.listingApi === "v6Iceberg"', async () => { - const { useResources, mocks } = await loadSubject('v6Iceberg'); - - const sample = { - flattenData: [{ id: 1 }], - totalCount: 123, - hasNextPage: false, - isLoading: false, - status: 'success' as const, - }; - mocks.useResourcesIcebergV6.mockReturnValue(sample); - - const { result } = renderHook(() => - useResources<{ id: number }>({ route: '/things', queryKey: ['listing', '/things'] }), - ); - - expect(mocks.useResourcesIcebergV6).toHaveBeenCalledWith({ - route: '/things', - queryKey: ['listing', '/things'], - }); - expect(result.current.flattenData).toEqual([{ id: 1 }]); - expect(result.current.totalCount).toBe(123); - expect(result.current.status).toBe('success'); - }); - - it('uses v2 when APP_FEATURES.listingApi === "v2"', async () => { - const { useResources, mocks } = await loadSubject('v2'); - - const sample = { - flattenData: [{ id: 'a' }], - hasNextPage: true, - fetchNextPage: vi.fn(), - isLoading: true, - status: 'pending' as const, - }; - mocks.useResourcesIcebergV2.mockReturnValue(sample); - - const { result } = renderHook(() => - useResources<{ id: string }>({ route: '/legacy', queryKey: ['listing', '/legacy'] }), - ); - - expect(mocks.useResourcesIcebergV2).toHaveBeenCalled(); - expect(result.current.flattenData).toEqual([{ id: 'a' }]); - // v2 mapping intentionally has no totalCount - expect(result.current.totalCount).toBeUndefined(); - expect(result.current.isLoading).toBe(true); - expect(result.current.hasNextPage).toBe(true); - }); - - it('falls back to v6 when APP_FEATURES.listingApi is "v6" or anything else', async () => { - const { useResources, mocks } = await loadSubject('v6'); - - const sample = { - flattenData: [{ id: 42 }], - totalCount: 1, - hasNextPage: false, - isLoading: false, - status: 'success' as const, - }; - mocks.useResourcesV6.mockReturnValue(sample); - - const { result } = renderHook(() => - useResources<{ id: number }>({ route: '/new', queryKey: ['listing', '/new'], columns: [] }), - ); - - expect(mocks.useResourcesV6).toHaveBeenCalledWith({ - route: '/new', - queryKey: ['listing', '/new'], - columns: [], - }); - expect(result.current.totalCount).toBe(1); - expect(result.current.flattenData).toEqual([{ id: 42 }]); - }); -}); - -// eslint-disable-next-line max-lines-per-function -describe('useListingData', () => { - it('derives items and total from v6Iceberg (uses totalCount when provided)', async () => { - const { useListingData, mocks } = await loadSubject('v6Iceberg'); - - const fetchNextPage = vi.fn(); - mocks.useResourcesIcebergV6.mockReturnValue({ - flattenData: [{ id: 1 }, { id: 2 }], - totalCount: 999, - hasNextPage: true, - fetchNextPage, - isLoading: false, - status: 'success' as const, - }); - - const { result } = renderHook(() => useListingData<{ id: number }>('/things')); - - expect(result.current.items).toEqual([{ id: 1 }, { id: 2 }]); - expect(result.current.total).toBe(999); // uses totalCount when present - expect(result.current.isLoading).toBe(false); - expect(result.current.hasNextPage).toBe(true); - - expect(fetchNextPage).not.toHaveBeenCalled(); - act(() => { - result.current.fetchNextPage?.(); - }); - expect(fetchNextPage).toHaveBeenCalledTimes(1); - }); - - it('derives total from items.length when totalCount is missing (v2)', async () => { - const { useListingData, mocks } = await loadSubject('v2'); - - mocks.useResourcesIcebergV2.mockReturnValue({ - flattenData: [{ id: 'x' }, { id: 'y' }, { id: 'z' }], - hasNextPage: false, - isLoading: true, - status: 'pending' as const, - }); - - const { result } = renderHook(() => useListingData<{ id: string }>('/legacy')); - - expect(result.current.items).toHaveLength(3); - expect(result.current.total).toBe(3); // fallback to items.length - expect(result.current.isLoading).toBe(true); - expect(result.current.hasNextPage).toBe(false); - expect(result.current.fetchNextPage).toBeUndefined(); // no hasNextPage => wrapper not exposed - }); - - it('handles empty/undefined flattenData gracefully', async () => { - const { useListingData, mocks } = await loadSubject('v6'); - - mocks.useResourcesV6.mockReturnValue({ - flattenData: undefined, - totalCount: undefined, - hasNextPage: false, - isLoading: false, - status: 'success' as const, - }); - - const { result } = renderHook(() => useListingData>('/empty')); - - expect(result.current.items).toEqual([]); - expect(result.current.total).toBe(0); - expect(result.current.hasNextPage).toBe(false); - }); -}); diff --git a/packages/manager/apps/backup-billing/src/data/hooks/useResources.ts b/packages/manager/apps/backup-billing/src/data/hooks/useResources.ts deleted file mode 100644 index 87ee6a2c7f30..000000000000 --- a/packages/manager/apps/backup-billing/src/data/hooks/useResources.ts +++ /dev/null @@ -1,123 +0,0 @@ -import React, { useMemo } from 'react'; - -import { - ColumnSort, - useResourcesIcebergV2, - useResourcesIcebergV6, - useResourcesV6, -} from '@ovh-ux/manager-react-components'; - -import { APP_FEATURES } from '@/App.constants'; -import { ResourcesFacadeResult, UseResourcesParams } from '@/types/ClientApi.type'; -import { ListingDataResultType } from '@/types/Listing.type'; - -function mapResponseWithTotal(response: { - flattenData?: T[]; - totalCount?: number; - hasNextPage?: boolean; - fetchNextPage?: () => Promise | void; - isLoading?: boolean; - status?: 'pending' | 'success' | 'error'; - sorting?: ColumnSort | undefined; - setSorting?: React.Dispatch>; - filters?: unknown; - search?: unknown; -}): ResourcesFacadeResult { - return { - flattenData: response.flattenData, - totalCount: response.totalCount, - hasNextPage: response.hasNextPage, - fetchNextPage: response.fetchNextPage, - isLoading: response.isLoading, - status: response.status, - sorting: response.sorting, - setSorting: response.setSorting, - filters: response.filters, - search: response.search, - }; -} - -function mapResponseWithoutTotal(response: { - flattenData?: T[]; - hasNextPage?: boolean; - fetchNextPage?: () => Promise | void; - isLoading?: boolean; - status?: 'pending' | 'success' | 'error'; - sorting?: ColumnSort | undefined; - setSorting?: React.Dispatch>; - filters?: unknown; - search?: unknown; -}): ResourcesFacadeResult { - return { - flattenData: response.flattenData, - hasNextPage: response.hasNextPage, - fetchNextPage: response.fetchNextPage, - isLoading: response.isLoading, - status: response.status, - sorting: response.sorting, - setSorting: response.setSorting, - filters: response.filters, - search: response.search, - }; -} - -/* eslint-disable react-hooks/rules-of-hooks */ -function createResourcesFactory>() { - return { - v6Iceberg: (params: UseResourcesParams): ResourcesFacadeResult => { - const response = useResourcesIcebergV6(params); - return mapResponseWithTotal(response); - }, - v2: (params: UseResourcesParams): ResourcesFacadeResult => { - const response = useResourcesIcebergV2(params); - return mapResponseWithoutTotal(response); - }, - v6: (params: UseResourcesParams): ResourcesFacadeResult => { - const response = useResourcesV6({ - ...params, - columns: params.columns ?? [], - }); - return mapResponseWithTotal(response); - }, - }; -} - -export function useResources = Record>( - params: UseResourcesParams, -): ResourcesFacadeResult { - const resourcesFactory = createResourcesFactory(); - const api = APP_FEATURES.listingApi; - - if (api === 'v6Iceberg') return resourcesFactory.v6Iceberg(params); - if (api === 'v2') return resourcesFactory.v2(params); - return resourcesFactory.v6(params); -} - -export function useListingData = Record>( - route: string, -): ListingDataResultType { - const raw = useResources({ - route, - queryKey: ['listing', route], - }); - - return useMemo>(() => { - const items = raw?.flattenData ?? []; - const total = typeof raw?.totalCount === 'number' ? raw.totalCount : items.length; - - const fetchNextPage = - raw?.hasNextPage && raw?.fetchNextPage - ? () => { - void raw.fetchNextPage?.(); - } - : undefined; - - return { - items, - total, - isLoading: !!raw?.isLoading, - hasNextPage: !!raw?.hasNextPage, - fetchNextPage, - }; - }, [raw]); -} diff --git a/packages/manager/apps/backup-billing/src/hooks/dashboard/useDashboardTabs.spec.tsx b/packages/manager/apps/backup-billing/src/hooks/dashboard/useDashboardTabs.spec.tsx deleted file mode 100644 index 5656a3ea6044..000000000000 --- a/packages/manager/apps/backup-billing/src/hooks/dashboard/useDashboardTabs.spec.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; - -import type { DashboardTabType } from '@/types/Dashboard.type'; - -import { useDashboardTabs } from './useDashboardTabs'; - -// --- Mock react-router-dom --- -vi.mock('react-router-dom', () => ({ - useLocation: () => ({ pathname: '/general-information/123' }), - useParams: () => ({ id: '123' }), -})); - -// --- Mock routes constants --- -vi.mock('@/routes/Routes.constants', () => { - const tabs: DashboardTabType[] = [ - { - name: 'general-information', - title: 'dashboard:general-information', - to: '/general-information/:id', - pathMatchers: [/^\/general-information\/[^/]+$/], - trackingActions: ['click::general-information-tab'], - }, - { - name: 'help', - title: 'dashboard:help', - to: '/help', - pathMatchers: [/\/help$/], - trackingActions: ['click::help-tab'], - }, - ]; - return { DASHBOARD_NAV_TABS: tabs }; -}); - -describe('useDashboardTabs', () => { - it('resolves the :id param in tab "to" field', () => { - const { result } = renderHook(() => useDashboardTabs()); - const generalTab = result.current.find((t) => t.name === 'general-information'); - expect(generalTab).toBeDefined(); - expect(generalTab!.to).toBe('/general-information/123'); - }); -}); diff --git a/packages/manager/apps/backup-billing/src/hooks/dashboard/useDashboardTabs.tsx b/packages/manager/apps/backup-billing/src/hooks/dashboard/useDashboardTabs.tsx deleted file mode 100644 index 2c9bce6361de..000000000000 --- a/packages/manager/apps/backup-billing/src/hooks/dashboard/useDashboardTabs.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useMemo } from 'react'; - -import { useLocation, useParams } from 'react-router-dom'; - -import { DASHBOARD_NAV_TABS } from '@/routes/Routes.constants'; -import { DashboardTabType } from '@/types/Dashboard.type'; - -export function useDashboardTabs(): DashboardTabType[] { - const { pathname } = useLocation(); - const { id } = useParams<{ id: string }>(); - - return useMemo( - () => - DASHBOARD_NAV_TABS.map((tab) => { - const resolvedTo = tab.to.replace(':id', id ?? ''); - return { - ...tab, - to: resolvedTo, - isActive: pathname === resolvedTo || tab.pathMatchers?.some((rx) => rx.test(pathname)), - }; - }), - [pathname, id], - ); -} diff --git a/packages/manager/apps/backup-billing/src/hooks/layout/useApplicationBreadcrumbItems.tsx b/packages/manager/apps/backup-billing/src/hooks/layout/useApplicationBreadcrumbItems.tsx deleted file mode 100644 index 359a8d74acb8..000000000000 --- a/packages/manager/apps/backup-billing/src/hooks/layout/useApplicationBreadcrumbItems.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { useHref } from 'react-router-dom'; - -import { useTranslation } from 'react-i18next'; - -import { BreadcrumbItem } from '@ovh-ux/manager-react-components'; - -import { productName } from '@/App.constants'; -import { urls } from '@/routes/Routes.constants'; - -export const useApplicationBreadcrumbItems = (): BreadcrumbItem[] => { - const { t } = useTranslation(['listing', 'dashboard']); - const listingHref = useHref(urls.listing); - const dashboardHref = useHref(urls.dashboard); - const helpHref = useHref(urls.help); - - const applicationBreadcrumbItems: BreadcrumbItem[] = [ - { - label: productName, - href: listingHref, - }, - { - label: t('dashboard:title'), - href: dashboardHref, - }, - { - label: t('dashboard:help'), - href: helpHref, - }, - ]; - - return applicationBreadcrumbItems; -}; diff --git a/packages/manager/apps/backup-billing/src/hooks/listing/useListingColumns.spec.tsx b/packages/manager/apps/backup-billing/src/hooks/listing/useListingColumns.spec.tsx deleted file mode 100644 index 43b48abc410b..000000000000 --- a/packages/manager/apps/backup-billing/src/hooks/listing/useListingColumns.spec.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { render, renderHook, screen } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; - -import { ListingItemType } from '@/types/Listing.type'; - -import { useListingColumns } from './useListingColumns'; - -// --- Mock translation --- -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ - t: (key: string, defaultValue?: string) => defaultValue ?? key, - }), -})); - -describe('useListingColumns', () => { - type TestRow = ListingItemType; - - it('returns two columns with correct labels', () => { - const { result } = renderHook(() => useListingColumns()); - expect(result.current).toHaveLength(2); - - const [idCol, nameCol] = result.current; - expect(idCol!.label).toBe('common:id'); - expect(nameCol!.label).toBe('common:name'); - }); - - it('renders cells with provided values', () => { - const { result } = renderHook(() => useListingColumns()); - const [idCol, nameCol] = result.current; - - const row: TestRow = { id: '42', name: 'Alice' }; - - render(idCol!.cell(row)); - expect(screen.getByText('42')).toBeInTheDocument(); - - render(nameCol!.cell(row)); - expect(screen.getByText('Alice')).toBeInTheDocument(); - }); - - it('renders fallback values when fields are missing', () => { - const { result } = renderHook(() => useListingColumns()); - const [idCol, nameCol] = result.current; - - const row = {} as TestRow; // missing both id and name - - render(idCol!.cell(row)); - expect(screen.getByText('N/A')).toBeInTheDocument(); // common:na fallback - - render(nameCol!.cell(row)); - expect(screen.getByText('—')).toBeInTheDocument(); // common:empty fallback - }); -}); diff --git a/packages/manager/apps/backup-billing/src/hooks/listing/useListingColumns.tsx b/packages/manager/apps/backup-billing/src/hooks/listing/useListingColumns.tsx deleted file mode 100644 index 660c8a3e6d1b..000000000000 --- a/packages/manager/apps/backup-billing/src/hooks/listing/useListingColumns.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useMemo } from 'react'; - -import { useTranslation } from 'react-i18next'; - -import { OdsText } from '@ovhcloud/ods-components/react'; - -import type { DatagridColumn } from '@ovh-ux/manager-react-components'; - -import { DefaultListingItemType } from '@/types/Listing.type'; - -export function useListingColumns< - T extends DefaultListingItemType = DefaultListingItemType, ->(): DatagridColumn[] { - const { t } = useTranslation(['common', 'status']); - - return useMemo[]>(() => { - const EMPTY = t('common:empty', '—'); - const NA = t('common:na', 'N/A'); - - return [ - { - id: 'id', - label: 'common:id', - isSortable: true, - cell: (row: T): JSX.Element => {row.id ?? NA}, - }, - { - id: 'name', - label: 'common:name', - isSortable: false, - cell: (row: T): JSX.Element => {row.name ?? EMPTY}, - }, - ]; - }, [t]); -} diff --git a/packages/manager/apps/backup-billing/src/hooks/onboarding/useOnboardingData.spec.tsx b/packages/manager/apps/backup-billing/src/hooks/onboarding/useOnboardingData.spec.tsx deleted file mode 100644 index 743df65f71b5..000000000000 --- a/packages/manager/apps/backup-billing/src/hooks/onboarding/useOnboardingData.spec.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import type { ReactNode } from 'react'; - -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook, waitFor } from '@testing-library/react'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; - -import type { OnboardingConfigType } from '@/types/Onboarding.type'; - -import { useGuideLinks, useOnboardingContent } from './useOnboardingData'; - -// --- Mock App.constants --- -vi.mock('@/App.constants', () => { - const mockConfig: OnboardingConfigType = { - productName: 'MyProduct', - productCategory: 'CategoryX', - brand: 'BrandY', - title: 'Welcome to MyProduct', - heroImage: { src: '/hero.png', alt: 'Hero' }, - tiles: [{ id: 1, key: 'discover', linkKey: 'discover' }], - links: { discover: '/discover', tutorial: '/tutorial', faq: '/faq' }, - }; - return { ONBOARDING_CONFIG: mockConfig }; -}); - -// --- Mock translation --- -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ - t: (key: string, opts?: { productName?: string }) => - `${key}${opts?.productName ? `:${opts.productName}` : ''}`, - }), -})); - -// --- Helper wrapper with React Query --- -const createWrapper = - () => - // eslint-disable-next-line react/display-name - ({ children }: { children: ReactNode }) => { - const client = new QueryClient(); - return {children}; - }; - -describe('useOnboardingContent', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('returns onboarding content with hero image and tiles', async () => { - const { result } = renderHook(() => useOnboardingContent(), { wrapper: createWrapper() }); - - await waitFor(() => { - expect(result.current.productName).toBe('MyProduct'); - expect(result.current.productCategory).toBe('CategoryX'); - expect(result.current.brand).toBe('BrandY'); - expect(result.current.heroImage?.src).toBe('/hero.png'); - expect(result.current.tiles).toHaveLength(1); - }); - }); - - it('falls back when productName is missing', async () => { - vi.doMock('@/App.constants', () => ({ - ONBOARDING_CONFIG: { - productCategory: 'X', - brand: 'Y', - tiles: [], - links: {}, - } as unknown as Partial, - })); - - const { result } = renderHook(() => useOnboardingContent(), { wrapper: createWrapper() }); - - await waitFor(() => { - expect(result.current.productName).toContain('onboarding:productDefaultName'); - }); - }); -}); - -describe('useGuideLinks', () => { - it('returns guide links correctly', async () => { - const { result } = renderHook(() => useGuideLinks(), { wrapper: createWrapper() }); - - await waitFor(() => { - expect(result.current.discover).toBe('/discover'); - expect(result.current.tutorial).toBe('/tutorial'); - expect(result.current.faq).toBe('/faq'); - }); - }); -}); diff --git a/packages/manager/apps/backup-billing/src/hooks/onboarding/useOnboardingData.tsx b/packages/manager/apps/backup-billing/src/hooks/onboarding/useOnboardingData.tsx deleted file mode 100644 index 0c639e478bdb..000000000000 --- a/packages/manager/apps/backup-billing/src/hooks/onboarding/useOnboardingData.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useMemo } from 'react'; - -import { useQuery } from '@tanstack/react-query'; -import { useTranslation } from 'react-i18next'; - -import * as AppC from '@/App.constants'; -import type { - OnboardingConfigType, - OnboardingContentType, - OnboardingImage, - OnboardingLinksType, -} from '@/types/Onboarding.type'; - -const QUERY_KEY = ['onboarding-config'] as const; - -async function getOnboardingConfig(): Promise { - return new Promise((resolve) => { - resolve(AppC.ONBOARDING_CONFIG); - }); -} - -export function useOnboardingContent(): OnboardingContentType { - const { t } = useTranslation(['onboarding', 'common']); - const { data } = useQuery({ - queryKey: QUERY_KEY, - queryFn: () => getOnboardingConfig(), - }); - - return useMemo(() => { - const productName = data?.productName ?? t('onboarding:productDefaultName', 'Product'); - - const heroImage: OnboardingImage | undefined = data?.heroImage?.src - ? { - src: data.heroImage.src, - alt: data.heroImage.alt ?? t('onboarding:heroAlt', { productName }), - } - : undefined; - - return { - productName, - productCategory: data?.productCategory, - brand: data?.brand, - title: data?.title, - heroImage, - tiles: Array.isArray(data?.tiles) ? data.tiles : [], - }; - }, [data, t]); -} - -export function useGuideLinks(): OnboardingLinksType { - const { data } = useQuery({ - queryKey: QUERY_KEY, - queryFn: () => getOnboardingConfig(), - }); - - return useMemo( - () => ({ - discover: data?.links?.discover ?? '', - tutorial: data?.links?.tutorial ?? '', - faq: data?.links?.faq ?? '', - }), - [data], - ); -} diff --git a/packages/manager/apps/backup-billing/src/i18n.ts b/packages/manager/apps/backup-billing/src/i18n.ts deleted file mode 100644 index 3dfc6a85f8fe..000000000000 --- a/packages/manager/apps/backup-billing/src/i18n.ts +++ /dev/null @@ -1,27 +0,0 @@ -import i18n from 'i18next'; -import I18NextHttpBackend from 'i18next-http-backend'; -import { initReactI18next } from 'react-i18next'; - -export default async function initI18n(locale = 'fr_FR', availablesLocales = ['fr_FR']) { - await i18n - .use(initReactI18next) - .use(I18NextHttpBackend) - .use({ - type: 'postProcessor', - name: 'normalize', - process: (value: string) => (value ? value.replace(/&/g, '&') : value), - }) - .init({ - lng: locale, - fallbackLng: 'fr_FR', - supportedLngs: availablesLocales, - defaultNS: 'common', - ns: ['common'], // namespaces to load by default - backend: { - loadPath: (lngs: string[], namespaces: string[]) => - `${import.meta.env.BASE_URL}translations/${namespaces[0]}/Messages_${lngs[0]}.json`, - }, - postProcess: 'normalize', - }); - return i18n; -} diff --git a/packages/manager/apps/backup-billing/src/index.scss b/packages/manager/apps/backup-billing/src/index.scss deleted file mode 100644 index a5c684d9dcdd..000000000000 --- a/packages/manager/apps/backup-billing/src/index.scss +++ /dev/null @@ -1,8 +0,0 @@ -@tailwind utilities; - -@import '@ovhcloud/ods-themes/default'; -@import '@ovh-ux/manager-react-components/dist/style.css'; - -html { - font-family: var(--ods-font-family-default); -} diff --git a/packages/manager/apps/backup-billing/src/index.tsx b/packages/manager/apps/backup-billing/src/index.tsx deleted file mode 100644 index 1a4b0f0b63d3..000000000000 --- a/packages/manager/apps/backup-billing/src/index.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; - -import ReactDOM from 'react-dom/client'; - -import { ShellContext, initI18n, initShellContext } from '@ovh-ux/manager-react-shell-client'; - -import { appName } from '@/App.constants'; - -import App from './App'; -import { APP_NAME, LEVEL2, SUB_UNIVERSE, UNIVERSE } from './Tracking.constants'; -import './index.scss'; -import './vite-hmr'; - -const trackingContext = { - chapter1: UNIVERSE, - chapter2: SUB_UNIVERSE, - chapter3: APP_NAME, - appName: APP_NAME, - pageTheme: UNIVERSE, - level2Config: LEVEL2, -}; - -const init = async (appName: string) => { - // Initialize shell context: auth, env, tracking, APIs - const context = await initShellContext(appName, trackingContext); - - // Initialize i18n with default namespaces - await initI18n({ - context, - reloadOnLocaleChange: true, - defaultNS: appName, - ns: ['listing', 'dashboard', 'onboarding'], - }); - - // Configure tracking per region - const region = context.environment.getRegion(); - context.shell.tracking.setConfig(region, LEVEL2); - - // Try loading region-specific runtime config (optional) - try { - await import(`./config-${region}.js`); - } catch { - // No-op when region config is absent - } - - // Mount React app - const rootElement = document.getElementById('root'); - if (!rootElement) { - throw new Error('Root element #root not found'); - } - - ReactDOM.createRoot(rootElement).render( - - - - - , - ); -}; - -// Start the app -void init(appName); diff --git a/packages/manager/apps/backup-billing/src/mocks/locations/locations.handler.ts b/packages/manager/apps/backup-billing/src/mocks/locations/locations.handler.ts deleted file mode 100644 index 0b528ef7b389..000000000000 --- a/packages/manager/apps/backup-billing/src/mocks/locations/locations.handler.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { PathParams } from 'msw'; - -import { Handler } from '@ovh-ux/manager-core-test-utils'; - -import { mockLocations } from '@/mocks/locations/locations'; - -export type TLocationMockParams = { - isLocationError?: boolean; -}; - -export const getLocationMocks = ({ - isLocationError = false, -}: TLocationMockParams = {}): Handler[] => [ - { - url: '/location/:locationName', - response: (_: unknown, params: PathParams) => { - return isLocationError - ? null - : mockLocations.find((location) => location.name === params.locationName); - }, - api: 'v2', - method: 'get', - status: isLocationError ? 500 : 200, - }, -]; diff --git a/packages/manager/apps/backup-billing/src/mocks/locations/locations.ts b/packages/manager/apps/backup-billing/src/mocks/locations/locations.ts deleted file mode 100644 index cc954a871cb6..000000000000 --- a/packages/manager/apps/backup-billing/src/mocks/locations/locations.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { Region } from '@/types/Location.type'; - -export const mockLocations: Region[] = [ - { - code: 'par', - name: 'eu-west-par', - location: 'Europe (France - Paris)', - type: 'REGION-3-AZ', - specificType: 'STANDARD', - cardinalPoint: 'WEST', - openingYear: 2023, - cityName: 'Paris', - cityCode: 'PAR', - cityLatitude: 48.85, - cityLongitude: 2.35, - countryName: 'France', - countryCode: 'FR', - geographyName: 'Europe', - geographyCode: 'eu', - availabilityZones: ['eu-west-par-a', 'eu-west-par-b', 'eu-west-par-c'], - }, - { - code: 'gra', - name: 'eu-west-gra', - location: 'Europe (France - Gravelines)', - type: 'REGION-1-AZ', - specificType: 'STANDARD', - cardinalPoint: 'WEST', - openingYear: 2013, - cityName: 'Gravelines', - cityCode: 'GRA', - cityLatitude: 50.9833333333333, - cityLongitude: 2.13333333333333, - countryName: 'France', - countryCode: 'FR', - geographyName: 'Europe', - geographyCode: 'eu', - availabilityZones: ['eu-west-gra-a'], - }, - { - code: 'rbx', - name: 'eu-west-rbx', - location: 'Europe (France - Roubaix)', - type: 'REGION-1-AZ', - specificType: 'STANDARD', - cardinalPoint: 'WEST', - openingYear: 2007, - cityName: 'Roubaix', - cityCode: 'RBX', - cityLatitude: 50.6833333333333, - cityLongitude: 3.18333333333333, - countryName: 'France', - countryCode: 'FR', - geographyName: 'Europe', - geographyCode: 'eu', - availabilityZones: ['eu-west-rbx-a'], - }, - { - code: 'sbg', - name: 'eu-west-sbg', - location: 'Europe (France - Strasbourg)', - type: 'REGION-1-AZ', - specificType: 'STANDARD', - cardinalPoint: 'WEST', - openingYear: 2017, - cityName: 'Strasbourg', - cityCode: 'SBG', - cityLatitude: 48.5666666666667, - cityLongitude: 7.75, - countryName: 'France', - countryCode: 'FR', - geographyName: 'Europe', - geographyCode: 'eu', - availabilityZones: ['eu-west-sbg-a'], - }, - { - code: 'lim', - name: 'eu-west-lim', - location: 'Europe (Germany - Limburg)', - type: 'REGION-1-AZ', - specificType: 'STANDARD', - cardinalPoint: 'WEST', - openingYear: 2017, - cityName: 'Limburg', - cityCode: 'LIM', - cityLatitude: 48.0333333333333, - cityLongitude: 12.1833333333333, - countryName: 'Germany', - countryCode: 'DE', - geographyName: 'Europe', - geographyCode: 'eu', - availabilityZones: ['eu-west-lim-a'], - }, - { - code: 'waw', - name: 'eu-central-waw', - location: 'Europe (Poland - Warsaw)', - type: 'REGION-1-AZ', - specificType: 'STANDARD', - cardinalPoint: 'CENTRAL', - openingYear: 2016, - cityName: 'Warsaw', - cityCode: 'WAW', - cityLatitude: 50.8833333333333, - cityLongitude: 21.6666666666667, - countryName: 'Poland', - countryCode: 'PL', - geographyName: 'Europe', - geographyCode: 'eu', - availabilityZones: ['eu-central-waw-a'], - }, - { - code: 'eri', - name: 'eu-west-eri', - location: 'Europe (United Kingdom - Erith)', - type: 'REGION-1-AZ', - specificType: 'STANDARD', - cardinalPoint: 'WEST', - openingYear: 2017, - cityName: 'Erith', - cityCode: 'ERI', - cityLatitude: 51.4833333333333, - cityLongitude: 0.183333333333333, - countryName: 'United Kingdom', - countryCode: 'GB', - geographyName: 'Europe', - geographyCode: 'eu', - availabilityZones: ['eu-west-eri-a'], - }, - { - code: 'vin', - name: 'us-east-vin', - location: 'North America (US - East - Vinthill)', - type: 'REGION-1-AZ', - specificType: 'STANDARD', - cardinalPoint: 'EAST', - openingYear: 2018, - cityName: 'Vinthill', - cityCode: 'VIN', - cityLatitude: 38.7333333333333, - cityLongitude: -77.6666666666667, - countryName: 'United States', - countryCode: 'US', - geographyName: 'North america', - geographyCode: 'us', - availabilityZones: ['us-east-vin-a'], - }, - { - code: 'hil', - name: 'us-west-hil', - location: 'North America (US - West - Hillsboro)', - type: 'REGION-1-AZ', - specificType: 'STANDARD', - cardinalPoint: 'WEST', - openingYear: 2018, - cityName: 'Hillsboro', - cityCode: 'HIL', - cityLatitude: 45.5166666666667, - cityLongitude: -122.983333333333, - countryName: 'United States', - countryCode: 'US', - geographyName: 'North america', - geographyCode: 'us', - availabilityZones: ['us-west-hil-a'], - }, - { - code: 'bhs', - name: 'ca-east-bhs', - location: 'North America (Canada - East - Beauharnois)', - type: 'REGION-1-AZ', - specificType: 'STANDARD', - cardinalPoint: 'EAST', - openingYear: 2012, - cityName: 'Beauharnois', - cityCode: 'BHS', - cityLatitude: 45.3, - cityLongitude: -73.8666666666667, - countryName: 'Canada', - countryCode: 'CA', - geographyName: 'North america', - geographyCode: 'ca', - availabilityZones: ['ca-east-bhs-a'], - }, - { - code: 'sgp', - name: 'ap-southeast-sgp', - location: 'Asia Pacific (Singapore -Singapore)', - type: 'REGION-1-AZ', - specificType: 'STANDARD', - cardinalPoint: 'SOUTHEAST', - openingYear: 2016, - cityName: 'Singapore', - cityCode: 'SGP', - cityLatitude: 1.35, - cityLongitude: 103.816666666667, - countryName: 'Singapore', - countryCode: 'SG', - geographyName: 'Asia Pacific', - geographyCode: 'ap', - availabilityZones: ['ap-southeast-sgp-a'], - }, - { - code: 'syd', - name: 'ap-southeast-syd', - location: 'Asia Pacific (Australia - Sydney)', - type: 'REGION-1-AZ', - specificType: 'STANDARD', - cardinalPoint: 'SOUTHEAST', - openingYear: 2016, - cityName: 'Sydney', - cityCode: 'SYD', - cityLatitude: -33.85, - cityLongitude: 151.2, - countryName: 'Australia', - countryCode: 'AU', - geographyName: 'Asia Pacific', - geographyCode: 'ap', - availabilityZones: ['ap-southeast-syd-a'], - }, - { - code: 'rbx-snc', - name: 'eu-west-rbx-snc', - location: 'Europe (France - Roubaix) (snc)', - type: 'REGION-1-AZ', - specificType: 'SNC', - cardinalPoint: 'NORTHWEST', - openingYear: 2007, - cityName: 'Roubaix', - cityCode: 'RBX', - cityLatitude: 50.6833333333333, - cityLongitude: 3.18333333333333, - countryName: 'France', - countryCode: 'FR', - geographyName: 'Europe', - geographyCode: 'eu', - availabilityZones: ['eu-west-rbx-snc-a'], - }, -]; diff --git a/packages/manager/apps/backup-billing/src/pages/Main.layout.tsx b/packages/manager/apps/backup-billing/src/pages/Main.layout.tsx deleted file mode 100644 index 6014bcdf9ffd..000000000000 --- a/packages/manager/apps/backup-billing/src/pages/Main.layout.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Suspense, useContext, useEffect } from 'react'; - -import { Outlet, useLocation, useMatches } from 'react-router-dom'; - -import { ShellContext, useOvhTracking, useRouteSynchro } from '@ovh-ux/manager-react-shell-client'; -import { defineCurrentPage } from '@ovh-ux/request-tagger'; - -import { appName } from '@/App.constants'; - -export default function MainLayout() { - const location = useLocation(); - const matches = useMatches(); - const { shell } = useContext(ShellContext); - const { trackCurrentPage } = useOvhTracking(); - - // Keep shell route state in sync with React Router - useRouteSynchro(); - - // Tag the current page for request correlation - useEffect(() => { - const lastMatch = matches.at(-1); // most specific route - if (lastMatch?.id) { - defineCurrentPage(`app.${appName}-${lastMatch.id}`); - } - }, [matches]); - - // Track page views on route change - useEffect(() => { - trackCurrentPage(); - }, [trackCurrentPage, location.pathname]); - - // Hide shell preloader on mount - useEffect(() => { - void shell?.ux.hidePreloader(); - }, [shell]); - - return ( - - - - ); -} diff --git a/packages/manager/apps/backup-billing/src/pages/dashboard/Dashboard.page.tsx b/packages/manager/apps/backup-billing/src/pages/dashboard/Dashboard.page.tsx deleted file mode 100644 index ab7deb0f4c0b..000000000000 --- a/packages/manager/apps/backup-billing/src/pages/dashboard/Dashboard.page.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Suspense, startTransition, useMemo } from 'react'; - -import { NavLink, Outlet, useLocation, useNavigate } from 'react-router-dom'; - -import { useTranslation } from 'react-i18next'; - -import { OdsTab, OdsTabs } from '@ovhcloud/ods-components/react'; - -import { BaseLayout, Breadcrumb } from '@ovh-ux/manager-react-components'; -import { useOvhTracking } from '@ovh-ux/manager-react-shell-client'; - -import { appName } from '@/App.constants'; -import { useDashboardTabs } from '@/hooks/dashboard/useDashboardTabs'; -import { urls } from '@/routes/Routes.constants'; - -export default function DashboardPage() { - const { t } = useTranslation(['common', 'dashboard']); - const navigate = useNavigate(); - const location = useLocation(); - const tabs = useDashboardTabs(); - const { trackClick } = useOvhTracking(); - - const activeTab = useMemo( - () => - tabs.find((tab) => location.pathname === tab.to) ?? - tabs.find((tab) => tab.to && location.pathname.startsWith(tab.to)), - [tabs, location.pathname], - ); - - const onNavigateBackClicked = () => { - startTransition(() => navigate(`../${urls.listing}`)); - }; - - return ( - <> - } - tabs={ - tabs.length > 0 ? ( - - {tabs.map((tab) => ( - { - if (tab.trackingActions?.length) { - trackClick({ actions: tab.trackingActions }); - } - }} - > - {t(tab.title)} - - ))} - - ) : undefined - } - /> - - - - - ); -} diff --git a/packages/manager/apps/backup-billing/src/pages/dashboard/Dashboard.spec.tsx b/packages/manager/apps/backup-billing/src/pages/dashboard/Dashboard.spec.tsx deleted file mode 100644 index c1c0cccfbde2..000000000000 --- a/packages/manager/apps/backup-billing/src/pages/dashboard/Dashboard.spec.tsx +++ /dev/null @@ -1,123 +0,0 @@ -/* eslint-disable react/no-multi-comp */ -import type { ReactNode } from 'react'; - -import { fireEvent, render, screen } from '@testing-library/react'; -import { vi } from 'vitest'; - -import DashboardPage from './Dashboard.page'; - -// --- Mock react-router-dom --- -vi.mock('react-router-dom', async () => { - const actual = await vi.importActual('react-router-dom'); - return { - ...actual, - useNavigate: () => vi.fn(), - useLocation: () => ({ pathname: '/general-information/123' }), - useParams: () => ({ id: '123' }), - Outlet: () =>
, - NavLink: ({ children, onClick }: { children: ReactNode; onClick?: () => void }) => ( - - ), - }; -}); - -// --- Mock translation --- -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ - t: (key: string) => key, - }), -})); - -// --- Mock OVH Tracking --- -const trackClick = vi.fn(); -vi.mock('@ovh-ux/manager-react-shell-client', () => ({ - useOvhTracking: () => ({ trackClick }), -})); - -// --- Mock BaseLayout + ODS Tabs --- -interface BaseLayoutProps { - header: { title: string }; - backLinkLabel: string; - onClickReturn: () => void; - breadcrumb: ReactNode; - tabs: ReactNode; -} -vi.mock('@ovh-ux/manager-react-components', () => ({ - BaseLayout: ({ header, backLinkLabel, onClickReturn, breadcrumb, tabs }: BaseLayoutProps) => ( -
-

{header.title}

- -
{breadcrumb}
-
{tabs}
-
- ), - Breadcrumb: () => { - const items = [{ label: 'Home' }, { label: 'Dashboard' }]; - return ; - }, -})); - -interface OdsTabsProps { - children: ReactNode; -} -interface OdsTabProps { - children: ReactNode; - isSelected?: boolean; -} -vi.mock('@ovhcloud/ods-components/react', () => ({ - OdsTabs: ({ children }: OdsTabsProps) =>
{children}
, - OdsTab: ({ children, isSelected }: OdsTabProps) => ( -
- {children} -
- ), -})); - -// --- Mock useDashboardTabs --- -vi.mock('@/hooks/dashboard/useDashboardTabs', () => ({ - useDashboardTabs: () => [ - { - name: 'general-information', - title: 'dashboard:general-information', - to: '/general-information/123', - trackingActions: ['click::general-information-tab'], - }, - { name: 'help', title: 'dashboard:help', to: '/help', trackingActions: ['click::help-tab'] }, - ], -})); - -describe('DashboardPage', () => { - beforeEach(() => { - trackClick.mockClear(); - }); - - it('renders layout with title, breadcrumb and tabs', () => { - render(); - expect(screen.getByText('dashboard:title')).toBeInTheDocument(); - expect(screen.getByTestId('breadcrumb-nav')).toHaveTextContent('Home / Dashboard'); - expect(screen.getAllByTestId('ods-tab')).toHaveLength(2); - }); - - it('marks the active tab as selected', () => { - render(); - const tabs = screen.getAllByTestId('ods-tab'); - expect(tabs[0]).toHaveAttribute('data-selected', 'true'); - expect(tabs[1]).toHaveAttribute('data-selected', 'false'); - }); - - it('calls trackClick when a tab with trackingActions is clicked', () => { - render(); - const helpTab = screen.getByText('dashboard:help'); - fireEvent.click(helpTab); - expect(trackClick).toHaveBeenCalledWith({ actions: ['click::help-tab'] }); - }); - - it('renders the Outlet inside Suspense', () => { - render(); - expect(screen.getByTestId('outlet')).toBeInTheDocument(); - }); -}); diff --git a/packages/manager/apps/backup-billing/src/pages/dashboard/general-information/GeneralInformation.page.tsx b/packages/manager/apps/backup-billing/src/pages/dashboard/general-information/GeneralInformation.page.tsx deleted file mode 100644 index ccdf1eebc2c5..000000000000 --- a/packages/manager/apps/backup-billing/src/pages/dashboard/general-information/GeneralInformation.page.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import React from 'react'; - -import { OdsText } from '@ovhcloud/ods-components/react'; - -import { Clipboard, LinkType, Links } from '@ovh-ux/manager-react-components'; - -import GeneralInformationTile from '@/components/dashboard/general-information/GeneralInformationTile.component'; - -export default function GeneralInformationPage() { - const tiles = [ - { - title: 'Service Information', - help: 'Basic details about the service', - items: [ - { - id: 'description', - label: 'Description', - value: ( - Hosted Private Cloud – VMware vSphere service running in GRA region. - ), - }, - { - id: 'api-url', - label: 'API URL', - value: , - }, - { - id: 'service-id', - label: 'Service ID', - value: , - }, - { - id: 'region', - label: 'Region', - value: GRA (Gravelines, France), - }, - { - id: 'status', - label: 'Status', - value: Active, - }, - ], - }, - { - title: 'Documentation', - help: 'Helpful external links', - items: [ - { - id: 'docs', - label: 'Docs', - value: ( - - ), - }, - { - id: 'api-reference', - label: 'API Reference', - value: ( - - ), - }, - { - id: 'guides', - label: 'Guides', - value: ( - - ), - }, - ], - }, - { - title: 'Network Information', - help: 'Details about networking and connectivity', - items: [ - { - id: 'ip-block', - label: 'Public IP Block', - value: , - }, - { - id: 'vrack', - label: 'vRack', - value: ( - - ), - }, - { - id: 'bandwidth', - label: 'Bandwidth', - value: 1 Gbps Guaranteed, - }, - ], - }, - { - title: 'Support', - help: 'Support and assistance', - items: [ - { - id: 'support-center', - label: 'Support Center', - value: ( - - ), - }, - { - id: 'tickets', - label: 'Support Tickets', - value: ( - - ), - }, - ], - }, - ]; - - return ( -
- -
- ); -} diff --git a/packages/manager/apps/backup-billing/src/pages/dashboard/general-information/GeneralInformation.spec.tsx b/packages/manager/apps/backup-billing/src/pages/dashboard/general-information/GeneralInformation.spec.tsx deleted file mode 100644 index 980c1f939744..000000000000 --- a/packages/manager/apps/backup-billing/src/pages/dashboard/general-information/GeneralInformation.spec.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React from 'react'; - -import { fireEvent, render, screen } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; - -import GeneralInformationPage from './GeneralInformation.page'; - -// --- Types for mocks --- -interface ClipboardProps { - value: string; -} -interface LinksProps { - href: string; - label: string; -} - -// --- Mock Clipboard & Links --- -vi.mock('@ovh-ux/manager-react-components', async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - Clipboard: ({ value }: ClipboardProps) => ( - - ), - // eslint-disable-next-line react/no-multi-comp - Links: ({ href, label }: LinksProps) => ( - - {label} - - ), - }; -}); - -describe('GeneralInformationPage', () => { - it('renders tile titles (section headings)', () => { - render(); - - expect(screen.getByText('Service Information')).toBeInTheDocument(); - expect(screen.getAllByText('Documentation')[0]).toBeInTheDocument(); - expect(screen.getByText('Network Information')).toBeInTheDocument(); - expect(screen.getByText('Support')).toBeInTheDocument(); - }); - - it('renders service details and allows clipboard copy', () => { - const writeText = vi.fn<() => Promise>(); - Object.assign(navigator, { - clipboard: { writeText }, - } as unknown as Navigator); - - render(); - - expect( - screen.getByText(/Hosted Private Cloud – VMware vSphere service running in GRA region./i), - ).toBeInTheDocument(); - - const clipboards = screen.getAllByTestId('clipboard'); - expect(clipboards[0]).toHaveAttribute( - 'data-value', - 'https://api.ovh.com/1.0/hostedprivatecloud', - ); - expect(clipboards[1]).toHaveAttribute('data-value', 'srv-12345-abcde'); - - fireEvent.click(clipboards.at(0)!); // non-null assertion for TS - expect(writeText).toHaveBeenCalledWith('https://api.ovh.com/1.0/hostedprivatecloud'); - - expect(screen.getByText('Active')).toBeInTheDocument(); - }); - - it('renders network information with correct values', () => { - render(); - - const ipClipboard = screen - .getAllByTestId('clipboard') - .find((el) => el.getAttribute('data-value')?.includes('51.210.0.0/28')); - expect(ipClipboard).toBeTruthy(); - - expect(screen.getByText(/1 Gbps Guaranteed/i)).toBeInTheDocument(); - }); - - it.each([ - ['Documentation', 'https://help.ovhcloud.com/csm/en-hosted-private-cloud'], - ['API Explorer', 'https://api.ovh.com/console/'], - [ - 'Getting Started Guide', - 'https://help.ovhcloud.com/csm/en-hosted-private-cloud-vmware-vsphere-getting-started', - ], - ['vrack-12345', 'https://www.ovhcloud.com/en/network/vrack/'], - ['Open Support Center', 'https://help.ovhcloud.com/'], - ['View Tickets', 'https://www.ovh.com/manager/dedicated/#/ticket'], - ])('renders link "%s" with correct href', (label: string, href: string) => { - render(); - - const link = screen.getByTestId(`link-${label}`); - expect(link).toHaveAttribute('href', href); - }); -}); diff --git a/packages/manager/apps/backup-billing/src/pages/dashboard/help/Help.page.tsx b/packages/manager/apps/backup-billing/src/pages/dashboard/help/Help.page.tsx deleted file mode 100644 index 86bca11dce33..000000000000 --- a/packages/manager/apps/backup-billing/src/pages/dashboard/help/Help.page.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { useCallback, useState } from 'react'; - -import { Controller, useForm } from 'react-hook-form'; -import { useTranslation } from 'react-i18next'; - -import { - OdsButton, - OdsFormField, - OdsInput, - OdsMessage, - OdsText, - OdsTextarea, -} from '@ovhcloud/ods-components/react'; - -import { HelpFormValues } from '@/types/Help.type'; - -export default function HelpPage() { - const { t } = useTranslation(['dashboard', 'common']); - const { control, handleSubmit, formState } = useForm({ - defaultValues: { email: '', subject: '', message: '' }, - mode: 'onTouched', - }); - - const [showNotification, setShowNotification] = useState(false); - - const onSubmit = useCallback((values: HelpFormValues) => { - // replace with API call - // await Promise.resolve(); - console.log('help form submitted', values); - - setShowNotification(true); - }, []); - - return ( -
- {showNotification && ( - { - setShowNotification(false); - }} - > - {t('dashboard:successMessage')} - - )} - -
{ - void handleSubmit(onSubmit)(e); - }} - > - {/* Email field */} - ( - - - field.onChange(ev.detail.value)} - onOdsBlur={field.onBlur} - /> - - )} - /> - - {/* Subject field */} - ( - - - field.onChange(ev.detail.value)} - onOdsBlur={field.onBlur} - /> - - )} - /> - - {/* Message textarea */} - ( - - - field.onChange(ev.detail.value)} - onOdsBlur={field.onBlur} - /> - - )} - /> - - {/* Submit button */} -
- -
- -
- ); -} diff --git a/packages/manager/apps/backup-billing/src/pages/dashboard/help/Help.spec.tsx b/packages/manager/apps/backup-billing/src/pages/dashboard/help/Help.spec.tsx deleted file mode 100644 index 1e6215eb7fda..000000000000 --- a/packages/manager/apps/backup-billing/src/pages/dashboard/help/Help.spec.tsx +++ /dev/null @@ -1,143 +0,0 @@ -/* eslint-disable */ -import type { ReactNode } from 'react'; -import { render, screen, fireEvent, act } from '@testing-library/react'; -import { vi } from 'vitest'; -import HelpPage from './Help.page'; - -// --- Mock translations --- -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ - t: (key: string) => key, - }), -})); - -// --- Typed ODS mocks --- -interface OdsFormFieldProps { - children: ReactNode; - error?: string; -} - -vi.mock('@ovhcloud/ods-components/react', () => { - return { - OdsFormField: ({ children, error }: OdsFormFieldProps) => ( -
- {children} - {error &&
{error}
} -
- ), - OdsInput: ({ - value, - onOdsChange, - onOdsBlur, - ...props - }: React.InputHTMLAttributes & { - value: string; - hasError?: boolean; - onOdsChange?: (e: { detail: { value: string } }) => void; - onOdsBlur?: () => void; - }) => ( - onOdsChange?.({ detail: { value: e.currentTarget.value } })} - onBlur={onOdsBlur} - /> - ), - OdsTextarea: ({ - value, - onOdsChange, - onOdsBlur, - ...props - }: React.TextareaHTMLAttributes & { - value: string; - hasError?: boolean; - onOdsChange?: (e: { detail: { value: string } }) => void; - onOdsBlur?: () => void; - }) => ( -