add restore functionality

This commit is contained in:
Calli
2023-06-23 22:20:58 +03:00
parent 808d983bd1
commit ed13e73587
7 changed files with 120 additions and 2 deletions

View File

@@ -23,6 +23,22 @@ Features:
2. Group the character to accounts by clicking on the character name and setting the account name
3. Make sure your extractors are running!
## Backing up or moving character list to another device
Because everything is stored in the browser there is no way to populate the list. To help with this EVE PI provides Backup and Restore functionality using basic JSON file.
To dowload your list:
1. Click Dowload button in the top button bar
2. Find your backup json file in your Downloads folder
To restore your list:
**Take note that restoring the list will overwrite any local list that you have!**
1. Click Restore button in the top button bar
2. Use the dialog to select the file you previously dowloaded to restore the list.
## Security
All eve sso information is stored in your browser and refresh token is encrypted with apps EVE SSO secret. Backend processes only the token exchange, refresh and revoke that need the EVE_SSO_SECRET. Everything else is handled in frontend.

View File

@@ -1,4 +1,4 @@
import { CharacterContext } from "@/app/context/Context";
import { CharacterContext, SessionContext } from "@/app/context/Context";
import { Button } from "@mui/material";
import { useContext } from "react";
@@ -9,7 +9,7 @@ export const DowloadButton = () => {
href={`data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify(characters)
)}`}
download="pi-avanto-tk-characters.json"
download={`eve-pi-characters.json`}
>
Backup
</Button>

View File

@@ -0,0 +1,21 @@
import { Box, Button } from "@mui/material";
import { useState } from "react";
import { UploadDialog } from "./UploadDialog";
export const UploadButton = () => {
const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
return (
<Box>
<Button
style={{ width: "100%" }}
onClick={() => setUploadDialogOpen(true)}
>
Restore
</Button>
<UploadDialog
open={uploadDialogOpen}
closeDialog={() => setUploadDialogOpen(false)}
/>
</Box>
);
};

View File

@@ -0,0 +1,72 @@
import DialogTitle from "@mui/material/DialogTitle";
import Dialog from "@mui/material/Dialog";
import Button from "@mui/material/Button";
import { DialogActions, DialogContent, Typography } from "@mui/material";
import { useContext, useEffect, useState } from "react";
import { CharacterContext } from "@/app/context/Context";
import { AccessToken } from "@/types";
export const UploadDialog = ({
open,
closeDialog,
}: {
open: boolean;
closeDialog: () => void;
}) => {
const [file, setFile] = useState();
const fileReader = new FileReader();
const { restoreCharacters } = useContext(CharacterContext);
const error = new Error("Invalid input");
const validate = (characters: AccessToken[]) => {
characters.forEach((c) => {
if (!c.access_token) throw error;
if (!c.expires_at) throw error;
if (!c.refresh_token) throw error;
if (!c.character) throw error;
if (!c.character.characterId) throw error;
if (!c.character.name) throw error;
});
return characters;
};
useEffect(() => {
if (file) {
fileReader.onload = (event) => {
if (!event || event.target === null) return;
const jsonOutput = event.target.result?.toString();
if (jsonOutput) restoreCharacters(validate(JSON.parse(jsonOutput)));
closeDialog();
};
fileReader.readAsText(file);
}
}, [file]);
const changeHandler = (event: any) => {
setFile(event.target.files[0]);
};
return (
<Dialog open={open} onClose={closeDialog}>
<DialogTitle>Restore your character list</DialogTitle>
<DialogContent>
<Typography>
The list must be exported from the same EVE PI instance to work!
</Typography>
<input
type="file"
name="file"
accept=".json"
onChange={changeHandler}
style={{ paddingTop: "1rem" }}
/>
</DialogContent>
<DialogActions>
<Button onClick={closeDialog}>Close</Button>
</DialogActions>
</Dialog>
);
};

View File

@@ -7,6 +7,7 @@ import { CharacterContext } from "../context/Context";
import { DowloadButton } from "./Backup/DowloadButton";
import { DiscordButton } from "./Discord/DiscordButton";
import { GitHubButton } from "./Github/GitHubButton";
import { UploadButton } from "./Backup/UploadButton";
interface Grouped {
[key: string]: AccessToken[];
@@ -28,6 +29,7 @@ export const MainGrid = ({ sessionReady }: { sessionReady: boolean }) => {
<Stack direction="row" spacing={1}>
<LoginButton />
<DowloadButton />
<UploadButton />
<DiscordButton />
<GitHubButton />
</Stack>

View File

@@ -5,10 +5,12 @@ export const CharacterContext = createContext<{
characters: AccessToken[];
deleteCharacter: (character: AccessToken) => void;
updateCharacter: (character: AccessToken, update: CharacterUpdate) => void;
restoreCharacters: (characters: AccessToken[]) => void;
}>({
characters: [],
deleteCharacter: () => {},
updateCharacter: () => {},
restoreCharacters: () => {},
});
export const SessionContext = createContext<{

View File

@@ -73,6 +73,10 @@ const Home = () => {
return characters;
};
const restoreCharacters = (characters: AccessToken[]) => {
refreshSession(characters).then(saveCharacters).then(setCharacters);
};
// Initialize EVE PI
useEffect(() => {
fetch("api/env")
@@ -106,6 +110,7 @@ const Home = () => {
characters,
deleteCharacter,
updateCharacter,
restoreCharacters,
}}
>
<MainGrid sessionReady={sessionReady} />