Add themeSelector component

This commit is contained in:
Nicola Benaglia
2025-04-03 09:24:52 +02:00
parent 57e33c97a3
commit d92d75040d
6 changed files with 492 additions and 321 deletions

View File

@@ -1,11 +1,16 @@
import React, { useCallback, useContext, useEffect, useRef, useState } from "react";
import React, {
useCallback,
useContext,
useEffect,
useRef,
useState,
} from "react";
import { Spacer } from "../common/Spacer";
import { CustomButton, TextItalic, TextP, TextSpan } from "../App-styles";
import { CustomButton, TextP, TextSpan } from "../App-styles";
import {
Box,
Button,
ButtonBase,
Checkbox,
Dialog,
DialogActions,
DialogContent,
@@ -16,37 +21,33 @@ import {
Switch,
Typography,
} from "@mui/material";
import Logo1 from "../assets/svgs/Logo1.svg";
import Logo1Dark from "../assets/svgs/Logo1Dark.svg";
import Info from "../assets/svgs/Info.svg";
import HelpIcon from '@mui/icons-material/Help';
import HelpIcon from "@mui/icons-material/Help";
import { CustomizedSnackbars } from "../components/Snackbar/Snackbar";
import { set } from "lodash";
import { cleanUrl, gateways, isUsingLocal } from "../background";
import { cleanUrl, gateways } from "../background";
import { GlobalContext } from "../App";
import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip';
import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip";
import ThemeSelector from "../components/Theme/ThemeSelector";
const manifestData = {
version: "0.5.2",
};
export const HtmlTooltip = styled(({ className, ...props }: TooltipProps) => (
<Tooltip {...props} classes={{ popper: className }} />
))(({ theme }) => ({
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: '#232428',
color: 'white',
backgroundColor: "#232428",
color: "white",
maxWidth: 320,
padding: '20px',
padding: "20px",
fontSize: theme.typography.pxToRem(12),
},
}));
function removeTrailingSlash(url) {
return url.replace(/\/+$/, '');
return url.replace(/\/+$/, "");
}
export const NotAuthenticated = ({
getRootProps,
getInputProps,
@@ -58,8 +59,8 @@ export const NotAuthenticated = ({
handleSetGlobalApikey,
currentNode,
setCurrentNode,
useLocalNode,
setUseLocalNode
useLocalNode,
setUseLocalNode,
}) => {
const [isValidApiKey, setIsValidApiKey] = useState<boolean | null>(null);
const [hasLocalNode, setHasLocalNode] = useState<boolean | null>(null);
@@ -78,7 +79,7 @@ export const NotAuthenticated = ({
const [customApikey, setCustomApiKey] = React.useState("");
const [customNodeToSaveIndex, setCustomNodeToSaveIndex] =
React.useState(null);
const { showTutorial, hasSeenGettingStarted } = useContext(GlobalContext);
const { showTutorial, hasSeenGettingStarted } = useContext(GlobalContext);
const importedApiKeyRef = useRef(null);
const currentNodeRef = useRef(null);
@@ -92,34 +93,32 @@ export const NotAuthenticated = ({
const text = e.target.result; // Get the file content
setImportedApiKey(text); // Store the file content in the state
if(customNodes){
setCustomNodes((prev)=> {
const copyPrev = [...prev]
const findLocalIndex = copyPrev?.findIndex((item)=> item?.url === 'http://127.0.0.1:12391')
if(findLocalIndex === -1){
if (customNodes) {
setCustomNodes((prev) => {
const copyPrev = [...prev];
const findLocalIndex = copyPrev?.findIndex(
(item) => item?.url === "http://127.0.0.1:12391"
);
if (findLocalIndex === -1) {
copyPrev.unshift({
url: "http://127.0.0.1:12391",
apikey: text
})
apikey: text,
});
} else {
copyPrev[findLocalIndex] = {
url: "http://127.0.0.1:12391",
apikey: text
}
apikey: text,
};
}
window
.sendMessage("setCustomNodes", copyPrev)
.catch((error) => {
window.sendMessage("setCustomNodes", copyPrev).catch((error) => {
console.error(
"Failed to set custom nodes:",
error.message || "An error occurred"
);
});
return copyPrev
})
return copyPrev;
});
}
};
reader.readAsText(file); // Read the file as text
}
@@ -137,14 +136,12 @@ export const NotAuthenticated = ({
const data = await response.json();
if (data?.height) {
setHasLocalNode(true);
return true
return true;
}
return false
return false;
} catch (error) {
return false
}
return false;
}
}, []);
useEffect(() => {
@@ -155,18 +152,20 @@ export const NotAuthenticated = ({
window
.sendMessage("getCustomNodesFromStorage")
.then((response) => {
setCustomNodes(response || []);
if(window?.electronAPI?.setAllowedDomains){
window.electronAPI.setAllowedDomains(response?.map((node)=> node.url))
setCustomNodes(response || []);
if (window?.electronAPI?.setAllowedDomains) {
window.electronAPI.setAllowedDomains(
response?.map((node) => node.url)
);
}
if (Array.isArray(response)) {
const findLocal = response?.find(
(item) => item?.url === "http://127.0.0.1:12391"
);
if (findLocal && findLocal?.apikey) {
setImportedApiKey(findLocal?.apikey);
}
if(Array.isArray(response)){
const findLocal = response?.find((item)=> item?.url === 'http://127.0.0.1:12391')
if(findLocal && findLocal?.apikey){
setImportedApiKey(findLocal?.apikey)
}
}
}
})
.catch((error) => {
console.error(
@@ -187,48 +186,50 @@ export const NotAuthenticated = ({
hasLocalNodeRef.current = hasLocalNode;
}, [hasLocalNode]);
const validateApiKey = useCallback(async (key, fromStartUp) => {
try {
if(key === "isGateway") return
if (key === "isGateway") return;
const isLocalKey = cleanUrl(key?.url) === "127.0.0.1:12391";
if (fromStartUp && key?.url && key?.apikey && !isLocalKey && !gateways.some(gateway => key?.url?.includes(gateway))) {
if (
fromStartUp &&
key?.url &&
key?.apikey &&
!isLocalKey &&
!gateways.some((gateway) => key?.url?.includes(gateway))
) {
setCurrentNode({
url: key?.url,
apikey: key?.apikey,
});
let isValid = false
let isValid = false;
const url = `${key?.url}/admin/settings/localAuthBypassEnabled`;
const response = await fetch(url);
// Assuming the response is in plain text and will be 'true' or 'false'
const data = await response.text();
if(data && data === 'true'){
isValid = true
if (data && data === "true") {
isValid = true;
} else {
const url2 = `${key?.url}/admin/apikey/test?apiKey=${key?.apikey}`;
const response2 = await fetch(url2);
// Assuming the response is in plain text and will be 'true' or 'false'
const data2 = await response2.text();
if (data2 === "true") {
isValid = true
isValid = true;
}
}
if (isValid) {
setIsValidApiKey(true);
setUseLocalNode(true);
return
return;
}
}
if (!currentNodeRef.current) return;
const stillHasLocal = await checkIfUserHasLocalNode()
const stillHasLocal = await checkIfUserHasLocalNode();
if (isLocalKey && !stillHasLocal && !fromStartUp) {
throw new Error("Please turn on your local node");
@@ -252,27 +253,25 @@ export const NotAuthenticated = ({
} else if (currentNodeRef.current) {
payload = currentNodeRef.current;
}
let isValid = false
let isValid = false;
const url = `${payload?.url}/admin/settings/localAuthBypassEnabled`;
const response = await fetch(url);
// Assuming the response is in plain text and will be 'true' or 'false'
const data = await response.text();
if(data && data === 'true'){
isValid = true
if (data && data === "true") {
isValid = true;
} else {
const url2 = `${payload?.url}/admin/apikey/test?apiKey=${payload?.apikey}`;
const response2 = await fetch(url2);
// Assuming the response is in plain text and will be 'true' or 'false'
const data2 = await response2.text();
if (data2 === "true") {
isValid = true
isValid = true;
}
}
if (isValid) {
window
@@ -296,14 +295,13 @@ export const NotAuthenticated = ({
} else {
setIsValidApiKey(false);
setUseLocalNode(false);
if(!fromStartUp){
if (!fromStartUp) {
setInfoSnack({
type: "error",
message: "Select a valid apikey",
});
setOpenSnack(true);
}
}
} catch (error) {
setIsValidApiKey(false);
@@ -326,15 +324,15 @@ export const NotAuthenticated = ({
error.message || "An error occurred"
);
});
return
return;
}
if (!fromStartUp) {
setInfoSnack({
type: "error",
message: error?.message || "Select a valid apikey",
});
setOpenSnack(true);
}
if(!fromStartUp){
setInfoSnack({
type: "error",
message: error?.message || "Select a valid apikey",
});
setOpenSnack(true);
}
console.error("Error validating API key:", error);
}
}, []);
@@ -363,7 +361,7 @@ export const NotAuthenticated = ({
}
setCustomNodes(nodes);
setCustomNodeToSaveIndex(null);
if (!nodes) return;
window
@@ -373,9 +371,11 @@ export const NotAuthenticated = ({
setMode("list");
setUrl("https://");
setCustomApiKey("");
if(window?.electronAPI?.setAllowedDomains){
window.electronAPI.setAllowedDomains(nodes?.map((node) => node.url))
}
if (window?.electronAPI?.setAllowedDomains) {
window.electronAPI.setAllowedDomains(
nodes?.map((node) => node.url)
);
}
// add alert if needed
}
})
@@ -404,15 +404,20 @@ export const NotAuthenticated = ({
sx={{
textAlign: "center",
lineHeight: 1.2,
fontSize: '18px'
fontSize: "18px",
}}
>
WELCOME TO
<TextSpan sx={{
fontSize: '18px'
}}> QORTAL</TextSpan>
WELCOME TO
<TextSpan
sx={{
fontSize: "18px",
}}
>
{" "}
QORTAL
</TextSpan>
</TextP>
<Spacer height="30px" />
<Box
sx={{
@@ -421,21 +426,31 @@ export const NotAuthenticated = ({
alignItems: "center",
}}
>
<HtmlTooltip
disableHoverListener={hasSeenGettingStarted === true}
placement="left"
title={
<React.Fragment>
<Typography color="inherit" sx={{
fontSize: '16px'
}}>Your wallet is like your digital ID on Qortal, and is how you will login to the Qortal User Interface. It holds your public address and the Qortal name you will eventually choose. Every transaction you make is linked to your ID, and this is where you manage all your QORT and other tradeable cryptocurrencies on Qortal.</Typography>
</React.Fragment>
}
>
<CustomButton onClick={()=> setExtstate('wallets')}>
{/* <input {...getInputProps()} /> */}
Accounts
</CustomButton>
<HtmlTooltip
disableHoverListener={hasSeenGettingStarted === true}
placement="left"
title={
<React.Fragment>
<Typography
color="inherit"
sx={{
fontSize: "16px",
}}
>
Your wallet is like your digital ID on Qortal, and is how you
will login to the Qortal User Interface. It holds your public
address and the Qortal name you will eventually choose. Every
transaction you make is linked to your ID, and this is where you
manage all your QORT and other tradeable cryptocurrencies on
Qortal.
</Typography>
</React.Fragment>
}
>
<CustomButton onClick={() => setExtstate("wallets")}>
{/* <input {...getInputProps()} /> */}
Accounts
</CustomButton>
</HtmlTooltip>
{/* <Tooltip title="Authenticate by importing your Qortal JSON file" arrow>
<img src={Info} />
@@ -448,42 +463,55 @@ export const NotAuthenticated = ({
display: "flex",
gap: "10px",
alignItems: "center",
}}
>
<HtmlTooltip
disableHoverListener={hasSeenGettingStarted === true}
placement="right"
title={
<React.Fragment>
<Typography color="inherit" sx={{
fontWeight: 'bold',
fontSize: '18px'
}}>New users start here!</Typography>
<Spacer height='10px'/>
<Typography color="inherit" sx={{
fontSize: '16px'
}}>Creating an account means creating a new wallet and digital ID to start using Qortal. Once you have made your account, you can start doing things like obtaining some QORT, buying a name and avatar, publishing videos and blogs, and much more.</Typography>
</React.Fragment>
}
>
<CustomButton
onClick={() => {
setExtstate("create-wallet");
}}
sx={{
backgroundColor: hasSeenGettingStarted === false && 'var(--green)',
color: hasSeenGettingStarted === false && 'black',
"&:hover": {
backgroundColor: hasSeenGettingStarted === false && 'var(--green)',
color: hasSeenGettingStarted === false && 'black'
}
}}
disableHoverListener={hasSeenGettingStarted === true}
placement="right"
title={
<React.Fragment>
<Typography
color="inherit"
sx={{
fontWeight: "bold",
fontSize: "18px",
}}
>
New users start here!
</Typography>
<Spacer height="10px" />
<Typography
color="inherit"
sx={{
fontSize: "16px",
}}
>
Creating an account means creating a new wallet and digital ID
to start using Qortal. Once you have made your account, you can
start doing things like obtaining some QORT, buying a name and
avatar, publishing videos and blogs, and much more.
</Typography>
</React.Fragment>
}
>
Create account
</CustomButton>
<CustomButton
onClick={() => {
setExtstate("create-wallet");
}}
sx={{
backgroundColor:
hasSeenGettingStarted === false && "var(--green)",
color: hasSeenGettingStarted === false && "black",
"&:hover": {
backgroundColor:
hasSeenGettingStarted === false && "var(--green)",
color: hasSeenGettingStarted === false && "black",
},
}}
>
Create account
</CustomButton>
</HtmlTooltip>
</Box>
<Spacer height="15px" />
@@ -503,15 +531,19 @@ export const NotAuthenticated = ({
gap: "10px",
alignItems: "center",
flexDirection: "column",
outline: '0.5px solid rgba(255, 255, 255, 0.5)',
padding: '20px 30px',
borderRadius: '5px',
outline: "0.5px solid rgba(255, 255, 255, 0.5)",
padding: "20px 30px",
borderRadius: "5px",
}}
>
<>
<Typography sx={{
textDecoration: 'underline'
}}>For advanced users</Typography>
<Typography
sx={{
textDecoration: "underline",
}}
>
For advanced users
</Typography>
<Box
sx={{
display: "flex",
@@ -522,12 +554,11 @@ export const NotAuthenticated = ({
}}
>
<FormControlLabel
sx={{
"& .MuiFormControlLabel-label": {
fontSize: '14px'
}
}}
sx={{
"& .MuiFormControlLabel-label": {
fontSize: "14px",
},
}}
control={
<Switch
sx={{
@@ -857,17 +888,25 @@ export const NotAuthenticated = ({
</DialogActions>
</Dialog>
)}
<ButtonBase onClick={()=> {
showTutorial('create-account', true)
}} sx={{
position: 'fixed',
bottom: '25px',
right: '25px'
}}>
<HelpIcon sx={{
color: 'var(--unread)'
}} />
</ButtonBase>
<ThemeSelector style={{ position: "fixed", bottom: "1%", left: "1%" }}/>
<ButtonBase
onClick={() => {
showTutorial("create-account", true);
}}
sx={{
position: "fixed",
bottom: "25px",
right: "25px",
}}
>
<HelpIcon
sx={{
color: "var(--unread)",
}}
/>
</ButtonBase>
</>
);
};