mirror of
https://github.com/cedoor/zuauth.git
synced 2026-01-09 12:47:58 -05:00
refactoring: add first draft for developer mode
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
"next": "14.0.1",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-icons": "^4.11.0",
|
||||
"zuauth": "0.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
31
example/src/components/TicketFieldsToReveal.tsx
Normal file
31
example/src/components/TicketFieldsToReveal.tsx
Normal 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;
|
||||
39
example/src/components/TicketRevealedFieldsInfo.tsx
Normal file
39
example/src/components/TicketRevealedFieldsInfo.tsx
Normal 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;
|
||||
30
example/src/components/Toggle.tsx
Normal file
30
example/src/components/Toggle.tsx
Normal 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;
|
||||
@@ -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 >
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,4 +24,4 @@ body {
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user