Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Copy this file to .env.local and set variables as needed.
# If unset, Next.js rewrites fall back to http://127.0.0.1:8000.
BACKEND_URL=
# If unset, frontend requests default to https://primerflow-be.onrender.com.
NEXT_PUBLIC_API_BASE_URL=
84 changes: 0 additions & 84 deletions app/api/v1/primer/design/route.ts

This file was deleted.

35 changes: 28 additions & 7 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
analyzeGenome,
type AnalyzeRequestInput,
} from "@/services/analysisService";
import { demoGenome } from "@/lib/mocks/demoGenome";
import type { GenomeData, PrimerDesignResponseUI } from "@/types";
import { useViewStore } from "@/store/useViewStore";
import {
Expand All @@ -31,6 +30,28 @@ const toGenomeDataFromResponse = (response: PrimerDesignResponseUI | null): Geno
return { length, tracks };
};

const DEFAULT_PREVIEW_GENOME: GenomeData = {
length: 12000,
tracks: [
{
id: "preview-primer-candidates",
name: "Primer 후보군",
height: 28,
features: [
{ id: "p-01", start: 400, end: 1200, label: "P-01", color: "#2563eb" },
{ id: "p-02", start: 1800, end: 2600, label: "P-02", color: "#0ea5e9" },
{ id: "p-03", start: 3200, end: 4300, label: "P-03", color: "#22c55e" },
],
},
{
id: "preview-target-region",
name: "Target 구간",
height: 18,
features: [{ id: "amplicon", start: 1500, end: 5200, label: "Amplicon", color: "#f97316" }],
},
],
};

export default function Home() {
const viewState = useViewStore((state) => state.viewState);
const setViewState = useViewStore((state) => state.setViewState);
Expand Down Expand Up @@ -118,7 +139,7 @@ export default function Home() {
const handleBack = () => handleStepChange(step - 1);
const isLastStep = step === totalSteps;

const previewGenome = demoGenome;
const previewGenome = DEFAULT_PREVIEW_GENOME;
const trackCount = previewGenome.tracks.length;
const featureCount = previewGenome.tracks.reduce(
(count, track) => count + track.features.length,
Expand All @@ -131,16 +152,16 @@ export default function Home() {
return;
}

const targetSeq =
validation.normalizedSequence && validation.normalizedSequence.trim().length > 0
? validation.normalizedSequence.trim()
: "ATGCGTACGTAGCTAGCTAGCTAGCTAATGCGTACGTAGCTAGCTAGCTAGCTA";
const targetSeq = validation.normalizedSequence?.trim() ?? "";
if (!targetSeq) {
setErrorMessage("Template sequence is required.");
return;
Comment on lines +155 to +158
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 사용자에게 노출되는 에러 메시지가 영어로 표시되고 있는데, 같은 파일의 다른 검증/경고 메시지는 한국어로 제공되고 있습니다. 사용자 경험 일관성을 위해 한국어 메시지로 통일하거나(또는 i18n/상수로 분리) 기존 메시지 스타일에 맞춰 주세요.

Copilot uses AI. Check for mistakes.
}

const payload: AnalyzeRequestInput = {
target_sequence: targetSeq,
species: "Homo sapiens",
analysis_type: "primer_generation",
notes: "UI mock request while backend is offline",
product_size_min: 100,
product_size_max: 300,
tm_min: 57,
Expand Down
6 changes: 3 additions & 3 deletions components/steps/Step1TemplateEssential.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ export default function Step1TemplateEssential({
<p className="mt-2 text-[11px] text-slate-500">
A, T, G, C 이외 문자는 입력 시 자동으로 제거됩니다.
<br />
Paste 버튼, Ctrl+V, Upload FASTA에서는 제거 전에 확인을 요청합니다.
Paste 버튼, Ctrl+V, Upload as file에서는 제거 전에 확인을 요청합니다.
</p>
{validationMessage && (
<p className="mt-2 text-xs text-red-300">{validationMessage}</p>
Expand All @@ -334,7 +334,7 @@ export default function Step1TemplateEssential({
<input
ref={fileInputRef}
type="file"
accept=".fa,.fasta,.txt"
accept=".fa,.fasta,.fna,.txt"
className="hidden"
onChange={handleFileChange}
/>
Expand All @@ -343,7 +343,7 @@ export default function Step1TemplateEssential({
className="flex items-center gap-2 px-3 py-2 rounded-lg text-xs font-bold text-slate-300 hover:text-white hover:bg-slate-800 transition-colors border border-transparent hover:border-slate-700"
onClick={handleUploadClick}
>
Upload FASTA
Upload as file
</button>
<button
type="button"
Expand Down
16 changes: 1 addition & 15 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,5 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
// ... 기타 설정들 ...

async rewrites() {
// BACKEND_URL 값이 없으면 로컬(8000)로 연결
const backendUrl = process.env.BACKEND_URL || "http://127.0.0.1:8000";

return [
{
source: "/api/v1/:path*",
destination: `${backendUrl}/:path*`, // 여기를 변수로 변경
},
];
},
};
const nextConfig: NextConfig = {};

export default nextConfig;
9 changes: 5 additions & 4 deletions src/lib/api/client.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import axios from "axios";

const DEFAULT_API_BASE_URL = "https://primerflow-be.onrender.com";
const apiBaseUrl =
process.env.NEXT_PUBLIC_API_BASE_URL?.trim() || DEFAULT_API_BASE_URL;

export const apiClient = axios.create({
// 모든 API 호출을 /api/v1 이하 상대 경로로 강제합니다.
baseURL: "/api/v1",
baseURL: apiBaseUrl,
headers: {
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apiClient의 baseURL을 외부 도메인(https://primerflow-be.onrender.com)으로 직접 지정하면, 클라이언트 컴포넌트에서 호출 시 브라우저 CORS 정책의 영향을 받습니다(이전 rewrites 프록시와 동작이 달라짐). 백엔드에서 CORS 허용이 보장되지 않으면 호출이 실패하므로, (1) Next.js rewrites/프록시를 유지하거나 (2) Next API route를 프록시로 두는 방식으로 same-origin을 유지하는 방안을 검토해 주세요. 또한 기본값이 곧바로 배포 서버를 가리키므로 로컬 개발에서 의도치 않게 프로덕션을 호출할 수 있어, 로컬 기본값(예: localhost) 또는 환경변수 미설정 시 명시적 에러 처리도 고려할 만합니다.

Copilot uses AI. Check for mistakes.
"Content-Type": "application/json",
},
// 필요한 경우 쿠키 공유를 위해 아래 옵션을 사용하세요.
// withCredentials: true,
});

export const api = apiClient;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/api/primer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const postDesignPrimers = async (
payload: PrimerDesignRequest,
): Promise<PrimerDesignResponse> => {
const response: AxiosResponse<PrimerDesignResponse> = await apiClient.post(
"/primer/design",
"/design",
payload,
);

Expand Down
25 changes: 0 additions & 25 deletions src/lib/mocks/demoGenome.ts

This file was deleted.

6 changes: 3 additions & 3 deletions src/services/analysisService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ export interface AnalyzeRequestInput {

// Adapter: flat UI -> official request schema
const toPrimerDesignRequest = (input: AnalyzeRequestInput): PrimerDesignRequest => {
const seq = input.target_sequence || input.templateSequence || "ATGC";
const seq = input.target_sequence || input.templateSequence || "";
const searchFrom = input.search_start ?? 1;
const searchTo =
input.search_end ??
searchFrom + (seq?.length || 1000);
searchFrom + Math.max(seq.length - 1, 0);

Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toPrimerDesignRequest에서 templateSequence 기본값을 빈 문자열로 변경하면서, 호출자가 실수로 시퀀스를 전달하지 않으면 그대로 빈 시퀀스/검색범위(from=to)가 백엔드로 전송될 수 있습니다. analyzeGenome 수준에서 templateSequence(또는 target_sequence)가 비어 있으면 요청 전에 명확한 에러를 throw 하거나, AnalyzeRequestInput 타입에서 해당 필드를 필수로 만들도록 정리하면 API 사용 실수를 조기에 잡을 수 있습니다.

Copilot uses AI. Check for mistakes.
return {
basic: {
Expand Down Expand Up @@ -224,7 +224,7 @@ export const analyzeGenome = async (

console.log("🚀 Sending Payload:", payload);

const response = await api.post<PrimerDesignResponse>("/primer/design", payload);
const response = await api.post<PrimerDesignResponse>("/design", payload);
const rawData = response.data;

const transformed = toUiResponse(rawData);
Expand Down