diff --git a/package-lock.json b/package-lock.json index d64b961..8df95e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,13 +37,15 @@ "short-unique-id": "^4.4.4", "slate": "^0.91.4", "slate-history": "^0.86.0", - "slate-react": "^0.91.11" + "slate-react": "^0.91.11", + "ua-parser-js": "^1.0.37" }, "devDependencies": { "@mui/types": "^7.2.3", "@types/react": "^18.0.28", "@types/react-copy-to-clipboard": "^5.0.4", "@types/react-dom": "^18.0.11", + "@types/ua-parser-js": "^0.7.39", "@vitejs/plugin-react-swc": "^3.2.0", "prettier": "^2.8.6", "typescript": "^4.9.3", @@ -1764,6 +1766,12 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "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": { "version": "0.0.3", "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_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": { "version": "1.0.10", "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", "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": { "version": "0.0.3", "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==", "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": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", diff --git a/package.json b/package.json index f15002d..bf2b04e 100644 --- a/package.json +++ b/package.json @@ -38,13 +38,15 @@ "short-unique-id": "^4.4.4", "slate": "^0.91.4", "slate-history": "^0.86.0", - "slate-react": "^0.91.11" + "slate-react": "^0.91.11", + "ua-parser-js": "^1.0.37" }, "devDependencies": { "@mui/types": "^7.2.3", "@types/react": "^18.0.28", "@types/react-copy-to-clipboard": "^5.0.4", "@types/react-dom": "^18.0.11", + "@types/ua-parser-js": "^0.7.39", "@vitejs/plugin-react-swc": "^3.2.0", "prettier": "^2.8.6", "typescript": "^4.9.3", diff --git a/src/assets/img/AbstractTechArt.png b/src/assets/img/AbstractTechArt.png new file mode 100644 index 0000000..2b55353 Binary files /dev/null and b/src/assets/img/AbstractTechArt.png differ diff --git a/src/assets/svgs/DownloadCircleSVG.tsx b/src/assets/svgs/DownloadCircleSVG.tsx new file mode 100644 index 0000000..d0fcf98 --- /dev/null +++ b/src/assets/svgs/DownloadCircleSVG.tsx @@ -0,0 +1,22 @@ +import { IconTypes } from "./IconTypes"; + +export const DownloadCircleSVG: React.FC = ({ + color, + height, + width, + className, + onClickFunc, +}) => { + return ( + + + + ); +}; diff --git a/src/components/layout/Navbar/Navbar.tsx b/src/components/layout/Navbar/Navbar.tsx index 88b6282..d744098 100644 --- a/src/components/layout/Navbar/Navbar.tsx +++ b/src/components/layout/Navbar/Navbar.tsx @@ -41,6 +41,7 @@ interface Props { authenticate: () => void; hasAttemptedToFetchShopInitial: boolean; setTheme: (val: string) => void; + displayDownloadGatewayModalFunc: () => void; } const NavBar: React.FC = ({ @@ -49,7 +50,8 @@ const NavBar: React.FC = ({ userAvatar, authenticate, hasAttemptedToFetchShopInitial, - setTheme + setTheme, + displayDownloadGatewayModalFunc }) => { const navigate = useNavigate(); const dispatch = useDispatch(); @@ -128,7 +130,14 @@ const NavBar: React.FC = ({ }} > {!isAuthenticated && ( - + { + const isGateWay = window.location.href.includes("https"); + if (isGateWay) { + displayDownloadGatewayModalFunc(); + return; + } + authenticate(); + }}> Authenticate diff --git a/src/components/modals/ReusableModal-styles.tsx b/src/components/modals/ReusableModal-styles.tsx index e1feabf..185f42e 100644 --- a/src/components/modals/ReusableModal-styles.tsx +++ b/src/components/modals/ReusableModal-styles.tsx @@ -1,5 +1,6 @@ import { styled } from "@mui/system"; import { Box } from "@mui/material"; +import AbstractTechArt from "../../assets/img/AbstractTechArt.png"; export const ReusableModalBody = styled(Box)(({ theme }) => ({ position: "absolute", @@ -35,5 +36,21 @@ export const ReusableModalBody = styled(Box)(({ theme }) => ({ }, "&::-webkit-scrollbar-thumb:hover": { 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 + } } })); diff --git a/src/components/modals/ReusableModal.tsx b/src/components/modals/ReusableModal.tsx index 89f2f2a..a54eac3 100644 --- a/src/components/modals/ReusableModal.tsx +++ b/src/components/modals/ReusableModal.tsx @@ -9,6 +9,7 @@ interface MyModalProps { children: any; customStyles?: any; id?: string; + className?: string; } export const ReusableModal: React.FC = ({ @@ -17,7 +18,8 @@ export const ReusableModal: React.FC = ({ onClose, // onSubmit, children, - customStyles = {} + customStyles = {}, + className }) => { const theme = useTheme(); return ( @@ -32,6 +34,7 @@ export const ReusableModal: React.FC = ({ sx={{ ...customStyles }} + className={className} > {children} diff --git a/src/index.css b/src/index.css index 9855117..5ff7ed3 100644 --- a/src/index.css +++ b/src/index.css @@ -43,6 +43,11 @@ src: url("./styles/fonts/Montserrat.ttf") format("truetype"); } +@font-face { + font-family: "Figtree"; + src: url("./styles/fonts/Figtree.ttf") format("truetype"); +} + :root { padding: 0px; margin: 0px; diff --git a/src/styles/fonts/Figtree.ttf b/src/styles/fonts/Figtree.ttf new file mode 100644 index 0000000..8d81222 Binary files /dev/null and b/src/styles/fonts/Figtree.ttf differ diff --git a/src/styles/theme.tsx b/src/styles/theme.tsx index 126eebf..2c8ce50 100644 --- a/src/styles/theme.tsx +++ b/src/styles/theme.tsx @@ -13,6 +13,7 @@ const commonThemeOptions = { "Catamaran", "Cairo", "Arial", + "Figtree" ].join(","), h1: { fontSize: "2rem", diff --git a/src/wrappers/GlobalWrapper-styles.tsx b/src/wrappers/GlobalWrapper-styles.tsx index 85034cf..dcbfe9e 100644 --- a/src/wrappers/GlobalWrapper-styles.tsx +++ b/src/wrappers/GlobalWrapper-styles.tsx @@ -1,5 +1,5 @@ import { styled } from "@mui/system"; -import { Button, Typography } from "@mui/material"; +import { Button, Stack, Typography } from "@mui/material"; export const CustomModalTitle = styled(Typography)({ textAlign: "center", @@ -24,3 +24,60 @@ export const CustomModalButton = styled(Button)(({ theme }) => ({ 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;" + } +})) \ No newline at end of file diff --git a/src/wrappers/GlobalWrapper.tsx b/src/wrappers/GlobalWrapper.tsx index d7763af..c1c0a64 100644 --- a/src/wrappers/GlobalWrapper.tsx +++ b/src/wrappers/GlobalWrapper.tsx @@ -3,23 +3,20 @@ import { useDispatch, useSelector } from "react-redux"; import { addUser } from "../state/features/authSlice"; import { RootState } from "../state/store"; import CreateStoreModal, { - onPublishParam + onPublishParam, } from "../components/modals/CreateStoreModal"; import EditStoreModal, { - onPublishParamEdit + onPublishParamEdit, } from "../components/modals/EditStoreModal"; import { setCurrentStore, setDataContainer, toggleEditStoreModal, - toggleCreateStoreModal, setIsLoadingGlobal, - resetProducts, resetListProducts, - DataContainer, ProductDataContainer, updateRecentlyVisitedStoreId, - clearDataCotainer + clearDataCotainer, } from "../state/features/globalSlice"; import NavBar from "../components/layout/Navbar/Navbar"; import PageLoader from "../components/common/PageLoader"; @@ -32,14 +29,26 @@ import { addToAllMyStores, addToHashMapStores, addToStores, - setAllMyStores + setAllMyStores, } from "../state/features/storeSlice"; import { useFetchStores } from "../hooks/useFetchStores"; import { DATA_CONTAINER_BASE, STORE_BASE } from "../constants/identifiers"; 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 { 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 { children: React.ReactNode; setTheme: (val: string) => void; @@ -54,6 +63,10 @@ interface ShortDataContainer { const GlobalWrapper: React.FC = ({ children, setTheme }) => { const dispatch = useDispatch(); const navigate = useNavigate(); + const theme = useTheme(); + + // Determine which OS they're on + const parser = new UAParser(); // Get user from auth const user = useSelector((state: RootState) => state.auth.user); @@ -94,6 +107,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { const [openDataContainerModal, setOpenDataContainer] = useState(false); const [retryDataContainer, setRetryDataContainer] = useState(false); + const [showDownloadModal, setShowDownloadModal] = useState(false); useEffect(() => { if (!user?.name) return; @@ -107,7 +121,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { action: "GET_QDN_RESOURCE_URL", name: user?.name, service: "THUMBNAIL", - identifier: "qortal_avatar" + identifier: "qortal_avatar", }); if (url === "Resource does not exist") return; @@ -142,8 +156,8 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { const responseBlogs = await fetch(url2, { method: "GET", headers: { - "Content-Type": "application/json" - } + "Content-Type": "application/json", + }, }); const dataMetadata = await responseBlogs.json(); if (dataMetadata.length === 0) { @@ -157,8 +171,8 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { const responseBlogs = await fetch(url, { method: "GET", headers: { - "Content-Type": "application/json" - } + "Content-Type": "application/json", + }, }); const responseDataBlogs = await responseBlogs.json(); const filterOut = responseDataBlogs.filter((blog: any) => @@ -173,8 +187,8 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { const response = await fetch(urlBlog, { method: "GET", headers: { - "Content-Type": "application/json" - } + "Content-Type": "application/json", + }, }); const responseData = await response.json(); @@ -182,8 +196,8 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { const response2 = await fetch(urlDataContainer, { method: "GET", headers: { - "Content-Type": "application/json" - } + "Content-Type": "application/json", + }, }); const responseData2 = await response2.json(); // Set currentStore in the Redux global state @@ -200,7 +214,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { logo: responseData?.logo || "", shortStoreId: responseData?.shortStoreId, supportedCoins: responseData?.supportedCoins || [], - foreignCoins: responseData?.foreignCoins || {} + foreignCoins: responseData?.foreignCoins || {}, }) ); // Set listProducts in the Redux global state @@ -208,7 +222,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { dispatch( setDataContainer({ ...responseData2, - id: `${store.identifier}-${DATA_CONTAINER_BASE}` + id: `${store.identifier}-${DATA_CONTAINER_BASE}`, }) ); } else { @@ -218,7 +232,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { storeId: store.identifier, shortStoreId: shortStoreId, owner: store.name, - products: {} + products: {}, }; const dataContainerToBase64 = await objectToBase64(dataContainer); @@ -227,15 +241,21 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { name: store.name, service: "DOCUMENT", 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 () => { try { let account = await qortalRequest({ - action: "GET_USER_ACCOUNT" + action: "GET_USER_ACCOUNT", }); const name = await getNameInfo(account.address); @@ -258,7 +278,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { service: "DOCUMENT", data64: dataContainerToBase64, identifier: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}`, - filename: "datacontainer.json" + filename: "datacontainer.json", }); if (isSuccessful(resourceResponse)) { await new Promise((res, rej) => { @@ -269,13 +289,13 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { dispatch( setDataContainer({ ...storedDataContainer, - id: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}` + id: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}`, }) ); dispatch( setNotification({ msg: "Shop successfully created", - alertType: "success" + alertType: "success", }) ); setCloseCreateStoreModal(true); @@ -289,7 +309,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { dispatch( setNotification({ msg: "You must create a data container in order to create a shop!", - alertType: "error" + alertType: "error", }) ); // Try again after 8 seconds automatically @@ -304,7 +324,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { service: "DOCUMENT", data64: dataContainerToBase64, identifier: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}`, - filename: "datacontainer.json" + filename: "datacontainer.json", }); if (isSuccessful(resourceResponse)) { await new Promise((res, rej) => { @@ -315,13 +335,13 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { dispatch( setDataContainer({ ...storedDataContainer, - id: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}` + id: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}`, }) ); dispatch( setNotification({ msg: "Shop successfully created", - alertType: "success" + alertType: "success", }) ); setCloseCreateStoreModal(true); @@ -339,7 +359,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { dispatch( setNotification({ 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 = ({ children, setTheme }) => { storeIdentifier, logo, foreignCoins, - supportedCoins + supportedCoins, }: onPublishParam) => { if (!user || !user.name) throw new Error("Cannot publish: You do not have a Qortal name"); @@ -394,18 +414,19 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { shortStoreId: formatStoreIdentifier, logo, foreignCoins, - supportedCoins + supportedCoins, }; if (!storeObj.shortStoreId) { 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. try { - const storeToBase64 = await objectToBase64(storeObj); // 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({ action: "PUBLISH_QDN_RESOURCE", @@ -415,7 +436,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { filename: "store.json", title, description: metadescription, - identifier: identifier + identifier: identifier, }); if (isSuccessful(resourceResponse)) { await new Promise((res, rej) => { @@ -428,7 +449,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { storeId: identifier, shortStoreId: formatStoreIdentifier, owner: name, - products: {} + products: {}, }; // Store data (other than the raw data or metadata) to add to Redux const storeData = { @@ -438,7 +459,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { owner: name, id: storeIdentifier, shortStoreId: formatStoreIdentifier, - logo: logo + logo: logo, }; // Store Full Object to send to redux hashMapStores const storefullObj = { @@ -447,7 +468,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { isValid: true, owner: name, created: createdAt, - updated: createdAt + updated: createdAt, }; dispatch(setCurrentStore(storefullObj)); @@ -464,17 +485,17 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { if (typeof error === "string") { notificationObj = { msg: error || "Failed to create store", - alertType: "error" + alertType: "error", }; } else if (typeof error?.error === "string") { notificationObj = { msg: error?.error || "Failed to create store", - alertType: "error" + alertType: "error", }; } else { notificationObj = { msg: error?.message || "Failed to create store", - alertType: "error" + alertType: "error", }; } if (!notificationObj) return; @@ -498,7 +519,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { shipsTo, logo, foreignCoins, - supportedCoins + supportedCoins, }: onPublishParamEdit) => { if (!user || !user.name || !currentStore) throw new Error("Cannot publish: You do not have a Qortal name"); @@ -522,13 +543,15 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { logo, shortStoreId: currentStore.shortStoreId ?? shortStoreId, foreignCoins, - supportedCoins + supportedCoins, }; try { 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({ action: "PUBLISH_QDN_RESOURCE", name: name, @@ -537,7 +560,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { filename: "store.json", title, description: metadescription, - identifier: currentStore.id + identifier: currentStore.id, }); await new Promise((res, rej) => { @@ -550,7 +573,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { dispatch( setNotification({ msg: "Shop successfully updated", - alertType: "success" + alertType: "success", }) ); } catch (error: any) { @@ -558,17 +581,17 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { if (typeof error === "string") { notificationObj = { msg: error || "Failed to update blog", - alertType: "error" + alertType: "error", }; } else if (typeof error?.error === "string") { notificationObj = { msg: error?.error || "Failed to update blog", - alertType: "error" + alertType: "error", }; } else { notificationObj = { msg: error?.message || "Failed to update blog", - alertType: "error" + alertType: "error", }; } if (!notificationObj) return; @@ -597,8 +620,8 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { const response = await fetch(url, { method: "GET", headers: { - "Content-Type": "application/json" - } + "Content-Type": "application/json", + }, }); const responseData = await response.json(); // Data returned from that endpoint of the API @@ -612,7 +635,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { created: storeItem.created, updated: storeItem.updated, owner: storeItem.name, - id: storeItem.identifier + id: storeItem.identifier, }; }); @@ -624,7 +647,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { if (content.owner && content.id) { const res = checkAndUpdateResource({ id: content.id, - updated: content.updated + updated: content.updated, }); if (res) { getStore(content.owner, content.id, content); @@ -668,8 +691,8 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { const shopData = await fetch(urlShop, { method: "GET", headers: { - "Content-Type": "application/json" - } + "Content-Type": "application/json", + }, }); const shopResource = await shopData.json(); // Clear product list from redux global state @@ -687,7 +710,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { logo: shopResource?.logo, shortStoreId: shopResource?.shortStoreId, supportedCoins: shopResource?.supportedCoins || [], - foreignCoins: shopResource?.foreignCoins || {} + foreignCoins: shopResource?.foreignCoins || {}, }) ); // Fetch data container data on QDN (product resources) @@ -695,8 +718,8 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { const response = await fetch(urlDataContainer, { method: "GET", headers: { - "Content-Type": "application/json" - } + "Content-Type": "application/json", + }, }); const responseData2 = await response.json(); if ( @@ -707,7 +730,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { dispatch( setDataContainer({ ...responseData2, - id: `${myStoreFound.id}-${DATA_CONTAINER_BASE}` + id: `${myStoreFound.id}-${DATA_CONTAINER_BASE}`, }) ); } else if (user?.name && recentlyVisitedStoreId) { @@ -722,7 +745,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { limit: 0, offset: 0, reverse: true, - mode: "ALL" + mode: "ALL", }); if (dataContainerExists?.length === 0) { // Publish Data Container to QDN @@ -737,7 +760,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { storeId: recentlyVisitedStoreId, shortStoreId: formatStoreIdentifier, owner: user?.name, - products: {} + products: {}, }; const dataContainerToBase64 = await objectToBase64( dataContainer @@ -749,20 +772,20 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { service: "DOCUMENT", data64: dataContainerToBase64, identifier: `${recentlyVisitedStoreId}-${DATA_CONTAINER_BASE}`, - filename: "datacontainer.json" + filename: "datacontainer.json", }); if (dataContainerCreated && !dataContainerCreated.error) { dispatch( setDataContainer({ ...dataContainer, - id: `${recentlyVisitedStoreId}-${DATA_CONTAINER_BASE}` + id: `${recentlyVisitedStoreId}-${DATA_CONTAINER_BASE}`, }) ); } dispatch( setNotification({ msg: "Data Container Created!", - alertType: "success" + alertType: "success", }) ); } catch (error) { @@ -770,7 +793,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { dispatch( setNotification({ msg: "Error when creating the data container. Please try again!", - alertType: "error" + alertType: "error", }) ); dispatch(updateRecentlyVisitedStoreId("")); @@ -780,7 +803,7 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { dispatch( setNotification({ msg: "Error when fetching store data. Please try again!", - alertType: "error" + alertType: "error", }) ); navigate("/"); @@ -862,11 +885,94 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { userName={user?.name || ""} userAvatar={userAvatar} authenticate={askForAccountInformation} + displayDownloadGatewayModalFunc={displayDownloadQortalGatewayModalFunc} hasAttemptedToFetchShopInitial={hasAttemptedToFetchShopInitial} /> {/* Cart opens when setIsOpen action is dispatched to Redux Global State */} + {showDownloadModal && ( + { + 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" + > + + + Download Qortal + + Experience a new internet paradigm, and start your Qortal + experience today by downloading and installing the Qortal + software. + + { + 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{" "} + + + + + )} {children} );