Skip to content

Commit

Permalink
Merge pull request #37 from Prodeko/better-rfid-login-ui
Browse files Browse the repository at this point in the history
Better rfid login UI
  • Loading branch information
ccruzkauppila authored Oct 29, 2024
2 parents 60ae0e3 + b5fd407 commit fc0b906
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 32 deletions.
55 changes: 52 additions & 3 deletions src/components/ui/AnimatedPopup.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -12,6 +13,7 @@ interface Props extends Partial<React.FC<Dialog.DialogProps>> {
TriggerComponent: ReactNode;
children: ReactNode;
ref?: React.RefObject<unknown>;
style?: "default" | "RFIDInstructions";
}

export interface PopupRefActions {
Expand All @@ -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<boolean>(false);
const [isAnimating, setIsAnimating] = useState<boolean>(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) {
Expand Down Expand Up @@ -80,7 +128,8 @@ export const AnimatedPopup = ({ ref = React.createRef(), ...props }: Props) => {
/>
<AnimatedDialog
style={containerAnimation}
className="fixed left-1/2 top-1/2 z-20 flex h-auto w-[90vw] items-center justify-center rounded-2xl bg-neutral-50 lg:w-[60vw] xl:w-fit"
className={popupStyles({ style })}
onInteractOutside={toggleContainer}
>
{children}
</AnimatedDialog>
Expand Down
69 changes: 50 additions & 19 deletions src/components/ui/RfidLoginDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,59 @@
"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 { animated, useTransition } from "@react-spring/web";

import { ThinButton } from "./Buttons/ThinButton";

const AnimatedDiv = animated("div");

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: <TiWiFi />,
},
{
title: "Scan successful",
description: "Logging in...",
icon: <IoIosCheckmarkCircleOutline />,
},
{ title: "OK", description: "Read successful, logging in..." },
];

export const RfidLoginDialog = () => {
const [step, setStep] = useState(0);
const popupRef = useRef<PopupRefActions>(undefined);
const transitions = useTransition(step, {
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
exitBeforeEnter: true,
});

const reader = useNfcReader();
const scan = async () => {
console.log("scan triggered");
console.log("reader is", reader);
try {
const tagId = await reader.scanOne();
console.log("successfully scanned:", tagId);
rfidLoginAction(tagId);
augmentStep();
//rfidLoginAction(tagId);
} catch (e) {
console.log("Failed to scan:", e);
setStep(0);
}
//augmentStep();
};
Expand Down Expand Up @@ -61,21 +83,30 @@ export const RfidLoginDialog = () => {
);

return (
<AnimatedPopup ref={popupRef} TriggerComponent={loginButton}>
<div className="flex flex-col items-center gap-12 px-12 py-12">
<h2 className="mt-6 text-4xl font-bold text-neutral-700">
{steps[step]?.title}
</h2>

<p className="text-xl">{steps[step]?.description}</p>
<p className="text-xl">Reader available: {String(reader.available)}</p>
<p className="text-xl">Reader scanning: {String(reader.scanning)}</p>
<FatButton
buttonType="button"
text="Cancel"
intent="tertiary"
onClick={closeModal}
/>
<AnimatedPopup
ref={popupRef}
TriggerComponent={loginButton}
style="RFIDInstructions"
>
<div className="flex flex-col items-center gap-4 px-6 py-5 md:gap-4 md:px-24 md:py-12">
{transitions((style, step) => (
<AnimatedDiv
style={style}
className="flex w-full items-center justify-center gap-8"
>
<span className="text-8xl text-primary-400">
{steps[step]?.icon}
</span>
<div className="flex flex-col gap-2">
<h2 className="text-3xl font-bold text-neutral-700">
{steps[step]?.title}
</h2>
<p className="text-xl text-neutral-600">
{steps[step]?.description}
</p>
</div>
</AnimatedDiv>
))}
</div>
</AnimatedPopup>
);
Expand Down
20 changes: 11 additions & 9 deletions src/components/ui/RfidSetupDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
},
});
Expand Down Expand Up @@ -70,18 +70,20 @@ export const RfidSetupDialog = () => {

return (
<AnimatedPopup ref={popupRef} TriggerComponent={setupButton}>
<div className="flex flex-col items-center gap-12 px-16 py-12">
<div className="-mb-12 flex w-full items-center justify-between">
<p className="text-3xl font-bold text-primary-400">NFC Connect</p>
<div className="flex flex-col items-center gap-4 px-6 py-6 md:gap-12 md:px-16 md:py-12">
<div className="-mb-6 flex w-full items-center justify-between md:-mb-12">
<p className="text-lg font-bold text-primary-400 md:text-3xl">
NFC Connect
</p>
{/* biome-ignore lint/a11y/useKeyWithClickEvents: <explanation> */}
<div
className="flex h-16 w-16 items-center justify-center rounded-full border-2 border-primary-400 bg-primary-50 text-4xl text-primary-400"
className="flex h-10 w-10 items-center justify-center rounded-full border-2 border-primary-400 bg-primary-50 text-2xl text-primary-400 md:h-16 md:w-16 md:border-2 md:text-4xl"
onClick={closeModal}
>
<HiX />
</div>
</div>
<h2 className="mt-6 text-4xl font-bold text-neutral-700">
<h2 className="mt-6 text-lg font-bold text-neutral-700 md:text-4xl">
Connect an NFC card for quick login!
</h2>
<div className="flex w-full flex-col gap-0">
Expand Down Expand Up @@ -111,7 +113,7 @@ export const RfidSetupDialog = () => {
{error !== "" && index === step ? <HiX /> : index + 1}
</p>
{index <= step && (
<p className="text-2xl">
<p className="text-md md:text-2xl">
{error && index === step ? error : currStep}
</p>
)}
Expand All @@ -120,7 +122,7 @@ export const RfidSetupDialog = () => {
))}
</div>

<p className="text-xl">
<p className="text-md md:text-xl">
Reader available: {String(reader.available)}, scanning:{" "}
{String(reader.scanning)}
</p>
Expand Down
1 change: 0 additions & 1 deletion src/server/actions/account/setupNfcLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,4 @@ export const setNfcLogin = async (tagId: string): Promise<void> => {
await setNfcSerialHash(idHash, user.id);

revalidatePath("/account");
redirect("/account");
};

0 comments on commit fc0b906

Please sign in to comment.