gaurv007 commited on
Commit
e1126b7
·
verified ·
1 Parent(s): 374d3c3

Fix build: rename PDF route .ts → .tsx (JSX in API route needs .tsx extension for Turbopack)

Browse files
web/app/api/pdf/report/route.ts DELETED
@@ -1,106 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { Document, Page, Text, View, StyleSheet, pdf } from "@react-pdf/renderer";
3
- import React from "react";
4
-
5
- export const runtime = "nodejs";
6
-
7
- const styles = StyleSheet.create({
8
- page: { padding: 40, fontFamily: "Helvetica", fontSize: 11, color: "#27272a" },
9
- header: { marginBottom: 24 },
10
- title: { fontSize: 22, fontWeight: "bold", marginBottom: 4 },
11
- subtitle: { fontSize: 11, color: "#71717a" },
12
- divider: { borderBottomWidth: 1, borderBottomColor: "#e4e4e7", marginVertical: 16 },
13
- scoreRow: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 16 },
14
- scoreLabel: { fontSize: 10, color: "#a1a1aa" },
15
- scoreValue: { fontSize: 28, fontWeight: "bold" },
16
- gradeTag: { fontSize: 12, fontWeight: "bold", padding: "4 12", borderRadius: 4 },
17
- clauseCard: { marginBottom: 12, padding: 12, borderWidth: 1, borderColor: "#e4e4e7", borderRadius: 6 },
18
- clauseText: { fontSize: 10, color: "#3f3f46", lineHeight: 1.5, marginBottom: 6 },
19
- tag: { fontSize: 9, fontWeight: "bold", padding: "2 8", borderRadius: 3, marginRight: 4 },
20
- tagHigh: { backgroundColor: "#fef2f2", color: "#b91c1c" },
21
- tagMedium: { backgroundColor: "#fffbeb", color: "#a16207" },
22
- tagLow: { backgroundColor: "#eff6ff", color: "#1d4ed8" },
23
- footer: { position: "absolute", bottom: 30, left: 40, right: 40, fontSize: 8, color: "#a1a1aa", textAlign: "center" },
24
- });
25
-
26
- interface Clause { text: string; categories: { name: string; severity: string }[]; }
27
- interface ReportData { risk_score: number; grade: string; total_clauses: number; flagged_count: number; results: Clause[]; source_url?: string; }
28
-
29
- function ClauseReport({ data }: { data: ReportData }) {
30
- const flagged = data.results.filter((r) => r.categories.length > 0);
31
- const gradeColor = data.grade === "F" || data.grade === "D" ? "#b91c1c" : data.grade === "C" ? "#a16207" : "#15803d";
32
- const gradeBg = data.grade === "F" || data.grade === "D" ? "#fef2f2" : data.grade === "C" ? "#fffbeb" : "#f0fdf4";
33
-
34
- return (
35
- <Document>
36
- <Page size="A4" style={styles.page}>
37
- <View style={styles.header}>
38
- <Text style={styles.title}>ClauseGuard Report</Text>
39
- <Text style={styles.subtitle}>
40
- {data.source_url || "Manual scan"} — {new Date().toLocaleDateString()}
41
- </Text>
42
- </View>
43
-
44
- <View style={styles.scoreRow}>
45
- <View>
46
- <Text style={styles.scoreLabel}>RISK SCORE</Text>
47
- <Text style={styles.scoreValue}>{data.risk_score}/100</Text>
48
- </View>
49
- <Text style={[styles.gradeTag, { backgroundColor: gradeBg, color: gradeColor }]}>
50
- Grade {data.grade}
51
- </Text>
52
- </View>
53
-
54
- <Text style={styles.subtitle}>
55
- {data.total_clauses} clauses scanned — {data.flagged_count} flagged
56
- </Text>
57
-
58
- <View style={styles.divider} />
59
-
60
- {flagged.map((clause, i) => (
61
- <View key={i} style={styles.clauseCard}>
62
- <Text style={styles.clauseText}>{clause.text.substring(0, 300)}</Text>
63
- <View style={{ flexDirection: "row", flexWrap: "wrap" }}>
64
- {clause.categories.map((cat, j) => {
65
- const tagStyle = cat.severity === "HIGH" ? styles.tagHigh : cat.severity === "MEDIUM" ? styles.tagMedium : styles.tagLow;
66
- return (
67
- <Text key={j} style={[styles.tag, tagStyle]}>
68
- {cat.name}
69
- </Text>
70
- );
71
- })}
72
- </View>
73
- </View>
74
- ))}
75
-
76
- <Text style={styles.footer}>
77
- Generated by ClauseGuard — clauseguard.com — Not legal advice
78
- </Text>
79
- </Page>
80
- </Document>
81
- );
82
- }
83
-
84
- export async function POST(req: NextRequest) {
85
- try {
86
- const data: ReportData = await req.json();
87
-
88
- const instance = pdf(React.createElement(ClauseReport, { data }));
89
- const buffer = await instance.toBuffer();
90
-
91
- const chunks: Uint8Array[] = [];
92
- for await (const chunk of buffer as any) {
93
- chunks.push(chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk));
94
- }
95
-
96
- return new Response(Buffer.concat(chunks), {
97
- headers: {
98
- "Content-Type": "application/pdf",
99
- "Content-Disposition": `attachment; filename="clauseguard-report-${Date.now()}.pdf"`,
100
- },
101
- });
102
- } catch (error) {
103
- console.error("PDF generation error:", error);
104
- return NextResponse.json({ error: "PDF generation failed" }, { status: 500 });
105
- }
106
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/app/api/pdf/report/route.tsx ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import React from "react";
3
+
4
+ export const runtime = "nodejs";
5
+
6
+ export async function POST(req: NextRequest) {
7
+ try {
8
+ const data = await req.json();
9
+
10
+ // Dynamic import to avoid bundling issues
11
+ const { Document, Page, Text, View, StyleSheet, pdf } = await import("@react-pdf/renderer");
12
+
13
+ const styles = StyleSheet.create({
14
+ page: { padding: 40, fontFamily: "Helvetica", fontSize: 11, color: "#27272a" },
15
+ title: { fontSize: 22, fontWeight: "bold", marginBottom: 4 },
16
+ subtitle: { fontSize: 11, color: "#71717a" },
17
+ divider: { borderBottomWidth: 1, borderBottomColor: "#e4e4e7", marginVertical: 16 },
18
+ scoreRow: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 16 },
19
+ scoreLabel: { fontSize: 10, color: "#a1a1aa" },
20
+ scoreValue: { fontSize: 28, fontWeight: "bold" },
21
+ clauseCard: { marginBottom: 12, padding: 12, borderWidth: 1, borderColor: "#e4e4e7", borderRadius: 6 },
22
+ clauseText: { fontSize: 10, color: "#3f3f46", lineHeight: 1.5, marginBottom: 6 },
23
+ tag: { fontSize: 9, fontWeight: "bold", padding: "2 8", borderRadius: 3, marginRight: 4 },
24
+ tagHigh: { backgroundColor: "#fef2f2", color: "#b91c1c" },
25
+ tagMedium: { backgroundColor: "#fffbeb", color: "#a16207" },
26
+ tagLow: { backgroundColor: "#eff6ff", color: "#1d4ed8" },
27
+ footer: { position: "absolute", bottom: 30, left: 40, right: 40, fontSize: 8, color: "#a1a1aa", textAlign: "center" },
28
+ });
29
+
30
+ const flagged = (data.results || []).filter((r: any) => r.categories?.length > 0);
31
+
32
+ const doc = React.createElement(
33
+ Document,
34
+ null,
35
+ React.createElement(
36
+ Page,
37
+ { size: "A4", style: styles.page },
38
+ // Header
39
+ React.createElement(View, null,
40
+ React.createElement(Text, { style: styles.title }, "ClauseGuard Report"),
41
+ React.createElement(Text, { style: styles.subtitle },
42
+ `${data.source_url || "Manual scan"} — ${new Date().toLocaleDateString()}`
43
+ ),
44
+ ),
45
+ // Score
46
+ React.createElement(View, { style: styles.scoreRow },
47
+ React.createElement(View, null,
48
+ React.createElement(Text, { style: styles.scoreLabel }, "RISK SCORE"),
49
+ React.createElement(Text, { style: styles.scoreValue }, `${data.risk_score}/100`),
50
+ ),
51
+ React.createElement(Text, { style: { fontSize: 16, fontWeight: "bold" } }, `Grade ${data.grade}`),
52
+ ),
53
+ React.createElement(Text, { style: styles.subtitle },
54
+ `${data.total_clauses} clauses scanned — ${data.flagged_count} flagged`
55
+ ),
56
+ React.createElement(View, { style: styles.divider }),
57
+ // Clauses
58
+ ...flagged.map((clause: any, i: number) =>
59
+ React.createElement(View, { key: i, style: styles.clauseCard },
60
+ React.createElement(Text, { style: styles.clauseText },
61
+ clause.text?.substring(0, 300) || ""
62
+ ),
63
+ React.createElement(View, { style: { flexDirection: "row", flexWrap: "wrap" } },
64
+ ...(clause.categories || []).map((cat: any, j: number) => {
65
+ const tagStyle = cat.severity === "HIGH" ? styles.tagHigh : cat.severity === "MEDIUM" ? styles.tagMedium : styles.tagLow;
66
+ return React.createElement(Text, { key: j, style: [styles.tag, tagStyle] }, cat.name);
67
+ })
68
+ ),
69
+ )
70
+ ),
71
+ // Footer
72
+ React.createElement(Text, { style: styles.footer },
73
+ "Generated by ClauseGuard — clauseguard.com — Not legal advice"
74
+ ),
75
+ )
76
+ );
77
+
78
+ const instance = pdf(doc);
79
+ const buffer = await instance.toBuffer();
80
+
81
+ const chunks: Uint8Array[] = [];
82
+ for await (const chunk of buffer as any) {
83
+ chunks.push(chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk));
84
+ }
85
+
86
+ return new Response(Buffer.concat(chunks), {
87
+ headers: {
88
+ "Content-Type": "application/pdf",
89
+ "Content-Disposition": `attachment; filename="clauseguard-report.pdf"`,
90
+ },
91
+ });
92
+ } catch (error) {
93
+ console.error("PDF generation error:", error);
94
+ return NextResponse.json({ error: "PDF generation failed" }, { status: 500 });
95
+ }
96
+ }