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
6 changes: 5 additions & 1 deletion Govt-Billing-React/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ VITE_FIREBASE_AUTH_DOMAIN="domain_name.firebaseapp.com"
VITE_FIREBASE_PROJECT_ID="project_id"
VITE_FIREBASE_STORAGE_BUCKET="storage_bucket"
VITE_FIREBASE_MESSAGING_SENDER_ID="sender_id"
VITE_FIREBASE_APP_ID="firebase_app_id"
VITE_FIREBASE_APP_ID="firebase_app_id"

# Required for Save to IPFS/Filecoin feature.
# Get a Lighthouse API key at https://lighthouse.storage
VITE_LIGHTHOUSE_API_KEY="your_lighthouse_api_key_here"
61 changes: 59 additions & 2 deletions Govt-Billing-React/src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { isPlatform, IonToast } from "@ionic/react";
import { EmailComposer } from "capacitor-email-composer";
import { Printer } from "@ionic-native/printer";
import { IonActionSheet, IonAlert } from "@ionic/react";
import { saveOutline, save, mail, print } from "ionicons/icons";
import { saveOutline, save, mail, print, cloudUpload } from "ionicons/icons";
import { APP_NAME } from "../../app-data.js";
import { uploadInvoiceToIPFS } from "../../services/lighthouseService";

const Menu: React.FC<{
showM: boolean;
Expand All @@ -20,8 +21,12 @@ const Menu: React.FC<{
const [showAlert2, setShowAlert2] = useState(false);
const [showAlert3, setShowAlert3] = useState(false);
const [showAlert4, setShowAlert4] = useState(false);
const [showAlert5, setShowAlert5] = useState(false);
const [showToast1, setShowToast1] = useState(false);
const [toastMessage, setToastMessage] = useState("");
const [showSaveAsAfterToast, setShowSaveAsAfterToast] = useState(false);
const [ipfsCid, setIpfsCid] = useState("");
const [ipfsUrl, setIpfsUrl] = useState("");
/* Utility functions */
const _validateName = async (filename) => {
filename = filename.trim();
Expand Down Expand Up @@ -108,11 +113,40 @@ const Menu: React.FC<{
props.updateSelectedFile(filename);
setShowAlert4(true);
} else {
setShowSaveAsAfterToast(true);
setShowToast1(true);
}
}
};

const doSaveToIPFS = async () => {
const apiKey = import.meta.env.VITE_LIGHTHOUSE_API_KEY;

if (!apiKey) {
setToastMessage(
"Lighthouse API key not set. Add VITE_LIGHTHOUSE_API_KEY to .env"
);
setShowSaveAsAfterToast(false);
setShowToast1(true);
return;
}

const content = AppGeneral.getSpreadsheetContent();
const filename =
props.file === "default" ? "invoice" : _formatString(props.file);

try {
const result = await uploadInvoiceToIPFS(content, filename, apiKey);
setIpfsCid(result.cid);
setIpfsUrl(result.url);
setShowAlert5(true);
} catch (err: any) {
setToastMessage(err?.message ?? "IPFS upload failed");
setShowSaveAsAfterToast(false);
setShowToast1(true);
}
};

const sendEmail = () => {
if (isPlatform("hybrid")) {
const content = AppGeneral.getCurrentHTMLContent();
Expand Down Expand Up @@ -156,6 +190,14 @@ const Menu: React.FC<{
console.log("Save As clicked");
},
},
{
text: "Save to IPFS",
icon: cloudUpload,
handler: () => {
doSaveToIPFS();
console.log("Save to IPFS clicked");
},
},
{
text: "Print",
icon: print,
Expand Down Expand Up @@ -225,12 +267,27 @@ const Menu: React.FC<{
}
buttons={["Ok"]}
/>
<IonAlert
animated
isOpen={showAlert5}
onDidDismiss={() => setShowAlert5(false)}
header="Saved to Filecoin/IPFS"
message={
"Invoice pinned successfully.<br/><br/>" +
`<strong>CID:</strong> ${ipfsCid}<br/><br/>` +
`<a href="${ipfsUrl}" target="_blank">View on IPFS Gateway</a>`
}
buttons={["Ok"]}
/>
<IonToast
animated
isOpen={showToast1}
onDidDismiss={() => {
setShowToast1(false);
setShowAlert3(true);
if (showSaveAsAfterToast) {
setShowSaveAsAfterToast(false);
setShowAlert3(true);
}
}}
position="bottom"
message={toastMessage}
Expand Down
39 changes: 39 additions & 0 deletions Govt-Billing-React/src/services/lighthouseService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export interface IPFSUploadResult {
cid: string;
url: string;
timestamp: string;
}

export async function uploadInvoiceToIPFS(
invoiceJson: string,
filename: string,
apiKey: string
): Promise<IPFSUploadResult> {
const file = new File([invoiceJson], `${filename}.json`, {
type: "application/json",
});
const formData = new FormData();
formData.append("file", file);

const response = await fetch("https://node.lighthouse.storage/api/v0/add", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
},
body: formData,
});

if (!response.ok) {
const errorText = await response.text();
throw new Error(`Lighthouse upload failed: ${response.status} ${errorText}`);
}

const data = await response.json();
const cid = data.Hash;

return {
cid,
url: `https://gateway.lighthouse.storage/ipfs/${cid}`,
timestamp: new Date().toISOString(),
};
}
1 change: 1 addition & 0 deletions Govt-Billing-React/src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface ImportMetaEnv {
readonly VITE_FIREBASE_STORAGE_BUCKET: string;
readonly VITE_FIREBASE_MESSAGING_SENDER_ID: string;
readonly VITE_FIREBASE_APP_ID: string;
readonly VITE_LIGHTHOUSE_API_KEY: string;
}

interface ImportMeta {
Expand Down