Browse Source

Added download for gateway link

feature/new-gateway-modal
Justin Ferrari 9 months ago
parent
commit
701c6da4fe
  1. 43
      package-lock.json
  2. 4
      package.json
  3. BIN
      src/assets/img/AbstractTechArt.png
  4. 22
      src/assets/svgs/DownloadCircleSVG.tsx
  5. 13
      src/components/layout/Navbar/Navbar.tsx
  6. 17
      src/components/modals/ReusableModal-styles.tsx
  7. 5
      src/components/modals/ReusableModal.tsx
  8. 5
      src/index.css
  9. BIN
      src/styles/fonts/Figtree.ttf
  10. 1
      src/styles/theme.tsx
  11. 59
      src/wrappers/GlobalWrapper-styles.tsx
  12. 240
      src/wrappers/GlobalWrapper.tsx

43
package-lock.json generated

@ -37,13 +37,15 @@
"short-unique-id": "^4.4.4", "short-unique-id": "^4.4.4",
"slate": "^0.91.4", "slate": "^0.91.4",
"slate-history": "^0.86.0", "slate-history": "^0.86.0",
"slate-react": "^0.91.11" "slate-react": "^0.91.11",
"ua-parser-js": "^1.0.37"
}, },
"devDependencies": { "devDependencies": {
"@mui/types": "^7.2.3", "@mui/types": "^7.2.3",
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-copy-to-clipboard": "^5.0.4", "@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@types/ua-parser-js": "^0.7.39",
"@vitejs/plugin-react-swc": "^3.2.0", "@vitejs/plugin-react-swc": "^3.2.0",
"prettier": "^2.8.6", "prettier": "^2.8.6",
"typescript": "^4.9.3", "typescript": "^4.9.3",
@ -1764,6 +1766,12 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
}, },
"node_modules/@types/ua-parser-js": {
"version": "0.7.39",
"resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz",
"integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==",
"dev": true
},
"node_modules/@types/use-sync-external-store": { "node_modules/@types/use-sync-external-store": {
"version": "0.0.3", "version": "0.0.3",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
@ -3874,6 +3882,28 @@
"node": ">=4.2.0" "node": ">=4.2.0"
} }
}, },
"node_modules/ua-parser-js": {
"version": "1.0.37",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz",
"integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
},
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
}
],
"engines": {
"node": "*"
}
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
@ -5190,6 +5220,12 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
}, },
"@types/ua-parser-js": {
"version": "0.7.39",
"resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz",
"integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==",
"dev": true
},
"@types/use-sync-external-store": { "@types/use-sync-external-store": {
"version": "0.0.3", "version": "0.0.3",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
@ -6781,6 +6817,11 @@
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true "dev": true
}, },
"ua-parser-js": {
"version": "1.0.37",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz",
"integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ=="
},
"update-browserslist-db": { "update-browserslist-db": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",

4
package.json

@ -38,13 +38,15 @@
"short-unique-id": "^4.4.4", "short-unique-id": "^4.4.4",
"slate": "^0.91.4", "slate": "^0.91.4",
"slate-history": "^0.86.0", "slate-history": "^0.86.0",
"slate-react": "^0.91.11" "slate-react": "^0.91.11",
"ua-parser-js": "^1.0.37"
}, },
"devDependencies": { "devDependencies": {
"@mui/types": "^7.2.3", "@mui/types": "^7.2.3",
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-copy-to-clipboard": "^5.0.4", "@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@types/ua-parser-js": "^0.7.39",
"@vitejs/plugin-react-swc": "^3.2.0", "@vitejs/plugin-react-swc": "^3.2.0",
"prettier": "^2.8.6", "prettier": "^2.8.6",
"typescript": "^4.9.3", "typescript": "^4.9.3",

BIN
src/assets/img/AbstractTechArt.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

22
src/assets/svgs/DownloadCircleSVG.tsx

@ -0,0 +1,22 @@
import { IconTypes } from "./IconTypes";
export const DownloadCircleSVG: React.FC<IconTypes> = ({
color,
height,
width,
className,
onClickFunc,
}) => {
return (
<svg
fill={color}
className={className}
xmlns="http://www.w3.org/2000/svg"
height={height}
viewBox="0 -960 960 960"
width={width}
>
<path d="M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" />
</svg>
);
};

13
src/components/layout/Navbar/Navbar.tsx

@ -41,6 +41,7 @@ interface Props {
authenticate: () => void; authenticate: () => void;
hasAttemptedToFetchShopInitial: boolean; hasAttemptedToFetchShopInitial: boolean;
setTheme: (val: string) => void; setTheme: (val: string) => void;
displayDownloadGatewayModalFunc: () => void;
} }
const NavBar: React.FC<Props> = ({ const NavBar: React.FC<Props> = ({
@ -49,7 +50,8 @@ const NavBar: React.FC<Props> = ({
userAvatar, userAvatar,
authenticate, authenticate,
hasAttemptedToFetchShopInitial, hasAttemptedToFetchShopInitial,
setTheme setTheme,
displayDownloadGatewayModalFunc
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -128,7 +130,14 @@ const NavBar: React.FC<Props> = ({
}} }}
> >
{!isAuthenticated && ( {!isAuthenticated && (
<AuthenticateButton onClick={authenticate}> <AuthenticateButton onClick={() => {
const isGateWay = window.location.href.includes("https");
if (isGateWay) {
displayDownloadGatewayModalFunc();
return;
}
authenticate();
}}>
<ExitToAppIcon /> <ExitToAppIcon />
Authenticate Authenticate
</AuthenticateButton> </AuthenticateButton>

17
src/components/modals/ReusableModal-styles.tsx

@ -1,5 +1,6 @@
import { styled } from "@mui/system"; import { styled } from "@mui/system";
import { Box } from "@mui/material"; import { Box } from "@mui/material";
import AbstractTechArt from "../../assets/img/AbstractTechArt.png";
export const ReusableModalBody = styled(Box)(({ theme }) => ({ export const ReusableModalBody = styled(Box)(({ theme }) => ({
position: "absolute", position: "absolute",
@ -35,5 +36,21 @@ export const ReusableModalBody = styled(Box)(({ theme }) => ({
}, },
"&::-webkit-scrollbar-thumb:hover": { "&::-webkit-scrollbar-thumb:hover": {
backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#474646" backgroundColor: theme.palette.mode === "light" ? "#b7bcc4" : "#474646"
},
"&.download-qortal-modal": {
"&::before": {
content: "''",
position: "absolute",
top: "0",
left: "0",
width: "100%",
height: "100%",
backgroundImage: `url(${AbstractTechArt})`,
backgroundSize: "cover",
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
filter: "blur(2px) contrast(0.3)",
zIndex: -1
}
} }
})); }));

5
src/components/modals/ReusableModal.tsx

@ -9,6 +9,7 @@ interface MyModalProps {
children: any; children: any;
customStyles?: any; customStyles?: any;
id?: string; id?: string;
className?: string;
} }
export const ReusableModal: React.FC<MyModalProps> = ({ export const ReusableModal: React.FC<MyModalProps> = ({
@ -17,7 +18,8 @@ export const ReusableModal: React.FC<MyModalProps> = ({
onClose, onClose,
// onSubmit, // onSubmit,
children, children,
customStyles = {} customStyles = {},
className
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
return ( return (
@ -32,6 +34,7 @@ export const ReusableModal: React.FC<MyModalProps> = ({
sx={{ sx={{
...customStyles ...customStyles
}} }}
className={className}
> >
{children} {children}
</ReusableModalBody> </ReusableModalBody>

5
src/index.css

@ -43,6 +43,11 @@
src: url("./styles/fonts/Montserrat.ttf") format("truetype"); src: url("./styles/fonts/Montserrat.ttf") format("truetype");
} }
@font-face {
font-family: "Figtree";
src: url("./styles/fonts/Figtree.ttf") format("truetype");
}
:root { :root {
padding: 0px; padding: 0px;
margin: 0px; margin: 0px;

BIN
src/styles/fonts/Figtree.ttf

Binary file not shown.

1
src/styles/theme.tsx

@ -13,6 +13,7 @@ const commonThemeOptions = {
"Catamaran", "Catamaran",
"Cairo", "Cairo",
"Arial", "Arial",
"Figtree"
].join(","), ].join(","),
h1: { h1: {
fontSize: "2rem", fontSize: "2rem",

59
src/wrappers/GlobalWrapper-styles.tsx

@ -1,5 +1,5 @@
import { styled } from "@mui/system"; import { styled } from "@mui/system";
import { Button, Typography } from "@mui/material"; import { Button, Stack, Typography } from "@mui/material";
export const CustomModalTitle = styled(Typography)({ export const CustomModalTitle = styled(Typography)({
textAlign: "center", textAlign: "center",
@ -24,3 +24,60 @@ export const CustomModalButton = styled(Button)(({ theme }) => ({
cursor: "pointer" cursor: "pointer"
} }
})); }));
export const DownloadQortalCol = styled(Stack)(({theme}) => ({
display: "flex",
flexDirection: "column",
justifyContent: "flex-start",
alignItems: "center",
gap: "15px",
textAlign: "center",
width: "100%",
height: "100%",
}))
export const QortalIcon = styled("img")({
width: "120px",
height: "120px",
userSelect: "none"
})
export const DownloadQortalFont = styled(Typography)(({theme}) => ({
fontFamily: "Figtree",
letterSpacing: "2.2px",
lineHeight: '52px',
fontSize: "50px",
fontWeight: 600,
color: theme.palette.text.primary
}))
export const DownloadQortalSubFont = styled(Typography)(({theme}) => ({
fontFamily: "Raleway",
fontSize: "25px",
lineHeight: "38px",
fontWeight: 500,
color: theme.palette.text.primary,
userSelect: "none"
}))
export const DownloadNowButton = styled(Button)(({theme}) => ({
fontFamily: "Montserrat",
fontSize: "22px",
marginTop: "20px",
fontWeight: 500,
width: "90%",
padding: "12px 20px",
gap: "10px",
backgroundColor: theme.palette.secondary.main,
color: "#fff",
transition: "all 0.3s ease-in-out",
borderRadius: "10px",
boxShadow:
"rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;",
"&:hover": {
cursor: "pointer",
backgroundColor: theme.palette.secondary.dark,
boxShadow:
"rgba(50, 50, 93, 0.35) 0px 3px 5px -1px, rgba(0, 0, 0, 0.4) 0px 2px 3px -1px;"
}
}))

240
src/wrappers/GlobalWrapper.tsx

@ -3,23 +3,20 @@ import { useDispatch, useSelector } from "react-redux";
import { addUser } from "../state/features/authSlice"; import { addUser } from "../state/features/authSlice";
import { RootState } from "../state/store"; import { RootState } from "../state/store";
import CreateStoreModal, { import CreateStoreModal, {
onPublishParam onPublishParam,
} from "../components/modals/CreateStoreModal"; } from "../components/modals/CreateStoreModal";
import EditStoreModal, { import EditStoreModal, {
onPublishParamEdit onPublishParamEdit,
} from "../components/modals/EditStoreModal"; } from "../components/modals/EditStoreModal";
import { import {
setCurrentStore, setCurrentStore,
setDataContainer, setDataContainer,
toggleEditStoreModal, toggleEditStoreModal,
toggleCreateStoreModal,
setIsLoadingGlobal, setIsLoadingGlobal,
resetProducts,
resetListProducts, resetListProducts,
DataContainer,
ProductDataContainer, ProductDataContainer,
updateRecentlyVisitedStoreId, updateRecentlyVisitedStoreId,
clearDataCotainer clearDataCotainer,
} from "../state/features/globalSlice"; } from "../state/features/globalSlice";
import NavBar from "../components/layout/Navbar/Navbar"; import NavBar from "../components/layout/Navbar/Navbar";
import PageLoader from "../components/common/PageLoader"; import PageLoader from "../components/common/PageLoader";
@ -32,14 +29,26 @@ import {
addToAllMyStores, addToAllMyStores,
addToHashMapStores, addToHashMapStores,
addToStores, addToStores,
setAllMyStores setAllMyStores,
} from "../state/features/storeSlice"; } from "../state/features/storeSlice";
import { useFetchStores } from "../hooks/useFetchStores"; import { useFetchStores } from "../hooks/useFetchStores";
import { DATA_CONTAINER_BASE, STORE_BASE } from "../constants/identifiers"; import { DATA_CONTAINER_BASE, STORE_BASE } from "../constants/identifiers";
import { ReusableModal } from "../components/modals/ReusableModal"; import { ReusableModal } from "../components/modals/ReusableModal";
import { Box, Button, Stack, Typography } from "@mui/material"; import { Stack, Typography, useTheme } from "@mui/material";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { CustomModalButton, CustomModalTitle } from "./GlobalWrapper-styles"; import {
CustomModalButton,
CustomModalTitle,
DownloadNowButton,
DownloadQortalCol,
DownloadQortalFont,
DownloadQortalSubFont,
QortalIcon,
} from "./GlobalWrapper-styles";
import QortalLogo from "../assets/img/Q-AppsLogo.webp";
import { DownloadCircleSVG } from "../assets/svgs/DownloadCircleSVG";
import { UAParser } from "ua-parser-js";
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
setTheme: (val: string) => void; setTheme: (val: string) => void;
@ -54,6 +63,10 @@ interface ShortDataContainer {
const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => { const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const theme = useTheme();
// Determine which OS they're on
const parser = new UAParser();
// Get user from auth // Get user from auth
const user = useSelector((state: RootState) => state.auth.user); const user = useSelector((state: RootState) => state.auth.user);
@ -94,6 +107,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
const [openDataContainerModal, setOpenDataContainer] = const [openDataContainerModal, setOpenDataContainer] =
useState<boolean>(false); useState<boolean>(false);
const [retryDataContainer, setRetryDataContainer] = useState<boolean>(false); const [retryDataContainer, setRetryDataContainer] = useState<boolean>(false);
const [showDownloadModal, setShowDownloadModal] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
if (!user?.name) return; if (!user?.name) return;
@ -107,7 +121,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
action: "GET_QDN_RESOURCE_URL", action: "GET_QDN_RESOURCE_URL",
name: user?.name, name: user?.name,
service: "THUMBNAIL", service: "THUMBNAIL",
identifier: "qortal_avatar" identifier: "qortal_avatar",
}); });
if (url === "Resource does not exist") return; if (url === "Resource does not exist") return;
@ -142,8 +156,8 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
const responseBlogs = await fetch(url2, { const responseBlogs = await fetch(url2, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
} },
}); });
const dataMetadata = await responseBlogs.json(); const dataMetadata = await responseBlogs.json();
if (dataMetadata.length === 0) { if (dataMetadata.length === 0) {
@ -157,8 +171,8 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
const responseBlogs = await fetch(url, { const responseBlogs = await fetch(url, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
} },
}); });
const responseDataBlogs = await responseBlogs.json(); const responseDataBlogs = await responseBlogs.json();
const filterOut = responseDataBlogs.filter((blog: any) => const filterOut = responseDataBlogs.filter((blog: any) =>
@ -173,8 +187,8 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
const response = await fetch(urlBlog, { const response = await fetch(urlBlog, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
} },
}); });
const responseData = await response.json(); const responseData = await response.json();
@ -182,8 +196,8 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
const response2 = await fetch(urlDataContainer, { const response2 = await fetch(urlDataContainer, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
} },
}); });
const responseData2 = await response2.json(); const responseData2 = await response2.json();
// Set currentStore in the Redux global state // Set currentStore in the Redux global state
@ -200,7 +214,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
logo: responseData?.logo || "", logo: responseData?.logo || "",
shortStoreId: responseData?.shortStoreId, shortStoreId: responseData?.shortStoreId,
supportedCoins: responseData?.supportedCoins || [], supportedCoins: responseData?.supportedCoins || [],
foreignCoins: responseData?.foreignCoins || {} foreignCoins: responseData?.foreignCoins || {},
}) })
); );
// Set listProducts in the Redux global state // Set listProducts in the Redux global state
@ -208,7 +222,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
dispatch( dispatch(
setDataContainer({ setDataContainer({
...responseData2, ...responseData2,
id: `${store.identifier}-${DATA_CONTAINER_BASE}` id: `${store.identifier}-${DATA_CONTAINER_BASE}`,
}) })
); );
} else { } else {
@ -218,7 +232,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
storeId: store.identifier, storeId: store.identifier,
shortStoreId: shortStoreId, shortStoreId: shortStoreId,
owner: store.name, owner: store.name,
products: {} products: {},
}; };
const dataContainerToBase64 = await objectToBase64(dataContainer); const dataContainerToBase64 = await objectToBase64(dataContainer);
@ -227,15 +241,21 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
name: store.name, name: store.name,
service: "DOCUMENT", service: "DOCUMENT",
data64: dataContainerToBase64, data64: dataContainerToBase64,
identifier: `${store.identifier}-${DATA_CONTAINER_BASE}` identifier: `${store.identifier}-${DATA_CONTAINER_BASE}`,
}); });
} }
} }
// Only called when user clicks authenticate from inside a gateway
const displayDownloadQortalGatewayModalFunc = () => {
setShowDownloadModal(true);
};
const askForAccountInformation = React.useCallback(async () => { const askForAccountInformation = React.useCallback(async () => {
try { try {
let account = await qortalRequest({ let account = await qortalRequest({
action: "GET_USER_ACCOUNT" action: "GET_USER_ACCOUNT",
}); });
const name = await getNameInfo(account.address); const name = await getNameInfo(account.address);
@ -258,7 +278,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
service: "DOCUMENT", service: "DOCUMENT",
data64: dataContainerToBase64, data64: dataContainerToBase64,
identifier: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}`, identifier: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}`,
filename: "datacontainer.json" filename: "datacontainer.json",
}); });
if (isSuccessful(resourceResponse)) { if (isSuccessful(resourceResponse)) {
await new Promise<void>((res, rej) => { await new Promise<void>((res, rej) => {
@ -269,13 +289,13 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
dispatch( dispatch(
setDataContainer({ setDataContainer({
...storedDataContainer, ...storedDataContainer,
id: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}` id: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}`,
}) })
); );
dispatch( dispatch(
setNotification({ setNotification({
msg: "Shop successfully created", msg: "Shop successfully created",
alertType: "success" alertType: "success",
}) })
); );
setCloseCreateStoreModal(true); setCloseCreateStoreModal(true);
@ -289,7 +309,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
dispatch( dispatch(
setNotification({ setNotification({
msg: "You must create a data container in order to create a shop!", msg: "You must create a data container in order to create a shop!",
alertType: "error" alertType: "error",
}) })
); );
// Try again after 8 seconds automatically // Try again after 8 seconds automatically
@ -304,7 +324,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
service: "DOCUMENT", service: "DOCUMENT",
data64: dataContainerToBase64, data64: dataContainerToBase64,
identifier: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}`, identifier: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}`,
filename: "datacontainer.json" filename: "datacontainer.json",
}); });
if (isSuccessful(resourceResponse)) { if (isSuccessful(resourceResponse)) {
await new Promise<void>((res, rej) => { await new Promise<void>((res, rej) => {
@ -315,13 +335,13 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
dispatch( dispatch(
setDataContainer({ setDataContainer({
...storedDataContainer, ...storedDataContainer,
id: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}` id: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}`,
}) })
); );
dispatch( dispatch(
setNotification({ setNotification({
msg: "Shop successfully created", msg: "Shop successfully created",
alertType: "success" alertType: "success",
}) })
); );
setCloseCreateStoreModal(true); setCloseCreateStoreModal(true);
@ -339,7 +359,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
dispatch( dispatch(
setNotification({ setNotification({
msg: "You must create a data container in order to create a shop!", msg: "You must create a data container in order to create a shop!",
alertType: "error" alertType: "error",
}) })
); );
} }
@ -359,7 +379,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
storeIdentifier, storeIdentifier,
logo, logo,
foreignCoins, foreignCoins,
supportedCoins supportedCoins,
}: onPublishParam) => { }: onPublishParam) => {
if (!user || !user.name) if (!user || !user.name)
throw new Error("Cannot publish: You do not have a Qortal name"); throw new Error("Cannot publish: You do not have a Qortal name");
@ -394,18 +414,19 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
shortStoreId: formatStoreIdentifier, shortStoreId: formatStoreIdentifier,
logo, logo,
foreignCoins, foreignCoins,
supportedCoins supportedCoins,
}; };
if (!storeObj.shortStoreId) { if (!storeObj.shortStoreId) {
throw new Error("Please insert a valid store id"); throw new Error("Please insert a valid store id");
} }
// Store Data Container to send to QDN (this will allow easier querying of products afterwards. Think of it as a database in the redux global state for the current store. Max 1 per store). At first there's no products, but they will be added later. We store this in the state so we can reuse it easily if our data container fails to publish. // Store Data Container to send to QDN (this will allow easier querying of products afterwards. Think of it as a database in the redux global state for the current store. Max 1 per store). At first there's no products, but they will be added later. We store this in the state so we can reuse it easily if our data container fails to publish.
try { try {
const storeToBase64 = await objectToBase64(storeObj); const storeToBase64 = await objectToBase64(storeObj);
// Publish Store to QDN // Publish Store to QDN
let metadescription = `**coins:QORTtrue,ARRR${supportedCoins.includes('ARRR')}**` + description.slice(0,180) let metadescription =
`**coins:QORTtrue,ARRR${supportedCoins.includes("ARRR")}**` +
description.slice(0, 180);
const resourceResponse = await qortalRequest({ const resourceResponse = await qortalRequest({
action: "PUBLISH_QDN_RESOURCE", action: "PUBLISH_QDN_RESOURCE",
@ -415,7 +436,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
filename: "store.json", filename: "store.json",
title, title,
description: metadescription, description: metadescription,
identifier: identifier identifier: identifier,
}); });
if (isSuccessful(resourceResponse)) { if (isSuccessful(resourceResponse)) {
await new Promise<void>((res, rej) => { await new Promise<void>((res, rej) => {
@ -428,7 +449,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
storeId: identifier, storeId: identifier,
shortStoreId: formatStoreIdentifier, shortStoreId: formatStoreIdentifier,
owner: name, owner: name,
products: {} products: {},
}; };
// Store data (other than the raw data or metadata) to add to Redux // Store data (other than the raw data or metadata) to add to Redux
const storeData = { const storeData = {
@ -438,7 +459,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
owner: name, owner: name,
id: storeIdentifier, id: storeIdentifier,
shortStoreId: formatStoreIdentifier, shortStoreId: formatStoreIdentifier,
logo: logo logo: logo,
}; };
// Store Full Object to send to redux hashMapStores // Store Full Object to send to redux hashMapStores
const storefullObj = { const storefullObj = {
@ -447,7 +468,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
isValid: true, isValid: true,
owner: name, owner: name,
created: createdAt, created: createdAt,
updated: createdAt updated: createdAt,
}; };
dispatch(setCurrentStore(storefullObj)); dispatch(setCurrentStore(storefullObj));
@ -464,17 +485,17 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
if (typeof error === "string") { if (typeof error === "string") {
notificationObj = { notificationObj = {
msg: error || "Failed to create store", msg: error || "Failed to create store",
alertType: "error" alertType: "error",
}; };
} else if (typeof error?.error === "string") { } else if (typeof error?.error === "string") {
notificationObj = { notificationObj = {
msg: error?.error || "Failed to create store", msg: error?.error || "Failed to create store",
alertType: "error" alertType: "error",
}; };
} else { } else {
notificationObj = { notificationObj = {
msg: error?.message || "Failed to create store", msg: error?.message || "Failed to create store",
alertType: "error" alertType: "error",
}; };
} }
if (!notificationObj) return; if (!notificationObj) return;
@ -498,7 +519,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
shipsTo, shipsTo,
logo, logo,
foreignCoins, foreignCoins,
supportedCoins supportedCoins,
}: onPublishParamEdit) => { }: onPublishParamEdit) => {
if (!user || !user.name || !currentStore) if (!user || !user.name || !currentStore)
throw new Error("Cannot publish: You do not have a Qortal name"); throw new Error("Cannot publish: You do not have a Qortal name");
@ -522,13 +543,15 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
logo, logo,
shortStoreId: currentStore.shortStoreId ?? shortStoreId, shortStoreId: currentStore.shortStoreId ?? shortStoreId,
foreignCoins, foreignCoins,
supportedCoins supportedCoins,
}; };
try { try {
const storeToBase64 = await objectToBase64(storeObj); const storeToBase64 = await objectToBase64(storeObj);
let metadescription = `**coins:QORTtrue,ARRR${supportedCoins.includes('ARRR')}**` + description.slice(0,180) let metadescription =
`**coins:QORTtrue,ARRR${supportedCoins.includes("ARRR")}**` +
description.slice(0, 180);
const resourceResponse = await qortalRequest({ const resourceResponse = await qortalRequest({
action: "PUBLISH_QDN_RESOURCE", action: "PUBLISH_QDN_RESOURCE",
name: name, name: name,
@ -537,7 +560,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
filename: "store.json", filename: "store.json",
title, title,
description: metadescription, description: metadescription,
identifier: currentStore.id identifier: currentStore.id,
}); });
await new Promise<void>((res, rej) => { await new Promise<void>((res, rej) => {
@ -550,7 +573,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
dispatch( dispatch(
setNotification({ setNotification({
msg: "Shop successfully updated", msg: "Shop successfully updated",
alertType: "success" alertType: "success",
}) })
); );
} catch (error: any) { } catch (error: any) {
@ -558,17 +581,17 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
if (typeof error === "string") { if (typeof error === "string") {
notificationObj = { notificationObj = {
msg: error || "Failed to update blog", msg: error || "Failed to update blog",
alertType: "error" alertType: "error",
}; };
} else if (typeof error?.error === "string") { } else if (typeof error?.error === "string") {
notificationObj = { notificationObj = {
msg: error?.error || "Failed to update blog", msg: error?.error || "Failed to update blog",
alertType: "error" alertType: "error",
}; };
} else { } else {
notificationObj = { notificationObj = {
msg: error?.message || "Failed to update blog", msg: error?.message || "Failed to update blog",
alertType: "error" alertType: "error",
}; };
} }
if (!notificationObj) return; if (!notificationObj) return;
@ -597,8 +620,8 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
const response = await fetch(url, { const response = await fetch(url, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
} },
}); });
const responseData = await response.json(); const responseData = await response.json();
// Data returned from that endpoint of the API // Data returned from that endpoint of the API
@ -612,7 +635,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
created: storeItem.created, created: storeItem.created,
updated: storeItem.updated, updated: storeItem.updated,
owner: storeItem.name, owner: storeItem.name,
id: storeItem.identifier id: storeItem.identifier,
}; };
}); });
@ -624,7 +647,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
if (content.owner && content.id) { if (content.owner && content.id) {
const res = checkAndUpdateResource({ const res = checkAndUpdateResource({
id: content.id, id: content.id,
updated: content.updated updated: content.updated,
}); });
if (res) { if (res) {
getStore(content.owner, content.id, content); getStore(content.owner, content.id, content);
@ -668,8 +691,8 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
const shopData = await fetch(urlShop, { const shopData = await fetch(urlShop, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
} },
}); });
const shopResource = await shopData.json(); const shopResource = await shopData.json();
// Clear product list from redux global state // Clear product list from redux global state
@ -687,7 +710,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
logo: shopResource?.logo, logo: shopResource?.logo,
shortStoreId: shopResource?.shortStoreId, shortStoreId: shopResource?.shortStoreId,
supportedCoins: shopResource?.supportedCoins || [], supportedCoins: shopResource?.supportedCoins || [],
foreignCoins: shopResource?.foreignCoins || {} foreignCoins: shopResource?.foreignCoins || {},
}) })
); );
// Fetch data container data on QDN (product resources) // Fetch data container data on QDN (product resources)
@ -695,8 +718,8 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
const response = await fetch(urlDataContainer, { const response = await fetch(urlDataContainer, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
} },
}); });
const responseData2 = await response.json(); const responseData2 = await response.json();
if ( if (
@ -707,7 +730,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
dispatch( dispatch(
setDataContainer({ setDataContainer({
...responseData2, ...responseData2,
id: `${myStoreFound.id}-${DATA_CONTAINER_BASE}` id: `${myStoreFound.id}-${DATA_CONTAINER_BASE}`,
}) })
); );
} else if (user?.name && recentlyVisitedStoreId) { } else if (user?.name && recentlyVisitedStoreId) {
@ -722,7 +745,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
limit: 0, limit: 0,
offset: 0, offset: 0,
reverse: true, reverse: true,
mode: "ALL" mode: "ALL",
}); });
if (dataContainerExists?.length === 0) { if (dataContainerExists?.length === 0) {
// Publish Data Container to QDN // Publish Data Container to QDN
@ -737,7 +760,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
storeId: recentlyVisitedStoreId, storeId: recentlyVisitedStoreId,
shortStoreId: formatStoreIdentifier, shortStoreId: formatStoreIdentifier,
owner: user?.name, owner: user?.name,
products: {} products: {},
}; };
const dataContainerToBase64 = await objectToBase64( const dataContainerToBase64 = await objectToBase64(
dataContainer dataContainer
@ -749,20 +772,20 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
service: "DOCUMENT", service: "DOCUMENT",
data64: dataContainerToBase64, data64: dataContainerToBase64,
identifier: `${recentlyVisitedStoreId}-${DATA_CONTAINER_BASE}`, identifier: `${recentlyVisitedStoreId}-${DATA_CONTAINER_BASE}`,
filename: "datacontainer.json" filename: "datacontainer.json",
}); });
if (dataContainerCreated && !dataContainerCreated.error) { if (dataContainerCreated && !dataContainerCreated.error) {
dispatch( dispatch(
setDataContainer({ setDataContainer({
...dataContainer, ...dataContainer,
id: `${recentlyVisitedStoreId}-${DATA_CONTAINER_BASE}` id: `${recentlyVisitedStoreId}-${DATA_CONTAINER_BASE}`,
}) })
); );
} }
dispatch( dispatch(
setNotification({ setNotification({
msg: "Data Container Created!", msg: "Data Container Created!",
alertType: "success" alertType: "success",
}) })
); );
} catch (error) { } catch (error) {
@ -770,7 +793,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
dispatch( dispatch(
setNotification({ setNotification({
msg: "Error when creating the data container. Please try again!", msg: "Error when creating the data container. Please try again!",
alertType: "error" alertType: "error",
}) })
); );
dispatch(updateRecentlyVisitedStoreId("")); dispatch(updateRecentlyVisitedStoreId(""));
@ -780,7 +803,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
dispatch( dispatch(
setNotification({ setNotification({
msg: "Error when fetching store data. Please try again!", msg: "Error when fetching store data. Please try again!",
alertType: "error" alertType: "error",
}) })
); );
navigate("/"); navigate("/");
@ -862,11 +885,94 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
userName={user?.name || ""} userName={user?.name || ""}
userAvatar={userAvatar} userAvatar={userAvatar}
authenticate={askForAccountInformation} authenticate={askForAccountInformation}
displayDownloadGatewayModalFunc={displayDownloadQortalGatewayModalFunc}
hasAttemptedToFetchShopInitial={hasAttemptedToFetchShopInitial} hasAttemptedToFetchShopInitial={hasAttemptedToFetchShopInitial}
/> />
<ConsentModal /> <ConsentModal />
{/* Cart opens when setIsOpen action is dispatched to Redux Global State */} {/* Cart opens when setIsOpen action is dispatched to Redux Global State */}
<Cart /> <Cart />
{showDownloadModal && (
<ReusableModal
open={showDownloadModal}
onClose={() => {
setShowDownloadModal(false);
}}
customStyles={{
width: "370px",
height: "80%",
backgroundColor:
theme.palette.mode === "light" ? "#e8e8e8" : "#030d1a",
position: "relative",
padding: "15px 20px",
borderRadius: "3px",
overflowY: "auto",
overflowX: "hidden",
maxHeight: "90vh",
}}
className="download-qortal-modal"
>
<DownloadQortalCol>
<QortalIcon src={QortalLogo} alt="qortal-icon" />
<DownloadQortalFont>Download Qortal</DownloadQortalFont>
<DownloadQortalSubFont>
Experience a new internet paradigm, and start your Qortal
experience today by downloading and installing the Qortal
software.
</DownloadQortalSubFont>
<DownloadNowButton
onClick={() => {
const userOS = parser.getOS().name;
if (userOS?.includes("Android" || "iOS")) {
dispatch(
setNotification({
msg: "Qortal is not available on mobile devices yet. Please download on a desktop or laptop.",
alertType: "error",
})
);
return;
} else if (userOS?.includes("Mac")) {
window.location.href =
"https://github.com/Qortal/qortal-ui/releases/latest/download/Qortal-Setup-macOS.dmg";
dispatch(
setNotification({
msg: "Download successful!",
alertType: "success",
})
);
return;
} else if (userOS?.includes("Windows")) {
window.location.href =
"https://github.com/Qortal/qortal-ui/releases/latest/download/Qortal-Setup-win64.exe";
dispatch(
setNotification({
msg: "Download successful!",
alertType: "success",
})
);
return;
} else if (userOS?.includes("Linux")) {
window.location.href =
"https://github.com/Qortal/qortal-ui/releases/latest/download/Qortal-Setup-amd64.AppImage";
dispatch(
setNotification({
msg: "Download successful!",
alertType: "success",
})
);
return;
}
}}
>
Download Now{" "}
<DownloadCircleSVG
color={theme.palette.text.primary}
height="30"
width="30"
/>
</DownloadNowButton>
</DownloadQortalCol>
</ReusableModal>
)}
{children} {children}
</> </>
); );

Loading…
Cancel
Save