2 Commits

Author SHA1 Message Date
calli
9de91f9982 Add partner code info 2025-12-27 22:44:59 +02:00
calli
14c2732fa0 some users have so many characters that we cant keep them in localSotrage. use db 2025-12-27 21:49:57 +02:00
5 changed files with 160 additions and 13 deletions

View File

@@ -4,6 +4,14 @@ Simple tool to track your PI planet extractors. Login with your characters and e
Any questions, feedback or suggestions are welcome at [EVE PI Discord](https://discord.gg/bCdXzU8PHK)
## Partner code
Consider using EVE partner code to support the project:
```
CALLIEVE
```
## [Hosted PI tool](https://pi.calli.fi)
![Screenshot of PI tool](https://github.com/calli-eve/eve-pi/blob/main/images/eve-pi.png)

View File

@@ -15,6 +15,7 @@ import { CCPButton } from "../CCP/CCPButton";
import { DiscordButton } from "../Discord/DiscordButton";
import { GitHubButton } from "../Github/GitHubButton";
import { LoginButton } from "../Login/LoginButton";
import { PartnerCodeButton } from "../PartnerCode/PartnerCodeButton";
import { SettingsButton } from "../Settings/SettingsButtons";
import {
Button,
@@ -128,6 +129,9 @@ function ResponsiveAppBar() {
<MenuItem onClick={handleCloseNavMenu}>
<CCPButton />
</MenuItem>
<MenuItem onClick={handleCloseNavMenu}>
<PartnerCodeButton />
</MenuItem>
</Menu>
</Box>
<PublicIcon sx={{ display: { xs: "flex", md: "none" }, mr: 1 }} />
@@ -162,12 +166,13 @@ function ResponsiveAppBar() {
<UploadButton />
<DiscordButton />
<GitHubButton />
<SettingsButton />
<Button onClick={() => setFaqOpen(true)} color="inherit">
FAQ
</Button>
<CCPButton />
<PartnerCodeButton />
</Box>
</Toolbar>
</Container>

View File

@@ -0,0 +1,32 @@
import { Box, Button, Tooltip } from "@mui/material";
import { useState } from "react";
export const PartnerCodeButton = () => {
const [copied, setCopied] = useState(false);
const handleClick = () => {
navigator.clipboard.writeText("CALLIEVE");
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<Box>
<Tooltip
title={
copied
? "Copied to clipboard!"
: "Click to copy partner code - Use for CCP purchases to support this project"
}
>
<Button
onClick={handleClick}
style={{ width: "100%" }}
sx={{ color: "white", display: "block" }}
>
Partner Code: CALLIEVE
</Button>
</Tooltip>
</Box>
);
};

View File

@@ -18,6 +18,7 @@ import { useSearchParams } from "next/navigation";
import { EvePraisalResult, fetchAllPrices } from "@/eve-praisal";
import { getPlanet, getPlanetUniverse, getPlanets } from "@/planets";
import { PlanetConfig } from "@/types";
import { saveCharacters as saveCharactersDB, loadCharacters } from "@/storage";
// Add batch processing utility
const processInBatches = async <T, R>(
@@ -108,13 +109,8 @@ const Home = () => {
return Promise.resolve(characters);
};
const initializeCharacters = useCallback((): AccessToken[] => {
const localStorageCharacters = localStorage.getItem("characters");
if (localStorageCharacters) {
const characterArray: AccessToken[] = JSON.parse(localStorageCharacters);
return characterArray.filter((c) => c.access_token && c.character);
}
return [];
const initializeCharacters = useCallback(async (): Promise<AccessToken[]> => {
return await loadCharacters();
}, []);
const initializeCharacterPlanets = (
@@ -139,9 +135,8 @@ const Home = () => {
};
});
const saveCharacters = (characters: AccessToken[]): AccessToken[] => {
localStorage.setItem("characters", JSON.stringify(characters));
return characters;
const saveCharacters = async (characters: AccessToken[]): Promise<AccessToken[]> => {
return await saveCharactersDB(characters);
};
const restoreCharacters = (characters: AccessToken[]) => {
@@ -279,8 +274,8 @@ const Home = () => {
useEffect(() => {
const ESI_CACHE_TIME_MS = 3000000;
const interval = setInterval(() => {
const characters = initializeCharacters();
const interval = setInterval(async () => {
const characters = await initializeCharacters();
refreshSession(characters)
.then(saveCharacters)
.then(initializeCharacterPlanets)

107
src/storage.ts Normal file
View File

@@ -0,0 +1,107 @@
import { AccessToken } from "./types";
const DB_NAME = "eve-pi-db";
const DB_VERSION = 1;
const STORE_NAME = "characters";
// Initialize IndexedDB
const initDB = (): Promise<IDBDatabase> => {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME);
}
};
});
};
// Save characters to IndexedDB
export const saveCharacters = async (
characters: AccessToken[]
): Promise<AccessToken[]> => {
try {
const db = await initDB();
const transaction = db.transaction([STORE_NAME], "readwrite");
const store = transaction.objectStore(STORE_NAME);
store.put(characters, "characters");
return new Promise((resolve, reject) => {
transaction.oncomplete = () => {
db.close();
resolve(characters);
};
transaction.onerror = () => {
db.close();
reject(transaction.error);
};
});
} catch (error) {
console.error("Failed to save to IndexedDB:", error);
// Fallback: save minimal data to localStorage
try {
const minimalCharacters = characters.map((c) => ({
...c,
planets: [], // Strip planet data to reduce size
}));
localStorage.setItem("characters", JSON.stringify(minimalCharacters));
console.warn("Saved minimal character data to localStorage fallback");
} catch (storageError) {
console.error("Failed to save to localStorage fallback:", storageError);
}
return characters;
}
};
// Load characters from IndexedDB
export const loadCharacters = async (): Promise<AccessToken[]> => {
try {
const db = await initDB();
const transaction = db.transaction([STORE_NAME], "readonly");
const store = transaction.objectStore(STORE_NAME);
const request = store.get("characters");
return new Promise((resolve, reject) => {
request.onsuccess = () => {
db.close();
const characters = request.result as AccessToken[] | undefined;
if (characters && characters.length > 0) {
resolve(characters);
} else {
// Try localStorage migration
resolve(migrateFromLocalStorage());
}
};
request.onerror = () => {
db.close();
reject(request.error);
};
});
} catch (error) {
console.error("Failed to load from IndexedDB:", error);
// Fallback to localStorage
return migrateFromLocalStorage();
}
};
// Migrate data from localStorage to IndexedDB
const migrateFromLocalStorage = (): AccessToken[] => {
try {
const localStorageCharacters = localStorage.getItem("characters");
if (localStorageCharacters) {
const characterArray: AccessToken[] = JSON.parse(localStorageCharacters);
const filtered = characterArray.filter((c) => c.access_token && c.character);
// Don't delete from localStorage yet - keep as backup
return filtered;
}
} catch (error) {
console.error("Failed to migrate from localStorage:", error);
}
return [];
};