All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m59s
The import page without a source no longer shows a bare "no source found" card. `ImportEmptyState` (built on the shared `EmptyState`) takes the place of the empty `SourceList` in both situations: without an import folder it offers "Choose a folder" as the single primary action; with a folder and no bank sub-folder it offers "Create a sample folder" (primary, disabled while in flight) and "Refresh" (secondary). Both variants, and the "?" help popover through the new optional `linkTo` prop of `PageHelp`, link to the bank CSV export guide at `/settings/users#import-export`. `SourceList` stays an untouched pure list. `useImportWizard` gains `createSampleFolders`: it invokes the Rust command `create_sample_import_folders` with the folder and the webview's LOCAL date (built from the local getters, not `toISOString()`), rescans the folder so the two cards appear without another click, then raises a closable success banner from the new `sampleCreated` state, which `reset` and a folder change clear. A refusal from Rust (a sample folder already exists) lands in the existing error banner. The log line carries the folder NAMES only, never the absolute path, since the logs travel with a feedback report. "Browse" on the folder card drops to the secondary style while no folder is configured, so a single primary button is on screen. The reducer and its initial state are exported as `wizardReducer` / `initialWizardState` and unit-tested; `ImportEmptyState` is tested by direct call. Resolves #356 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
78 lines
2.7 KiB
TypeScript
78 lines
2.7 KiB
TypeScript
import { useState, useRef, useEffect } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Link } from "react-router-dom";
|
|
import { CircleHelp, X } from "lucide-react";
|
|
|
|
interface PageHelpProps {
|
|
helpKey: string;
|
|
/**
|
|
* Optional route rendered as a link under the tips (#356), labelled by
|
|
* `${helpKey}.help.linkLabel`. No close handler on click: navigating away
|
|
* unmounts the page and the popover with it.
|
|
*/
|
|
linkTo?: string;
|
|
}
|
|
|
|
export function PageHelp({ helpKey, linkTo }: PageHelpProps) {
|
|
const { t } = useTranslation();
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
|
|
// Close on outside click (same pattern as CategoryCombobox)
|
|
useEffect(() => {
|
|
if (!isOpen) return;
|
|
const handler = (e: MouseEvent) => {
|
|
if (ref.current && !ref.current.contains(e.target as Node)) {
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener("mousedown", handler);
|
|
return () => document.removeEventListener("mousedown", handler);
|
|
}, [isOpen]);
|
|
|
|
const tips = t(`${helpKey}.help.tips`, { returnObjects: true }) as string[];
|
|
|
|
return (
|
|
<div ref={ref} className="relative">
|
|
<button
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
className="text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-colors"
|
|
aria-label={t(`${helpKey}.help.title`)}
|
|
>
|
|
<CircleHelp size={20} />
|
|
</button>
|
|
{isOpen && (
|
|
<div className="absolute left-0 top-full mt-2 z-40 w-[calc(100vw-var(--sidebar-width,16rem)-6rem)] max-w-3xl bg-[var(--card)] border border-[var(--border)] rounded-xl p-5 shadow-lg">
|
|
<div className="flex items-start justify-between gap-4 mb-3">
|
|
<h3 className="font-semibold text-[var(--foreground)]">
|
|
{t(`${helpKey}.help.title`)}
|
|
</h3>
|
|
<button
|
|
onClick={() => setIsOpen(false)}
|
|
className="text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-colors shrink-0"
|
|
>
|
|
<X size={18} />
|
|
</button>
|
|
</div>
|
|
<ul className="space-y-1.5 text-sm text-[var(--muted-foreground)]">
|
|
{Array.isArray(tips) &&
|
|
tips.map((tip, i) => (
|
|
<li key={i} className="flex gap-2">
|
|
<span className="shrink-0">•</span>
|
|
<span>{tip}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
{linkTo && (
|
|
<Link
|
|
to={linkTo}
|
|
className="inline-block mt-3 text-sm text-[var(--primary)] hover:underline"
|
|
>
|
|
{t(`${helpKey}.help.linkLabel`)}
|
|
</Link>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|