@@ -0,0 +1,137 @@
|
|||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
Box,
|
||||||
|
Collapse,
|
||||||
|
HStack,
|
||||||
|
Icon,
|
||||||
|
IconButton,
|
||||||
|
Link,
|
||||||
|
Spinner,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
} from '@chakra-ui/react'
|
||||||
|
import { EditIcon } from '@chakra-ui/icons'
|
||||||
|
import type { Demo, DemoDetails } from '../lib/api'
|
||||||
|
import { statusColor, statusLabel } from '../lib/format'
|
||||||
|
import { ChevronIcon, PodStatusPanel } from './PodStatusPanel'
|
||||||
|
|
||||||
|
interface DemoCardProps {
|
||||||
|
demo: Demo
|
||||||
|
isOpen: boolean
|
||||||
|
onToggle: () => void
|
||||||
|
detailsLoading: boolean
|
||||||
|
details: DemoDetails | null
|
||||||
|
onEditDomain: () => void
|
||||||
|
/** Affiché sous le badge de statut si fourni (ex. "Expire dans 12 jours"). */
|
||||||
|
expiresLabel?: string
|
||||||
|
/** Boutons d'action (extend/détruire...) affichés en bas de carte. */
|
||||||
|
actions?: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// DemoCard : équivalent "carte" d'une ligne de tableau démo, pour mobile —
|
||||||
|
// évite le scroll horizontal du tableau. Tap sur la carte (hors liens/
|
||||||
|
// boutons) = déplie l'état live des pods, identique au clic de ligne en
|
||||||
|
// desktop.
|
||||||
|
export function DemoCard({
|
||||||
|
demo,
|
||||||
|
isOpen,
|
||||||
|
onToggle,
|
||||||
|
detailsLoading,
|
||||||
|
details,
|
||||||
|
onEditDomain,
|
||||||
|
expiresLabel,
|
||||||
|
actions,
|
||||||
|
}: DemoCardProps) {
|
||||||
|
return (
|
||||||
|
<Box borderWidth="1px" borderRadius="lg" overflow="hidden" bg="bg-surface">
|
||||||
|
<Box p={4} cursor="pointer" onClick={onToggle} _active={{ bg: 'chakra-subtle-bg' }}>
|
||||||
|
<HStack justify="space-between" align="start">
|
||||||
|
<HStack spacing={2} minW={0}>
|
||||||
|
<Icon
|
||||||
|
as={ChevronIcon}
|
||||||
|
boxSize={3}
|
||||||
|
color="gray.400"
|
||||||
|
flexShrink={0}
|
||||||
|
transform={isOpen ? 'rotate(90deg)' : undefined}
|
||||||
|
transition="transform 0.15s"
|
||||||
|
/>
|
||||||
|
<Text fontFamily="mono" fontSize="sm" noOfLines={1} wordBreak="break-all">
|
||||||
|
{demo.namespace}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
<Badge colorScheme={statusColor(demo.status)} flexShrink={0}>
|
||||||
|
{statusLabel(demo.status)}
|
||||||
|
</Badge>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
<Stack spacing={1} mt={3} fontSize="sm">
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<Text color="gray.500">Client</Text>
|
||||||
|
<Text>{demo.username || '—'}</Text>
|
||||||
|
</HStack>
|
||||||
|
<HStack justify="space-between" align="start">
|
||||||
|
<Text color="gray.500" flexShrink={0}>
|
||||||
|
URL
|
||||||
|
</Text>
|
||||||
|
<HStack spacing={1} minW={0}>
|
||||||
|
{demo.status === 'ready' ? (
|
||||||
|
<Link
|
||||||
|
href={demo.url}
|
||||||
|
color="primary.500"
|
||||||
|
isExternal
|
||||||
|
noOfLines={1}
|
||||||
|
wordBreak="break-all"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{demo.url}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<Text color="gray.400">—</Text>
|
||||||
|
)}
|
||||||
|
<IconButton
|
||||||
|
aria-label="Modifier le domaine"
|
||||||
|
icon={<EditIcon />}
|
||||||
|
size="xs"
|
||||||
|
variant="ghost"
|
||||||
|
flexShrink={0}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
onEditDomain()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
{expiresLabel && (
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<Text color="gray.500">Expire dans</Text>
|
||||||
|
<Text>{expiresLabel}</Text>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{actions && (
|
||||||
|
<HStack mt={3} spacing={2} onClick={(e) => e.stopPropagation()}>
|
||||||
|
{actions}
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Collapse in={isOpen} unmountOnExit animateOpacity>
|
||||||
|
<Box p={4} bg="chakra-subtle-bg" borderTopWidth="1px">
|
||||||
|
{detailsLoading && !details ? (
|
||||||
|
<HStack justify="center" py={2}>
|
||||||
|
<Spinner size="sm" />
|
||||||
|
</HStack>
|
||||||
|
) : details ? (
|
||||||
|
<PodStatusPanel state={details.state} />
|
||||||
|
) : (
|
||||||
|
<Text color="gray.500" fontSize="sm">
|
||||||
|
Aucune donnée.
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Collapse>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
+44
-28
@@ -14,6 +14,8 @@ import {
|
|||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { Link as RouterLink } from 'react-router-dom'
|
import { Link as RouterLink } from 'react-router-dom'
|
||||||
|
|
||||||
|
const SUPPORT_TELEGRAM_URL = 'https://t.me/OMNEX_CORP'
|
||||||
|
|
||||||
interface Plan {
|
interface Plan {
|
||||||
name: string
|
name: string
|
||||||
price: string
|
price: string
|
||||||
@@ -21,9 +23,14 @@ interface Plan {
|
|||||||
description: string
|
description: string
|
||||||
features: string[]
|
features: string[]
|
||||||
cta: string
|
cta: string
|
||||||
|
ctaHref: string
|
||||||
|
ctaExternal?: boolean
|
||||||
highlighted?: boolean
|
highlighted?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Seuls 2 types d'abonnement existent réellement dans le système : Démo
|
||||||
|
// (essai gratuit 30 jours) et Premium (stockage persistant, n'expire plus —
|
||||||
|
// voir demos.Status/TypeAbo côté backend).
|
||||||
const plans: Plan[] = [
|
const plans: Plan[] = [
|
||||||
{
|
{
|
||||||
name: 'Démo',
|
name: 'Démo',
|
||||||
@@ -33,41 +40,27 @@ const plans: Plan[] = [
|
|||||||
features: [
|
features: [
|
||||||
'Plateforme complète en conditions réelles',
|
'Plateforme complète en conditions réelles',
|
||||||
'Environnement dédié et isolé',
|
'Environnement dédié et isolé',
|
||||||
'Données de démonstration pré-remplies',
|
|
||||||
'Disponible 30 jours',
|
'Disponible 30 jours',
|
||||||
'Accompagnement commercial',
|
'Accompagnement commercial',
|
||||||
],
|
],
|
||||||
cta: 'Créer un compte',
|
cta: 'Créer un compte',
|
||||||
|
ctaHref: '/register',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Pro',
|
name: 'Premium',
|
||||||
price: 'Sur devis',
|
price: 'Sur devis',
|
||||||
period: 'par mois',
|
period: '',
|
||||||
description: 'Pour déployer la plateforme en production sur votre activité.',
|
description: 'Passez en production : stockage persistant, votre démo n\'expire plus.',
|
||||||
features: [
|
features: [
|
||||||
'Tout ce qui est inclus dans Démo',
|
'Tout ce qui est inclus dans Démo',
|
||||||
'Déploiement production dédié',
|
'Stockage persistant, n\'expire plus',
|
||||||
'WAF, TLS, sauvegardes',
|
|
||||||
'GPS, paiements et notifications',
|
|
||||||
'Support prioritaire',
|
'Support prioritaire',
|
||||||
],
|
],
|
||||||
cta: 'Nous contacter',
|
cta: 'Nous contacter',
|
||||||
|
ctaHref: SUPPORT_TELEGRAM_URL,
|
||||||
|
ctaExternal: true,
|
||||||
highlighted: true,
|
highlighted: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'Entreprise',
|
|
||||||
price: 'Sur mesure',
|
|
||||||
period: '',
|
|
||||||
description: 'Multi-sites, SLA et intégrations spécifiques.',
|
|
||||||
features: [
|
|
||||||
'Tout ce qui est inclus dans Pro',
|
|
||||||
'Haute disponibilité multi-régions',
|
|
||||||
'SLA et supervision 24/7',
|
|
||||||
'Intégrations sur mesure',
|
|
||||||
'Accompagnement dédié',
|
|
||||||
],
|
|
||||||
cta: 'Nous contacter',
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export function Pricing() {
|
export function Pricing() {
|
||||||
@@ -76,18 +69,19 @@ export function Pricing() {
|
|||||||
<Stack spacing={4} textAlign="center" mb={12} align="center">
|
<Stack spacing={4} textAlign="center" mb={12} align="center">
|
||||||
<Heading size="2xl">Tarifs</Heading>
|
<Heading size="2xl">Tarifs</Heading>
|
||||||
<Text fontSize="lg" color="gray.600" maxW="2xl">
|
<Text fontSize="lg" color="gray.600" maxW="2xl">
|
||||||
Commencez par une démo gratuite de 30 jours, puis passez en production quand vous êtes prêt.
|
Commencez par une démo gratuite de 30 jours, puis passez en premium quand vous êtes prêt.
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<SimpleGrid columns={{ base: 1, md: 3 }} spacing={8} alignItems="stretch">
|
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={8} alignItems="stretch" maxW="2xl" mx="auto">
|
||||||
{plans.map((plan) => (
|
{plans.map((plan) => (
|
||||||
<PlanCard key={plan.name} plan={plan} />
|
<PlanCard key={plan.name} plan={plan} />
|
||||||
))}
|
))}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
|
|
||||||
<Text textAlign="center" color="gray.500" mt={10} fontSize="sm">
|
<Text textAlign="center" color="gray.500" mt={10} fontSize="sm">
|
||||||
Besoin d'un devis précis ? <RouterLinkText to="/register">Créez un compte</RouterLinkText> — un
|
Besoin d'un devis précis ?{' '}
|
||||||
|
<ExternalLinkText href={SUPPORT_TELEGRAM_URL}>Contactez-nous</ExternalLinkText> — un
|
||||||
commercial vous recontacte.
|
commercial vous recontacte.
|
||||||
</Text>
|
</Text>
|
||||||
</Container>
|
</Container>
|
||||||
@@ -144,22 +138,44 @@ function PlanCard({ plan }: { plan: Plan }) {
|
|||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
|
{plan.ctaExternal ? (
|
||||||
<Button
|
<Button
|
||||||
as={RouterLink}
|
as="a"
|
||||||
to="/register"
|
href={plan.ctaHref}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
colorScheme="primary"
|
colorScheme="primary"
|
||||||
variant={plan.highlighted ? 'solid' : 'outline'}
|
variant={plan.highlighted ? 'solid' : 'outline'}
|
||||||
size="lg"
|
size="lg"
|
||||||
>
|
>
|
||||||
{plan.cta}
|
{plan.cta}
|
||||||
</Button>
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
as={RouterLink}
|
||||||
|
to={plan.ctaHref}
|
||||||
|
colorScheme="primary"
|
||||||
|
variant={plan.highlighted ? 'solid' : 'outline'}
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
{plan.cta}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function RouterLinkText({ to, children }: { to: string; children: React.ReactNode }) {
|
function ExternalLinkText({ href, children }: { href: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<Box as={RouterLink} to={to} color="primary.500" fontWeight="medium" display="inline">
|
<Box
|
||||||
|
as="a"
|
||||||
|
href={href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
color="primary.500"
|
||||||
|
fontWeight="medium"
|
||||||
|
display="inline"
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
Spacer,
|
Spacer,
|
||||||
Spinner,
|
Spinner,
|
||||||
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
Tbody,
|
Tbody,
|
||||||
@@ -29,6 +30,7 @@ import { statusColor, statusLabel, timeRemaining } from '../../lib/format'
|
|||||||
import { ConfirmDialog } from '../../components/ConfirmDialog'
|
import { ConfirmDialog } from '../../components/ConfirmDialog'
|
||||||
import { CreateDemoModal } from '../../components/CreateDemoModal'
|
import { CreateDemoModal } from '../../components/CreateDemoModal'
|
||||||
import { EditDomainModal } from '../../components/EditDomainModal'
|
import { EditDomainModal } from '../../components/EditDomainModal'
|
||||||
|
import { DemoCard } from '../../components/DemoCard'
|
||||||
import { ChevronIcon, PodStatusPanel } from '../../components/PodStatusPanel'
|
import { ChevronIcon, PodStatusPanel } from '../../components/PodStatusPanel'
|
||||||
|
|
||||||
// Un provisioning en cours => on rafraîchit régulièrement.
|
// Un provisioning en cours => on rafraîchit régulièrement.
|
||||||
@@ -144,6 +146,34 @@ export function Demos() {
|
|||||||
|
|
||||||
const isAlive = (s: Demo['status']) => s !== 'expired' && s !== 'failed'
|
const isAlive = (s: Demo['status']) => s !== 'expired' && s !== 'failed'
|
||||||
|
|
||||||
|
const renderActions = (d: Demo) => (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
isDisabled={!isAlive(d.status) || busy === d.id}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
extend(d)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+30 j
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
colorScheme="red"
|
||||||
|
variant="outline"
|
||||||
|
isDisabled={!isAlive(d.status)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setToDelete(d)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Détruire
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex mb={6} align="center" gap={4} wrap="wrap">
|
<Flex mb={6} align="center" gap={4} wrap="wrap">
|
||||||
@@ -167,7 +197,9 @@ export function Demos() {
|
|||||||
) : demos.length === 0 ? (
|
) : demos.length === 0 ? (
|
||||||
<Text color="gray.500">Aucune démo active. Lancez-en une avec le bouton ci-dessus.</Text>
|
<Text color="gray.500">Aucune démo active. Lancez-en une avec le bouton ci-dessus.</Text>
|
||||||
) : (
|
) : (
|
||||||
<TableContainer borderWidth="1px" borderRadius="lg">
|
<>
|
||||||
|
{/* Desktop/tablette : tableau. */}
|
||||||
|
<TableContainer borderWidth="1px" borderRadius="lg" display={{ base: 'none', md: 'block' }}>
|
||||||
<Table>
|
<Table>
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
@@ -240,31 +272,7 @@ export function Demos() {
|
|||||||
</Td>
|
</Td>
|
||||||
<Td>{isAlive(d.status) ? timeRemaining(d.expires_at) : '—'}</Td>
|
<Td>{isAlive(d.status) ? timeRemaining(d.expires_at) : '—'}</Td>
|
||||||
<Td textAlign="right">
|
<Td textAlign="right">
|
||||||
<HStack justify="flex-end">
|
<HStack justify="flex-end">{renderActions(d)}</HStack>
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
isDisabled={!isAlive(d.status) || busy === d.id}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
extend(d)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
+30 j
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
colorScheme="red"
|
|
||||||
variant="outline"
|
|
||||||
isDisabled={!isAlive(d.status)}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
setToDelete(d)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Détruire
|
|
||||||
</Button>
|
|
||||||
</HStack>
|
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
<Tr>
|
<Tr>
|
||||||
@@ -292,6 +300,24 @@ export function Demos() {
|
|||||||
</Tbody>
|
</Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
|
||||||
|
{/* Mobile : cartes (pas de scroll horizontal). */}
|
||||||
|
<Stack spacing={3} display={{ base: 'flex', md: 'none' }}>
|
||||||
|
{demos.map((d) => (
|
||||||
|
<DemoCard
|
||||||
|
key={d.id}
|
||||||
|
demo={d}
|
||||||
|
isOpen={expandedId === d.id}
|
||||||
|
onToggle={() => toggleRow(d)}
|
||||||
|
detailsLoading={detailsLoading}
|
||||||
|
details={expandedId === d.id ? details : null}
|
||||||
|
onEditDomain={() => setEditingDomain(d)}
|
||||||
|
expiresLabel={isAlive(d.status) ? timeRemaining(d.expires_at) : '—'}
|
||||||
|
actions={renderActions(d)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
Spacer,
|
Spacer,
|
||||||
Spinner,
|
Spinner,
|
||||||
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
Tbody,
|
Tbody,
|
||||||
@@ -29,6 +30,7 @@ import { statusColor, statusLabel } from '../../lib/format'
|
|||||||
import { ChevronIcon, PodStatusPanel } from '../../components/PodStatusPanel'
|
import { ChevronIcon, PodStatusPanel } from '../../components/PodStatusPanel'
|
||||||
import { CreateDemoModal } from '../../components/CreateDemoModal'
|
import { CreateDemoModal } from '../../components/CreateDemoModal'
|
||||||
import { EditDomainModal } from '../../components/EditDomainModal'
|
import { EditDomainModal } from '../../components/EditDomainModal'
|
||||||
|
import { DemoCard } from '../../components/DemoCard'
|
||||||
|
|
||||||
// Un provisioning en cours => on rafraîchit régulièrement.
|
// Un provisioning en cours => on rafraîchit régulièrement.
|
||||||
const POLL_MS = 5000
|
const POLL_MS = 5000
|
||||||
@@ -131,7 +133,9 @@ export function PremiumDemos() {
|
|||||||
) : demos.length === 0 ? (
|
) : demos.length === 0 ? (
|
||||||
<Text color="gray.500">Aucune démo premium pour le moment.</Text>
|
<Text color="gray.500">Aucune démo premium pour le moment.</Text>
|
||||||
) : (
|
) : (
|
||||||
<TableContainer borderWidth="1px" borderRadius="lg">
|
<>
|
||||||
|
{/* Desktop/tablette : tableau. */}
|
||||||
|
<TableContainer borderWidth="1px" borderRadius="lg" display={{ base: 'none', md: 'block' }}>
|
||||||
<Table>
|
<Table>
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
@@ -218,6 +222,22 @@ export function PremiumDemos() {
|
|||||||
</Tbody>
|
</Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
|
||||||
|
{/* Mobile : cartes (pas de scroll horizontal). */}
|
||||||
|
<Stack spacing={3} display={{ base: 'flex', md: 'none' }}>
|
||||||
|
{demos.map((d) => (
|
||||||
|
<DemoCard
|
||||||
|
key={d.id}
|
||||||
|
demo={d}
|
||||||
|
isOpen={expandedId === d.id}
|
||||||
|
onToggle={() => toggleRow(d)}
|
||||||
|
detailsLoading={detailsLoading}
|
||||||
|
details={expandedId === d.id ? details : null}
|
||||||
|
onEditDomain={() => setEditingDomain(d)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<EditDomainModal
|
<EditDomainModal
|
||||||
|
|||||||
@@ -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/EditDomainModal.tsx","./src/components/Footer.tsx","./src/components/Header.tsx","./src/components/PasswordInput.tsx","./src/components/PodStatusPanel.tsx","./src/components/PublicLayout.tsx","./src/lib/api.ts","./src/lib/auth.tsx","./src/lib/format.test.ts","./src/lib/format.ts","./src/pages/AdminLogin.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.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/lib/api.ts","./src/lib/auth.tsx","./src/lib/format.test.ts","./src/lib/format.ts","./src/pages/AdminLogin.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"}
|
||||||
Reference in New Issue
Block a user