Skip to content
Draft

Fq&a #1754

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 75 additions & 1 deletion apps/admin/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,78 @@
"submitting": "Resetting..."
}
}
},
"faqs": {
"title": "Robot Game Rules FAQ",
"actions": {
"create": "Create FAQ"
},
"filter": {
"season": "Season",
"all-seasons": "All Seasons"
},
"table": {
"question": "Question",
"answer": "Answer",
"season": "Season",
"created-by": "Created By",
"order": "Order",
"actions": "Actions"
},
"empty-state": "No FAQs found. Create your first FAQ to get started.",
"errors": {
"no-permission": "You don't have permission to manage FAQs.",
"load-failed": "Failed to load FAQs. Please try again."
},
"editor": {
"title-create": "Create FAQ",
"title-edit": "Edit FAQ",
"fields": {
"season": "Season",
"question": "Question",
"answer": "Answer",
"display-order": "Display Order",
"display-order-help": "Leave empty to add at the end"
},
"toolbar": {
"bold": "Bold",
"italic": "Italic",
"color": "Text color",
"bullets": "Bulleted list",
"image": "Insert image",
"media": "Insert media",
"delete-image": "Click to delete image",
"delete-video": "Click to delete video",
"confirm-delete-image": "Are you sure you want to delete this image?",
"confirm-delete-video": "Are you sure you want to delete this video?"
},
"errors": {
"required-fields": "Please fill in all required fields.",
"save-failed": "Failed to save FAQ. Please try again.",
"invalid-image-type": "Image must be a PNG or JPEG file.",
"image-too-large": "Image size must not exceed 2 MB.",
"image-upload-failed": "Failed to upload image. Please try again.",
"invalid-video-type": "Video must be an MP4 or WebM file.",
"video-too-large": "Video size must not exceed 50 MB.",
"video-upload-failed": "Failed to upload video. Please try again."
},
"actions": {
"cancel": "Cancel",
"save": "Save",
"saving": "Saving..."
}
},
"delete": {
"title": "Delete FAQ",
"message": "Are you sure you want to delete this FAQ? This action cannot be undone.",
"question": "Question",
"error": "Failed to delete FAQ. Please try again.",
"actions": {
"cancel": "Cancel",
"delete": "Delete",
"deleting": "Deleting..."
}
}
}
},
"general": {
Expand All @@ -1082,7 +1154,8 @@
"manage-events": "Manage Events",
"manage-event-details": "Manage Event Details",
"manage-teams": "Manage Teams",
"view-insights": "View Insights"
"view-insights": "View Insights",
"manage-faq": "Manage FAQ"
}
},
"layouts": {
Expand All @@ -1093,6 +1166,7 @@
"events": "Events",
"teams": "Teams",
"insights": "Insights",
"faqs": "FAQ",
"graphql": "GraphQL"
},
"user-menu": {
Expand Down
76 changes: 75 additions & 1 deletion apps/admin/locale/he.json
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,78 @@
"submitting": "מאפס..."
}
}
},
"faqs": {
"title": "שאלות נפוצות - חוקי משחק הרובוט",
"actions": {
"create": "יצירת שאלה נפוצה"
},
"filter": {
"season": "עונה",
"all-seasons": "כל העונות"
},
"table": {
"question": "שאלה",
"answer": "תשובה",
"season": "עונה",
"created-by": "נוצר על ידי",
"order": "סדר",
"actions": "פעולות"
},
"empty-state": "לא נמצאו שאלות נפוצות. צור את השאלה הנפוצה הראשונה שלך כדי להתחיל.",
"errors": {
"no-permission": "אין לך הרשאה לנהל שאלות נפוצות.",
"load-failed": "טעינת השאלות הנפוצות נכשלה. אנא נסה שוב."
},
"editor": {
"title-create": "יצירת שאלה נפוצה",
"title-edit": "עריכת שאלה נפוצה",
"fields": {
"season": "עונה",
"question": "שאלה",
"answer": "תשובה",
"display-order": "סדר תצוגה",
"display-order-help": "השאר ריק כדי להוסיף בסוף"
},
"toolbar": {
"bold": "מודגש",
"italic": "נטוי",
"color": "צבע טקסט",
"bullets": "רשימת נקודות",
"image": "הוספת תמונה",
"media": "הוספת מדיה",
"delete-image": "לחץ למחיקת התמונה",
"delete-video": "לחץ למחיקת הוידאו",
"confirm-delete-image": "האם אתה בטוח שברצונך למחוק תמונה זו?",
"confirm-delete-video": "האם אתה בטוח שברצונך למחוק וידאו זה?"
},
"errors": {
"required-fields": "אנא מלא את כל השדות הנדרשים.",
"save-failed": "שמירת השאלה הנפוצה נכשלה. אנא נסה שוב.",
"invalid-image-type": "התמונה חייבת להיות קובץ PNG או JPEG.",
"image-too-large": "גודל התמונה לא יכול לעלות על 2 MB.",
"image-upload-failed": "העלאת התמונה נכשלה. אנא נסה שוב.",
"invalid-video-type": "הוידאו חייב להיות קובץ MP4 או WebM.",
"video-too-large": "גודל הוידאו לא יכול לעלות על 50 MB.",
"video-upload-failed": "העלאת הוידאו נכשלה. אנא נסה שוב."
},
"actions": {
"cancel": "ביטול",
"save": "שמירה",
"saving": "שומר..."
}
},
"delete": {
"title": "מחיקת שאלה נפוצה",
"message": "האם אתה בטוח שברצונך למחוק שאלה נפוצה זו? פעולה זו אינה ניתנת לביטול.",
"question": "שאלה",
"error": "מחיקת השאלה הנפוצה נכשלה. אנא נסה שוב.",
"actions": {
"cancel": "ביטול",
"delete": "מחיקה",
"deleting": "מוחק..."
}
}
}
},
"general": {
Expand All @@ -1082,7 +1154,8 @@
"manage-events": "ניהול אירועים",
"manage-event-details": "ניהול פרטי אירועים",
"manage-teams": "ניהול קבוצות",
"view-insights": "צפייה בתובנות"
"view-insights": "צפייה בתובנות",
"manage-faq": "ניהול שאלות נפוצות"
}
},
"layouts": {
Expand All @@ -1093,6 +1166,7 @@
"events": "אירועים",
"teams": "קבוצות",
"insights": "תובנות",
"faqs": "שאלות נפוצות",
"graphql": "GraphQL"
},
"user-menu": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { IconButton, Tooltip, Box, Typography } from '@mui/material';
import { Palette } from '@mui/icons-material';
import { ColorPicker } from '@lems/shared';
import { HsvaColor, hsvaToHex } from '@uiw/react-color';
interface ColorPaletteProps {
textColor: HsvaColor;
usedColors: string[];
onColorChange: (color: string) => void;
onSaveSelection: () => void;
disabled?: boolean;
}

export function ColorPalette({
textColor,
usedColors,
onColorChange,
onSaveSelection,
disabled = false
}: ColorPaletteProps) {
const currentColor = hsvaToHex(textColor);

return (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<ColorPicker value={textColor} onChange={color => onColorChange(hsvaToHex(color))}>
<IconButton
size="small"
onMouseDown={onSaveSelection}
onClick={() => onColorChange(currentColor)}
disabled={disabled}
sx={{
border: '1px solid',
borderColor: 'divider',
'&:hover': {
backgroundColor: 'action.hover'
}
}}
>
<Palette sx={{ color: currentColor }} />
</IconButton>
</ColorPicker>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{usedColors
.filter(color => color.toLowerCase() !== currentColor.toLowerCase())
.map(color => (
<Tooltip key={color} title={color}>
<Box
onClick={() => onColorChange(color)}
sx={{
width: 18,
height: 18,
borderRadius: '50%',
bgcolor: color,
cursor: disabled ? 'default' : 'pointer',
border: '1px solid',
borderColor: 'divider'
}}
/>
</Tooltip>
))}
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Box
sx={{
width: 16,
height: 16,
borderRadius: '50%',
bgcolor: currentColor,
border: '1px solid',
borderColor: 'divider',
boxShadow: theme => `0 0 0 2px ${theme.palette.primary.main}`
}}
/>
<Typography variant="caption" sx={{ fontFamily: 'monospace', color: 'text.secondary' }}>
{currentColor}
</Typography>
</Box>
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use client';

import { useState } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Typography,
Alert,
CircularProgress
} from '@mui/material';
import { useTranslations } from 'next-intl';
import { mutate } from 'swr';
import { FaqResponse } from '@lems/types/api/admin';
import { apiFetch } from '@lems/shared';

interface DeleteConfirmDialogProps {
open: boolean;
faq: FaqResponse;
onClose: () => void;
}

export function DeleteConfirmDialog({ open, faq, onClose }: DeleteConfirmDialogProps) {
const t = useTranslations('pages.faqs.delete');
const [isDeleting, setIsDeleting] = useState(false);
const [error, setError] = useState<string | null>(null);

const handleDelete = async () => {
setIsDeleting(true);
setError(null);

try {
const result = await apiFetch(`/admin/faqs/${faq.id}`, {
method: 'DELETE'
});

if (!result.ok) {
throw new Error('Failed to delete FAQ');
}

// Refresh FAQ lists
await Promise.all([
mutate('/admin/faqs'),
mutate(key => typeof key === 'string' && key.startsWith('/admin/faqs/season/'))
]);

onClose();
} catch (err) {
setError(err instanceof Error ? err.message : t('error'));
} finally {
setIsDeleting(false);
}
};

return (
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
<DialogTitle>{t('title')}</DialogTitle>
<DialogContent>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<Typography>{t('message')}</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}>
<strong>{t('question')}:</strong> {faq.question}
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={onClose} disabled={isDeleting}>
{t('actions.cancel')}
</Button>
<Button
onClick={handleDelete}
color="error"
variant="contained"
disabled={isDeleting}
startIcon={isDeleting ? <CircularProgress size={20} /> : undefined}
>
{isDeleting ? t('actions.deleting') : t('actions.delete')}
</Button>
</DialogActions>
</Dialog>
);
}
Loading