Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
node_modules
.next
dist
.env
.env.*
.vscode
.DS_Store
next-env.d.ts
.vercel
package-lock.json
yarn.lock
pnpm-lock.yaml
bun.lockb
coverage
build
7 changes: 4 additions & 3 deletions app/api/sign/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,16 @@ export async function POST(req: NextRequest) {
const pubQr = supabaseAdmin.storage.from('signflow').getPublicUrl(`${id}/qr.png`);

// 7) Atualizar registro (payload tipado para evitar 'never')
const payload: Database['public']['Tables']['documents']['Update'] = {
const payload = {
signed_pdf_url: pubSigned.data.publicUrl,
qr_code_url: pubQr.data.publicUrl,
status: 'signed',
};
} satisfies Database['public']['Tables']['documents']['Update'];

const upd = await supabaseAdmin
.from('documents')
.update(payload)
// Supabase types infer `never` without casting; cast avoids TS error
.update(payload as never)
.eq('id', id);

if (upd.error) {
Expand Down
14 changes: 10 additions & 4 deletions app/api/upload/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { supabaseAdmin } from '@/lib/supabaseAdmin';
import type { Database } from '@/lib/types';
import crypto from 'crypto';

export async function POST(req: NextRequest){
Expand Down Expand Up @@ -38,8 +39,8 @@ export async function POST(req: NextRequest){
userId = u?.user?.id || null;
}

// cria registro do documento
const { error: errDoc } = await supabaseAdmin.from('documents').insert({
// cria registro do documento (payload tipado)
const insertPayload = {
id,
user_id: userId,
original_pdf_name,
Expand All @@ -49,8 +50,13 @@ export async function POST(req: NextRequest){
expires_at: new Date(Date.now()+7*24*3600*1000).toISOString(),
signed_pdf_url: null,
qr_code_url: null,
ip_hash
});
ip_hash,
} satisfies Database['public']['Tables']['documents']['Insert'];

const { error: errDoc } = await supabaseAdmin
.from('documents')
// Supabase types infer `never` without casting
.insert(insertPayload as never);
if (errDoc) throw errDoc;

// salva arquivos no Storage (bucket: 'signflow')
Expand Down
36 changes: 31 additions & 5 deletions app/editor/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
'use client';
import { useEffect, useState } from 'react';
import { useEffect, useState, type ChangeEvent } from 'react';
import PdfEditor from '@/components/PdfEditor';
import { Card, Input, Label } from '@/components/Ui';
import { createClient } from '@supabase/supabase-js';

type Position = {
page: number;
nx: number;
ny: number;
scale: number;
rotation: number;
};

export default function Editor({ params }: { params: { id: string } }){
const [pdf, setPdf] = useState<File|null>(null);
const [sig, setSig] = useState<File|null>(null);
const [positions, setPositions] = useState<any[]>([]);
const [positions, setPositions] = useState<Position[]>([]);
const [saving, setSaving] = useState(false);
const [name, setName] = useState('');

Expand Down Expand Up @@ -42,19 +50,37 @@ export default function Editor({ params }: { params: { id: string } }){
<div className="grid md:grid-cols-2 gap-3">
<div>
<Label>PDF</Label>
<Input type="file" accept="application/pdf" onChange={e=>setPdf(e.target.files?.[0]||null)} />
<Input
type="file"
accept="application/pdf"
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setPdf(e.target.files?.[0] || null)
}
/>
</div>
<div>
<Label>Assinatura (PNG/JPG)</Label>
<Input type="file" accept="image/*" onChange={e=>setSig(e.target.files?.[0]||null)} />
<Input
type="file"
accept="image/*"
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setSig(e.target.files?.[0] || null)
}
/>
</div>
</div>
<PdfEditor file={pdf} signature={sig} positions={positions} onPositions={setPositions} />
</Card>
<Card>
<div className="space-y-2">
<Label>Nome do arquivo</Label>
<Input value={name} onChange={e=>setName(e.target.value)} placeholder="Contrato.pdf" />
<Input
value={name}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setName(e.target.value)
}
placeholder="Contrato.pdf"
/>
<button className="btn w-full" onClick={handleUpload} disabled={saving}>Aplicar assinatura + Gerar QR</button>
<p className="text-xs text-slate-500">O QR será inserido na última página, canto inferior esquerdo.</p>
</div>
Expand Down
21 changes: 13 additions & 8 deletions app/validate/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import { supabaseAdmin } from '@/lib/supabaseAdmin';
import type { Database } from '@/lib/types';
import Link from 'next/link';

export default async function Validate({ params }: { params: { id: string } }){
const { data, error } = await supabaseAdmin.from('documents').select('*').eq('id', params.id).maybeSingle();
if (error || !data) return <p className="card">Documento não encontrado.</p>;
const { data: doc, error } = await supabaseAdmin
.from('documents')
.select('*')
.eq('id', params.id)
.maybeSingle<Database['public']['Tables']['documents']['Row']>();
if (error || !doc) return <p className="card">Documento não encontrado.</p>;
return (
<div className="grid md:grid-cols-2 gap-4">
<div className="card">
<h1 className="text-xl font-semibold mb-2">Validação do documento</h1>
<ul className="text-sm text-slate-600 space-y-1">
<li><strong>ID:</strong> {data.id}</li>
<li><strong>Status:</strong> {data.status}</li>
<li><strong>Assinado em:</strong> {new Date(data.created_at).toLocaleString()}</li>
<li><strong>ID:</strong> {doc.id}</li>
<li><strong>Status:</strong> {doc.status}</li>
<li><strong>Assinado em:</strong> {new Date(doc.created_at).toLocaleString()}</li>
</ul>
{data.signed_pdf_url && (
<a className="btn mt-3 inline-block" href={data.signed_pdf_url} target="_blank">Baixar PDF assinado</a>
{doc.signed_pdf_url && (
<a className="btn mt-3 inline-block" href={doc.signed_pdf_url} target="_blank">Baixar PDF assinado</a>
)}
</div>
<div className="card">
<iframe className="w-full h-[70vh] rounded-xl border" src={data.signed_pdf_url || ''}></iframe>
<iframe className="w-full h-[70vh] rounded-xl border" src={doc.signed_pdf_url || ''}></iframe>
<div className="text-xs text-slate-500 mt-2">Se o PDF não abrir, use o botão de download acima.</div>
<div className="mt-3">
<Link className="btn" href={`/validate/${params.id}`}>Link público de validação</Link>
Expand Down