@creezio/shell-ui 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +336 -0
- package/dist/adapters/nav-shell.d.ts +54 -0
- package/dist/adapters/nav-shell.d.ts.map +1 -0
- package/dist/adapters/nav-shell.js +112 -0
- package/dist/adapters/nav-shell.js.map +1 -0
- package/dist/brand.d.ts +64 -0
- package/dist/brand.d.ts.map +1 -0
- package/dist/brand.js +59 -0
- package/dist/brand.js.map +1 -0
- package/dist/core-nav.d.ts +17 -0
- package/dist/core-nav.d.ts.map +1 -0
- package/dist/core-nav.js +40 -0
- package/dist/core-nav.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-scopes.d.ts +24 -0
- package/dist/lib/api-scopes.d.ts.map +1 -0
- package/dist/lib/api-scopes.js +78 -0
- package/dist/lib/api-scopes.js.map +1 -0
- package/dist/lib/catalog-suspense-key.d.ts +3 -0
- package/dist/lib/catalog-suspense-key.d.ts.map +1 -0
- package/dist/lib/catalog-suspense-key.js +12 -0
- package/dist/lib/catalog-suspense-key.js.map +1 -0
- package/dist/lib/data-changed.d.ts +51 -0
- package/dist/lib/data-changed.d.ts.map +1 -0
- package/dist/lib/data-changed.js +227 -0
- package/dist/lib/data-changed.js.map +1 -0
- package/dist/lib/desktop-home-path.d.ts +14 -0
- package/dist/lib/desktop-home-path.d.ts.map +1 -0
- package/dist/lib/desktop-home-path.js +29 -0
- package/dist/lib/desktop-home-path.js.map +1 -0
- package/dist/lib/geo-distance.d.ts +6 -0
- package/dist/lib/geo-distance.d.ts.map +1 -0
- package/dist/lib/geo-distance.js +27 -0
- package/dist/lib/geo-distance.js.map +1 -0
- package/dist/lib/img.d.ts +2 -0
- package/dist/lib/img.d.ts.map +1 -0
- package/dist/lib/img.js +40 -0
- package/dist/lib/img.js.map +1 -0
- package/dist/lib/keepalive-eviction.d.ts +14 -0
- package/dist/lib/keepalive-eviction.d.ts.map +1 -0
- package/dist/lib/keepalive-eviction.js +44 -0
- package/dist/lib/keepalive-eviction.js.map +1 -0
- package/dist/lib/ops-track.d.ts +29 -0
- package/dist/lib/ops-track.d.ts.map +1 -0
- package/dist/lib/ops-track.js +37 -0
- package/dist/lib/ops-track.js.map +1 -0
- package/dist/lib/optimize-cover-url.d.ts +10 -0
- package/dist/lib/optimize-cover-url.d.ts.map +1 -0
- package/dist/lib/optimize-cover-url.js +80 -0
- package/dist/lib/optimize-cover-url.js.map +1 -0
- package/dist/lib/page-trails.d.ts +9 -0
- package/dist/lib/page-trails.d.ts.map +1 -0
- package/dist/lib/page-trails.js +20 -0
- package/dist/lib/page-trails.js.map +1 -0
- package/dist/lib/public-origin.d.ts +32 -0
- package/dist/lib/public-origin.d.ts.map +1 -0
- package/dist/lib/public-origin.js +125 -0
- package/dist/lib/public-origin.js.map +1 -0
- package/dist/lib/server-incident.d.ts +12 -0
- package/dist/lib/server-incident.d.ts.map +1 -0
- package/dist/lib/server-incident.js +72 -0
- package/dist/lib/server-incident.js.map +1 -0
- package/dist/lib/tab-document-url.d.ts +13 -0
- package/dist/lib/tab-document-url.d.ts.map +1 -0
- package/dist/lib/tab-document-url.js +60 -0
- package/dist/lib/tab-document-url.js.map +1 -0
- package/dist/lib/utils.d.ts +20 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +87 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/nav-catalog.d.ts +97 -0
- package/dist/nav-catalog.d.ts.map +1 -0
- package/dist/nav-catalog.js +282 -0
- package/dist/nav-catalog.js.map +1 -0
- package/dist/registry.d.ts +11 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +60 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist-cjs/adapters/nav-shell.js +115 -0
- package/dist-cjs/adapters/nav-shell.js.map +1 -0
- package/dist-cjs/brand.js +66 -0
- package/dist-cjs/brand.js.map +1 -0
- package/dist-cjs/core-nav.js +43 -0
- package/dist-cjs/core-nav.js.map +1 -0
- package/dist-cjs/index.js +95 -0
- package/dist-cjs/index.js.map +1 -0
- package/dist-cjs/lib/api-scopes.js +86 -0
- package/dist-cjs/lib/api-scopes.js.map +1 -0
- package/dist-cjs/lib/catalog-suspense-key.js +15 -0
- package/dist-cjs/lib/catalog-suspense-key.js.map +1 -0
- package/dist-cjs/lib/data-changed.js +236 -0
- package/dist-cjs/lib/data-changed.js.map +1 -0
- package/dist-cjs/lib/desktop-home-path.js +34 -0
- package/dist-cjs/lib/desktop-home-path.js.map +1 -0
- package/dist-cjs/lib/geo-distance.js +30 -0
- package/dist-cjs/lib/geo-distance.js.map +1 -0
- package/dist-cjs/lib/img.js +43 -0
- package/dist-cjs/lib/img.js.map +1 -0
- package/dist-cjs/lib/keepalive-eviction.js +49 -0
- package/dist-cjs/lib/keepalive-eviction.js.map +1 -0
- package/dist-cjs/lib/ops-track.js +41 -0
- package/dist-cjs/lib/ops-track.js.map +1 -0
- package/dist-cjs/lib/optimize-cover-url.js +83 -0
- package/dist-cjs/lib/optimize-cover-url.js.map +1 -0
- package/dist-cjs/lib/page-trails.js +25 -0
- package/dist-cjs/lib/page-trails.js.map +1 -0
- package/dist-cjs/lib/public-origin.js +130 -0
- package/dist-cjs/lib/public-origin.js.map +1 -0
- package/dist-cjs/lib/server-incident.js +75 -0
- package/dist-cjs/lib/server-incident.js.map +1 -0
- package/dist-cjs/lib/tab-document-url.js +65 -0
- package/dist-cjs/lib/tab-document-url.js.map +1 -0
- package/dist-cjs/lib/utils.js +97 -0
- package/dist-cjs/lib/utils.js.map +1 -0
- package/dist-cjs/nav-catalog.js +293 -0
- package/dist-cjs/nav-catalog.js.map +1 -0
- package/dist-cjs/package.json +3 -0
- package/dist-cjs/registry.js +64 -0
- package/dist-cjs/registry.js.map +1 -0
- package/dist-cjs/types.js +3 -0
- package/dist-cjs/types.js.map +1 -0
- package/package.json +163 -0
- package/ui/app-error-boundary.tsx +59 -0
- package/ui/data-table.tsx +121 -0
- package/ui/desktop/auth-window-chrome.tsx +51 -0
- package/ui/desktop/desktop-bridge.tsx +87 -0
- package/ui/desktop/external-site-slot.tsx +313 -0
- package/ui/desktop/external-site-surface.ts +58 -0
- package/ui/desktop/site-link.tsx +105 -0
- package/ui/desktop/window-chrome-controls.tsx +5 -0
- package/ui/desktop-types.ts +30 -0
- package/ui/faceted-filters.tsx +119 -0
- package/ui/global-search-host.ts +20 -0
- package/ui/global-search.tsx +66 -0
- package/ui/index.ts +178 -0
- package/ui/kit.ts +30 -0
- package/ui/layout/app-shell.tsx +58 -0
- package/ui/layout/desktop-update-banner.tsx +100 -0
- package/ui/layout/entity-header.tsx +44 -0
- package/ui/layout/native-os-nav.ts +104 -0
- package/ui/layout/nav-catalog-loader.tsx +98 -0
- package/ui/layout/nav-icons.ts +67 -0
- package/ui/layout/page-chrome.tsx +90 -0
- package/ui/layout/page-toolbar-context.tsx +82 -0
- package/ui/layout/sandbox-banner.tsx +38 -0
- package/ui/layout/section-view-shell.tsx +73 -0
- package/ui/layout/sidebar-host.ts +144 -0
- package/ui/layout/sidebar.tsx +1150 -0
- package/ui/lib/ai-screencast-hub.ts +127 -0
- package/ui/lib/ai-workspace-client.ts +46 -0
- package/ui/lib/aid.ts +22 -0
- package/ui/lib/desktop-host.ts +114 -0
- package/ui/lib/fleet-tracker-client.ts +237 -0
- package/ui/lib/hermes-ui.ts +114 -0
- package/ui/lib/n8n-ui.ts +115 -0
- package/ui/lib/use-creezio-resource.tsx +121 -0
- package/ui/lib/use-shell-ui-brand.ts +22 -0
- package/ui/list-toolbar.tsx +309 -0
- package/ui/os-pages/desktop-settings-page.tsx +158 -0
- package/ui/os-pages/index.ts +8 -0
- package/ui/page-loading/entity-page-loading.tsx +59 -0
- package/ui/page-loading/list-page-loading.tsx +61 -0
- package/ui/pagination.tsx +56 -0
- package/ui/primitives/avatar.tsx +33 -0
- package/ui/primitives/badge.tsx +36 -0
- package/ui/primitives/breadcrumb.tsx +93 -0
- package/ui/primitives/button.tsx +44 -0
- package/ui/primitives/card.tsx +44 -0
- package/ui/primitives/chart.tsx +317 -0
- package/ui/primitives/command.tsx +111 -0
- package/ui/primitives/dialog.tsx +100 -0
- package/ui/primitives/dropdown-menu.tsx +187 -0
- package/ui/primitives/input.tsx +19 -0
- package/ui/primitives/label.tsx +17 -0
- package/ui/primitives/resizable.tsx +45 -0
- package/ui/primitives/scroll-area.tsx +41 -0
- package/ui/primitives/select.tsx +73 -0
- package/ui/primitives/separator.tsx +26 -0
- package/ui/primitives/sheet.tsx +90 -0
- package/ui/primitives/skeleton.tsx +7 -0
- package/ui/primitives/sonner.tsx +22 -0
- package/ui/primitives/tabs.tsx +51 -0
- package/ui/primitives/textarea.tsx +22 -0
- package/ui/primitives/tooltip.tsx +31 -0
- package/ui/pwa/client-error-reporter.tsx +62 -0
- package/ui/pwa/register-sw.tsx +50 -0
- package/ui/range-filters.tsx +174 -0
- package/ui/search/global-search-config.ts +32 -0
- package/ui/search/global-search-provider.tsx +528 -0
- package/ui/search/search-history.ts +74 -0
- package/ui/search-input.tsx +146 -0
- package/ui/settings/account-settings.tsx +140 -0
- package/ui/settings/agent-profile-settings.tsx +184 -0
- package/ui/settings/api-keys-settings.tsx +349 -0
- package/ui/settings/desktop-background-settings.tsx +128 -0
- package/ui/settings/desktop-connection-settings.tsx +251 -0
- package/ui/settings/desktop-embed-env-panel.tsx +257 -0
- package/ui/settings/desktop-fleet-telemetry-settings.tsx +269 -0
- package/ui/settings/desktop-hermes-settings.tsx +341 -0
- package/ui/settings/desktop-llm-keys.tsx +192 -0
- package/ui/settings/desktop-n8n-settings.tsx +306 -0
- package/ui/settings/desktop-tunnel.tsx +264 -0
- package/ui/settings/desktop-update-settings.tsx +175 -0
- package/ui/settings/factory-reset-settings.tsx +89 -0
- package/ui/settings/host-only-settings.tsx +62 -0
- package/ui/settings/locked-config-field.tsx +30 -0
- package/ui/settings/ops-diagnostic-settings.tsx +242 -0
- package/ui/settings/search-reindex-settings.tsx +158 -0
- package/ui/settings/server-mode-cards.tsx +801 -0
- package/ui/theme/tailwind-preset.cjs +82 -0
- package/ui/theme/theme.css +328 -0
- package/ui/workspace/ai-activity-panel-host.tsx +19 -0
- package/ui/workspace/ai-workspace-agent-host.tsx +54 -0
- package/ui/workspace/ai-workspace-banner.tsx +168 -0
- package/ui/workspace/impersonation-banner.tsx +39 -0
- package/ui/workspace/keep-alive.tsx +220 -0
- package/ui/workspace/tab-workspace-context.tsx +1172 -0
- package/ui/workspace/tab-workspace-host.ts +89 -0
- package/ui/workspace/types.ts +500 -0
- package/ui/workspace/use-location-search.tsx +50 -0
- package/ui/workspace/window-chrome-controls.tsx +116 -0
- package/ui/workspace/workspace-config.ts +47 -0
- package/ui/workspace/workspace-root.tsx +147 -0
- package/ui/workspace/workspace-shell.tsx +292 -0
- package/ui/workspace/workspace-tab-bar.tsx +572 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { FormEvent, useEffect, useRef, useState, useTransition, type ChangeEvent } from "react";
|
|
4
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
5
|
+
import { Input } from "./primitives/input";
|
|
6
|
+
import { Button } from "./primitives/button";
|
|
7
|
+
import { cn } from "@creezio/shell-ui";
|
|
8
|
+
import { aidProps } from "./lib/aid";
|
|
9
|
+
|
|
10
|
+
export const SEARCH_DEBOUNCE_MS = 300;
|
|
11
|
+
|
|
12
|
+
type SearchInputProps = {
|
|
13
|
+
searchKey?: string;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
className?: string;
|
|
16
|
+
inputClassName?: string;
|
|
17
|
+
/** Affiche un bouton submit optionnel (défaut : masqué, debounce suffit). */
|
|
18
|
+
showSubmit?: boolean;
|
|
19
|
+
submitLabel?: string;
|
|
20
|
+
debounceMs?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Mode filtre client (données déjà en props) : met à jour l’URL sans
|
|
23
|
+
* `router.refresh()` — évite un re-fetch RSC/SQLite à chaque frappe.
|
|
24
|
+
*/
|
|
25
|
+
skipRefresh?: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Champ de recherche URL-driven avec debounce (replace, sans empiler l’historique).
|
|
30
|
+
* Centralisé pour les listes ops (clients, véhicules, assurances, etc.).
|
|
31
|
+
*
|
|
32
|
+
* Next.js 14 : `router.replace` met à jour l’URL ; `router.refresh()` force le
|
|
33
|
+
* re-fetch RSC (sinon soft-nav peut laisser le tableau stale). Un seul refresh
|
|
34
|
+
* après replace — pas de history.replaceState seul (refresh ignore alors le `q`).
|
|
35
|
+
*/
|
|
36
|
+
export function SearchInput({
|
|
37
|
+
searchKey = "q",
|
|
38
|
+
placeholder = "Rechercher…",
|
|
39
|
+
className,
|
|
40
|
+
inputClassName,
|
|
41
|
+
showSubmit = false,
|
|
42
|
+
submitLabel = "Filtrer",
|
|
43
|
+
debounceMs = SEARCH_DEBOUNCE_MS,
|
|
44
|
+
skipRefresh = false,
|
|
45
|
+
}: SearchInputProps) {
|
|
46
|
+
const router = useRouter();
|
|
47
|
+
const pathname = usePathname();
|
|
48
|
+
const params = useSearchParams();
|
|
49
|
+
const urlSearch = params.get(searchKey) || "";
|
|
50
|
+
const [searchValue, setSearchValue] = useState(urlSearch);
|
|
51
|
+
const [, startTransition] = useTransition();
|
|
52
|
+
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
53
|
+
const paramsRef = useRef(params);
|
|
54
|
+
const pathnameRef = useRef(pathname);
|
|
55
|
+
const navSeqRef = useRef(0);
|
|
56
|
+
/** Dernière valeur poussée vers l’URL (ou reçue de l’extérieur). */
|
|
57
|
+
const appliedRef = useRef(urlSearch);
|
|
58
|
+
/** true tant que l’utilisateur tape et que le debounce n’a pas appliqué. */
|
|
59
|
+
const dirtyRef = useRef(false);
|
|
60
|
+
paramsRef.current = params;
|
|
61
|
+
pathnameRef.current = pathname;
|
|
62
|
+
|
|
63
|
+
// Sync input ← URL (back/forward, lien, clear externe) sans écraser une frappe en cours.
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (dirtyRef.current) {
|
|
66
|
+
if (urlSearch === searchValue.trim()) {
|
|
67
|
+
dirtyRef.current = false;
|
|
68
|
+
appliedRef.current = urlSearch;
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (urlSearch !== appliedRef.current) {
|
|
73
|
+
appliedRef.current = urlSearch;
|
|
74
|
+
setSearchValue(urlSearch);
|
|
75
|
+
}
|
|
76
|
+
}, [urlSearch, searchValue]);
|
|
77
|
+
|
|
78
|
+
function applySearch(raw: string) {
|
|
79
|
+
const q = raw.trim();
|
|
80
|
+
const next = new URLSearchParams(paramsRef.current.toString());
|
|
81
|
+
const current = next.get(searchKey) || "";
|
|
82
|
+
if (q === current) {
|
|
83
|
+
dirtyRef.current = false;
|
|
84
|
+
appliedRef.current = q;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (q) next.set(searchKey, q);
|
|
88
|
+
else next.delete(searchKey);
|
|
89
|
+
next.delete("page");
|
|
90
|
+
const qs = next.toString();
|
|
91
|
+
const href = qs ? `${pathnameRef.current}?${qs}` : pathnameRef.current;
|
|
92
|
+
const seq = ++navSeqRef.current;
|
|
93
|
+
dirtyRef.current = false;
|
|
94
|
+
appliedRef.current = q;
|
|
95
|
+
startTransition(() => {
|
|
96
|
+
if (seq !== navSeqRef.current) return;
|
|
97
|
+
router.replace(href, { scroll: false });
|
|
98
|
+
if (!skipRefresh) router.refresh();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (searchValue.trim() === urlSearch && !dirtyRef.current) return;
|
|
104
|
+
if (searchValue.trim() === appliedRef.current && !dirtyRef.current) return;
|
|
105
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
106
|
+
debounceRef.current = setTimeout(() => {
|
|
107
|
+
applySearch(searchValue);
|
|
108
|
+
}, debounceMs);
|
|
109
|
+
return () => {
|
|
110
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
111
|
+
};
|
|
112
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- debounce sur la frappe uniquement
|
|
113
|
+
}, [searchValue, urlSearch, searchKey, debounceMs]);
|
|
114
|
+
|
|
115
|
+
function onSubmit(e: FormEvent) {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
118
|
+
applySearch(searchValue);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<form onSubmit={onSubmit} className={cn("flex min-w-[200px] flex-1 gap-2", className)}>
|
|
123
|
+
<Input
|
|
124
|
+
name={searchKey}
|
|
125
|
+
value={searchValue}
|
|
126
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
|
127
|
+
dirtyRef.current = true;
|
|
128
|
+
setSearchValue(e.target.value);
|
|
129
|
+
}}
|
|
130
|
+
placeholder={placeholder}
|
|
131
|
+
{...aidProps(`search.${searchKey}`)}
|
|
132
|
+
className={cn("max-w-sm", inputClassName)}
|
|
133
|
+
autoComplete="off"
|
|
134
|
+
/>
|
|
135
|
+
{showSubmit ? (
|
|
136
|
+
<Button type="submit" variant="secondary">
|
|
137
|
+
{submitLabel}
|
|
138
|
+
</Button>
|
|
139
|
+
) : (
|
|
140
|
+
<button type="submit" className="sr-only">
|
|
141
|
+
Rechercher
|
|
142
|
+
</button>
|
|
143
|
+
)}
|
|
144
|
+
</form>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { getShellDesktopApi, getShellUiBrand } from "@creezio/shell-ui";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Compte local desktop : identifiant + changement de mot de passe
|
|
7
|
+
* (écrit local-config + redémarre Next, comme BYOK).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { FormEvent, useEffect, useState } from "react";
|
|
11
|
+
import { toast } from "sonner";
|
|
12
|
+
import { Loader2, UserRound } from "lucide-react";
|
|
13
|
+
import { Button } from "../primitives/button";
|
|
14
|
+
import { Input } from "../primitives/input";
|
|
15
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../primitives/card";
|
|
16
|
+
|
|
17
|
+
export function AccountSettings() {
|
|
18
|
+
const [desktop, setDesktop] = useState(false);
|
|
19
|
+
const [username, setUsername] = useState<string | null>(null);
|
|
20
|
+
const [currentPassword, setCurrentPassword] = useState("");
|
|
21
|
+
const [newPassword, setNewPassword] = useState("");
|
|
22
|
+
const [newPassword2, setNewPassword2] = useState("");
|
|
23
|
+
const [busy, setBusy] = useState(false);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const api = getShellDesktopApi();
|
|
27
|
+
if (!api?.getAccount) {
|
|
28
|
+
// Hors desktop : afficher le user session si possible
|
|
29
|
+
void fetch("/api/v1/auth/me")
|
|
30
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
31
|
+
.then((d: { user?: string } | null) => {
|
|
32
|
+
if (d?.user) setUsername(d.user);
|
|
33
|
+
})
|
|
34
|
+
.catch(() => {});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
setDesktop(true);
|
|
38
|
+
void api.getAccount().then((a: { username: string }) => setUsername(a.username));
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
async function onChangePassword(e: FormEvent) {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
const api = getShellDesktopApi();
|
|
44
|
+
if (!api?.changePassword) {
|
|
45
|
+
toast.error("Changement de mot de passe disponible dans l'app desktop");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (newPassword.length < 6) {
|
|
49
|
+
toast.error("Nouveau mot de passe trop court (min. 6 caractères)");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (newPassword !== newPassword2) {
|
|
53
|
+
toast.error("Les mots de passe ne correspondent pas");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
setBusy(true);
|
|
57
|
+
try {
|
|
58
|
+
const r = await api.changePassword({ currentPassword, newPassword });
|
|
59
|
+
if (!r.ok) {
|
|
60
|
+
toast.error(r.error || "Échec du changement de mot de passe");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
setCurrentPassword("");
|
|
64
|
+
setNewPassword("");
|
|
65
|
+
setNewPassword2("");
|
|
66
|
+
toast.success("Mot de passe mis à jour — serveur local redémarré");
|
|
67
|
+
} catch (err) {
|
|
68
|
+
toast.error(err instanceof Error ? err.message : "Erreur");
|
|
69
|
+
} finally {
|
|
70
|
+
setBusy(false);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Card>
|
|
76
|
+
<CardHeader>
|
|
77
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
78
|
+
<UserRound className="h-4 w-4" /> Compte
|
|
79
|
+
</CardTitle>
|
|
80
|
+
<CardDescription>
|
|
81
|
+
Identifiant local de cette installation. Le mot de passe est stocké de façon sécurisée
|
|
82
|
+
sur cet ordinateur.
|
|
83
|
+
</CardDescription>
|
|
84
|
+
</CardHeader>
|
|
85
|
+
<CardContent className="space-y-4">
|
|
86
|
+
<div>
|
|
87
|
+
<p className="mb-1 text-[13px] font-medium text-slate-700">Identifiant</p>
|
|
88
|
+
<p className="rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm text-slate-900">
|
|
89
|
+
{username || "—"}
|
|
90
|
+
</p>
|
|
91
|
+
</div>
|
|
92
|
+
{desktop ? (
|
|
93
|
+
<form onSubmit={(e) => void onChangePassword(e)} className="space-y-3">
|
|
94
|
+
<p className="text-[13px] font-medium text-slate-700">Changer le mot de passe</p>
|
|
95
|
+
<Input
|
|
96
|
+
type="password"
|
|
97
|
+
autoComplete="current-password"
|
|
98
|
+
placeholder="Mot de passe actuel"
|
|
99
|
+
value={currentPassword}
|
|
100
|
+
onChange={(e) => setCurrentPassword(e.target.value)}
|
|
101
|
+
required
|
|
102
|
+
disabled={busy}
|
|
103
|
+
/>
|
|
104
|
+
<Input
|
|
105
|
+
type="password"
|
|
106
|
+
autoComplete="new-password"
|
|
107
|
+
placeholder="Nouveau mot de passe"
|
|
108
|
+
value={newPassword}
|
|
109
|
+
onChange={(e) => setNewPassword(e.target.value)}
|
|
110
|
+
required
|
|
111
|
+
disabled={busy}
|
|
112
|
+
/>
|
|
113
|
+
<Input
|
|
114
|
+
type="password"
|
|
115
|
+
autoComplete="new-password"
|
|
116
|
+
placeholder="Confirmer le nouveau mot de passe"
|
|
117
|
+
value={newPassword2}
|
|
118
|
+
onChange={(e) => setNewPassword2(e.target.value)}
|
|
119
|
+
required
|
|
120
|
+
disabled={busy}
|
|
121
|
+
/>
|
|
122
|
+
<Button type="submit" disabled={busy} variant="outline">
|
|
123
|
+
{busy ? (
|
|
124
|
+
<>
|
|
125
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Mise à jour…
|
|
126
|
+
</>
|
|
127
|
+
) : (
|
|
128
|
+
"Enregistrer le mot de passe"
|
|
129
|
+
)}
|
|
130
|
+
</Button>
|
|
131
|
+
</form>
|
|
132
|
+
) : (
|
|
133
|
+
<p className="text-sm text-slate-500">
|
|
134
|
+
Le changement de mot de passe se fait dans l'application desktop.
|
|
135
|
+
</p>
|
|
136
|
+
)}
|
|
137
|
+
</CardContent>
|
|
138
|
+
</Card>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Profil d'agent Work (D3) — par utilisateur.
|
|
5
|
+
* `company` : Hermes embarqué de l'app Serveur (skills creezio-*).
|
|
6
|
+
* `personal` : le Hermes personnel de l'utilisateur (URL + clé API),
|
|
7
|
+
* utilisé à la place du moteur entreprise pour SES sessions Work.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
11
|
+
import { toast } from "sonner";
|
|
12
|
+
import { AlertTriangle, Bot, KeyRound } from "lucide-react";
|
|
13
|
+
import { Button } from "../primitives/button";
|
|
14
|
+
import {
|
|
15
|
+
Card,
|
|
16
|
+
CardContent,
|
|
17
|
+
CardDescription,
|
|
18
|
+
CardHeader,
|
|
19
|
+
CardTitle,
|
|
20
|
+
} from "../primitives/card";
|
|
21
|
+
import { Input } from "../primitives/input";
|
|
22
|
+
import { Label } from "../primitives/label";
|
|
23
|
+
import { aidProps } from "../lib/aid";
|
|
24
|
+
import {
|
|
25
|
+
Select,
|
|
26
|
+
SelectContent,
|
|
27
|
+
SelectItem,
|
|
28
|
+
SelectTrigger,
|
|
29
|
+
SelectValue,
|
|
30
|
+
} from "../primitives/select";
|
|
31
|
+
|
|
32
|
+
type AgentProfile = {
|
|
33
|
+
kind: "company" | "personal";
|
|
34
|
+
apiUrl: string | null;
|
|
35
|
+
hasKey: boolean;
|
|
36
|
+
incomplete?: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function AgentProfileSettings() {
|
|
40
|
+
const [loaded, setLoaded] = useState(false);
|
|
41
|
+
const [kind, setKind] = useState<"company" | "personal">("company");
|
|
42
|
+
const [apiUrl, setApiUrl] = useState("");
|
|
43
|
+
const [apiKey, setApiKey] = useState("");
|
|
44
|
+
const [hasKey, setHasKey] = useState(false);
|
|
45
|
+
const [saving, setSaving] = useState(false);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
let alive = true;
|
|
49
|
+
void fetch("/api/v1/assistant/agent-profile")
|
|
50
|
+
.then((r) => (r.ok ? (r.json() as Promise<AgentProfile>) : null))
|
|
51
|
+
.then((p) => {
|
|
52
|
+
if (!alive || !p) return;
|
|
53
|
+
setKind(p.kind);
|
|
54
|
+
setApiUrl(p.apiUrl || "");
|
|
55
|
+
setHasKey(p.hasKey);
|
|
56
|
+
})
|
|
57
|
+
.finally(() => alive && setLoaded(true));
|
|
58
|
+
return () => {
|
|
59
|
+
alive = false;
|
|
60
|
+
};
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
const save = async () => {
|
|
64
|
+
setSaving(true);
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetch("/api/v1/assistant/agent-profile", {
|
|
67
|
+
method: "PUT",
|
|
68
|
+
headers: { "Content-Type": "application/json" },
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
kind,
|
|
71
|
+
apiUrl,
|
|
72
|
+
// Champ vide = conserver la clé déjà enregistrée.
|
|
73
|
+
...(apiKey ? { apiKey } : {}),
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
76
|
+
const data = (await res.json()) as AgentProfile & { error?: string };
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
toast.error(data.error || "Enregistrement impossible");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
setHasKey(data.hasKey);
|
|
82
|
+
setApiKey("");
|
|
83
|
+
if (data.incomplete) {
|
|
84
|
+
toast.warning(
|
|
85
|
+
"Profil enregistré mais incomplet : sans URL et clé, l'agent de l'entreprise reste utilisé.",
|
|
86
|
+
);
|
|
87
|
+
} else {
|
|
88
|
+
toast.success(
|
|
89
|
+
data.kind === "personal"
|
|
90
|
+
? "Mode Work → votre agent personnel"
|
|
91
|
+
: "Mode Work → agent de l'entreprise",
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
toast.error("Enregistrement impossible");
|
|
96
|
+
} finally {
|
|
97
|
+
setSaving(false);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (!loaded) return null;
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Card {...aidProps("agent-profile-settings")}>
|
|
105
|
+
<CardHeader>
|
|
106
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
107
|
+
<Bot className="h-4 w-4" />
|
|
108
|
+
Agent (mode Work)
|
|
109
|
+
</CardTitle>
|
|
110
|
+
<CardDescription>
|
|
111
|
+
Choisissez quel agent exécute vos missions Work : celui de
|
|
112
|
+
l'entreprise (accès CRM, n8n, modules) ou votre agent Hermes
|
|
113
|
+
personnel.
|
|
114
|
+
</CardDescription>
|
|
115
|
+
</CardHeader>
|
|
116
|
+
<CardContent className="space-y-4">
|
|
117
|
+
<div className="space-y-2">
|
|
118
|
+
<Label>Agent utilisé</Label>
|
|
119
|
+
<Select
|
|
120
|
+
value={kind}
|
|
121
|
+
onValueChange={(v) => setKind(v === "personal" ? "personal" : "company")}
|
|
122
|
+
>
|
|
123
|
+
<SelectTrigger className="w-full sm:w-80">
|
|
124
|
+
<SelectValue />
|
|
125
|
+
</SelectTrigger>
|
|
126
|
+
<SelectContent>
|
|
127
|
+
<SelectItem value="company">
|
|
128
|
+
Agent de l'entreprise (recommandé)
|
|
129
|
+
</SelectItem>
|
|
130
|
+
<SelectItem value="personal">Mon agent personnel</SelectItem>
|
|
131
|
+
</SelectContent>
|
|
132
|
+
</Select>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
{kind === "personal" && (!apiUrl.trim() || (!hasKey && !apiKey)) ? (
|
|
136
|
+
<div className="flex items-start gap-2 rounded-lg border border-amber-300 bg-amber-50 p-3 text-sm text-amber-900">
|
|
137
|
+
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" />
|
|
138
|
+
<span>
|
|
139
|
+
Profil incomplet : renseignez l'URL et la clé API, sinon vos
|
|
140
|
+
missions Work continueront d'utiliser l'agent de
|
|
141
|
+
l'entreprise.
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
) : null}
|
|
145
|
+
|
|
146
|
+
{kind === "personal" ? (
|
|
147
|
+
<div className="space-y-3 rounded-lg border p-3">
|
|
148
|
+
<div className="space-y-2">
|
|
149
|
+
<Label htmlFor="agent-personal-url">URL de l'API Hermes</Label>
|
|
150
|
+
<Input
|
|
151
|
+
id="agent-personal-url"
|
|
152
|
+
placeholder="https://mon-hermes.exemple.com"
|
|
153
|
+
value={apiUrl}
|
|
154
|
+
onChange={(e) => setApiUrl(e.target.value)}
|
|
155
|
+
/>
|
|
156
|
+
</div>
|
|
157
|
+
<div className="space-y-2">
|
|
158
|
+
<Label htmlFor="agent-personal-key" className="flex items-center gap-1.5">
|
|
159
|
+
<KeyRound className="h-3.5 w-3.5" />
|
|
160
|
+
Clé API
|
|
161
|
+
</Label>
|
|
162
|
+
<Input
|
|
163
|
+
id="agent-personal-key"
|
|
164
|
+
type="password"
|
|
165
|
+
placeholder={hasKey ? "•••••••• (enregistrée — laisser vide pour conserver)" : "hermes-…"}
|
|
166
|
+
value={apiKey}
|
|
167
|
+
onChange={(e) => setApiKey(e.target.value)}
|
|
168
|
+
/>
|
|
169
|
+
</div>
|
|
170
|
+
<p className="text-xs text-muted-foreground">
|
|
171
|
+
Votre agent personnel n'a pas accès aux données Creezio
|
|
172
|
+
(CRM, n8n, modules) : pour ces missions, repassez sur l'agent
|
|
173
|
+
de l'entreprise.
|
|
174
|
+
</p>
|
|
175
|
+
</div>
|
|
176
|
+
) : null}
|
|
177
|
+
|
|
178
|
+
<Button onClick={() => void save()} disabled={saving}>
|
|
179
|
+
{saving ? "Enregistrement…" : "Enregistrer"}
|
|
180
|
+
</Button>
|
|
181
|
+
</CardContent>
|
|
182
|
+
</Card>
|
|
183
|
+
);
|
|
184
|
+
}
|