Files
ftdl/app/download/SearchForm.tsx
tech 8227f36cbe refactor: organize tests and extract formatting utilities
- Move test files to lib/__tests__/ directory
- Extract formatting utilities (formatVersion, formatFileSize, formatDateTime)
  from fs-utils.ts to new utils.ts module
- Add Jest test configuration and test scripts
- Update component imports to use new utils module
- Add CLAUDE.md documentation for project structure

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-04 02:33:30 +08:00

122 lines
3.7 KiB
TypeScript

// app/download/SearchForm.tsx
'use client';
import { useState } from 'react';
import useSWR from 'swr';
import { formatVersion } from '@/lib/utils';
const fetcher = async (url: string) => {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP error: ${res.status}`);
}
return res.json();
};
interface SearchFormProps {
onSearch: (project: string, version: string, commitId: string) => void;
}
export default function SearchForm({ onSearch }: SearchFormProps) {
const [selectedProject, setSelectedProject] = useState('');
const [selectedVersion, setSelectedVersion] = useState('');
const [commitId, setCommitId] = useState('');
// 获取项目列表
const { data: projects, isLoading: loadingProjects } = useSWR(
'/api/projects',
fetcher
);
// 获取版本列表
const { data: versions, isLoading: loadingVersions } = useSWR(
selectedProject ? `/api/versions?project=${selectedProject}` : null,
fetcher
);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (selectedProject && selectedVersion) {
onSearch(selectedProject, selectedVersion, commitId);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="project" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<select
id="project"
value={selectedProject}
onChange={(e) => {
setSelectedProject(e.target.value);
setSelectedVersion('');
}}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
>
<option value=""></option>
{loadingProjects ? (
<option disabled>...</option>
) : (
projects?.map((project: string) => (
<option key={project} value={project}>
{project}
</option>
))
)}
</select>
</div>
<div>
<label htmlFor="version" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<select
id="version"
value={selectedVersion}
onChange={(e) => setSelectedVersion(e.target.value)}
disabled={!selectedProject}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
required
>
<option value=""></option>
{loadingVersions ? (
<option disabled>...</option>
) : (
versions?.map((version: string) => (
<option key={version} value={version}>
{formatVersion(version)}
</option>
))
)}
</select>
</div>
<div>
<label htmlFor="commit" className="block text-sm font-medium text-gray-700 mb-2">
Commit ID ()
</label>
<input
type="text"
id="commit"
value={commitId}
onChange={(e) => setCommitId(e.target.value)}
placeholder="输入 commit id..."
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<button
type="submit"
disabled={!selectedProject || !selectedVersion}
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition-colors disabled:bg-gray-300 disabled:cursor-not-allowed"
>
</button>
</form>
);
}