diff --git a/CHANGELOG.md b/CHANGELOG.md index 13b5d0ce..f1ffc9bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Public-vehicle badge.** Vehicles marked *Show on profile* now carry a small **Public** badge in the garage's Vehicles tab, so you can see at a glance which are shared on your driver profile. +- **Request a datalogger.** The contact form has a new **New datalogger connection** + category, and the "Download from logger" picker has a **Request additional + dataloggers** button that opens the contact form with that category preselected. - **Custom tracks ride along with leaderboard snapshots.** When you submit a snapshot for a track that isn't in the built-in list, its layout + sectors are included with the snapshot (so others can view the lap) and the track is **automatically submitted diff --git a/src/components/ContactDialog.tsx b/src/components/ContactDialog.tsx index 9964dbe1..c4b461c9 100644 --- a/src/components/ContactDialog.tsx +++ b/src/components/ContactDialog.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, type ReactNode } from "react"; import { useTranslation } from "react-i18next"; import { Mail } from "lucide-react"; import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; @@ -9,8 +9,14 @@ import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { toast } from "@/hooks/use-toast"; +// The "request a new datalogger" category — exported so the logger picker can +// open this dialog with it preselected. Keep the VALUE in sync with the edge +// function's ALLOWED_CATEGORIES + the admin MessagesTab category map. // eslint-disable-next-line react-refresh/only-export-components -- co-located with the dialog that owns the categories -export const MESSAGE_CATEGORIES = ["Comment", "Feature Request", "Complaint", "Bug Report"] as const; +export const CATEGORY_NEW_DATALOGGER = "New Datalogger Connection"; + +// eslint-disable-next-line react-refresh/only-export-components -- co-located with the dialog that owns the categories +export const MESSAGE_CATEGORIES = ["Comment", "Feature Request", "Complaint", "Bug Report", CATEGORY_NEW_DATALOGGER] as const; // The category VALUE submitted to the backend stays the English string above; // this only maps it to a locale key for display. @@ -19,16 +25,33 @@ const CATEGORY_KEYS = { "Feature Request": "featureRequest", "Complaint": "complaint", "Bug Report": "bugReport", + [CATEGORY_NEW_DATALOGGER]: "newDatalogger", } as const; -export function ContactDialog({ variant = "footer" }: { variant?: "header" | "footer" }) { +export function ContactDialog({ + variant = "footer", + trigger, + defaultCategory, +}: { + variant?: "header" | "footer"; + /** Custom trigger element (overrides the default header/footer button). */ + trigger?: ReactNode; + /** Preselect a category when the dialog opens (e.g. from the logger picker). */ + defaultCategory?: string; +}) { const { t } = useTranslation("landing"); const [open, setOpen] = useState(false); - const [category, setCategory] = useState(""); + const [category, setCategory] = useState(defaultCategory ?? ""); const [email, setEmail] = useState(""); const [message, setMessage] = useState(""); const [submitting, setSubmitting] = useState(false); + // Re-seed the preselected category each time the dialog opens. + const handleOpenChange = (next: boolean) => { + setOpen(next); + if (next && defaultCategory) setCategory(defaultCategory); + }; + const handleSubmit = async () => { if (!category || !message.trim()) { toast({ title: t("contact.missingFields"), description: t("contact.missingFieldsDesc"), variant: "destructive" }); @@ -55,7 +78,7 @@ export function ContactDialog({ variant = "footer" }: { variant?: "header" | "fo } toast({ title: t("contact.sent"), description: t("contact.sentDesc") }); - setCategory(""); + setCategory(defaultCategory ?? ""); setEmail(""); setMessage(""); setOpen(false); @@ -67,9 +90,11 @@ export function ContactDialog({ variant = "footer" }: { variant?: "header" | "fo }; return ( - + - {variant === "header" ? ( + {trigger ? ( + trigger + ) : variant === "header" ? ( + } + /> +

{t("trademarks")}

diff --git a/src/components/admin/MessagesTab.tsx b/src/components/admin/MessagesTab.tsx index fe15e5fd..8be84005 100644 --- a/src/components/admin/MessagesTab.tsx +++ b/src/components/admin/MessagesTab.tsx @@ -24,6 +24,7 @@ const categoryColors: Record = { "Feature Request": "bg-green-500/20 text-green-400 border-green-500/30", "Complaint": "bg-red-500/20 text-red-400 border-red-500/30", "Bug Report": "bg-orange-500/20 text-orange-400 border-orange-500/30", + "New Datalogger Connection": "bg-purple-500/20 text-purple-400 border-purple-500/30", }; // Map the English category value stored in the DB to its locale key. @@ -32,6 +33,7 @@ const categoryKeys = { "Feature Request": "featureRequest", "Complaint": "complaint", "Bug Report": "bugReport", + "New Datalogger Connection": "newDatalogger", } as const; export function MessagesTab({ onUnreadCount }: { onUnreadCount?: (count: number) => void }) { diff --git a/src/locales/de/admin.json b/src/locales/de/admin.json index 09ec3ea6..56c43556 100644 --- a/src/locales/de/admin.json +++ b/src/locales/de/admin.json @@ -52,7 +52,8 @@ "comment": "Kommentar", "featureRequest": "Funktionswunsch", "complaint": "Beschwerde", - "bugReport": "Fehlerbericht" + "bugReport": "Fehlerbericht", + "newDatalogger": "New datalogger connection" }, "emailLine": "E-Mail: {{email}}", "ipLine": "IP: {{ip}}", diff --git a/src/locales/de/landing.json b/src/locales/de/landing.json index 4f011c16..c9847f62 100644 --- a/src/locales/de/landing.json +++ b/src/locales/de/landing.json @@ -226,7 +226,8 @@ "comment": "Kommentar", "featureRequest": "Funktionswunsch", "complaint": "Beschwerde", - "bugReport": "Fehlerbericht" + "bugReport": "Fehlerbericht", + "newDatalogger": "New datalogger connection" }, "emailLabel": "E-Mail", "emailOptional": "(optional, für eine Antwort)", diff --git a/src/locales/de/logger.json b/src/locales/de/logger.json index e477e53a..563e7501 100644 --- a/src/locales/de/logger.json +++ b/src/locales/de/logger.json @@ -70,5 +70,6 @@ "retry": "Erneut versuchen", "cancel": "Abbrechen" } - } + }, + "requestLogger": "Request additional dataloggers" } diff --git a/src/locales/en/admin.json b/src/locales/en/admin.json index 11dc2a73..6056a0a6 100644 --- a/src/locales/en/admin.json +++ b/src/locales/en/admin.json @@ -89,7 +89,8 @@ "comment": "Comment", "featureRequest": "Feature Request", "complaint": "Complaint", - "bugReport": "Bug Report" + "bugReport": "Bug Report", + "newDatalogger": "New datalogger connection" }, "emailLine": "Email: {{email}}", "ipLine": "IP: {{ip}}", diff --git a/src/locales/en/landing.json b/src/locales/en/landing.json index 9e7256dc..4111ba57 100644 --- a/src/locales/en/landing.json +++ b/src/locales/en/landing.json @@ -181,7 +181,8 @@ "comment": "Comment", "featureRequest": "Feature Request", "complaint": "Complaint", - "bugReport": "Bug Report" + "bugReport": "Bug Report", + "newDatalogger": "New datalogger connection" }, "emailLabel": "Email", "emailOptional": "(optional, for a reply)", diff --git a/src/locales/en/logger.json b/src/locales/en/logger.json index bca677b7..fffe2cce 100644 --- a/src/locales/en/logger.json +++ b/src/locales/en/logger.json @@ -3,6 +3,7 @@ "subtitle": "Choose your logger to start a download.", "comingSoon": "Coming soon", "close": "Close", + "requestLogger": "Request additional dataloggers", "trademarks": "MyChron is a trademark of AiM Tech S.r.l. and Alfano is a trademark of Alfano S.A. LapWing is independent and not affiliated with or endorsed by them; all product names, logos, and brands are the property of their respective owners.", "tags": { "fledgling": "DIY logger · Bluetooth LE", diff --git a/src/locales/es/admin.json b/src/locales/es/admin.json index 3a014c27..b72ed56c 100644 --- a/src/locales/es/admin.json +++ b/src/locales/es/admin.json @@ -52,7 +52,8 @@ "comment": "Comentario", "featureRequest": "Solicitud de función", "complaint": "Queja", - "bugReport": "Informe de error" + "bugReport": "Informe de error", + "newDatalogger": "New datalogger connection" }, "emailLine": "Correo: {{email}}", "ipLine": "IP: {{ip}}", diff --git a/src/locales/es/landing.json b/src/locales/es/landing.json index 9183df39..bba82057 100644 --- a/src/locales/es/landing.json +++ b/src/locales/es/landing.json @@ -226,7 +226,8 @@ "comment": "Comentario", "featureRequest": "Solicitud de función", "complaint": "Queja", - "bugReport": "Informe de error" + "bugReport": "Informe de error", + "newDatalogger": "New datalogger connection" }, "emailLabel": "Correo electrónico", "emailOptional": "(opcional, para responderte)", diff --git a/src/locales/es/logger.json b/src/locales/es/logger.json index 3fd12c8f..ad43e1bf 100644 --- a/src/locales/es/logger.json +++ b/src/locales/es/logger.json @@ -70,5 +70,6 @@ "retry": "Reintentar", "cancel": "Cancelar" } - } + }, + "requestLogger": "Request additional dataloggers" } diff --git a/src/locales/fr/admin.json b/src/locales/fr/admin.json index 79848e5d..d9afcda0 100644 --- a/src/locales/fr/admin.json +++ b/src/locales/fr/admin.json @@ -52,7 +52,8 @@ "comment": "Commentaire", "featureRequest": "Demande de fonctionnalité", "complaint": "Réclamation", - "bugReport": "Rapport de bug" + "bugReport": "Rapport de bug", + "newDatalogger": "New datalogger connection" }, "emailLine": "E-mail : {{email}}", "ipLine": "IP : {{ip}}", diff --git a/src/locales/fr/landing.json b/src/locales/fr/landing.json index 27ee5f22..57900f16 100644 --- a/src/locales/fr/landing.json +++ b/src/locales/fr/landing.json @@ -226,7 +226,8 @@ "comment": "Commentaire", "featureRequest": "Demande de fonctionnalité", "complaint": "Réclamation", - "bugReport": "Rapport de bug" + "bugReport": "Rapport de bug", + "newDatalogger": "New datalogger connection" }, "emailLabel": "E-mail", "emailOptional": "(facultatif, pour une réponse)", diff --git a/src/locales/fr/logger.json b/src/locales/fr/logger.json index 6f65ef24..dcdaba12 100644 --- a/src/locales/fr/logger.json +++ b/src/locales/fr/logger.json @@ -70,5 +70,6 @@ "retry": "Réessayer", "cancel": "Annuler" } - } + }, + "requestLogger": "Request additional dataloggers" } diff --git a/src/locales/it/admin.json b/src/locales/it/admin.json index f77708d5..a6a2bb7b 100644 --- a/src/locales/it/admin.json +++ b/src/locales/it/admin.json @@ -52,7 +52,8 @@ "comment": "Commento", "featureRequest": "Richiesta di funzionalità", "complaint": "Reclamo", - "bugReport": "Segnalazione di bug" + "bugReport": "Segnalazione di bug", + "newDatalogger": "New datalogger connection" }, "emailLine": "Email: {{email}}", "ipLine": "IP: {{ip}}", diff --git a/src/locales/it/landing.json b/src/locales/it/landing.json index a9b1813c..218026dc 100644 --- a/src/locales/it/landing.json +++ b/src/locales/it/landing.json @@ -226,7 +226,8 @@ "comment": "Commento", "featureRequest": "Richiesta di funzionalità", "complaint": "Reclamo", - "bugReport": "Segnalazione di bug" + "bugReport": "Segnalazione di bug", + "newDatalogger": "New datalogger connection" }, "emailLabel": "Email", "emailOptional": "(facoltativa, per una risposta)", diff --git a/src/locales/it/logger.json b/src/locales/it/logger.json index 4ebb8dd7..49ee800a 100644 --- a/src/locales/it/logger.json +++ b/src/locales/it/logger.json @@ -70,5 +70,6 @@ "retry": "Riprova", "cancel": "Annulla" } - } + }, + "requestLogger": "Request additional dataloggers" } diff --git a/src/locales/ja/admin.json b/src/locales/ja/admin.json index 3753dda1..53dc3ec3 100644 --- a/src/locales/ja/admin.json +++ b/src/locales/ja/admin.json @@ -52,7 +52,8 @@ "comment": "コメント", "featureRequest": "機能リクエスト", "complaint": "苦情", - "bugReport": "バグ報告" + "bugReport": "バグ報告", + "newDatalogger": "New datalogger connection" }, "emailLine": "メール: {{email}}", "ipLine": "IP: {{ip}}", diff --git a/src/locales/ja/landing.json b/src/locales/ja/landing.json index e4b657ad..79446e46 100644 --- a/src/locales/ja/landing.json +++ b/src/locales/ja/landing.json @@ -226,7 +226,8 @@ "comment": "コメント", "featureRequest": "機能リクエスト", "complaint": "苦情", - "bugReport": "バグ報告" + "bugReport": "バグ報告", + "newDatalogger": "New datalogger connection" }, "emailLabel": "メールアドレス", "emailOptional": "(任意、返信用)", diff --git a/src/locales/ja/logger.json b/src/locales/ja/logger.json index decb1ea9..3611d913 100644 --- a/src/locales/ja/logger.json +++ b/src/locales/ja/logger.json @@ -70,5 +70,6 @@ "retry": "再試行", "cancel": "キャンセル" } - } + }, + "requestLogger": "Request additional dataloggers" } diff --git a/src/locales/pt-BR/admin.json b/src/locales/pt-BR/admin.json index 82d02bc1..fa347b25 100644 --- a/src/locales/pt-BR/admin.json +++ b/src/locales/pt-BR/admin.json @@ -52,7 +52,8 @@ "comment": "Comentário", "featureRequest": "Solicitação de recurso", "complaint": "Reclamação", - "bugReport": "Relatório de bug" + "bugReport": "Relatório de bug", + "newDatalogger": "New datalogger connection" }, "emailLine": "E-mail: {{email}}", "ipLine": "IP: {{ip}}", diff --git a/src/locales/pt-BR/landing.json b/src/locales/pt-BR/landing.json index a7337ca9..786b982f 100644 --- a/src/locales/pt-BR/landing.json +++ b/src/locales/pt-BR/landing.json @@ -226,7 +226,8 @@ "comment": "Comentário", "featureRequest": "Solicitação de recurso", "complaint": "Reclamação", - "bugReport": "Relatório de bug" + "bugReport": "Relatório de bug", + "newDatalogger": "New datalogger connection" }, "emailLabel": "E-mail", "emailOptional": "(opcional, para resposta)", diff --git a/src/locales/pt-BR/logger.json b/src/locales/pt-BR/logger.json index 9ec1e5cf..860b9fe6 100644 --- a/src/locales/pt-BR/logger.json +++ b/src/locales/pt-BR/logger.json @@ -70,5 +70,6 @@ "retry": "Tentar novamente", "cancel": "Cancelar" } - } + }, + "requestLogger": "Request additional dataloggers" } diff --git a/supabase/functions/submit-message/index.ts b/supabase/functions/submit-message/index.ts index c7b67402..e19cfc5b 100644 --- a/supabase/functions/submit-message/index.ts +++ b/supabase/functions/submit-message/index.ts @@ -5,7 +5,7 @@ const corsHeaders = { 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-supabase-client-platform, x-supabase-client-platform-version, x-supabase-client-runtime, x-supabase-client-runtime-version', }; -const ALLOWED_CATEGORIES = ["Comment", "Feature Request", "Complaint", "Bug Report"]; +const ALLOWED_CATEGORIES = ["Comment", "Feature Request", "Complaint", "Bug Report", "New Datalogger Connection"]; Deno.serve(async (req) => { if (req.method === 'OPTIONS') {