diff --git a/web/package.json b/web/package.json index 817cb3e..c63316d 100644 --- a/web/package.json +++ b/web/package.json @@ -17,6 +17,7 @@ "i18next": "^23.2.3", "lodash-es": "^4.17.21", "lucide-react": "^0.252.0", + "qrcode.react": "^3.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hot-toast": "^2.4.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 7d0367a..2614b6d 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -35,6 +35,9 @@ dependencies: lucide-react: specifier: ^0.252.0 version: 0.252.0(react@18.2.0) + qrcode.react: + specifier: ^3.1.0 + version: 3.1.0(react@18.2.0) react: specifier: ^18.2.0 version: 18.2.0 @@ -2513,6 +2516,14 @@ packages: engines: {node: '>=6'} dev: true + /qrcode.react@3.1.0(react@18.2.0): + resolution: {integrity: sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} diff --git a/web/src/components/GenerateQRCodeDialog.tsx b/web/src/components/GenerateQRCodeDialog.tsx new file mode 100644 index 0000000..72494dd --- /dev/null +++ b/web/src/components/GenerateQRCodeDialog.tsx @@ -0,0 +1,61 @@ +import { Button, Modal, ModalDialog } from "@mui/joy"; +import { useRef } from "react"; +import { toast } from "react-hot-toast"; +import { QRCodeCanvas } from "qrcode.react"; +import { absolutifyLink } from "../helpers/utils"; +import Icon from "./Icon"; + +interface Props { + shortcut: Shortcut; + onClose: () => void; +} + +const GenerateQRCodeDialog: React.FC = (props: Props) => { + const { shortcut, onClose } = props; + const containerRef = useRef(null); + const shortifyLink = absolutifyLink(`/s/${shortcut.name}`); + + const handleCloseBtnClick = () => { + onClose(); + }; + + const handleDownloadQRCodeClick = () => { + const canvas = containerRef.current?.querySelector("canvas"); + if (!canvas) { + toast.error("Failed to get qr code canvas"); + return; + } + + const link = document.createElement("a"); + link.download = "filename.png"; + link.href = canvas.toDataURL(); + link.click(); + handleCloseBtnClick(); + }; + + return ( + + +
+ Download QR Code + +
+
+
+ +
+
+ +
+
+
+
+ ); +}; + +export default GenerateQRCodeDialog; diff --git a/web/src/components/ShortcutView.tsx b/web/src/components/ShortcutView.tsx index a6c926e..66ee493 100644 --- a/web/src/components/ShortcutView.tsx +++ b/web/src/components/ShortcutView.tsx @@ -11,6 +11,7 @@ import { showCommonDialog } from "./Alert"; import Icon from "./Icon"; import Dropdown from "./common/Dropdown"; import VisibilityIcon from "./VisibilityIcon"; +import GenerateQRCodeDialog from "./GenerateQRCodeDialog"; interface Props { shortcut: Shortcut; @@ -23,6 +24,7 @@ const ShortcutView = (props: Props) => { const user = useAppSelector((state) => state.user.user as User); const faviconStore = useFaviconStore(); const [favicon, setFavicon] = useState(undefined); + const [showQRCodeDialog, setShowQRCodeDialog] = useState(false); const havePermission = user.role === "ADMIN" || shortcut.creatorId === user.id; const shortifyLink = absolutifyLink(`/s/${shortcut.name}`); @@ -34,10 +36,6 @@ const ShortcutView = (props: Props) => { }); }, [shortcut.link]); - const gotoShortcutLink = () => { - window.open(shortifyLink, "_blank"); - }; - const handleCopyButtonClick = () => { copy(shortifyLink); toast.success("Shortcut link copied to clipboard."); @@ -55,87 +53,104 @@ const ShortcutView = (props: Props) => { }; return ( -
-
-
-
- {favicon ? ( - - ) : ( - + <> +
+
+
+
+ {favicon ? ( + + ) : ( + + )} +
+ + s/ + {shortcut.name} + + + + + + +
+
+ {havePermission && ( + + + + + } + > )}
- - - - -
-
- {havePermission && ( - - - - - } - > - )} + {shortcut.description &&

{shortcut.description}

} + {shortcut.tags.length > 0 && ( +
+ + {shortcut.tags.map((tag) => { + return ( + + #{tag} + + ); + })} +
+ )} +
+ +
+ + {shortcut.creator.nickname} +
+
+ +
+ + {t(`shortcut.visibility.${shortcut.visibility.toLowerCase()}.self`)} +
+
+ +
+ + {shortcut.view} visits +
+
- {shortcut.description &&

{shortcut.description}

} - {shortcut.tags.length > 0 && ( -
- - {shortcut.tags.map((tag) => { - return ( - - #{tag} - - ); - })} -
- )} -
- -
- - {shortcut.creator.nickname} -
-
- -
- - {t(`shortcut.visibility.${shortcut.visibility.toLowerCase()}.self`)} -
-
- -
- - {shortcut.view} visits -
-
-
-
+ + {showQRCodeDialog && setShowQRCodeDialog(false)} />} + ); };