spb/trouve-ka Public
Trouve-KA — moteur de recherche web indépendant, Québec-first. Crawler distribué, index OpenSearch, ranking bilingue, galerie d'images. En prod : www.trouve-ka.com
Python 76.8%
TypeScript 15.7%
SQL 3.9%
Shell 1.4%
CSS 1.3%
Dockerfile 0.7%
1/**2 * Trouve-KA — formulaire client de soumission d'URL3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 */67"use client";89import { useState, type FormEvent } from "react";10import { Button } from "@/components/ui/button";11import { Input } from "@/components/ui/input";12import type { SubmitResponse } from "@/lib/types";1314type FormState =15 | { kind: "idle" }16 | { kind: "loading" }17 | { kind: "success"; message: string }18 | { kind: "error"; message: string };1920function isValidHttpUrl(value: string): boolean {21 try {22 const url = new URL(value);23 return url.protocol === "http:" || url.protocol === "https:";24 } catch {25 return false;26 }27}2829export function SubmitForm() {30 const [url, setUrl] = useState("");31 const [state, setState] = useState<FormState>({ kind: "idle" });3233 async function onSubmit(event: FormEvent) {34 event.preventDefault();35 const value = url.trim();3637 if (!isValidHttpUrl(value)) {38 setState({39 kind: "error",40 message:41 "Entrez une URL valide commençant par http:// ou https:// (ex. https://exemple.quebec).",42 });43 return;44 }4546 setState({ kind: "loading" });47 try {48 const res = await fetch("/api/submit", {49 method: "POST",50 headers: { "Content-Type": "application/json" },51 body: JSON.stringify({ url: value }),52 });53 if (!res.ok) throw new Error(`Erreur ${res.status}`);54 const data = (await res.json()) as SubmitResponse;55 if (data.accepted) {56 setState({57 kind: "success",58 message:59 data.message ||60 "Merci! Le site a été ajouté à la file du robot d'indexation.",61 });62 setUrl("");63 } else {64 setState({65 kind: "error",66 message: data.message || "Cette URL n'a pas été acceptée.",67 });68 }69 } catch {70 setState({71 kind: "error",72 message:73 "Impossible de joindre le moteur pour le moment. Réessayez sous peu.",74 });75 }76 }7778 return (79 <form onSubmit={onSubmit} className="mt-6">80 <label htmlFor="url-soumission" className="klabel block">81 Adresse du site (URL)82 </label>83 <div className="mt-2 flex flex-col gap-2 sm:flex-row">84 <Input85 id="url-soumission"86 type="url"87 inputMode="url"88 placeholder="https://exemple.quebec"89 value={url}90 onChange={(event) => setUrl(event.target.value)}91 required92 className="flex-1"93 />94 <Button type="submit" disabled={state.kind === "loading"}>95 {state.kind === "loading" ? "Envoi…" : "Soumettre"}96 </Button>97 </div>98 <p aria-live="polite" className="mt-3 min-h-5 text-sm">99 {state.kind === "success" ? (100 <span className="font-medium text-green">{state.message}</span>101 ) : state.kind === "error" ? (102 <span className="font-medium text-danger">{state.message}</span>103 ) : null}104 </p>105 </form>106 );107}108