Vendored
+1510
File diff suppressed because one or more lines are too long
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Omnex — Plateforme de gestion de commandes & livraison</title>
|
||||||
|
<meta name="description" content="Déployez en un clic une démo complète de la plateforme de gestion de commandes et de livraison." />
|
||||||
|
<script type="module" crossorigin src="/assets/index-CbXT_Xos.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -14,8 +14,10 @@ import { AppDownloads } from './pages/backoffice/AppDownloads'
|
|||||||
import { Profile } from './pages/backoffice/Profile'
|
import { Profile } from './pages/backoffice/Profile'
|
||||||
import { PublicLayout } from './components/PublicLayout'
|
import { PublicLayout } from './components/PublicLayout'
|
||||||
import { BackofficeLayout } from './components/BackofficeLayout'
|
import { BackofficeLayout } from './components/BackofficeLayout'
|
||||||
|
import { Contact } from './pages/Contact'
|
||||||
import { useAuth } from './lib/auth'
|
import { useAuth } from './lib/auth'
|
||||||
import type { JSX } from 'react'
|
import type { JSX } from 'react'
|
||||||
|
import { MyDemoPlat } from './pages/backoffice/MyDemoPlat'
|
||||||
|
|
||||||
// Chemins admin uniquement : un accès non authentifié y renvoie vers la
|
// Chemins admin uniquement : un accès non authentifié y renvoie vers la
|
||||||
// page de connexion admin plutôt que la page client.
|
// page de connexion admin plutôt que la page client.
|
||||||
@@ -61,6 +63,7 @@ export function App() {
|
|||||||
<Route path="/" element={<Landing />} />
|
<Route path="/" element={<Landing />} />
|
||||||
<Route path="/tarifs" element={<Pricing />} />
|
<Route path="/tarifs" element={<Pricing />} />
|
||||||
<Route path="/documentation" element={<Documentation />} />
|
<Route path="/documentation" element={<Documentation />} />
|
||||||
|
<Route path="/contact" element={<Contact />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route path="/admin/login" element={<AdminLogin />} />
|
<Route path="/admin/login" element={<AdminLogin />} />
|
||||||
@@ -106,6 +109,7 @@ export function App() {
|
|||||||
<Route path="subscription" element={<Subscription />} />
|
<Route path="subscription" element={<Subscription />} />
|
||||||
<Route path="downloads" element={<AppDownloads />} />
|
<Route path="downloads" element={<AppDownloads />} />
|
||||||
<Route path="profile" element={<Profile />} />
|
<Route path="profile" element={<Profile />} />
|
||||||
|
<Route path="myservices" element={<MyDemoPlat />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="*" element={<Navigate to="/" replace />} />
|
<Route path="*" element={<Navigate to="/" replace />} />
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
|
||||||
import { statusColor, statusLabel, timeRemaining } from './format'
|
|
||||||
|
|
||||||
describe('format', () => {
|
|
||||||
it('mappe les statuts vers un libellé FR', () => {
|
|
||||||
expect(statusLabel('ready')).toBe('Active')
|
|
||||||
expect(statusLabel('provisioning')).toBe('Déploiement…')
|
|
||||||
expect(statusLabel('expired')).toBe('Expirée')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('associe une couleur cohérente au statut', () => {
|
|
||||||
expect(statusColor('ready')).toBe('green')
|
|
||||||
expect(statusColor('failed')).toBe('red')
|
|
||||||
expect(statusColor('expiring')).toBe('orange')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('calcule le temps restant en jours/heures', () => {
|
|
||||||
const now = Date.parse('2026-07-01T00:00:00Z')
|
|
||||||
const in30d = '2026-07-31T00:00:00Z'
|
|
||||||
expect(timeRemaining(in30d, now)).toBe('30 j 0 h')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renvoie "expirée" quand l’échéance est passée', () => {
|
|
||||||
const now = Date.parse('2026-07-10T00:00:00Z')
|
|
||||||
expect(timeRemaining('2026-07-01T00:00:00Z', now)).toBe('expirée')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -362,6 +362,7 @@ export function Demos() {
|
|||||||
isOpen={!!toExtend}
|
isOpen={!!toExtend}
|
||||||
title="Prolonger la démo de 30 jours ?"
|
title="Prolonger la démo de 30 jours ?"
|
||||||
confirmLabel="Prolonger"
|
confirmLabel="Prolonger"
|
||||||
|
confirmColorScheme="primary"
|
||||||
isLoading={!!toExtend && busy === toExtend.id}
|
isLoading={!!toExtend && busy === toExtend.id}
|
||||||
onConfirm={confirmExtend}
|
onConfirm={confirmExtend}
|
||||||
onClose={() => setToExtend(null)}
|
onClose={() => setToExtend(null)}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Heading,
|
||||||
|
HStack,
|
||||||
|
Link,
|
||||||
|
Spacer,
|
||||||
|
Spinner,
|
||||||
|
Text,
|
||||||
|
VStack,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { api, ApiError, type Demo } from '../../lib/api'
|
||||||
|
import { statusColor, statusLabel } from '../../lib/format'
|
||||||
|
|
||||||
|
export function MyDemoPlat() {
|
||||||
|
const toast = useToast()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [typeAbo, setTypeAbo] = useState<string | null>(null)
|
||||||
|
const [demos, setDemos] = useState<Demo[]>([])
|
||||||
|
const [demosLoading, setDemosLoading] = useState(true)
|
||||||
|
|
||||||
|
const load = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const me = await api.me()
|
||||||
|
setTypeAbo(me.type_abonnement ?? null)
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError && err.status === 401) navigate('/login')
|
||||||
|
}
|
||||||
|
}, [navigate])
|
||||||
|
|
||||||
|
const loadDemos = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const res = await api.listMyDemos()
|
||||||
|
setDemos(res.items ?? [])
|
||||||
|
} catch {
|
||||||
|
toast({ status: 'error', title: 'Chargement des démos impossible' })
|
||||||
|
} finally {
|
||||||
|
setDemosLoading(false)
|
||||||
|
}
|
||||||
|
}, [toast])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void load()
|
||||||
|
void loadDemos()
|
||||||
|
}, [load, loadDemos])
|
||||||
|
|
||||||
|
const isPremium = typeAbo === 'premium'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex mb={6} align="center">
|
||||||
|
<Heading size="md">{isPremium ? 'Ma plateforme' : 'Ma démo'}</Heading>
|
||||||
|
<Spacer />
|
||||||
|
</Flex>
|
||||||
|
<Box mb={8} p={6} borderWidth="1px" borderRadius="lg" bg="bg-surface">
|
||||||
|
{demosLoading ? (
|
||||||
|
<Spinner size="sm" />
|
||||||
|
) : demos.length === 0 ? (
|
||||||
|
<Text color="gray.500">
|
||||||
|
{isPremium ? 'Aucune plateforme pour le moment.' : 'Aucune démo pour le moment.'}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<VStack align="stretch" spacing={3}>
|
||||||
|
{demos.map((d) => (
|
||||||
|
<HStack key={d.id} justify="space-between" flexWrap="wrap" rowGap={2}>
|
||||||
|
{d.status === 'ready' ? (
|
||||||
|
<Link href={d.url} color="primary.500" isExternal fontFamily="mono">
|
||||||
|
{d.url}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<Text color="gray.400" fontFamily="mono">
|
||||||
|
{d.url || '—'}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Badge colorScheme={statusColor(d.status)}>{statusLabel(d.status)}</Badge>
|
||||||
|
</HStack>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -146,7 +146,7 @@ export function PremiumDemos() {
|
|||||||
<>
|
<>
|
||||||
<Flex mb={2} align="center" gap={4} wrap="wrap">
|
<Flex mb={2} align="center" gap={4} wrap="wrap">
|
||||||
<Heading size="md" mr={4}>
|
<Heading size="md" mr={4}>
|
||||||
Démos Premium
|
Plateforme Premium
|
||||||
</Heading>
|
</Heading>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Button colorScheme="primary" onClick={() => setCreateModalOpen(true)}>
|
<Button colorScheme="primary" onClick={() => setCreateModalOpen(true)}>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
Heading,
|
Heading,
|
||||||
HStack,
|
HStack,
|
||||||
Input,
|
Input,
|
||||||
Link,
|
|
||||||
Spacer,
|
Spacer,
|
||||||
Spinner,
|
Spinner,
|
||||||
Stack,
|
Stack,
|
||||||
@@ -18,8 +17,7 @@ import {
|
|||||||
useToast,
|
useToast,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { api, ApiError, type Demo } from '../../lib/api'
|
import { api, ApiError } from '../../lib/api'
|
||||||
import { statusColor, statusLabel } from '../../lib/format'
|
|
||||||
|
|
||||||
export function Subscription() {
|
export function Subscription() {
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -30,8 +28,6 @@ export function Subscription() {
|
|||||||
const [busy, setBusy] = useState(false)
|
const [busy, setBusy] = useState(false)
|
||||||
const [code, setCode] = useState('')
|
const [code, setCode] = useState('')
|
||||||
const [showRenewForm, setShowRenewForm] = useState(false)
|
const [showRenewForm, setShowRenewForm] = useState(false)
|
||||||
const [demos, setDemos] = useState<Demo[]>([])
|
|
||||||
const [demosLoading, setDemosLoading] = useState(true)
|
|
||||||
|
|
||||||
const load = useCallback(async () => {
|
const load = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -46,21 +42,9 @@ export function Subscription() {
|
|||||||
}
|
}
|
||||||
}, [navigate, toast])
|
}, [navigate, toast])
|
||||||
|
|
||||||
const loadDemos = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const res = await api.listMyDemos()
|
|
||||||
setDemos(res.items ?? [])
|
|
||||||
} catch {
|
|
||||||
// Silencieux : l'absence de démo n'est pas une erreur à afficher ici.
|
|
||||||
} finally {
|
|
||||||
setDemosLoading(false)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void load()
|
void load()
|
||||||
void loadDemos()
|
}, [load])
|
||||||
}, [load, loadDemos])
|
|
||||||
|
|
||||||
const submitCode = async (e: React.FormEvent) => {
|
const submitCode = async (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -92,38 +76,6 @@ export function Subscription() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex mb={6} align="center">
|
|
||||||
<Heading size="md">{isPremium ? 'Ma plateforme' : 'Ma démo'}</Heading>
|
|
||||||
<Spacer />
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Box mb={8} p={6} borderWidth="1px" borderRadius="lg" bg="bg-surface">
|
|
||||||
{demosLoading ? (
|
|
||||||
<Spinner size="sm" />
|
|
||||||
) : demos.length === 0 ? (
|
|
||||||
<Text color="gray.500">
|
|
||||||
{isPremium ? 'Aucune plateforme pour le moment.' : 'Aucune démo pour le moment.'}
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<VStack align="stretch" spacing={3}>
|
|
||||||
{demos.map((d) => (
|
|
||||||
<HStack key={d.id} justify="space-between" flexWrap="wrap" rowGap={2}>
|
|
||||||
{d.status === 'ready' ? (
|
|
||||||
<Link href={d.url} color="primary.500" isExternal fontFamily="mono">
|
|
||||||
{d.url}
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<Text color="gray.400" fontFamily="mono">
|
|
||||||
{d.url || '—'}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
<Badge colorScheme={statusColor(d.status)}>{statusLabel(d.status)}</Badge>
|
|
||||||
</HStack>
|
|
||||||
))}
|
|
||||||
</VStack>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Flex mb={6} align="center">
|
<Flex mb={6} align="center">
|
||||||
<Heading size="md">Mon abonnement</Heading>
|
<Heading size="md">Mon abonnement</Heading>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"root":["./src/App.tsx","./src/main.tsx","./src/setupTests.ts","./src/theme.ts","./src/vite-env.d.ts","./src/components/BackofficeLayout.tsx","./src/components/ColorModeToggle.tsx","./src/components/ConfirmDialog.test.tsx","./src/components/ConfirmDialog.tsx","./src/components/CreateDemoModal.tsx","./src/components/DemoCard.tsx","./src/components/EditDomainModal.tsx","./src/components/Footer.tsx","./src/components/Header.tsx","./src/components/PasswordInput.tsx","./src/components/PodStatusPanel.tsx","./src/components/PublicLayout.tsx","./src/components/docs/AdminMockup.tsx","./src/components/docs/sections/AdressesSection.tsx","./src/components/docs/sections/AlertesSection.tsx","./src/components/docs/sections/CategoriesSection.tsx","./src/components/docs/sections/CommandesSection.tsx","./src/components/docs/sections/DashboardSection.tsx","./src/components/docs/sections/LivraisonSection.tsx","./src/components/docs/sections/ParametresSection.tsx","./src/components/docs/sections/ProduitsSection.tsx","./src/components/docs/sections/StatistiquesSection.tsx","./src/components/docs/sections/UtilisateursSection.tsx","./src/components/docs/sections/cabine/CabineAlertsSection.tsx","./src/components/docs/sections/cabine/CabineDashboardSection.tsx","./src/components/docs/sections/cabine/CabineDeliverySection.tsx","./src/components/docs/sections/cabine/CabineOrdersSection.tsx","./src/components/docs/sections/cabine/CabineUsersSection.tsx","./src/components/docs/sections/delivery/DeliveryAlertsSection.tsx","./src/components/docs/sections/delivery/DeliveryDashboardSection.tsx","./src/components/docs/sections/delivery/DeliveryRatingsSection.tsx","./src/components/docs/sections/delivery/DeliveryStatsSection.tsx","./src/lib/api.ts","./src/lib/auth.tsx","./src/lib/format.test.ts","./src/lib/format.ts","./src/pages/AdminLogin.tsx","./src/pages/Documentation.tsx","./src/pages/Landing.tsx","./src/pages/Login.tsx","./src/pages/Pricing.tsx","./src/pages/Register.tsx","./src/pages/backoffice/Codes.tsx","./src/pages/backoffice/Demos.tsx","./src/pages/backoffice/PremiumDemos.tsx","./src/pages/backoffice/Profile.tsx","./src/pages/backoffice/Subscription.tsx"],"version":"5.9.3"}
|
{"root":["./src/App.tsx","./src/main.tsx","./src/setupTests.ts","./src/theme.ts","./src/vite-env.d.ts","./src/components/BackofficeLayout.tsx","./src/components/ColorModeToggle.tsx","./src/components/ConfirmDialog.tsx","./src/components/CreateDemoModal.tsx","./src/components/DemoCard.tsx","./src/components/EditDomainModal.tsx","./src/components/Footer.tsx","./src/components/Header.tsx","./src/components/PasswordInput.tsx","./src/components/PodStatusPanel.tsx","./src/components/PublicLayout.tsx","./src/components/docs/AdminMockup.tsx","./src/components/docs/sections/AdressesSection.tsx","./src/components/docs/sections/AlertesSection.tsx","./src/components/docs/sections/CategoriesSection.tsx","./src/components/docs/sections/CommandesSection.tsx","./src/components/docs/sections/DashboardSection.tsx","./src/components/docs/sections/LivraisonSection.tsx","./src/components/docs/sections/ParametresSection.tsx","./src/components/docs/sections/ProduitsSection.tsx","./src/components/docs/sections/StatistiquesSection.tsx","./src/components/docs/sections/UtilisateursSection.tsx","./src/components/docs/sections/cabine/CabineAlertsSection.tsx","./src/components/docs/sections/cabine/CabineDashboardSection.tsx","./src/components/docs/sections/cabine/CabineDeliverySection.tsx","./src/components/docs/sections/cabine/CabineOrdersSection.tsx","./src/components/docs/sections/cabine/CabineUsersSection.tsx","./src/components/docs/sections/delivery/DeliveryAlertsSection.tsx","./src/components/docs/sections/delivery/DeliveryDashboardSection.tsx","./src/components/docs/sections/delivery/DeliveryRatingsSection.tsx","./src/components/docs/sections/delivery/DeliveryStatsSection.tsx","./src/lib/api.ts","./src/lib/auth.tsx","./src/lib/format.ts","./src/pages/AdminLogin.tsx","./src/pages/Contact.tsx","./src/pages/Documentation.tsx","./src/pages/Landing.tsx","./src/pages/Login.tsx","./src/pages/Pricing.tsx","./src/pages/Register.tsx","./src/pages/backoffice/AppDownloads.tsx","./src/pages/backoffice/Codes.tsx","./src/pages/backoffice/Demos.tsx","./src/pages/backoffice/MyDemoPlat.tsx","./src/pages/backoffice/PremiumDemos.tsx","./src/pages/backoffice/Profile.tsx","./src/pages/backoffice/Subscription.tsx"],"version":"5.9.3"}
|
||||||
Reference in New Issue
Block a user