Add support
This commit is contained in:
parent
ecde2d798c
commit
7b879ebfb8
6 changed files with 290 additions and 3 deletions
|
|
@ -151,6 +151,13 @@ export type Database = {
|
|||
referencedRelation: "user_tablos"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "fk_tablo_access_user_id_from_profiles"
|
||||
columns: ["user_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "profiles"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
tablo_invites: {
|
||||
|
|
@ -244,7 +251,15 @@ export type Database = {
|
|||
status: string | null
|
||||
user_id: string | null
|
||||
}
|
||||
Relationships: []
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "fk_tablo_access_user_id_from_profiles"
|
||||
columns: ["user_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "profiles"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
Functions: {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { PlanningPage } from "./pages/planning";
|
|||
import { ChantiersPage } from "./pages/chantiers";
|
||||
import { ChatPage } from "./pages/chat";
|
||||
import { FeedbackPage } from "./pages/feedback";
|
||||
import { SupportPage } from "./pages/support";
|
||||
import { AllCommunityModule, ModuleRegistry } from "ag-grid-community";
|
||||
import ChatProvider from "./providers/ChatProvider";
|
||||
import { UserStoreProvider } from "./providers/UserStoreProvider";
|
||||
|
|
@ -106,6 +107,14 @@ export const App = () => {
|
|||
</Layout>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="support"
|
||||
element={
|
||||
<Layout>
|
||||
<SupportPage />
|
||||
</Layout>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route
|
||||
|
|
|
|||
|
|
@ -329,7 +329,7 @@ export function MainNavigation({ isCollapsed }: { isCollapsed: boolean }) {
|
|||
<li>
|
||||
<NavLink>
|
||||
<RouterLink
|
||||
to="/"
|
||||
to="/support"
|
||||
className={twMerge("w-full", isCollapsed ? "" : "pl-2")}
|
||||
aria-label={isCollapsed ? "Support" : undefined}
|
||||
>
|
||||
|
|
|
|||
41
ui/src/hooks/support.ts
Normal file
41
ui/src/hooks/support.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { useMutation } from "@tanstack/react-query";
|
||||
import { supabase } from "./auth";
|
||||
import { useUser } from "@ui/providers/UserStoreProvider";
|
||||
|
||||
export interface SupportTicketData {
|
||||
issue_type:
|
||||
| "bug"
|
||||
| "performance"
|
||||
| "security"
|
||||
| "feature_request"
|
||||
| "account"
|
||||
| "other";
|
||||
severity: "low" | "medium" | "high" | "critical";
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// Create new support ticket
|
||||
export const useCreateSupportTicket = () => {
|
||||
const user = useUser();
|
||||
const { mutate, isSuccess, isPending } = useMutation({
|
||||
mutationFn: async ({
|
||||
issue_type,
|
||||
severity,
|
||||
title,
|
||||
description,
|
||||
}: SupportTicketData) => {
|
||||
const { error } = await supabase.from("support_tickets").insert({
|
||||
issue_type,
|
||||
severity,
|
||||
title,
|
||||
description,
|
||||
user_id: user?.id ?? "",
|
||||
});
|
||||
if (error) throw error;
|
||||
},
|
||||
onSuccess: () => {},
|
||||
});
|
||||
|
||||
return { createSupportTicket: mutate, isSuccess, isPending };
|
||||
};
|
||||
207
ui/src/pages/support.tsx
Normal file
207
ui/src/pages/support.tsx
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
import React, { useState } from "react";
|
||||
import { Button } from "@ui/ui-library/button";
|
||||
import { Form } from "@ui/ui-library/form";
|
||||
import { TextField, Label, TextArea, Description } from "@ui/ui-library/field";
|
||||
import { Text } from "@ui/ui-library/text";
|
||||
import { Separator } from "react-aria-components";
|
||||
import { SendIcon, ArrowLeftIcon } from "lucide-react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useCreateSupportTicket, SupportTicketData } from "@ui/hooks/support";
|
||||
|
||||
export function SupportPage() {
|
||||
const navigate = useNavigate();
|
||||
const [formData, setFormData] = useState<SupportTicketData>({
|
||||
issue_type: "bug",
|
||||
severity: "medium",
|
||||
title: "",
|
||||
description: "",
|
||||
});
|
||||
|
||||
const { createSupportTicket, isSuccess, isPending } =
|
||||
useCreateSupportTicket();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
createSupportTicket(formData);
|
||||
};
|
||||
|
||||
const handleInputChange = (field: keyof SupportTicketData, value: string) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto p-6">
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
isIconOnly
|
||||
onPress={() => navigate(-1)}
|
||||
aria-label="Retour"
|
||||
className="shrink-0"
|
||||
>
|
||||
<ArrowLeftIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<Text className="text-2xl font-bold">Support technique</Text>
|
||||
<Text className="text-gray-600 dark:text-gray-400 mt-1">
|
||||
Signalez un problème ou demandez de l'aide
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator className="mb-6" />
|
||||
|
||||
{isSuccess ? (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-green-600 mb-4">
|
||||
<SendIcon className="w-12 h-12 mx-auto" />
|
||||
</div>
|
||||
<Text className="text-xl font-medium text-green-600 mb-2">
|
||||
Votre ticket de support a été créé !
|
||||
</Text>
|
||||
<Text className="text-gray-600 dark:text-gray-400 mb-6">
|
||||
Votre demande a été envoyée avec succès. Notre équipe de support
|
||||
vous répondra dans les plus brefs délais.
|
||||
</Text>
|
||||
<Button variant="outline" onPress={() => navigate(-1)}>
|
||||
<ArrowLeftIcon className="w-4 h-4 mr-2" />
|
||||
Retour
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg border p-6">
|
||||
<Form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Issue Type */}
|
||||
<TextField>
|
||||
<Label>Type de problème</Label>
|
||||
<select
|
||||
value={formData.issue_type}
|
||||
onChange={(e) =>
|
||||
handleInputChange(
|
||||
"issue_type",
|
||||
e.target.value as SupportTicketData["issue_type"]
|
||||
)
|
||||
}
|
||||
className={twMerge(
|
||||
"w-full rounded-md border border-gray-300 dark:border-gray-600",
|
||||
"px-3 py-2 bg-white dark:bg-gray-700",
|
||||
"text-gray-900 dark:text-gray-100",
|
||||
"focus:border-blue-500 focus:ring-1 focus:ring-blue-500",
|
||||
"outline-none"
|
||||
)}
|
||||
required
|
||||
>
|
||||
<option value="bug">Bug / Erreur</option>
|
||||
<option value="performance">Problème de performance</option>
|
||||
<option value="security">Problème de sécurité</option>
|
||||
<option value="account">Problème de compte</option>
|
||||
<option value="other">Autre</option>
|
||||
</select>
|
||||
</TextField>
|
||||
|
||||
{/* Severity */}
|
||||
<TextField>
|
||||
<Label>Niveau de priorité</Label>
|
||||
<select
|
||||
value={formData.severity}
|
||||
onChange={(e) =>
|
||||
handleInputChange(
|
||||
"severity",
|
||||
e.target.value as SupportTicketData["severity"]
|
||||
)
|
||||
}
|
||||
className={twMerge(
|
||||
"w-full rounded-md border border-gray-300 dark:border-gray-600",
|
||||
"px-3 py-2 bg-white dark:bg-gray-700",
|
||||
"text-gray-900 dark:text-gray-100",
|
||||
"focus:border-blue-500 focus:ring-1 focus:ring-blue-500",
|
||||
"outline-none"
|
||||
)}
|
||||
required
|
||||
>
|
||||
<option value="low">Faible</option>
|
||||
<option value="medium">Moyen</option>
|
||||
<option value="high">Élevé</option>
|
||||
<option value="critical">Critique</option>
|
||||
</select>
|
||||
</TextField>
|
||||
|
||||
{/* Title Field */}
|
||||
<TextField>
|
||||
<Label>Titre du problème</Label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.title}
|
||||
onChange={(e) => handleInputChange("title", e.target.value)}
|
||||
placeholder="Titre du problème"
|
||||
className={twMerge(
|
||||
"w-full rounded-md border border-gray-300 dark:border-gray-600",
|
||||
"px-3 py-2 bg-white dark:bg-gray-700",
|
||||
"text-gray-900 dark:text-gray-100",
|
||||
"placeholder-gray-400 dark:placeholder-gray-500",
|
||||
"focus:border-blue-500 focus:ring-1 focus:ring-blue-500",
|
||||
"outline-none"
|
||||
)}
|
||||
required
|
||||
/>
|
||||
</TextField>
|
||||
|
||||
{/* Description Field */}
|
||||
<TextField>
|
||||
<Label>Description détaillée</Label>
|
||||
<TextArea
|
||||
value={formData.description}
|
||||
onChange={(e) =>
|
||||
handleInputChange("description", e.target.value)
|
||||
}
|
||||
placeholder="Veuillez décrire votre problème en détail..."
|
||||
rows={8}
|
||||
required
|
||||
className="resize-none"
|
||||
/>
|
||||
<Description>
|
||||
Plus vous donnez de détails, plus nous pourrons vous aider
|
||||
rapidement
|
||||
</Description>
|
||||
</TextField>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="flex justify-end gap-3 pt-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
onPress={() => navigate(-1)}
|
||||
type="button"
|
||||
>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
type="submit"
|
||||
isDisabled={
|
||||
isPending || !formData.title || !formData.description
|
||||
}
|
||||
className="min-w-32"
|
||||
>
|
||||
{isPending ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
Envoi en cours...
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-2">
|
||||
<SendIcon className="w-4 h-4" />
|
||||
Envoyer le ticket
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -151,6 +151,13 @@ export type Database = {
|
|||
referencedRelation: "user_tablos"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "fk_tablo_access_user_id_from_profiles"
|
||||
columns: ["user_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "profiles"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
tablo_invites: {
|
||||
|
|
@ -244,7 +251,15 @@ export type Database = {
|
|||
status: string | null
|
||||
user_id: string | null
|
||||
}
|
||||
Relationships: []
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "fk_tablo_access_user_id_from_profiles"
|
||||
columns: ["user_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "profiles"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
Functions: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue