feat(i18n): localize frontend components and add missing translations
- Replaced hardcoded UI strings with translation keys across frontend files - Added and updated English(en) and Khmer(km) translation files for settings, notifications, and server components - Improved localization and multi-language support throughout the frontend
This commit is contained in:
@@ -11,6 +11,7 @@ import { Plus, X, Server } from 'lucide-react';
|
||||
import { StatusPageComponentRecord } from '@/types/statusPageComponents.types';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { serviceService } from '@/services/serviceService';
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
|
||||
interface ComponentsSelectorProps {
|
||||
selectedComponents: Partial<StatusPageComponentRecord>[];
|
||||
@@ -19,6 +20,7 @@ interface ComponentsSelectorProps {
|
||||
}
|
||||
|
||||
export const ComponentsSelector = ({ selectedComponents, onComponentsChange, onComponentDelete }: ComponentsSelectorProps) => {
|
||||
const { t } = useLanguage();
|
||||
const [showAddForm, setShowAddForm] = useState(false);
|
||||
const [newComponent, setNewComponent] = useState({
|
||||
name: '',
|
||||
@@ -71,16 +73,16 @@ export const ComponentsSelector = ({ selectedComponents, onComponentsChange, onC
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Server className="h-5 w-5" />
|
||||
Status Page Components
|
||||
{t('statusPageComponents')}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Add monitoring components like uptime services, SSL certificates, and incident tracking
|
||||
{t('addMonitoringComponentsDesc')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{selectedComponents.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<Label>Selected Components</Label>
|
||||
<Label>{t('selectedComponents')}</Label>
|
||||
<div className="space-y-2">
|
||||
{selectedComponents.map((component, index) => (
|
||||
<div key={component.id || index} className="flex items-center justify-between p-3 border rounded-lg">
|
||||
@@ -92,12 +94,12 @@ export const ComponentsSelector = ({ selectedComponents, onComponentsChange, onC
|
||||
<div className="flex gap-2 mt-1">
|
||||
{component.service_id && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Service: {services.find(s => s.id === component.service_id)?.name || component.service_id}
|
||||
{t('service')}: {services.find(s => s.id === component.service_id)?.name || component.service_id}
|
||||
</Badge>
|
||||
)}
|
||||
{component.server_id && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Server: {component.server_id}
|
||||
{t('server')}: {component.server_id}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -122,22 +124,22 @@ export const ComponentsSelector = ({ selectedComponents, onComponentsChange, onC
|
||||
className="w-full"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Component
|
||||
{t('addComponent')}
|
||||
</Button>
|
||||
) : (
|
||||
<div className="border rounded-lg p-4 space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="component-name">Component Name</Label>
|
||||
<Label htmlFor="component-name">{t('componentName')}</Label>
|
||||
<Input
|
||||
id="component-name"
|
||||
placeholder="e.g., Main Website"
|
||||
placeholder={t('componentNamePlaceholder')}
|
||||
value={newComponent.name}
|
||||
onChange={(e) => setNewComponent({ ...newComponent, name: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="display-order">Display Order</Label>
|
||||
<Label htmlFor="display-order">{t('displayOrder')}</Label>
|
||||
<Input
|
||||
id="display-order"
|
||||
type="number"
|
||||
@@ -148,10 +150,10 @@ export const ComponentsSelector = ({ selectedComponents, onComponentsChange, onC
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="component-description">Description (Optional)</Label>
|
||||
<Label htmlFor="component-description">{t('descriptionOptional')}</Label>
|
||||
<Textarea
|
||||
id="component-description"
|
||||
placeholder="Brief description of this component"
|
||||
placeholder={t('descriptionPlaceholder')}
|
||||
value={newComponent.description}
|
||||
onChange={(e) => setNewComponent({ ...newComponent, description: e.target.value })}
|
||||
/>
|
||||
@@ -159,10 +161,10 @@ export const ComponentsSelector = ({ selectedComponents, onComponentsChange, onC
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="service-id">Uptime Service (Optional)</Label>
|
||||
<Label htmlFor="service-id">{t('uptimeServiceOptional')}</Label>
|
||||
<Select onValueChange={(value) => setNewComponent({ ...newComponent, service_id: value })}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select an uptime service" />
|
||||
<SelectValue placeholder={t('selectUptimeService')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{services.map((service) => (
|
||||
@@ -181,10 +183,10 @@ export const ComponentsSelector = ({ selectedComponents, onComponentsChange, onC
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="server-id">Server ID (Optional)</Label>
|
||||
<Label htmlFor="server-id">{t('serverIdOptional')}</Label>
|
||||
<Input
|
||||
id="server-id"
|
||||
placeholder="server_456"
|
||||
placeholder={t('serverIdPlaceholder')}
|
||||
value={newComponent.server_id}
|
||||
onChange={(e) => setNewComponent({ ...newComponent, server_id: e.target.value })}
|
||||
/>
|
||||
@@ -193,10 +195,10 @@ export const ComponentsSelector = ({ selectedComponents, onComponentsChange, onC
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={addComponent} disabled={!newComponent.name.trim()}>
|
||||
Add Component
|
||||
{t('addComponent')}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => setShowAddForm(false)}>
|
||||
Cancel
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useCreateStatusPageComponent } from '@/hooks/useStatusPageComponents';
|
||||
import { ComponentsSelector } from './ComponentsSelector';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { StatusPageComponentRecord } from '@/types/statusPageComponents.types';
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
|
||||
const formSchema = z.object({
|
||||
title: z.string().min(1, 'Title is required'),
|
||||
@@ -31,6 +32,7 @@ const formSchema = z.object({
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const CreateOperationalPageDialog = () => {
|
||||
const { t } = useLanguage();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedComponents, setSelectedComponents] = useState<Partial<StatusPageComponentRecord>[]>([]);
|
||||
const createMutation = useCreateOperationalPage();
|
||||
@@ -102,14 +104,14 @@ export const CreateOperationalPageDialog = () => {
|
||||
<DialogTrigger asChild>
|
||||
<Button className="gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
Create Page
|
||||
{t('createPage')}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[800px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create Operational Page</DialogTitle>
|
||||
<DialogTitle>{t('createOperationalPage')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create a new operational status page to monitor your services and components.
|
||||
{t('createOperationalPageDesc')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -121,9 +123,9 @@ export const CreateOperationalPageDialog = () => {
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Title</FormLabel>
|
||||
<FormLabel>{t('title')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="My Service Status" {...field} />
|
||||
<Input placeholder={t('myServiceStatusPlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -135,9 +137,9 @@ export const CreateOperationalPageDialog = () => {
|
||||
name="slug"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Slug</FormLabel>
|
||||
<FormLabel>{t('slug')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="my-service-status" {...field} />
|
||||
<Input placeholder={t('myServiceStatusSlugPlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -150,10 +152,10 @@ export const CreateOperationalPageDialog = () => {
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormLabel>{t('description')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="A brief description of your operational page"
|
||||
placeholder={t('operationalPageDescriptionPlaceholder')}
|
||||
className="min-h-[80px]"
|
||||
{...field}
|
||||
/>
|
||||
@@ -169,17 +171,17 @@ export const CreateOperationalPageDialog = () => {
|
||||
name="theme"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Theme</FormLabel>
|
||||
<FormLabel>{t('theme')}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select theme" />
|
||||
<SelectValue placeholder={t('selectTheme')} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="default">Default</SelectItem>
|
||||
<SelectItem value="dark">Dark</SelectItem>
|
||||
<SelectItem value="light">Light</SelectItem>
|
||||
<SelectItem value="default">{t('themeDefault')}</SelectItem>
|
||||
<SelectItem value="dark">{t('themeDark')}</SelectItem>
|
||||
<SelectItem value="light">{t('themeLight')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
@@ -192,18 +194,18 @@ export const CreateOperationalPageDialog = () => {
|
||||
name="status"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Initial Status</FormLabel>
|
||||
<FormLabel>{t('initialStatus')}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select status" />
|
||||
<SelectValue placeholder={t('selectStatus')} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="operational">Operational</SelectItem>
|
||||
<SelectItem value="degraded">Degraded Performance</SelectItem>
|
||||
<SelectItem value="maintenance">Under Maintenance</SelectItem>
|
||||
<SelectItem value="major_outage">Major Outage</SelectItem>
|
||||
<SelectItem value="operational">{t('statusOperational')}</SelectItem>
|
||||
<SelectItem value="degraded">{t('statusDegraded')}</SelectItem>
|
||||
<SelectItem value="maintenance">{t('statusMaintenance')}</SelectItem>
|
||||
<SelectItem value="major_outage">{t('statusMajorOutage')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
@@ -218,9 +220,9 @@ export const CreateOperationalPageDialog = () => {
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Public Page</FormLabel>
|
||||
<FormLabel>{t('publicPage')}</FormLabel>
|
||||
<FormDescription>
|
||||
Make this page publicly accessible
|
||||
{t('makePagePublic')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
@@ -238,12 +240,12 @@ export const CreateOperationalPageDialog = () => {
|
||||
name="custom_domain"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Custom Domain (Optional)</FormLabel>
|
||||
<FormLabel>{t('customDomainOptional')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="status.yourdomain.com" {...field} />
|
||||
<Input placeholder={t('customDomainPlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Custom domain for your status page
|
||||
{t('customDomainDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -261,13 +263,13 @@ export const CreateOperationalPageDialog = () => {
|
||||
variant="outline"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={createMutation.isPending || createComponentMutation.isPending}
|
||||
>
|
||||
{createMutation.isPending || createComponentMutation.isPending ? 'Creating...' : 'Create Page'}
|
||||
{createMutation.isPending || createComponentMutation.isPending ? t('creating') : t('createPage')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useCreateStatusPageComponent, useStatusPageComponentsByOperationalId, u
|
||||
import { ComponentsSelector } from './ComponentsSelector';
|
||||
import { OperationalPageRecord } from '@/types/operational.types';
|
||||
import { StatusPageComponentRecord } from '@/types/statusPageComponents.types';
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
|
||||
const formSchema = z.object({
|
||||
title: z.string().min(1, 'Title is required'),
|
||||
@@ -37,6 +38,7 @@ interface EditOperationalPageDialogProps {
|
||||
}
|
||||
|
||||
export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOperationalPageDialogProps) => {
|
||||
const { t } = useLanguage();
|
||||
const [selectedComponents, setSelectedComponents] = useState<Partial<StatusPageComponentRecord>[]>([]);
|
||||
const [isFormSubmitting, setIsFormSubmitting] = useState(false);
|
||||
const [componentsLoaded, setComponentsLoaded] = useState(false);
|
||||
@@ -195,9 +197,9 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[800px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Operational Page</DialogTitle>
|
||||
<DialogTitle>{t('editOperationalPage')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Update your operational status page settings and manage components.
|
||||
{t('updateYourOperationalPage')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -209,9 +211,9 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Title</FormLabel>
|
||||
<FormLabel>{t('title')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="My Service Status" {...field} />
|
||||
<Input placeholder={t('myServiceStatusPlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -223,9 +225,9 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
||||
name="slug"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Slug</FormLabel>
|
||||
<FormLabel>{t('slug')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="my-service-status" {...field} />
|
||||
<Input placeholder={t('myServiceStatusSlugPlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -238,10 +240,10 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormLabel>{t('description')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="A brief description of your operational page"
|
||||
placeholder={t('operationalPageDescriptionPlaceholder')}
|
||||
className="min-h-[80px]"
|
||||
{...field}
|
||||
/>
|
||||
@@ -257,17 +259,17 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
||||
name="theme"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Theme</FormLabel>
|
||||
<FormLabel>{t('theme')}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select theme" />
|
||||
<SelectValue placeholder={t('selectTheme')} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="default">Default</SelectItem>
|
||||
<SelectItem value="dark">Dark</SelectItem>
|
||||
<SelectItem value="light">Light</SelectItem>
|
||||
<SelectItem value="default">{t('themeDefault')}</SelectItem>
|
||||
<SelectItem value="dark">{t('themeDark')}</SelectItem>
|
||||
<SelectItem value="light">{t('themeLight')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
@@ -280,18 +282,18 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
||||
name="status"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Status</FormLabel>
|
||||
<FormLabel>{t('status')}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select status" />
|
||||
<SelectValue placeholder={t('selectStatus')} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="operational">Operational</SelectItem>
|
||||
<SelectItem value="degraded">Degraded Performance</SelectItem>
|
||||
<SelectItem value="maintenance">Under Maintenance</SelectItem>
|
||||
<SelectItem value="major_outage">Major Outage</SelectItem>
|
||||
<SelectItem value="operational">{t('statusOperational')}</SelectItem>
|
||||
<SelectItem value="degraded">{t('statusDegraded')}</SelectItem>
|
||||
<SelectItem value="maintenance">{t('statusMaintenance')}</SelectItem>
|
||||
<SelectItem value="major_outage">{t('statusMajorOutage')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
@@ -306,9 +308,9 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Public Page</FormLabel>
|
||||
<FormLabel>{t('publicPage')}</FormLabel>
|
||||
<FormDescription>
|
||||
Make this page publicly accessible
|
||||
{t('makePagePublic')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
@@ -326,12 +328,12 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
||||
name="custom_domain"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Custom Domain (Optional)</FormLabel>
|
||||
<FormLabel>{t('customDomainOptional')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="status.yourdomain.com" {...field} />
|
||||
<Input placeholder={t('customDomainPlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Custom domain for your status page
|
||||
{t('customDomainDescription')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -350,13 +352,13 @@ export const EditOperationalPageDialog = ({ page, open, onOpenChange }: EditOper
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
Cancel
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isFormSubmitting || updateMutation.isPending || createComponentMutation.isPending}
|
||||
>
|
||||
{isFormSubmitting || updateMutation.isPending || createComponentMutation.isPending ? 'Updating...' : 'Update Page'}
|
||||
{isFormSubmitting || updateMutation.isPending || createComponentMutation.isPending ? t('updating') : t('updatePage')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { OperationalPageRecord } from '@/types/operational.types';
|
||||
import { StatusBadge } from './StatusBadge';
|
||||
import { Globe, ExternalLink, Eye, Settings, Trash2 } from 'lucide-react';
|
||||
import { format } from 'date-fns';
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
|
||||
interface OperationalPageCardProps {
|
||||
page: OperationalPageRecord;
|
||||
@@ -15,6 +16,8 @@ interface OperationalPageCardProps {
|
||||
}
|
||||
|
||||
export const OperationalPageCard = ({ page, onEdit, onView, onDelete }: OperationalPageCardProps) => {
|
||||
const { t } = useLanguage();
|
||||
|
||||
return (
|
||||
<Card className="hover:shadow-lg transition-shadow duration-200">
|
||||
<CardHeader className="pb-3">
|
||||
@@ -32,23 +35,23 @@ export const OperationalPageCard = ({ page, onEdit, onView, onDelete }: Operatio
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="font-medium text-muted-foreground">Slug:</span>
|
||||
<span className="font-medium text-muted-foreground">{t('slug')}:</span>
|
||||
<p className="mt-1">{page.slug}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-muted-foreground">Theme:</span>
|
||||
<span className="font-medium text-muted-foreground">{t('theme')}:</span>
|
||||
<p className="mt-1 capitalize">{page.theme}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-muted-foreground">Public:</span>
|
||||
<span className="font-medium text-muted-foreground">{t('public')}:</span>
|
||||
<div className="mt-1">
|
||||
<Badge variant={page.is_public === 'true' ? 'default' : 'secondary'}>
|
||||
{page.is_public === 'true' ? 'Yes' : 'No'}
|
||||
{page.is_public === 'true' ? t('yes') : t('no')}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-muted-foreground">Updated:</span>
|
||||
<span className="font-medium text-muted-foreground">{t('updated')}:</span>
|
||||
<p className="mt-1">{format(new Date(page.updated), 'MMM dd, yyyy')}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,7 +73,7 @@ export const OperationalPageCard = ({ page, onEdit, onView, onDelete }: Operatio
|
||||
className="flex-1"
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-2" />
|
||||
View
|
||||
{t('view')}
|
||||
</Button>
|
||||
)}
|
||||
{onEdit && (
|
||||
@@ -81,7 +84,7 @@ export const OperationalPageCard = ({ page, onEdit, onView, onDelete }: Operatio
|
||||
className="flex-1"
|
||||
>
|
||||
<Settings className="h-4 w-4 mr-2" />
|
||||
Edit
|
||||
{t('edit')}
|
||||
</Button>
|
||||
)}
|
||||
{onDelete && (
|
||||
|
||||
@@ -9,6 +9,8 @@ import { Button } from '@/components/ui/button';
|
||||
import { OperationalPageRecord } from '@/types/operational.types';
|
||||
import { Activity, Plus, RefreshCw } from 'lucide-react';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { useLanguage } from "@/contexts/LanguageContext";
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -21,6 +23,7 @@ import {
|
||||
} from '@/components/ui/alert-dialog';
|
||||
|
||||
export const OperationalPageContent = () => {
|
||||
const { t } = useLanguage();
|
||||
const navigate = useNavigate();
|
||||
const { data: pages, isLoading, error, refetch, isRefetching } = useOperationalPages();
|
||||
const deleteMutation = useDeleteOperationalPage();
|
||||
@@ -68,13 +71,13 @@ export const OperationalPageContent = () => {
|
||||
<div className="mb-4">
|
||||
<Activity className="h-12 w-12 text-muted-foreground mx-auto" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-2">Failed to load operational pages</h3>
|
||||
<h3 className="text-lg font-semibold mb-2">{t('failedToLoadOperationalPages')}</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
There was an error loading your operational pages. Please try again.
|
||||
{t('loadingoperationalPages')}
|
||||
</p>
|
||||
<Button onClick={() => refetch()} variant="outline">
|
||||
<RefreshCw className="h-4 w-4 mr-2" />
|
||||
Try Again
|
||||
{t('tryagain')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,9 +89,9 @@ export const OperationalPageContent = () => {
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight mb-2">Operational Pages</h1>
|
||||
<h1 className="text-3xl font-bold tracking-tight mb-2"> {t('operationalPages')}</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Manage your public status pages and monitor service health
|
||||
{t('describeOperation')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -100,7 +103,7 @@ export const OperationalPageContent = () => {
|
||||
disabled={isRefetching}
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 mr-2 ${isRefetching ? 'animate-spin' : ''}`} />
|
||||
Refresh
|
||||
{t('refresh')}
|
||||
</Button>
|
||||
<CreateOperationalPageDialog />
|
||||
</div>
|
||||
@@ -135,9 +138,9 @@ export const OperationalPageContent = () => {
|
||||
<div className="mb-4">
|
||||
<Activity className="h-12 w-12 text-muted-foreground mx-auto" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-2">No operational pages found</h3>
|
||||
<h3 className="text-lg font-semibold mb-2">{t('noOperationalPagesFound')}</h3>
|
||||
<p className="text-muted-foreground mb-6">
|
||||
Create your first operational page to start monitoring your services and communicate status to your users.
|
||||
{t('createYourFirstOperationalPage')}
|
||||
</p>
|
||||
<CreateOperationalPageDialog />
|
||||
</CardContent>
|
||||
@@ -164,14 +167,14 @@ export const OperationalPageContent = () => {
|
||||
<div className="mt-8 pt-6 border-t">
|
||||
<div className="flex flex-wrap gap-6 text-sm text-muted-foreground">
|
||||
<div>
|
||||
<span className="font-medium">Total Pages:</span> {pages.length}
|
||||
<span className="font-medium">{t('totalPages')}:</span> {pages.length}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Public Pages:</span>{' '}
|
||||
<span className="font-medium">{t('totalPages')}:</span>{' '}
|
||||
{pages.filter(p => p.is_public === 'true').length}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Operational:</span>{' '}
|
||||
<span className="font-medium">{t('operational')}:</span>{' '}
|
||||
{pages.filter(p => p.status === 'operational').length}
|
||||
</div>
|
||||
</div>
|
||||
@@ -189,19 +192,19 @@ export const OperationalPageContent = () => {
|
||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete Operational Page</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t('deleteOperationalPage')}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to delete "{pageToDelete?.title}"? This action cannot be undone and will permanently remove the operational page and all its components.
|
||||
{t('deleteOperationalPageConfirm').replace('{title}', pageToDelete?.title ?? '')}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={confirmDelete}
|
||||
className="bg-red-600 hover:bg-red-700"
|
||||
disabled={deleteMutation.isPending}
|
||||
>
|
||||
{deleteMutation.isPending ? 'Deleting...' : 'Delete'}
|
||||
{deleteMutation.isPending ? t('deleting') : t('delete')}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
||||
Reference in New Issue
Block a user