refactoring: add first draft for developer mode

This commit is contained in:
Jeeiii
2023-11-08 11:30:01 +01:00
parent 7192ad69fe
commit 391edc646e
7 changed files with 239 additions and 19 deletions

View File

@@ -18,6 +18,7 @@
"next": "14.0.1",
"react": "^18",
"react-dom": "^18",
"react-icons": "^4.11.0",
"zuauth": "0.2.1"
},
"devDependencies": {

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { EdDSATicketFieldsToReveal } from "@pcd/zk-eddsa-event-ticket-pcd";
import Toggle from '@/components/Toggle';
interface TicketFieldsToRevealGridProps {
ticketFieldsToReveal: EdDSATicketFieldsToReveal;
onToggleField: (fieldName: keyof EdDSATicketFieldsToReveal) => void;
disabled?: boolean
}
// Renders a grid of toggles for revealing specific ticket fields only.
const TicketFieldsToRevealGrid: React.FC<TicketFieldsToRevealGridProps> = ({ ticketFieldsToReveal, onToggleField, disabled }) => {
const toggleKeys = Object.keys(ticketFieldsToReveal) as Array<keyof EdDSATicketFieldsToReveal>;
return (
<div className="grid grid-cols-4 gap-4">
{toggleKeys.map(fieldName => (
<div key={fieldName} className="flex flex-col items-center">
<p className="text-center">{fieldName}</p>
<Toggle
checked={ticketFieldsToReveal[fieldName]}
onToggle={() => onToggleField(fieldName)}
disabled={disabled}
/>
</div>
))}
</div>
);
}
export default TicketFieldsToRevealGrid;

View File

@@ -0,0 +1,39 @@
import React from 'react';
import { EdDSATicketFieldsToReveal } from "@pcd/zk-eddsa-event-ticket-pcd";
interface RevealedFieldsInfoProps {
user: {
[key: string]: boolean | string | number;
};
revealedFields: EdDSATicketFieldsToReveal;
}
const RevealedFieldsInfo: React.FC<RevealedFieldsInfoProps> = ({ user, revealedFields }) => {
const renderedFields = Object.entries(revealedFields).map(([fieldName, shouldReveal]) => {
if (shouldReveal) {
const replaced = fieldName.replace('reveal', '').charAt(0).toLowerCase() + fieldName.slice(7)
const fieldValue = user[replaced];
return (
<div key={fieldName} className="my-2 text-center">
<div className="font-bold">{replaced}</div>
<div className="ml-4">{fieldValue.toString()}</div>
</div>
);
}
return null;
});
const allNull = renderedFields.filter(field => field !== null).length === 0;
return (
<div>
{allNull ? (
<div className="text-center">You're logged in without revealing anything :)</div>
) : (
renderedFields
)}
</div>
);
};
export default RevealedFieldsInfo;

View File

@@ -0,0 +1,30 @@
import React from 'react';
import { FaToggleOff, FaToggleOn } from "react-icons/fa";
interface ToggleProps {
checked?: boolean;
onToggle: () => void;
disabled?: boolean;
}
const Toggle: React.FC<ToggleProps> = ({ checked, onToggle, disabled }) => {
const isChecked = checked !== undefined ? checked : false;
const handleClick = () => {
if (!disabled) {
onToggle();
}
};
return (
<button
className={`toggle-icon ${isChecked ? 'active' : ''}`}
onClick={handleClick}
disabled={disabled}
>
{isChecked ? <FaToggleOn size={30} className="text-blue-800"/> : <FaToggleOff size={30} />}
</button>
);
}
export default Toggle;

View File

@@ -3,18 +3,85 @@ import Head from "next/head"
import Image from "next/image"
import { useCallback, useEffect, useState } from "react"
import { useZuAuth } from "zuauth"
import { EdDSATicketFieldsToReveal } from "@pcd/zk-eddsa-event-ticket-pcd"
import TicketFieldsToRevealGrid from "@/components/TicketFieldsToReveal"
import Toggle from "@/components/Toggle"
import RevealedFieldsInfo from "@/components/TicketRevealedFieldsInfo"
export default function Home() {
const { authenticate, pcd } = useZuAuth()
const [user, setUser] = useState<any>()
const [developerMode, setDeveloperMode] = useState(false);
const [ticketFieldsToReveal, setTicketFieldsToReveal] = useState<EdDSATicketFieldsToReveal>({
revealTicketId: false,
revealEventId: true,
revealProductId: true,
revealTimestampConsumed: false,
revealTimestampSigned: false,
revealAttendeeSemaphoreId: false,
revealIsConsumed: false,
revealIsRevoked: false,
revealTicketCategory: false,
revealAttendeeEmail: true,
revealAttendeeName: false
});
const handleToggleField = (fieldName: keyof EdDSATicketFieldsToReveal) => {
setTicketFieldsToReveal((prevState: EdDSATicketFieldsToReveal) => {
const revealedFields = {
...prevState,
[fieldName]: !prevState[fieldName]
};
localStorage.setItem("ticketFieldsToReveal", JSON.stringify(revealedFields));
return revealedFields;
});
};
const handleSetDeveloperMode = () => {
setDeveloperMode((value: boolean) => {
localStorage.setItem("developerMode", JSON.stringify(!value))
if (!value === true) {
localStorage.setItem("ticketFieldsToReveal", JSON.stringify(ticketFieldsToReveal));
} else {
setTicketFieldsToReveal({
revealTicketId: false,
revealEventId: true,
revealProductId: true,
revealTimestampConsumed: false,
revealTimestampSigned: false,
revealAttendeeSemaphoreId: false,
revealIsConsumed: false,
revealIsRevoked: false,
revealTicketCategory: false,
revealAttendeeEmail: true,
revealAttendeeName: false
})
localStorage.removeItem("ticketFieldsToReveal")
}
return !value
})
}
// Every time the page loads, an API call is made to check if the
// user is logged in and, if they are, to retrieve the current session's user data.
useEffect(() => {
;(async function () {
; (async function () {
const { data } = await axios.get("/api/user")
setUser(data.user)
const savedFields = localStorage.getItem("ticketFieldsToReveal");
if (savedFields) {
setTicketFieldsToReveal(JSON.parse(savedFields));
}
const developerMode = localStorage.getItem("developerMode")
if (developerMode) {
setDeveloperMode(JSON.parse(developerMode))
}
})()
}, [])
@@ -23,23 +90,23 @@ export default function Home() {
// Note that the nonce is used as a watermark for the PCD. Therefore,
// it will be necessary on the server side to verify that the PCD's
// watermark matches the session nonce.
const login = useCallback(async () => {
const login = async () => {
const { data } = await axios.post("/api/nonce")
authenticate(
{
developerMode ? { ...ticketFieldsToReveal } : {
revealAttendeeEmail: true,
revealEventId: true,
revealProductId: true
},
data.nonce
)
}, [authenticate])
}
// When the popup is closed and the user successfully
// generates the PCD, they can login.
useEffect(() => {
;(async function () {
; (async function () {
if (pcd) {
const { data } = await axios.post("/api/login", { pcd })
@@ -49,11 +116,30 @@ export default function Home() {
}, [pcd])
// Logging out simply clears the active session.
const logout = useCallback(async () => {
const logout = async () => {
await axios.post("/api/logout")
localStorage.removeItem("ticketFieldsToReveal")
localStorage.removeItem("developerMode")
setTicketFieldsToReveal({
revealTicketId: false,
revealEventId: true,
revealProductId: true,
revealTimestampConsumed: false,
revealTimestampSigned: false,
revealAttendeeSemaphoreId: false,
revealIsConsumed: false,
revealIsRevoked: false,
revealTicketCategory: false,
revealAttendeeEmail: true,
revealAttendeeName: false
})
setDeveloperMode(false)
setUser(false)
}, [])
}
return (
<main className="flex min-h-screen flex-col items-center justify-center p-12 pb-32">
@@ -61,14 +147,20 @@ export default function Home() {
<title>ZuAuth Example</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
</Head>
<div className="max-w-xl w-full">
<div className="max-w-4xl w-full mx-auto">
<div className="flex justify-center">
<Image width="150" height="150" alt="ZuAuth Icon" src="/light-icon.png" />
</div>
<h1 className="my-8 text-2xl font-semibold text-center">Login</h1>
<h1 className="my-4 text-2xl font-semibold text-center">
ZuAuth Example
</h1>
<p className="text-justify">
<div className="text-center">
</div>
<p className="my-8 text-justify">
This demo illustrates how the{" "}
<a
className="text-blue-600 visited:text-purple-600"
@@ -109,14 +201,31 @@ export default function Home() {
</button>
</div>
{user && <div className="text-center">User: {user.attendeeEmail}</div>}
{!user &&
<>
<div className="my-8 text-center flex flex-col items-center">
<p className="mt-2 text-center">Developer Mode</p>
<Toggle
checked={developerMode}
onToggle={handleSetDeveloperMode}
/>
</div>
<div className="mt-10 text-center">
<a href="https://github.com/cedoor/zuauth" className="underline" target="_blank">
Github
</a>
</div>
<div style={{ height: "300px" }}>
{developerMode && (
<TicketFieldsToRevealGrid
ticketFieldsToReveal={ticketFieldsToReveal}
onToggleField={handleToggleField}
disabled={!!user}
/>
)}
</div>
</>
}
{user && <div className="my-8 text-center">
<RevealedFieldsInfo user={user} revealedFields={ticketFieldsToReveal} /> </div>}
</div>
</main>
</main >
)
}

View File

@@ -24,4 +24,4 @@ body {
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
}

View File

@@ -4755,6 +4755,15 @@ __metadata:
languageName: node
linkType: hard
"react-icons@npm:^4.11.0":
version: 4.11.0
resolution: "react-icons@npm:4.11.0"
peerDependencies:
react: "*"
checksum: 95e837e11ece80cc39ef1beac026d10f96cd7e567afc718e717517beb35b82dd59307a758c10b3a449dc15d6682d6551ecc630b2821d9365819af921fa279a73
languageName: node
linkType: hard
"react-is@npm:^16.13.1, react-is@npm:^16.7.0":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
@@ -5828,6 +5837,7 @@ __metadata:
postcss: "npm:^8"
react: "npm:^18"
react-dom: "npm:^18"
react-icons: "npm:^4.11.0"
tailwindcss: "npm:^3.3.0"
typescript: "npm:^5"
zuauth: "npm:0.2.1"