Browse Source

feat: add file upload preview pages and QuestionFile component implementation

master
Muhammad A. Ghorbani 2 weeks ago
parent
commit
7d1f8647a4
  1. 99
      src/app/[lang]/preview-upload/page.tsx
  2. 106
      src/app/preview-upload/page.tsx
  3. 16
      src/components/Componentes/question-file.tsx

99
src/app/[lang]/preview-upload/page.tsx

@ -0,0 +1,99 @@
"use client";
import React, { useMemo } from "react";
import { QuestionFile } from "@/components/Componentes/question-file";
import { QuestionAnswersProvider } from "@/components/Componentes/question-answer-storage";
import type { QuestionField } from "@/lib/schema-adapter";
import { I18nProvider } from "@/translations/provider";
const mockQuestion: QuestionField = {
id: "documents_verification.other_supporting_documents",
title: "Other Supporting Documents",
type: "file",
order: 1,
required: false,
baseRequired: false,
isVisible: true,
description: "",
tooltip: "Supporting documents for identity verification",
extras: {
options: [".pdf", ".jpg", ".jpeg", ".png"],
},
options: [],
};
export default function PreviewUploadPage() {
const initialAnswers = useMemo(
() => ({
"documents_verification.other_supporting_documents": [
"/media/tmp/MKROrQ-Screenshot_2026.png",
"/media/tmp/0UKhA-Screenshot_2026.png",
],
}),
[],
);
return (
<I18nProvider initialLocale="en">
<QuestionAnswersProvider initialAnswers={initialAnswers}>
<div className="mx-auto flex min-h-screen max-w-[430px] flex-col bg-[#FFF7F7] px-4 py-3 font-sans antialiased text-[#111111]">
{/* Top Bar */}
<div className="relative flex items-center rounded-2xl bg-gradient-to-r from-[#F0445B] to-[#F87171] px-4 py-3 text-white shadow-sm">
<button type="button" className="flex h-8 w-8 items-center justify-center rounded-full bg-white/20 text-white">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7" />
</svg>
</button>
<span className="ml-3 truncate font-bold text-sm">
Document Upload, Identity Verific...
</span>
</div>
{/* Progress Indicator */}
<div className="mt-4 flex flex-col gap-1.5 px-1">
<div className="flex justify-between text-[11px] font-medium text-gray-500">
<span>fields to complete</span>
<span>4 / 4</span>
</div>
<div className="h-1.5 w-full overflow-hidden rounded-full bg-gray-200">
<div className="h-full w-full rounded-full bg-[#F0445B]" />
</div>
</div>
{/* Advisor Badge */}
<div className="mt-6 flex justify-center">
<div className="inline-flex items-center gap-1.5 rounded-full border border-[#FED7D7] bg-[#FFF5F5] px-3 py-1 text-xs font-semibold text-[#E53E3E]">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
<line x1="1" y1="1" x2="23" y2="23" />
</svg>
<span>Private (Advisors Only)</span>
</div>
</div>
{/* Question Component */}
<div className="mt-4">
<QuestionFile question={mockQuestion} debugProgress={100} />
</div>
{/* Spacer */}
<div className="flex-1" />
{/* Bottom Button */}
<div className="mt-8 mb-4">
<button
type="button"
className="flex h-12 w-full items-center justify-center rounded-2xl bg-[#F0445B] text-white shadow-md active:opacity-90"
>
<div className="flex items-center gap-1.5">
<div className="h-2 w-2 rounded-full bg-white animate-pulse" />
<div className="h-2 w-2 rounded-full bg-white animate-pulse delay-100" />
<div className="h-2 w-2 rounded-full bg-white animate-pulse delay-200" />
</div>
</button>
</div>
</div>
</QuestionAnswersProvider>
</I18nProvider>
);
}

106
src/app/preview-upload/page.tsx

@ -0,0 +1,106 @@
"use client";
import React from "react";
import { QuestionFile } from "@/components/Componentes/question-file";
import { QuestionAnswersProvider } from "@/components/Componentes/question-answer-storage";
import type { QuestionField } from "@/lib/schema-adapter";
import { I18nProvider } from "@/translations/provider";
const mockQuestion: QuestionField = {
id: "documents_verification.other_supporting_documents",
title: "Other Supporting Documents",
type: "file",
order: 1,
required: false,
baseRequired: false,
isVisible: true,
description: "",
tooltip: "Supporting documents for identity verification",
extras: {
options: [".pdf", ".jpg", ".jpeg", ".png"],
},
options: [],
};
const mockDocs = [
{
url: "/media/tmp/MKROrQ-Screenshot_2026.png",
name: "MKROrQ-Screenshot_202...",
previewUrl: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 48 48'><rect width='48' height='48' fill='%23F3F4F6'/><rect x='8' y='10' width='32' height='4' rx='2' fill='%239CA3AF'/><rect x='8' y='18' width='24' height='3' rx='1.5' fill='%23D1D5DB'/><rect x='8' y='25' width='28' height='3' rx='1.5' fill='%23D1D5DB'/><rect x='8' y='32' width='16' height='3' rx='1.5' fill='%23EF4444'/></svg>",
},
{
url: "/media/tmp/0UKhA-Screenshot_2026.png",
name: "0UKhA-Screenshot_202...",
previewUrl: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 48 48'><rect width='48' height='48' fill='%23F3F4F6'/><rect x='8' y='10' width='32' height='4' rx='2' fill='%239CA3AF'/><rect x='8' y='18' width='24' height='3' rx='1.5' fill='%23D1D5DB'/><rect x='8' y='25' width='28' height='3' rx='1.5' fill='%23D1D5DB'/><rect x='8' y='32' width='16' height='3' rx='1.5' fill='%23EF4444'/></svg>",
},
];
export default function RootPreviewUploadPage() {
return (
<I18nProvider locale="en">
<QuestionAnswersProvider>
<div className="mx-auto flex min-h-screen max-w-[430px] flex-col bg-[#FFF7F7] px-4 py-4 font-sans antialiased text-[#111111]">
{/* Top Bar */}
<div className="relative flex items-center rounded-2xl bg-gradient-to-r from-[#F0445B] to-[#F87171] px-4 py-3.5 text-white shadow-sm">
<button type="button" className="flex h-8 w-8 items-center justify-center rounded-full bg-white/20 text-white">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7" />
</svg>
</button>
<span className="ml-3 truncate font-bold text-sm">
Document Upload, Identity Verific...
</span>
</div>
{/* Progress Indicator */}
<div className="mt-4 flex flex-col gap-1.5 px-1">
<div className="flex justify-between text-[11px] font-medium text-gray-500">
<span>fields to complete</span>
<span>4 / 4</span>
</div>
<div className="h-1.5 w-full overflow-hidden rounded-full bg-gray-200">
<div className="h-full w-full rounded-full bg-[#F0445B]" />
</div>
</div>
{/* Advisor Badge */}
<div className="mt-6 flex justify-center">
<div className="inline-flex items-center gap-1.5 rounded-full border border-[#FED7D7] bg-[#FFF5F5] px-3 py-1 text-xs font-semibold text-[#E53E3E]">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
<line x1="1" y1="1" x2="23" y2="23" />
</svg>
<span>Private (Advisors Only)</span>
</div>
</div>
{/* Question Component */}
<div className="mt-4">
<QuestionFile
question={mockQuestion}
debugProgress={100}
initialDocs={mockDocs}
/>
</div>
{/* Spacer */}
<div className="flex-1" />
{/* Bottom Button */}
<div className="mt-8 mb-4">
<button
type="button"
className="flex h-12 w-full items-center justify-center rounded-2xl bg-[#F0445B] text-white shadow-md active:opacity-90"
>
<div className="flex items-center gap-1.5">
<div className="h-2 w-2 rounded-full bg-white animate-pulse" />
<div className="h-2 w-2 rounded-full bg-white animate-pulse delay-100" />
<div className="h-2 w-2 rounded-full bg-white animate-pulse delay-200" />
</div>
</button>
</div>
</div>
</QuestionAnswersProvider>
</I18nProvider>
);
}

16
src/components/Componentes/question-file.tsx

@ -17,6 +17,8 @@ import { LoadingSkeleton } from "./loading-skeleton";
type QuestionFileProps = { type QuestionFileProps = {
question: QuestionField; question: QuestionField;
disabled?: boolean; disabled?: boolean;
debugProgress?: number;
initialDocs?: UploadedDoc[];
}; };
const FILE_I18N: Record< const FILE_I18N: Record<
@ -170,6 +172,8 @@ export type UploadedDoc = {
export function QuestionFile({ export function QuestionFile({
question, question,
disabled, disabled,
debugProgress,
initialDocs,
}: QuestionFileProps) { }: QuestionFileProps) {
const { locale } = useI18n(); const { locale } = useI18n();
const t = const t =
@ -232,10 +236,12 @@ export function QuestionFile({
); );
const [uploadedDocs, setUploadedDocs] = useState<UploadedDoc[]>(() => const [uploadedDocs, setUploadedDocs] = useState<UploadedDoc[]>(() =>
parseInitialDocs(storedValue),
initialDocs ?? parseInitialDocs(storedValue),
);
const [isUploading, setIsUploading] = useState(debugProgress !== undefined);
const [uploadProgress, setUploadProgress] = useState<number | null>(
debugProgress ?? null,
); );
const [isUploading, setIsUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState<number | null>(null);
const isInitiatorRef = useRef(false); const isInitiatorRef = useRef(false);
const pendingDeferredRef = useRef<{ const pendingDeferredRef = useRef<{
resolve: (val?: any) => void; resolve: (val?: any) => void;
@ -679,9 +685,7 @@ export function QuestionFile({
)} )}
</div> </div>
<span className="text-xs font-semibold text-white"> <span className="text-xs font-semibold text-white">
{uploadProgress !== null && uploadProgress > 0
? `${t.uploading} (${uploadProgress}%)`
: t.uploading}
{t.uploading}
</span> </span>
</div> </div>
)} )}

Loading…
Cancel
Save