diff --git a/components/history-card.tsx b/components/history-card.tsx index 1f753c27..7f6f7a73 100644 --- a/components/history-card.tsx +++ b/components/history-card.tsx @@ -147,8 +147,8 @@ export default function HistoryItem(props: { {props.item.text} @@ -188,7 +188,7 @@ export default function HistoryItem(props: { {props.item.text} diff --git a/components/ui/calendar.tsx b/components/ui/calendar.tsx new file mode 100644 index 00000000..c10239fa --- /dev/null +++ b/components/ui/calendar.tsx @@ -0,0 +1,72 @@ +"use client"; + +import * as React from "react"; +import { DayPicker } from "react-day-picker"; + +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; +import { + ChevronLeft16Regular, + ChevronRight16Regular, +} from "@fluentui/react-icons"; + +export type CalendarProps = React.ComponentProps; + +function Calendar({ + className, + classNames, + showOutsideDays = true, + ...props +}: CalendarProps) { + return ( + ( + + ), + IconRight: ({ ...props }) => ( + + ), + }} + {...props} + /> + ); +} +Calendar.displayName = "Calendar"; + +export { Calendar }; diff --git a/components/ui/command.tsx b/components/ui/command.tsx index 6cd1d93d..2b40c3b3 100644 --- a/components/ui/command.tsx +++ b/components/ui/command.tsx @@ -120,7 +120,7 @@ const CommandItem = React.forwardRef< (); + const { t } = useTranslation("common"); + return ( + + + + + + { + setDate(d); + props.setDate(`${formatDate(d)}`); + }} + initialFocus + /> + + + ); +} +function formatDate(date: Date | undefined) { + if (!date) return ""; + var year = date.getFullYear(); + var month = (1 + date.getMonth()).toString().padStart(2, "0"); + var day = date.getDate().toString().padStart(2, "0"); + + return year + month + day; +} diff --git a/components/ui/select.tsx b/components/ui/select.tsx index c80ab602..9929068b 100644 --- a/components/ui/select.tsx +++ b/components/ui/select.tsx @@ -43,7 +43,7 @@ const SelectContent = React.forwardRef< =6" } @@ -4414,6 +4422,15 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4804,11 +4821,11 @@ } }, "node_modules/eslint-config-next": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.1.tgz", - "integrity": "sha512-BgD0kPCWMlqoItRf3xe9fG0MqwObKfVch+f2ccwDpZiCJA8ghkz2wrASH+bI6nLZzGcOJOpMm1v1Q1euhfpt4Q==", + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.3.tgz", + "integrity": "sha512-ZkNztm3Q7hjqvB1rRlOX8P9E/cXRL9ajRcs8jufEtwMfTVYRqnmtnaSu57QqHyBlovMuiB8LEzfLBkh5RYV6Fg==", "dependencies": { - "@next/eslint-plugin-next": "14.2.1", + "@next/eslint-plugin-next": "14.2.3", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", "eslint-import-resolver-node": "^0.3.6", @@ -5500,7 +5517,8 @@ "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "peer": true }, "node_modules/globals": { "version": "13.23.0", @@ -6548,38 +6566,38 @@ "peer": true }, "node_modules/next": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.19.tgz", - "integrity": "sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==", + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.3.tgz", + "integrity": "sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==", "dependencies": { - "@next/env": "13.4.19", - "@swc/helpers": "0.5.1", + "@next/env": "14.2.3", + "@swc/helpers": "0.5.5", "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001406", - "postcss": "8.4.14", - "styled-jsx": "5.1.1", - "watchpack": "2.4.0", - "zod": "3.21.4" + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=16.8.0" + "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.4.19", - "@next/swc-darwin-x64": "13.4.19", - "@next/swc-linux-arm64-gnu": "13.4.19", - "@next/swc-linux-arm64-musl": "13.4.19", - "@next/swc-linux-x64-gnu": "13.4.19", - "@next/swc-linux-x64-musl": "13.4.19", - "@next/swc-win32-arm64-msvc": "13.4.19", - "@next/swc-win32-ia32-msvc": "13.4.19", - "@next/swc-win32-x64-msvc": "13.4.19" + "@next/swc-darwin-arm64": "14.2.3", + "@next/swc-darwin-x64": "14.2.3", + "@next/swc-linux-arm64-gnu": "14.2.3", + "@next/swc-linux-arm64-musl": "14.2.3", + "@next/swc-linux-x64-gnu": "14.2.3", + "@next/swc-linux-x64-musl": "14.2.3", + "@next/swc-win32-arm64-msvc": "14.2.3", + "@next/swc-win32-ia32-msvc": "14.2.3", + "@next/swc-win32-x64-msvc": "14.2.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" @@ -6588,6 +6606,9 @@ "@opentelemetry/api": { "optional": true }, + "@playwright/test": { + "optional": true + }, "sass": { "optional": true } @@ -6658,9 +6679,9 @@ } }, "node_modules/next/node_modules/postcss": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", - "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -6669,10 +6690,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -7220,9 +7245,9 @@ } }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.13.tgz", - "integrity": "sha512-2tPWHCFNC+WRjAC4SIWQNSOdcL1NNkydXim8w7TDqlZi+/ulZYz2OouAI6qMtkggnPt7lGamboj6LcTMwcCvoQ==", + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.14.tgz", + "integrity": "sha512-Puaz+wPUAhFp8Lo9HuciYKM2Y2XExESjeT+9NQoVFXZsPPnc9VYss2SpxdQ6vbatmt8/4+SN0oe0I1cPDABg9Q==", "dev": true, "engines": { "node": ">=14.21.3" @@ -7350,9 +7375,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -7360,16 +7385,29 @@ "node": ">=0.10.0" } }, + "node_modules/react-day-picker": { + "version": "8.10.1", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz", + "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "date-fns": "^2.28.0 || ^3.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-is": { @@ -7763,9 +7801,9 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dependencies": { "loose-envify": "^1.1.0" } @@ -8193,11 +8231,11 @@ } }, "node_modules/tailwind-merge": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.2.2.tgz", - "integrity": "sha512-tWANXsnmJzgw6mQ07nE3aCDkCK4QdT3ThPMCzawoYA2Pws7vSTCvz3Vrjg61jVUGfFZPJzxEP+NimbcW+EdaDw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.3.0.tgz", + "integrity": "sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==", "dependencies": { - "@babel/runtime": "^7.24.0" + "@babel/runtime": "^7.24.1" }, "funding": { "type": "github", @@ -8720,9 +8758,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vaul": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/vaul/-/vaul-0.9.0.tgz", - "integrity": "sha512-bZSySGbAHiTXmZychprnX/dE0EsSige88xtyyL3/MCRbrFotRPQZo7UdydGXZWw+CKbNOw5Ow8gwAo93/nB/Cg==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-0.9.1.tgz", + "integrity": "sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw==", "dependencies": { "@radix-ui/react-dialog": "^1.0.4" }, @@ -8735,6 +8773,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -9305,14 +9344,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zod": { - "version": "3.21.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", - "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } diff --git a/package.json b/package.json index 2320901b..e491d0e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "qrix", - "version": "1.7.0", + "version": "1.8.0", "private": true, "scripts": { "dev": "next dev", @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "@fluentui/react-icons": "^2.0.235", + "@fluentui/react-icons": "^2.0.239", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5", @@ -22,34 +22,36 @@ "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", - "@types/node": "20.12.7", - "@types/react": "18.2.78", - "@types/react-dom": "18.2.25", + "@types/node": "20.12.12", + "@types/react": "18.3.2", + "@types/react-dom": "18.3.0", "autoprefixer": "10.4.19", "bwip-js": "^4.3.2", "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", + "clsx": "^2.1.1", "cmdk": "^1.0.0", + "date-fns": "^3.6.0", "eslint": "8.56.0", - "eslint-config-next": "14.2.1", + "eslint-config-next": "14.2.3", "file-saver": "^2.0.5", - "next": "13.4.19", + "next": "14.2.3", "next-pwa": "^5.6.0", "next-themes": "^0.3.0", "next-translate": "^2.6.2", "next-translate-plugin": "^2.6.2", "postcss": "8.4.38", - "react": "18.2.0", - "react-dom": "18.2.0", - "tailwind-merge": "^2.2.2", + "react": "18.3.1", + "react-day-picker": "^8.10.1", + "react-dom": "18.3.1", + "tailwind-merge": "^2.3.0", "tailwindcss": "3.4.3", "tailwindcss-animate": "^1.0.7", "typescript": "5.4.5", - "vaul": "^0.9.0" + "vaul": "^0.9.1" }, "devDependencies": { "@types/file-saver": "^2.0.7", "prettier": "^3.2.5", - "prettier-plugin-tailwindcss": "^0.5.13" + "prettier-plugin-tailwindcss": "^0.5.14" } } diff --git a/pages/barcode.tsx b/pages/barcode.tsx index 573a8b02..e7500a1f 100644 --- a/pages/barcode.tsx +++ b/pages/barcode.tsx @@ -54,6 +54,7 @@ import { barcodeTypes } from "@/lib/barcodeTypes"; import { TextXAlign } from "@/types/text-x-align"; import { TextYAlign } from "@/types/text-y-align"; import { ScrollArea } from "@/components/ui/scroll-area"; +import { RotateOption } from "@/types/rotate-type"; export default function BarcodePage() { const { t, lang } = useTranslation("common"); const settings: Settings = GetSettings(); @@ -68,6 +69,9 @@ export default function BarcodePage() { const [bg, setBg] = useState(settings.barcodeBg); const [textxalign, setTextXAlign] = useState(settings.textxalign); const [textyalign, setTextYAlign] = useState(settings.textyalign); + const [rotation, setRotation] = useState( + settings.barcodeRotation ?? "N", + ); const [fontSize, setFontSize] = useState(settings.textsize); const [open, setOpen] = useState(false); const [vis, setVis] = useState(false); @@ -116,6 +120,19 @@ export default function BarcodePage() { } } + function toRotation(s: string): RotateOption { + switch (s) { + case "I": + return "I"; + case "L": + return "L"; + case "R": + return "R"; + default: + return "N"; + } + } + function genBarcode() { try { // The return value is the canvas element @@ -133,6 +150,7 @@ export default function BarcodePage() { textcolor: fg.substring(1), textsize: fontSize, alttext: alt, + rotate: rotation, }); AddHistory( { @@ -148,6 +166,7 @@ export default function BarcodePage() { textcolor: fg.substring(1), textsize: fontSize, alttext: alt, + rotate: rotation, }, "barcode", ); @@ -415,6 +434,23 @@ export default function BarcodePage() { {t("below")} +

{t("rotation")}

+

{t("font-size")}

( settings.qrTextyalign, ); + const [rotation, setRotation] = useState( + settings.qrRotation ?? "N", + ); const [fontSize, setFontSize] = useState(settings.qrTextsize); const [alt, setAlt] = useState(""); const [open, setOpen] = useState(false); @@ -111,6 +116,13 @@ export default function BarcodePage() { website: "", }); + const [event, setEvent] = useState({ + title: "", + description: "", + location: "", + start: "", + end: "", + }); const handleInputChange = (event: { target: { value: SetStateAction }; }) => { @@ -142,6 +154,9 @@ export default function BarcodePage() { case "contact": textContent = `BEGIN:VCARD\nVERSION:3.0\nN:${contact.lastName};${contact.firstName};\nFN:${contact.firstName} ${contact.lastName}\nORG:${contact.company}\nTEL;TYPE="cell,home":${contact.mobile}\nTEL;TYPE="fax,work":${contact.fax}\nTEL;TYPE="voice,home":${contact.phone}\nEMAIL:${contact.email}\nADR;TYPE=dom,home,postal,parcel:;;${contact.address.street};${contact.address.city};${contact.address.state};${contact.address.zip};${contact.address.country};\nTITLE:${contact.job}\nURL:${contact.website}\nEND:VCARD`; break; + case "event": + textContent = `BEGIN:VEVENT\nSUMMARY:${event.title}\nDESCRIPTION:${event.description}\nLOCATION:${event.location}\nDTSTART:${event.start}\nDTEND:${event.end}\nEND:VEVENT`; + break; default: textContent = content; break; @@ -160,6 +175,7 @@ export default function BarcodePage() { textsize: fontSize, textyalign: textyalign, textxalign: textxalign, + rotate: rotation, }); AddHistory( { @@ -174,6 +190,8 @@ export default function BarcodePage() { barcolor: fg.substring(1), textcolor: fg.substring(1), alttext: showText ? textContent : "", + + rotate: rotation, }, "qrcode", ); @@ -254,6 +272,19 @@ export default function BarcodePage() { return "center"; } } + + function toRotation(s: string): RotateOption { + switch (s) { + case "I": + return "I"; + case "L": + return "L"; + case "R": + return "R"; + default: + return "N"; + } + } return ( @@ -291,6 +322,9 @@ export default function BarcodePage() { > {t("contact")} + setTab("event")} value="event"> + {t("event")} +
@@ -611,6 +645,57 @@ export default function BarcodePage() { />
+ +
+

{t("event-title")}

+ { + let e = Object.create(event); + e.title = v.target.value; + setEvent(e); + }} + value={event.title} + placeholder={t("event")} + /> + +

{t("description")}

+