mirror of
https://github.com/Qortal/q-shop.git
synced 2025-01-28 22:02:21 +00:00
Added download for gateway link
This commit is contained in:
parent
3afb8f8506
commit
701c6da4fe
43
package-lock.json
generated
43
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
BIN
src/assets/img/AbstractTechArt.png
Normal file
BIN
src/assets/img/AbstractTechArt.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
22
src/assets/svgs/DownloadCircleSVG.tsx
Normal file
22
src/assets/svgs/DownloadCircleSVG.tsx
Normal file
@ -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>
|
||||
);
|
||||
};
|
@ -41,6 +41,7 @@ interface Props {
|
||||
authenticate: () => void;
|
||||
hasAttemptedToFetchShopInitial: boolean;
|
||||
setTheme: (val: string) => void;
|
||||
displayDownloadGatewayModalFunc: () => void;
|
||||
}
|
||||
|
||||
const NavBar: React.FC<Props> = ({
|
||||
@ -49,7 +50,8 @@ const NavBar: React.FC<Props> = ({
|
||||
userAvatar,
|
||||
authenticate,
|
||||
hasAttemptedToFetchShopInitial,
|
||||
setTheme
|
||||
setTheme,
|
||||
displayDownloadGatewayModalFunc
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
@ -128,7 +130,14 @@ const NavBar: React.FC<Props> = ({
|
||||
}}
|
||||
>
|
||||
{!isAuthenticated && (
|
||||
<AuthenticateButton onClick={authenticate}>
|
||||
<AuthenticateButton onClick={() => {
|
||||
const isGateWay = window.location.href.includes("https");
|
||||
if (isGateWay) {
|
||||
displayDownloadGatewayModalFunc();
|
||||
return;
|
||||
}
|
||||
authenticate();
|
||||
}}>
|
||||
<ExitToAppIcon />
|
||||
Authenticate
|
||||
</AuthenticateButton>
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
@ -9,6 +9,7 @@ interface MyModalProps {
|
||||
children: any;
|
||||
customStyles?: any;
|
||||
id?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ReusableModal: React.FC<MyModalProps> = ({
|
||||
@ -17,7 +18,8 @@ export const ReusableModal: React.FC<MyModalProps> = ({
|
||||
onClose,
|
||||
// onSubmit,
|
||||
children,
|
||||
customStyles = {}
|
||||
customStyles = {},
|
||||
className
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
@ -32,6 +34,7 @@ export const ReusableModal: React.FC<MyModalProps> = ({
|
||||
sx={{
|
||||
...customStyles
|
||||
}}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</ReusableModalBody>
|
||||
|
@ -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;
|
||||
|
BIN
src/styles/fonts/Figtree.ttf
Normal file
BIN
src/styles/fonts/Figtree.ttf
Normal file
Binary file not shown.
@ -13,6 +13,7 @@ const commonThemeOptions = {
|
||||
"Catamaran",
|
||||
"Cairo",
|
||||
"Arial",
|
||||
"Figtree"
|
||||
].join(","),
|
||||
h1: {
|
||||
fontSize: "2rem",
|
||||
|
@ -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;"
|
||||
}
|
||||
}))
|
@ -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<Props> = ({ 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<Props> = ({ children, setTheme }) => {
|
||||
const [openDataContainerModal, setOpenDataContainer] =
|
||||
useState<boolean>(false);
|
||||
const [retryDataContainer, setRetryDataContainer] = useState<boolean>(false);
|
||||
const [showDownloadModal, setShowDownloadModal] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user?.name) return;
|
||||
@ -107,7 +121,7 @@ const GlobalWrapper: React.FC<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ children, setTheme }) => {
|
||||
service: "DOCUMENT",
|
||||
data64: dataContainerToBase64,
|
||||
identifier: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}`,
|
||||
filename: "datacontainer.json"
|
||||
filename: "datacontainer.json",
|
||||
});
|
||||
if (isSuccessful(resourceResponse)) {
|
||||
await new Promise<void>((res, rej) => {
|
||||
@ -269,13 +289,13 @@ const GlobalWrapper: React.FC<Props> = ({ 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<Props> = ({ 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<Props> = ({ children, setTheme }) => {
|
||||
service: "DOCUMENT",
|
||||
data64: dataContainerToBase64,
|
||||
identifier: `${storedDataContainer?.storeId}-${DATA_CONTAINER_BASE}`,
|
||||
filename: "datacontainer.json"
|
||||
filename: "datacontainer.json",
|
||||
});
|
||||
if (isSuccessful(resourceResponse)) {
|
||||
await new Promise<void>((res, rej) => {
|
||||
@ -315,13 +335,13 @@ const GlobalWrapper: React.FC<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ children, setTheme }) => {
|
||||
filename: "store.json",
|
||||
title,
|
||||
description: metadescription,
|
||||
identifier: identifier
|
||||
identifier: identifier,
|
||||
});
|
||||
if (isSuccessful(resourceResponse)) {
|
||||
await new Promise<void>((res, rej) => {
|
||||
@ -428,7 +449,7 @@ const GlobalWrapper: React.FC<Props> = ({ 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<Props> = ({ 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<Props> = ({ children, setTheme }) => {
|
||||
isValid: true,
|
||||
owner: name,
|
||||
created: createdAt,
|
||||
updated: createdAt
|
||||
updated: createdAt,
|
||||
};
|
||||
|
||||
dispatch(setCurrentStore(storefullObj));
|
||||
@ -464,17 +485,17 @@ const GlobalWrapper: React.FC<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ children, setTheme }) => {
|
||||
filename: "store.json",
|
||||
title,
|
||||
description: metadescription,
|
||||
identifier: currentStore.id
|
||||
identifier: currentStore.id,
|
||||
});
|
||||
|
||||
await new Promise<void>((res, rej) => {
|
||||
@ -550,7 +573,7 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
|
||||
dispatch(
|
||||
setNotification({
|
||||
msg: "Shop successfully updated",
|
||||
alertType: "success"
|
||||
alertType: "success",
|
||||
})
|
||||
);
|
||||
} catch (error: any) {
|
||||
@ -558,17 +581,17 @@ const GlobalWrapper: React.FC<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ children, setTheme }) => {
|
||||
storeId: recentlyVisitedStoreId,
|
||||
shortStoreId: formatStoreIdentifier,
|
||||
owner: user?.name,
|
||||
products: {}
|
||||
products: {},
|
||||
};
|
||||
const dataContainerToBase64 = await objectToBase64(
|
||||
dataContainer
|
||||
@ -749,20 +772,20 @@ const GlobalWrapper: React.FC<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ children, setTheme }) => {
|
||||
userName={user?.name || ""}
|
||||
userAvatar={userAvatar}
|
||||
authenticate={askForAccountInformation}
|
||||
displayDownloadGatewayModalFunc={displayDownloadQortalGatewayModalFunc}
|
||||
hasAttemptedToFetchShopInitial={hasAttemptedToFetchShopInitial}
|
||||
/>
|
||||
<ConsentModal />
|
||||
{/* Cart opens when setIsOpen action is dispatched to Redux Global State */}
|
||||
<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}
|
||||
</>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user