diff --git a/src/components/Dashboard/Dashboard.tsx b/src/components/Dashboard/Dashboard.tsx index dcbed8d..852cb23 100644 --- a/src/components/Dashboard/Dashboard.tsx +++ b/src/components/Dashboard/Dashboard.tsx @@ -8,7 +8,7 @@ import { documentTextOutline, cloudUploadOutline, cloudDownloadOutline, shareSocialOutline, trashOutline, addCircleOutline, serverOutline, hardwareChipOutline, openOutline, timeOutline, statsChartOutline, searchOutline, documentOutline } from "ionicons/icons"; -import { Local, File as LocalFile, BackupRecord, ActivityLog, AppMetrics } from "../Storage/LocalStorage"; +import { Local, File as LocalFile, FileData, FileListItem, BackupRecord, ActivityLog, AppMetrics } from "../Storage/LocalStorage"; import { backupInvoiceToIPFS, restoreInvoiceFromIPFS, testConnection } from "../../services/MeshkitService"; import { BackupSuccessModal } from "../Modals/BackupSuccessModal"; import { RestoreSuccessModal } from "../Modals/RestoreSuccessModal"; @@ -23,7 +23,7 @@ interface DashboardProps { } const Dashboard: React.FC = ({ store, onOpenFile, currentBillType }) => { - const [fileList, setFileList] = useState<{ [key: string]: any }>({}); + const [fileList, setFileList] = useState>({}); const [backupHistory, setBackupHistory] = useState([]); const [activityLogs, setActivityLogs] = useState([]); const [metrics, setMetrics] = useState({ invoicesCreated: 0, invoicesBackedUp: 0, successfulRestores: 0, filesUploaded: 0, messagesSent: 0 }); @@ -88,7 +88,7 @@ const Dashboard: React.FC = ({ store, onOpenFile, currentBillTyp }; const handleOpen = (key: string) => { - store._getFile(key).then((data: any) => { + store._getFile(key).then((data: FileData) => { AppGeneral.viewFile(key, decodeURIComponent(data.content)); onOpenFile(key, data.billType); }); @@ -97,7 +97,7 @@ const Dashboard: React.FC = ({ store, onOpenFile, currentBillTyp const handleBackup = async (key: string) => { setIsLoading(true); try { - const data: any = await store._getFile(key); + const data: FileData = await store._getFile(key); const record = await backupInvoiceToIPFS({ name: key, created: data.created, @@ -203,7 +203,7 @@ const Dashboard: React.FC = ({ store, onOpenFile, currentBillTyp displayToast("Filename already exists"); return; } - const content = encodeURIComponent(JSON.stringify(DATA["home"][AppGeneral.getDeviceType()]["msc"])); + const content = encodeURIComponent(JSON.stringify(DATA["home"][AppGeneral.getDeviceType() as keyof typeof DATA["home"]]["msc"])); const file = new LocalFile(new Date().toString(), new Date().toString(), content, filename, currentBillType); await store._saveFile(file); await store._incrementMetric('invoicesCreated'); @@ -305,7 +305,7 @@ const Dashboard: React.FC = ({ store, onOpenFile, currentBillTyp {fileList[key].cid && ( - CID: {fileList[key].cid.substring(0, 8)}... + CID: {fileList[key].cid?.substring(0, 8)}... )} @@ -316,13 +316,13 @@ const Dashboard: React.FC = ({ store, onOpenFile, currentBillTyp handleBackup(key)}> Backup - {fileList[key].backedUp && ( - handleRestore(fileList[key].cid, key)}> + {fileList[key].backedUp && fileList[key].cid && ( + handleRestore(fileList[key].cid!, key)}> Restore )} - {fileList[key].backedUp && ( - handleShare(fileList[key].cid)}> + {fileList[key].backedUp && fileList[key].cid && ( + handleShare(fileList[key].cid!)}> Share )} diff --git a/src/components/Files/Files.tsx b/src/components/Files/Files.tsx index b8fcb25..caa8eb9 100644 --- a/src/components/Files/Files.tsx +++ b/src/components/Files/Files.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react"; import "./Files.css"; import * as AppGeneral from "../socialcalc/index.js"; import { DATA } from "../../app-data.js"; -import { File as LocalFile, Local, BackupRecord } from "../Storage/LocalStorage"; +import { File as LocalFile, Local, FileData, FileListItem, BackupRecord } from "../Storage/LocalStorage"; import { IonIcon, IonModal, @@ -26,18 +26,20 @@ import { import { fileTrayFull, ellipsisVertical, cloudUploadOutline, cloudDownloadOutline, shareSocialOutline, trash, folderOpenOutline, timeOutline } from "ionicons/icons"; import { backupInvoiceToIPFS, restoreInvoiceFromIPFS } from "../../services/MeshkitService"; -const Files: React.FC<{ +interface FilesProps { store: Local; file: string; - updateSelectedFile: Function; - updateBillType: Function; -}> = (props) => { + updateSelectedFile: (file: string) => void; + updateBillType: (billType: number) => void; +} + +const Files: React.FC = (props) => { const [modal, setModal] = useState(null); const [listFiles, setListFiles] = useState(false); const [showAlertDelete, setShowAlertDelete] = useState(false); const [currentKey, setCurrentKey] = useState(null); - const [fileList, setFileList] = useState<{ [key: string]: any }>({}); + const [fileList, setFileList] = useState>({}); const [actionSheetOpen, setActionSheetOpen] = useState(false); const [selectedActionFile, setSelectedActionFile] = useState(null); @@ -71,7 +73,7 @@ const Files: React.FC<{ }; const editFile = (key: string) => { - props.store._getFile(key).then((data: any) => { + props.store._getFile(key).then((data: FileData) => { AppGeneral.viewFile(key, decodeURIComponent(data.content)); props.updateSelectedFile(key); props.updateBillType(data.billType); @@ -85,7 +87,7 @@ const Files: React.FC<{ }; const loadDefault = () => { - const msc = DATA["home"][AppGeneral.getDeviceType()]["msc"]; + const msc = DATA["home"][AppGeneral.getDeviceType() as keyof typeof DATA["home"]]["msc"]; AppGeneral.viewFile("default", JSON.stringify(msc)); props.updateSelectedFile("default"); }; @@ -98,7 +100,7 @@ const Files: React.FC<{ const handleBackup = async (key: string) => { setIsLoading(true); try { - const data: any = await props.store._getFile(key); + const data: FileData = await props.store._getFile(key); const record = await backupInvoiceToIPFS({ name: key, created: data.created, @@ -185,16 +187,16 @@ const Files: React.FC<{ } ]; - if (isBackedUp) { + if (isBackedUp && fileData.cid) { buttons.push({ text: 'Restore (Overwrite Local)', icon: cloudDownloadOutline, - handler: () => handleRestore(selectedActionFile, fileData.cid) + handler: () => handleRestore(selectedActionFile, fileData.cid!) }); buttons.push({ text: 'Share', icon: shareSocialOutline, - handler: () => handleShare(fileData.cid) + handler: () => handleShare(fileData.cid!) }); } diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index 0a861ae..ce5b4b3 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import * as AppGeneral from "../socialcalc/index.js"; -import { File, Local } from "../Storage/LocalStorage"; +import { File, Local, FileData } from "../Storage/LocalStorage"; import { isPlatform, IonToast } from "@ionic/react"; import { EmailComposer } from "capacitor-email-composer"; import { Printer } from "@ionic-native/printer"; @@ -10,14 +10,16 @@ import { APP_NAME } from "../../app-data.js"; import { backupInvoiceToIPFS, restoreInvoiceFromIPFS } from "../../services/MeshkitService"; import { BackupSuccessModal } from "../Modals/BackupSuccessModal"; -const Menu: React.FC<{ +interface MenuProps { showM: boolean; - setM: Function; + setM: () => void; file: string; - updateSelectedFile: Function; + updateSelectedFile: (file: string) => void; store: Local; bT: number; -}> = (props) => { +} + +const Menu: React.FC = (props) => { const [showAlert1, setShowAlert1] = useState(false); const [showAlert2, setShowAlert2] = useState(false); const [showAlert3, setShowAlert3] = useState(false); @@ -35,7 +37,7 @@ const Menu: React.FC<{ const [showAlertRestoreError, setShowAlertRestoreError] = useState(false); /* Utility functions */ - const _validateName = async (filename) => { + const _validateName = async (filename: string) => { filename = filename.trim(); if (filename === "default" || filename === "Untitled") { setToastMessage("Cannot update default file!"); @@ -58,7 +60,7 @@ const Menu: React.FC<{ const getCurrentFileName = () => { return props.file; }; - const _formatString = (filename) => { + const _formatString = (filename: string) => { /* Remove whitespaces */ while (filename.indexOf(" ") !== -1) { filename = filename.replace(" ", ""); @@ -72,29 +74,31 @@ const Menu: React.FC<{ } else { const content = AppGeneral.getCurrentHTMLContent(); const printWindow = window.open("/printwindow", "Print Invoice"); - printWindow.document.write(content); - printWindow.print(); + if (printWindow) { + printWindow.document.write(content); + printWindow.print(); + } } }; - const doSave = () => { + const doSave = async () => { if (props.file === "default") { setShowAlert1(true); return; } const content = encodeURIComponent(AppGeneral.getSpreadsheetContent()); - const data = props.store._getFile(props.file); + const data = await props.store._getFile(props.file); const file = new File( - (data as any).created, + data.created, new Date().toString(), content, props.file, props.bT ); - props.store._saveFile(file); + await props.store._saveFile(file); props.updateSelectedFile(props.file); setShowAlert2(true); }; - const doSaveAs = async (filename) => { + const doSaveAs = async (filename: string) => { if (filename) { if (await _validateName(filename)) { const content = encodeURIComponent(AppGeneral.getSpreadsheetContent()); @@ -123,7 +127,7 @@ const Menu: React.FC<{ const content = encodeURIComponent(AppGeneral.getSpreadsheetContent()); const record = await backupInvoiceToIPFS({ name: props.file, - created: (data as any).created, + created: data.created, modified: new Date().toString(), billType: props.bT, content, @@ -132,9 +136,9 @@ const Menu: React.FC<{ // Update local file metadata const file = new File( - (data as any).created, - (data as any).modified, // Keep original modified date - (data as any).content, + data.created, + data.modified, // Keep original modified date + data.content, props.file, props.bT, true, // backedUp @@ -156,7 +160,7 @@ const Menu: React.FC<{ } }; - const doRestoreFromIPFS = async (cid) => { + const doRestoreFromIPFS = async (cid: string) => { if (!cid) return; try { const backup = await restoreInvoiceFromIPFS(cid); diff --git a/src/components/NewFile/NewFile.tsx b/src/components/NewFile/NewFile.tsx index f1adffa..4de5b7f 100644 --- a/src/components/NewFile/NewFile.tsx +++ b/src/components/NewFile/NewFile.tsx @@ -1,32 +1,34 @@ import React, { useState } from "react"; import * as AppGeneral from "../socialcalc/index.js"; -import { File, Local } from "../Storage/LocalStorage"; +import { File, Local, FileData } from "../Storage/LocalStorage"; import { DATA } from "../../app-data.js"; import { IonAlert, IonIcon } from "@ionic/react"; import { add } from "ionicons/icons"; -const NewFile: React.FC<{ +interface NewFileProps { file: string; - updateSelectedFile: Function; + updateSelectedFile: (file: string) => void; store: Local; billType: number; -}> = (props) => { +} + +const NewFile: React.FC = (props) => { const [showAlertNewFileCreated, setShowAlertNewFileCreated] = useState(false); - const newFile = () => { + const newFile = async () => { if (props.file !== "default") { const content = encodeURIComponent(AppGeneral.getSpreadsheetContent()); - const data = props.store._getFile(props.file); + const data: FileData = await props.store._getFile(props.file); const file = new File( - (data as any).created, + data.created, new Date().toString(), content, props.file, props.billType ); - props.store._saveFile(file); + await props.store._saveFile(file); props.updateSelectedFile(props.file); } - const msc = DATA["home"][AppGeneral.getDeviceType()]["msc"]; + const msc = DATA["home"][AppGeneral.getDeviceType() as keyof typeof DATA["home"]]["msc"]; AppGeneral.viewFile("default", JSON.stringify(msc)); props.updateSelectedFile("default"); setShowAlertNewFileCreated(true); diff --git a/src/components/Storage/LocalStorage.ts b/src/components/Storage/LocalStorage.ts index 67d124e..2e1d251 100644 --- a/src/components/Storage/LocalStorage.ts +++ b/src/components/Storage/LocalStorage.ts @@ -22,6 +22,24 @@ export interface AppMetrics { messagesSent: number; } +export interface FileData { + created: string; + modified: string; + content: string; + name: string; + billType: number; + backedUp?: boolean; + cid?: string; + lastBackupAt?: string; +} + +export interface FileListItem { + modified: string; + backedUp?: boolean; + cid?: string; + lastBackupAt?: string; +} + export class File { created: string; modified: string; @@ -71,23 +89,26 @@ export class Local { }); }; - _getFile = async (name: string) => { + _getFile = async (name: string): Promise => { const rawData = await Preferences.get({ key: name }); - return JSON.parse(rawData.value); + if (!rawData.value) { + throw new Error(`File "${name}" not found in storage`); + } + return JSON.parse(rawData.value) as FileData; }; - _getAllFiles = async () => { - let arr = {}; + _getAllFiles = async (): Promise> => { + const arr: Record = {}; const { keys } = await Preferences.keys(); for (let i = 0; i < keys.length; i++) { - let fname = keys[i]; + const fname = keys[i]; if (fname === "_MeshKit_BackupHistory" || fname === "_MeshKit_ActivityLogs" || fname === "_MeshKit_Metrics") continue; const data = await this._getFile(fname); arr[fname] = { - modified: (data as any).modified, - backedUp: (data as any).backedUp, - cid: (data as any).cid, - lastBackupAt: (data as any).lastBackupAt, + modified: data.modified, + backedUp: data.backedUp, + cid: data.cid, + lastBackupAt: data.lastBackupAt, }; } return arr; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 876a058..c5e5ba7 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -34,7 +34,7 @@ const Home: React.FC = () => { }>({ open: false, event: undefined }); const [selectedFile, updateSelectedFile] = useState("default"); const [billType, updateBillType] = useState(1); - const [device] = useState("default"); + const [device] = useState<"default">("default"); const history = useHistory(); const store = new Local(); @@ -43,7 +43,7 @@ const Home: React.FC = () => { setShowMenu(false); }; - const activateFooter = (footer) => { + const activateFooter = (footer: number) => { AppGeneral.activateFooterButton(footer); }; diff --git a/src/pages/MeshKit.tsx b/src/pages/MeshKit.tsx index 5640c4f..ee7cc7d 100644 --- a/src/pages/MeshKit.tsx +++ b/src/pages/MeshKit.tsx @@ -59,7 +59,7 @@ const MeshKitPage: React.FC = () => { // Retrieve JSON const [retrieveCid, setRetrieveCid] = useState(""); - const [retrievedJson, setRetrievedJson] = useState(null); + const [retrievedJson, setRetrievedJson] = useState | null>(null); const [retrieveLoading, setRetrieveLoading] = useState(false); // Upload File @@ -80,7 +80,7 @@ const MeshKitPage: React.FC = () => { // Messaging (Receive) const [receiveCid, setReceiveCid] = useState(""); - const [receivedMessage, setReceivedMessage] = useState(null); + const [receivedMessage, setReceivedMessage] = useState | null>(null); const [receiveLoading, setReceiveLoading] = useState(false); // Revoke CID @@ -143,7 +143,7 @@ const MeshKitPage: React.FC = () => { if (!retrieveCid) return displayToast("Please enter a valid CID"); setRetrieveLoading(true); try { - const data = await retrieveJSON(retrieveCid); + const data = await retrieveJSON>(retrieveCid); setRetrievedJson(data); displayToast("JSON retrieved successfully"); } catch (error) { diff --git a/src/services/MeshkitService.ts b/src/services/MeshkitService.ts index 4811439..0d72617 100644 --- a/src/services/MeshkitService.ts +++ b/src/services/MeshkitService.ts @@ -57,7 +57,7 @@ export async function retrieveJSON(cid: string): Promise { return await meshkit.retrieve(cid); } -export async function uploadFile(file: File): Promise> { +export async function uploadFile(file: Blob): Promise> { const meshkit = await getMeshkit(); return await meshkit.upload(file); } @@ -67,12 +67,12 @@ export async function downloadFile(cid: string): Promise { return await meshkit.download(cid); } -export async function sendMessage(recipientId: string, payload: any): Promise> { +export async function sendMessage(recipientId: string, payload: Record): Promise>> { const meshkit = await getMeshkit(); return await meshkit.send(recipientId, payload); } -export async function receiveMessage(cid: string): Promise { +export async function receiveMessage(cid: string): Promise> { const meshkit = await getMeshkit(); return await meshkit.receive(cid); } diff --git a/src/types/meshkit-ionic.d.ts b/src/types/meshkit-ionic.d.ts new file mode 100644 index 0000000..7f9c6d8 --- /dev/null +++ b/src/types/meshkit-ionic.d.ts @@ -0,0 +1,22 @@ +declare module "@meshkit/ionic" { + export interface MeshkitRecord { + cid: string; + data: T; + } + + export class Meshkit { + static init(options: { + provider: string; + providerToken: string; + }): Promise; + + testConnection(): Promise; + store(data: T): Promise>; + retrieve(cid: string): Promise; + upload(file: Blob): Promise>; + download(cid: string): Promise; + send(recipientId: string, payload: Record): Promise>>; + receive(cid: string): Promise>; + revoke(cid: string): Promise; + } +} diff --git a/src/types/socialcalc.d.ts b/src/types/socialcalc.d.ts new file mode 100644 index 0000000..658e641 --- /dev/null +++ b/src/types/socialcalc.d.ts @@ -0,0 +1,17 @@ +declare module "../components/socialcalc/index.js" { + export function getDeviceType(): string; + export function initializeApp(data: string): void; + export function activateFooterButton(index: number): void; + export function viewFile(name: string, content: string): void; + export function getSpreadsheetContent(): string; + export function getCurrentHTMLContent(): string; +} + +declare module "../socialcalc/index.js" { + export function getDeviceType(): string; + export function initializeApp(data: string): void; + export function activateFooterButton(index: number): void; + export function viewFile(name: string, content: string): void; + export function getSpreadsheetContent(): string; + export function getCurrentHTMLContent(): string; +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index eecb811..fae974a 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,6 +1,7 @@ /// interface ImportMetaEnv { + readonly VITE_PINATA_JWT: string; readonly VITE_FIREBASE_VITE_APP_TITLE: string; readonly VITE_FIREBASE_API_KEY: string; readonly VITE_FIREBASE_AUTH_DOMAIN: string; diff --git a/tsconfig.json b/tsconfig.json index 7b0213a..8a10db2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "skipLibCheck": true, "esModuleInterop": false, "allowSyntheticDefaultImports": true, - "strict": false, + "strict": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", "moduleResolution": "Node",