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
92 changes: 92 additions & 0 deletions client/app/analysis/ClipboardCopyButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use client';
import {useEffect, useState} from 'react';

async function copyToClipboard(value: string) {
try {
if ('clipboard' in navigator) {
await navigator.clipboard.writeText(value);
return true;
}

const element = document.createElement('textarea');
element.value = value;
document.body.append(element);
element.select();
document.execCommand('copy');
element.remove();

return true;
} catch {
return false;
}
}

interface ClipboardCopyButtonProps {
value: string;
}

enum State {
Idle = 'idle',
Copy = 'copy',
Copied = 'copied',
}

function ClipboardCopyButton({value}: ClipboardCopyButtonProps) {
const [state, setState] = useState<State>(State.Idle);

useEffect(() => {
async function transition() {
switch (state) {
case State.Copy: {
await copyToClipboard(value);
setState(State.Copied);
break;
}
case State.Copied: {
setTimeout(() => {
setState(State.Idle);
}, 2000);
break;
}
default:
break;
}
}
void transition();
}, [state, value]);

return (
<button
className="border-none flex items-center bg-transparent text-neutral-700 dark:text-neutral-300"
onClick={() => setState(State.Copy)}
>
<span className="inline-block mr-1">
{state === State.Copied ? 'Copied' : 'Share Link'}
</span>
{state === State.Copied ? (

<CheckIcon />
) : (

<CopyIcon />
)}
</button>
);
}

function CopyIcon() {
return <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M7.217 10.907a2.25 2.25 0 1 0 0 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186 9.566-5.314m-9.566 7.5 9.566 5.314m0 0a2.25 2.25 0 1 0 3.935 2.186 2.25 2.25 0 0 0-3.935-2.186Zm0-12.814a2.25 2.25 0 1 0 3.933-2.185 2.25 2.25 0 0 0-3.933 2.185Z" />
</svg>


}

function CheckIcon() {
return <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M10.125 2.25h-4.5c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125v-9M10.125 2.25h.375a9 9 0 0 1 9 9v.375M10.125 2.25A3.375 3.375 0 0 1 13.5 5.625v1.5c0 .621.504 1.125 1.125 1.125h1.5a3.375 3.375 0 0 1 3.375 3.375M9 15l2.25 2.25L15 12" />
</svg>

}

export default ClipboardCopyButton;
84 changes: 72 additions & 12 deletions client/app/analysis/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import React, { useRef } from "react";
import React, { useRef, useState } from "react";
import { useReactToPrint } from "react-to-print";
import { Button } from "../../components/ui/button";
import "../../styles/AnalysisPagePdfStyles.css";
Expand All @@ -10,29 +10,89 @@ import { Recommendations } from "@/containers/analysis-page/recomendation-sectio
import NavBarAnalysis from "@/components/NavbarAnalysis";
import Footer from "@/components/Footer";
import { MostCommonPosition } from "@/containers/analysis-page/most-common-position-section";


import ClipboardCopyButton from "./ClipboardCopyButton";

const AnalysisPage = () => {
const pageRef = useRef<HTMLElement | null>(null);
const [isGenerating, setIsGenerating] = useState(false);
const [shareLink, setShareLink] = useState();

const handlePDF = useReactToPrint({
content: () => pageRef.current,
documentTitle: "analysis-data",
onAfterPrint() {},
});

function getAllCSS() {
let css = "";

for (let i = 0; i < document.styleSheets.length; i++) {
const styleSheet = document.styleSheets[i];

if ("cssRules" in styleSheet) {
for (let j = 0; j < styleSheet.cssRules.length; j++) {
const rule = styleSheet.cssRules[j];
if ("cssText" in rule) {
css += rule.cssText + "\n";
}
}
}
}

return css;
}

const generatePdfBlob = () => {
setIsGenerating(true);
const html = document.documentElement.outerHTML;

fetch("/api/upload", {
method: "POST",
headers: {
Content: "application/json",
},
body: JSON.stringify({
html,
css: getAllCSS(),
}),
})
.then((res) => res.json())
.then((res) => {
setShareLink(res.url);
})
.finally(() => {
setIsGenerating(false);
});
};

return (
<main className="h-dvh" ref={pageRef}>
<NavBarAnalysis />
<AnalysisHeader />
<Button
onClick={handlePDF}
className="absolute top-4 right-6 rounded-full bg-blue-600 noprint"
>
Save as PDF
</Button>
{/* <Button className="absolute top-4 right-36 rounded-full bg-blue-600 noprint">
Share PDF
</Button> */}
<aside className="absolute top-4 right-6 z-20 flex gap-3 noprint">
{shareLink ? (
<ClipboardCopyButton value={shareLink} />
) : (
<Button
onClick={generatePdfBlob}
className="relative rounded-full bg-blue-600 noprint min-w-36"
disabled={isGenerating}
>
{isGenerating ? (
<span className="animate-pulse">Generating...</span>
) : (
"Share PDF"
)}
</Button>
)}
<Button
onClick={handlePDF}
className="relative rounded-full bg-blue-600"
>
Save as PDF
</Button>
</aside>

<OveralAnalysis />
<Insights />
<MostCommonPosition />
Expand Down
35 changes: 35 additions & 0 deletions client/app/api/upload/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { BlobServiceClient } from "@azure/storage-blob";
import puppeteer from "puppeteer";

const connectionstring = process.env.AZURE_CONNECTION_STRING as string
const containerName = "pdfs";

const blobServiceClient =
BlobServiceClient.fromConnectionString(connectionstring);
const containerClient = blobServiceClient.getContainerClient(containerName);

export async function POST(request: Request) {
const { html, css } = await request.json();

const browser = await puppeteer.launch({ headless: "new" });
const page = await browser.newPage();

await page.setContent(`<style>${css}</style>${html}`, {
waitUntil: "networkidle0",
});

const pdf = await page.pdf({format: "A4"});

await browser.close();

const blobName = `analysis_${new Date().getTime()}.pdf`;

const blobClient = containerClient.getBlockBlobClient(blobName);
const blobOptions = {
blobHTTPHeaders: { blobContentType: "application/pdf" },
};

await blobClient.upload(pdf, pdf.length, blobOptions);

return Response.json({ url: blobClient.url });
}
2 changes: 1 addition & 1 deletion client/components/Upload/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const UploadFile = () => {

setData(response.data);
toast({
className: "text-white font-bold tracking-wide",
className: "text-white font-bold tracking-wide noprint",
variant: "success",
description: "Analysis Completed: Presenting insights from your file",
});
Expand Down
Loading