From 7f346298b13d6c98c9f8b6c82d1a6c6c7877ebf0 Mon Sep 17 00:00:00 2001 From: ccruzkauppila Date: Tue, 29 Oct 2024 10:21:50 +0000 Subject: [PATCH 1/3] Responsive UI for RFID setup dialog --- src/components/ui/RfidSetupDialog.tsx | 20 +++++++++++--------- src/server/actions/account/setupNfcLogin.ts | 1 - 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/ui/RfidSetupDialog.tsx b/src/components/ui/RfidSetupDialog.tsx index f4e51003..f85d5648 100644 --- a/src/components/ui/RfidSetupDialog.tsx +++ b/src/components/ui/RfidSetupDialog.tsx @@ -27,8 +27,8 @@ const stepStyles = cva(" ", { part: { indicator: - "flex h-16 w-16 flex-shrink-0 items-center justify-center rounded-full border-2 text-3xl font-bold ", - line: "h-8 w-8 border-r-2 border-dashed", + "flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full border-2 text-xl font-bold md:h-16 md:w-16 md:text-3xl ", + line: "h-8 w-6 border-r-2 border-dashed", }, }, }); @@ -70,18 +70,20 @@ export const RfidSetupDialog = () => { return ( -
-
-

NFC Connect

+
+
+

+ NFC Connect +

{/* biome-ignore lint/a11y/useKeyWithClickEvents: */}
-

+

Connect an NFC card for quick login!

@@ -111,7 +113,7 @@ export const RfidSetupDialog = () => { {error !== "" && index === step ? : index + 1}

{index <= step && ( -

+

{error && index === step ? error : currStep}

)} @@ -120,7 +122,7 @@ export const RfidSetupDialog = () => { ))}
-

+

Reader available: {String(reader.available)}, scanning:{" "} {String(reader.scanning)}

diff --git a/src/server/actions/account/setupNfcLogin.ts b/src/server/actions/account/setupNfcLogin.ts index c45616e3..7dce0318 100644 --- a/src/server/actions/account/setupNfcLogin.ts +++ b/src/server/actions/account/setupNfcLogin.ts @@ -35,5 +35,4 @@ export const setNfcLogin = async (tagId: string): Promise => { await setNfcSerialHash(idHash, user.id); revalidatePath("/account"); - redirect("/account"); }; From ce1d2d944c0d462ee4398027e7db20de25647298 Mon Sep 17 00:00:00 2001 From: ccruzkauppila Date: Tue, 29 Oct 2024 11:25:06 +0000 Subject: [PATCH 2/3] Improved RFIDLoginDialog UI --- src/components/ui/AnimatedPopup.tsx | 55 ++++++++++++++++++++++-- src/components/ui/RfidLoginDialog.tsx | 62 +++++++++++++++++++++------ 2 files changed, 100 insertions(+), 17 deletions(-) diff --git a/src/components/ui/AnimatedPopup.tsx b/src/components/ui/AnimatedPopup.tsx index b519ab84..002ec3f4 100644 --- a/src/components/ui/AnimatedPopup.tsx +++ b/src/components/ui/AnimatedPopup.tsx @@ -1,5 +1,6 @@ "use client"; +import { cva } from "class-variance-authority"; import React, { type ReactNode, useImperativeHandle, useState } from "react"; import * as Dialog from "@radix-ui/react-dialog"; @@ -12,6 +13,7 @@ interface Props extends Partial> { TriggerComponent: ReactNode; children: ReactNode; ref?: React.RefObject; + style?: "default" | "RFIDInstructions"; } export interface PopupRefActions { @@ -20,14 +22,60 @@ export interface PopupRefActions { toggleContainer: () => void; } -export const AnimatedPopup = ({ ref = React.createRef(), ...props }: Props) => { +const popupStyles = cva( + "fixed left-1/2 z-20 flex h-auto translate-y-1/2 items-center justify-center bg-neutral-50 lg:w-[60vw] xl:w-fit", + { + variants: { + style: { + default: "top-1/2 w-[90vw] rounded-2xl", + RFIDInstructions: + "top-0 w-full rounded-bl-[50%_15%] rounded-br-[50%_15%]", + }, + }, + }, +); + +const transformStyles = cva(" ", { + variants: { + style: { default: {}, RFIDInstructions: {} }, + open: { true: {}, false: {} }, + }, + compoundVariants: [ + { + style: "default", + open: true, + class: "translate(-50%, -50%)", + }, + { + style: "default", + open: false, + class: "translate(-50%, 100%)", + }, + { + style: "RFIDInstructions", + open: true, + class: "translate(-50%, -10%)", + }, + { + style: "RFIDInstructions", + open: false, + class: "translate(-50%, -100%)", + }, + ], +}); + +export const AnimatedPopup = ({ + ref = React.createRef(), + style = "default", + ...props +}: Props) => { const { TriggerComponent, children, ...restProps } = props; const [open, setOpen] = useState(false); const [isAnimating, setIsAnimating] = useState(false); const toggleContainer = () => setOpen((prev) => !prev); const containerAnimation = useSpring({ - transform: open ? "translate(-50%, -50%)" : "translate(-50%, 100%)", + transform: transformStyles({ style, open }), opacity: open ? 1 : 0, onRest: () => { if (!open && isAnimating) { @@ -80,7 +128,8 @@ export const AnimatedPopup = ({ ref = React.createRef(), ...props }: Props) => { /> {children} diff --git a/src/components/ui/RfidLoginDialog.tsx b/src/components/ui/RfidLoginDialog.tsx index 315c66be..900231d3 100644 --- a/src/components/ui/RfidLoginDialog.tsx +++ b/src/components/ui/RfidLoginDialog.tsx @@ -1,27 +1,39 @@ "use client"; import { cva } from "class-variance-authority"; +import { set } from "lodash"; import { ReactNode, useEffect, useRef, useState } from "react"; +import { IoIosCheckmarkCircleOutline } from "react-icons/io"; +import { LuNfc } from "react-icons/lu"; +import { LuSmartphoneNfc } from "react-icons/lu"; +import { TiWiFi } from "react-icons/ti"; import { AnimatedPopup, PopupRefActions } from "@/components/ui/AnimatedPopup"; import { FatButton } from "@/components/ui/Buttons/FatButton"; import { rfidLoginAction } from "@/server/actions/auth/login"; import { useNfcReader } from "@/state/useNfcReader"; +import { useAutoAnimate } from "@formkit/auto-animate/react"; import { ThinButton } from "./Buttons/ThinButton"; const steps = [ { title: "Scanning...", - description: - "Please place your access card on the NFC reader on the back of the device.", + description: "Place your access card on the back of the device.", + icon: , + }, + { + title: "Scan successful", + description: "Logging in...", + icon: , }, - { title: "OK", description: "Read successful, logging in..." }, ]; export const RfidLoginDialog = () => { const [step, setStep] = useState(0); const popupRef = useRef(undefined); + const [parent] = useAutoAnimate({ duration: 2000 }); + const reader = useNfcReader(); const scan = async () => { console.log("scan triggered"); @@ -29,9 +41,13 @@ export const RfidLoginDialog = () => { try { const tagId = await reader.scanOne(); console.log("successfully scanned:", tagId); - rfidLoginAction(tagId); + setTimeout(() => { + augmentStep(); + }, 1000); + //rfidLoginAction(tagId); } catch (e) { console.log("Failed to scan:", e); + setStep(0); } //augmentStep(); }; @@ -61,21 +77,39 @@ export const RfidLoginDialog = () => { ); return ( - -
-

- {steps[step]?.title} -

+ +
+
+ {steps[step]?.icon} +
+

+ {steps[step]?.title} +

+

+ {steps[step]?.description} +

+
+
-

{steps[step]?.description}

-

Reader available: {String(reader.available)}

-

Reader scanning: {String(reader.scanning)}

- +

+ Reader available: {String(reader.available)} +

+

Reader scanning: {String(reader.scanning)}

+
*/} + {/* + /> */}
); From b5fd4071e3716a33496532b39d35a092fabe4975 Mon Sep 17 00:00:00 2001 From: ccruzkauppila Date: Tue, 29 Oct 2024 11:47:18 +0000 Subject: [PATCH 3/3] Neat animation for rfid login --- src/components/ui/RfidLoginDialog.tsx | 59 +++++++++++++-------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/components/ui/RfidLoginDialog.tsx b/src/components/ui/RfidLoginDialog.tsx index 900231d3..4b19ba10 100644 --- a/src/components/ui/RfidLoginDialog.tsx +++ b/src/components/ui/RfidLoginDialog.tsx @@ -13,9 +13,12 @@ import { FatButton } from "@/components/ui/Buttons/FatButton"; import { rfidLoginAction } from "@/server/actions/auth/login"; import { useNfcReader } from "@/state/useNfcReader"; import { useAutoAnimate } from "@formkit/auto-animate/react"; +import { animated, useTransition } from "@react-spring/web"; import { ThinButton } from "./Buttons/ThinButton"; +const AnimatedDiv = animated("div"); + const steps = [ { title: "Scanning...", @@ -32,7 +35,12 @@ const steps = [ export const RfidLoginDialog = () => { const [step, setStep] = useState(0); const popupRef = useRef(undefined); - const [parent] = useAutoAnimate({ duration: 2000 }); + const transitions = useTransition(step, { + from: { opacity: 0 }, + enter: { opacity: 1 }, + leave: { opacity: 0 }, + exitBeforeEnter: true, + }); const reader = useNfcReader(); const scan = async () => { @@ -41,9 +49,7 @@ export const RfidLoginDialog = () => { try { const tagId = await reader.scanOne(); console.log("successfully scanned:", tagId); - setTimeout(() => { - augmentStep(); - }, 1000); + augmentStep(); //rfidLoginAction(tagId); } catch (e) { console.log("Failed to scan:", e); @@ -83,33 +89,24 @@ export const RfidLoginDialog = () => { style="RFIDInstructions" >
-
- {steps[step]?.icon} -
-

- {steps[step]?.title} -

-

- {steps[step]?.description} -

-
-
- - {/*
-

- Reader available: {String(reader.available)} -

-

Reader scanning: {String(reader.scanning)}

-
*/} - {/* */} + {transitions((style, step) => ( + + + {steps[step]?.icon} + +
+

+ {steps[step]?.title} +

+

+ {steps[step]?.description} +

+
+
+ ))}
);