mirror of
https://github.com/0xPARC/zkmessage.xyz.git
synced 2026-01-08 22:07:58 -05:00
add login -> backup -> connect pages
This commit is contained in:
1218
package-lock.json
generated
1218
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -20,8 +20,11 @@
|
||||
"homepage": "https://github.com/joeltg/zk-group-sigs-server#readme",
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.4.2",
|
||||
"@ethersproject/providers": "^5.5.0",
|
||||
"@metamask/detect-provider": "^1.2.0",
|
||||
"@prisma/client": "^3.5.0",
|
||||
"buffer": "^6.0.3",
|
||||
"ethers": "^5.5.1",
|
||||
"next": "^12.0.4",
|
||||
"prisma": "^3.5.0",
|
||||
"react": "^17.0.2",
|
||||
|
||||
38
pages/backup.tsx
Normal file
38
pages/backup.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { LOCAL_STORAGE_SECRET_KEY } from "utils/localStorage"
|
||||
import { mimcHash } from "utils/mimc"
|
||||
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/router"
|
||||
|
||||
export default function BackupPage(props: {}) {
|
||||
const router = useRouter()
|
||||
const [secret, setSecret] = useState<null | string>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const secret = localStorage.getItem(LOCAL_STORAGE_SECRET_KEY)
|
||||
if (secret === null) {
|
||||
router.push("/login")
|
||||
} else {
|
||||
const n = BigInt("0x" + secret)
|
||||
const h = mimcHash(n)
|
||||
setSecret(h.toString(16))
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="max-w-lg m-auto font-mono">
|
||||
<h1 className="uppercase font-bold pt-16 pb-6">zk chat</h1>
|
||||
<div className="border border-gray-300 rounded-xl p-6">
|
||||
<p>This is your ZK CHAT token.</p>
|
||||
<p>Save it somewhere safe:</p>
|
||||
<div className="break-all mt-6 mb-6">{secret}</div>
|
||||
<Link href="/connect">
|
||||
<div className="cursor-pointer bg-pink text-white rounded-xl px-4 py-2">
|
||||
Next
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -4,7 +4,10 @@ export default function ConnectPage(props: {}) {
|
||||
return (
|
||||
<div className="max-w-lg m-auto font-mono">
|
||||
<h1 className="uppercase font-bold pt-16 pb-6">zk chat</h1>
|
||||
<div className="border border-gray-300 rounded-xl p-6">cool</div>
|
||||
<div className="border border-gray-300 rounded-xl p-6">
|
||||
<div>Sign this message with your Ethereum wallet:</div>
|
||||
<button>Sign in with Ethereum</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
30
pages/hash.tsx
Normal file
30
pages/hash.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, { useMemo, useState } from "react"
|
||||
import { mimcHash } from "utils/mimc"
|
||||
|
||||
export default function MiMCPage(props: {}) {
|
||||
const [value, setValue] = useState("")
|
||||
const hash = useMemo(() => {
|
||||
const bytes = Buffer.from(value)
|
||||
const n = BigInt(bytes.length > 0 ? "0x" + bytes.toString("hex") : 0n)
|
||||
return mimcHash(n)
|
||||
}, [value])
|
||||
|
||||
return (
|
||||
<div className="max-w-lg m-auto font-mono">
|
||||
<h1 className="uppercase font-bold pt-16 pb-6">zk chat</h1>
|
||||
<div className="border border-gray-300 rounded-xl p-6">
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(event) => setValue(event.target.value)}
|
||||
/>
|
||||
<hr />
|
||||
<div className="break-all">
|
||||
<p>{hash.toString()}</p>
|
||||
<hr />
|
||||
<p>0x{hash.toString(16)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
208
pages/index.tsx
208
pages/index.tsx
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from "react"
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import { Buffer } from "buffer"
|
||||
import type { GetServerSideProps } from "next"
|
||||
import Link from "next/link"
|
||||
@@ -7,6 +7,8 @@ import { Menu, Transition } from "@headlessui/react"
|
||||
import { mimcHash } from "utils/mimc"
|
||||
import { prove } from "utils/prove"
|
||||
import { prisma } from "utils/prisma"
|
||||
import { LOCAL_STORAGE_SECRET_KEY } from "utils/localStorage"
|
||||
import { useRouter } from "next/router"
|
||||
|
||||
interface IndexPageProps {
|
||||
userCount: number
|
||||
@@ -20,14 +22,27 @@ export const getServerSideProps: GetServerSideProps<IndexPageProps, {}> =
|
||||
}
|
||||
}
|
||||
|
||||
function UserIcon({ address }) {
|
||||
return <div className="inline-block h-6 w-6 bg-gray-200 rounded-full text-center text-gray-400 pt-0.5 ml-0.5">
|
||||
{address}
|
||||
</div>;
|
||||
function UserIcon({ address }: { address: string }) {
|
||||
return (
|
||||
<div className="inline-block h-6 w-6 bg-gray-200 rounded-full text-center text-gray-400 pt-0.5 ml-0.5">
|
||||
{address}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Index(props: IndexPageProps) {
|
||||
console.log("we have", props.userCount, "users")
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
const secret = localStorage.getItem(LOCAL_STORAGE_SECRET_KEY)
|
||||
if (secret === null) {
|
||||
// user doesn't have a secret key in localstorage
|
||||
router.push("/login")
|
||||
}
|
||||
}, [])
|
||||
|
||||
const [secret, setSecret] = useState("")
|
||||
const hash1 = useMemo(() => {
|
||||
const hex = Buffer.from(secret).toString("hex")
|
||||
@@ -42,27 +57,28 @@ export default function Index(props: IndexPageProps) {
|
||||
const [message, setMessage] = useState("")
|
||||
|
||||
const messages = [
|
||||
{
|
||||
message: 'Hello world!',
|
||||
proof: '',
|
||||
group: ['0', '1', '2'],
|
||||
reveals: [],
|
||||
denials: [],
|
||||
},
|
||||
{
|
||||
message: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempo',
|
||||
proof: '',
|
||||
group: ['0', '1', '2'],
|
||||
reveals: ['0'],
|
||||
denials: ['1', '2'],
|
||||
},
|
||||
{
|
||||
message: 'Random message',
|
||||
proof: '',
|
||||
group: ['0', '1', '2'],
|
||||
reveals: [],
|
||||
denials: [],
|
||||
}
|
||||
{
|
||||
message: "Hello world!",
|
||||
proof: "",
|
||||
group: ["0", "1", "2"],
|
||||
reveals: [],
|
||||
denials: [],
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempo",
|
||||
proof: "",
|
||||
group: ["0", "1", "2"],
|
||||
reveals: ["0"],
|
||||
denials: ["1", "2"],
|
||||
},
|
||||
{
|
||||
message: "Random message",
|
||||
proof: "",
|
||||
group: ["0", "1", "2"],
|
||||
reveals: [],
|
||||
denials: [],
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
@@ -71,66 +87,68 @@ export default function Index(props: IndexPageProps) {
|
||||
<h1 className="uppercase font-bold mt-16 mb-6 flex-1">zk chat</h1>
|
||||
<div>
|
||||
<Link href="/login">
|
||||
<div className="cursor-pointer bg-pink text-white rounded-xl px-4 py-2 mt-14">Login</div>
|
||||
<div className="cursor-pointer bg-pink text-white rounded-xl px-4 py-2 mt-14">
|
||||
Login
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-gray-300 rounded-xl p-6">
|
||||
<fieldset>
|
||||
<legend>identify yourself</legend>
|
||||
<label>
|
||||
<span>secret pre-image</span>
|
||||
<fieldset>
|
||||
<legend>identify yourself</legend>
|
||||
<label>
|
||||
<span>secret pre-image</span>
|
||||
<br />
|
||||
<input
|
||||
type="text"
|
||||
value={secret}
|
||||
onChange={(event) => setSecret(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<br />
|
||||
<input
|
||||
type="text"
|
||||
value={secret}
|
||||
onChange={(event) => setSecret(event.target.value)}
|
||||
<label>
|
||||
<span>hash1</span>
|
||||
<br />
|
||||
<code>{hash1}</code>
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>identify your group</legend>
|
||||
<label>
|
||||
<span>hash2</span>
|
||||
<br />
|
||||
<input
|
||||
type="text"
|
||||
value={hash2}
|
||||
onChange={(event) => setHash2(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<br />
|
||||
<label>
|
||||
<span>hash3</span>
|
||||
<br />
|
||||
<input
|
||||
type="text"
|
||||
value={hash3}
|
||||
onChange={(event) => setHash3(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>message</legend>
|
||||
<br />
|
||||
<textarea
|
||||
className="block w-full"
|
||||
value={message}
|
||||
onChange={(event) => setMessage(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<br />
|
||||
<label>
|
||||
<span>hash1</span>
|
||||
<br />
|
||||
<code>{hash1}</code>
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>identify your group</legend>
|
||||
<label>
|
||||
<span>hash2</span>
|
||||
<br />
|
||||
<input
|
||||
type="text"
|
||||
value={hash2}
|
||||
onChange={(event) => setHash2(event.target.value)}
|
||||
className="bg-pink text-white rounded-xl px-4 py-2 mt-6"
|
||||
type="button"
|
||||
onClick={() => prove({ secret, hash1, hash2, hash3, msg: message })}
|
||||
value="Send your first message"
|
||||
/>
|
||||
</label>
|
||||
<br />
|
||||
<label>
|
||||
<span>hash3</span>
|
||||
<br />
|
||||
<input
|
||||
type="text"
|
||||
value={hash3}
|
||||
onChange={(event) => setHash3(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>message</legend>
|
||||
<br />
|
||||
<textarea
|
||||
className="block w-full"
|
||||
value={message}
|
||||
onChange={(event) => setMessage(event.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="bg-pink text-white rounded-xl px-4 py-2 mt-6"
|
||||
type="button"
|
||||
onClick={() => prove({ secret, hash1, hash2, hash3, msg: message })}
|
||||
value="Send your first message"
|
||||
/>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div className="pt-6 pb-12">
|
||||
<div className="mb-8 flex">
|
||||
@@ -148,8 +166,11 @@ export default function Index(props: IndexPageProps) {
|
||||
</div>
|
||||
|
||||
{messages.map((message, index) => (
|
||||
<div key={index} className="bg-white rounded-2xl px-6 pt-5 pb-4 mb-4 leading-snug relative">
|
||||
<div className="absolute top-3 right-5 text-right">
|
||||
<div
|
||||
key={index}
|
||||
className="bg-white rounded-2xl px-6 pt-5 pb-4 mb-4 leading-snug relative"
|
||||
>
|
||||
<div className="absolute top-3 right-5 text-right">
|
||||
<Menu>
|
||||
<Menu.Button className="text-gray-300">…</Menu.Button>
|
||||
<Transition
|
||||
@@ -164,7 +185,9 @@ export default function Index(props: IndexPageProps) {
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
className={`block ${active && 'bg-blue-500 text-white'}`}
|
||||
className={`block ${
|
||||
active && "bg-blue-500 text-white"
|
||||
}`}
|
||||
href="#"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
@@ -175,7 +198,9 @@ export default function Index(props: IndexPageProps) {
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
className={`block ${active && 'bg-blue-500 text-white'}`}
|
||||
className={`block ${
|
||||
active && "bg-blue-500 text-white"
|
||||
}`}
|
||||
href="#"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
@@ -187,17 +212,22 @@ export default function Index(props: IndexPageProps) {
|
||||
</Transition>
|
||||
</Menu>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
{message.message}
|
||||
</div>
|
||||
<div className="mb-5">{message.message}</div>
|
||||
<div className="flex text-sm">
|
||||
<div className="flex-1 text-gray-400">
|
||||
{message.reveals.length > 0 ? 'From ' : 'From one of '}
|
||||
{(message.reveals.length > 0 ? message.reveals : message.group).map((r) => <UserIcon address={r} />)}
|
||||
{message.reveals.length > 0 ? "From " : "From one of "}
|
||||
{(message.reveals.length > 0
|
||||
? message.reveals
|
||||
: message.group
|
||||
).map((r) => (
|
||||
<UserIcon key={r} address={r} />
|
||||
))}
|
||||
</div>
|
||||
<div className="text-right text-gray-400">
|
||||
{message.reveals.length > 0 && 'Not from '}
|
||||
{message.denials.map((r) => <UserIcon address={r} />)}
|
||||
{message.reveals.length > 0 && "Not from "}
|
||||
{message.denials.map((r) => (
|
||||
<UserIcon key={r} address={r} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import Link from "next/link"
|
||||
import React, { useCallback, useEffect, useState } from "react"
|
||||
|
||||
import { useRouter } from "next/router"
|
||||
|
||||
import { Buffer } from "buffer"
|
||||
|
||||
import { LOCAL_STORAGE_SECRET_KEY } from "utils/localStorage"
|
||||
import { useRouter } from "next/router"
|
||||
|
||||
export default function LoginPage(props: {}) {
|
||||
const [value, setValue] = useState("")
|
||||
@@ -14,23 +14,23 @@ export default function LoginPage(props: {}) {
|
||||
useEffect(() => {
|
||||
const secret = localStorage.getItem(LOCAL_STORAGE_SECRET_KEY)
|
||||
if (secret !== null) {
|
||||
Buffer.from(secret, "hex").toString()
|
||||
setValue(secret)
|
||||
}
|
||||
})
|
||||
|
||||
const save = useCallback((secret: Buffer) => {
|
||||
localStorage.setItem(LOCAL_STORAGE_SECRET_KEY, secret.toString("hex"))
|
||||
router.push("/connect")
|
||||
}, [])
|
||||
|
||||
const handleLogin = useCallback((value: string) => {
|
||||
save(Buffer.from(value))
|
||||
const _ = Buffer.from(value, "hex") // just make sure it's a valid hex string
|
||||
localStorage.setItem(LOCAL_STORAGE_SECRET_KEY, value)
|
||||
router.push("/backup")
|
||||
}, [])
|
||||
|
||||
const handleGenerateKey = useCallback(() => {
|
||||
console.log("generating key")
|
||||
const array = new Uint8Array(32)
|
||||
crypto.getRandomValues(array)
|
||||
save(Buffer.from(array))
|
||||
const secret = Buffer.from(array).toString("hex")
|
||||
localStorage.setItem(LOCAL_STORAGE_SECRET_KEY, secret)
|
||||
router.push("/backup")
|
||||
}, [])
|
||||
|
||||
return (
|
||||
@@ -38,6 +38,7 @@ export default function LoginPage(props: {}) {
|
||||
<h1 className="uppercase font-bold pt-16 pb-6">zk chat</h1>
|
||||
<div className="border border-gray-300 rounded-xl p-6 text-center">
|
||||
<input
|
||||
className="p-2"
|
||||
type="text"
|
||||
placeholder="Your secret token"
|
||||
value={value}
|
||||
@@ -52,11 +53,13 @@ export default function LoginPage(props: {}) {
|
||||
Login
|
||||
</button>
|
||||
or
|
||||
<Link href="/connect">
|
||||
<div className="cursor-pointer bg-pink text-white rounded-xl px-4 py-2 mt-3 text-center">
|
||||
Sign up (generate a new key)
|
||||
</div>
|
||||
</Link>
|
||||
<button
|
||||
disabled={value === ""}
|
||||
onClick={() => handleGenerateKey()}
|
||||
className="block w-full cursor-pointer bg-pink text-white rounded-xl px-4 py-2 mt-2 mb-3 text-center"
|
||||
>
|
||||
Sign up (generate a new secret token)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user