Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ceremony update #2987

Merged
merged 5 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ceremony/src/lib/client/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const get = async <T>(
export const post = async <T>(
resource: string,
params: Params,
body: Record<string, unknown>,
body: Record<string, unknown> | string,
_fetch: Fetch = fetch
): Promise<T | undefined> => {
try {
Expand Down
13 changes: 13 additions & 0 deletions ceremony/src/lib/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,16 @@ export const checkState = async (): Promise<ClientState> => {
return "offline"
}
}

export const generateSecret = async (email: string | undefined) => {
if (!email) {
console.log("No email")
return
}
try {
return await post<string>("secret_key", {}, email)
} catch (error) {
console.log("Error fetching secret:", error)
return undefined
}
}
50 changes: 41 additions & 9 deletions ceremony/src/lib/components/Button.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
<script lang="ts">
import Spinner from "$lib/components/Spinner.svelte"
import type { HTMLButtonAttributes, HTMLAnchorAttributes } from "svelte/elements"

let { children, class: className = "", loading = false, ...props } = $props()
interface Props extends HTMLButtonAttributes, HTMLAnchorAttributes {
children?: any
class?: string
loading?: boolean
variant?: "primary" | "secondary"
href?: string
}

let {
children,
class: className = "",
loading = false,
variant = "primary",
href,
...props
}: Props = $props()

const styles = {
primary: "bg-union-accent-500 text-black hover:text-black",
secondary: "bg-transparent text-white border-2 border-white hover:bg-neutral-800"
}
const getClass = (type: "primary" | "secondary") => styles[type] || styles.primary
let combinedClasses = $derived(
`flex items-center w-fit gap-2 px-4 py-2 font-bold uppercase justify-center ${getClass(variant)} ${className}`
)
</script>

<button
{...props}
class={`flex items-center w-fit gap-2 px-4 py-2 text-black font-bold hover:text-black bg-union-accent-500 uppercase justify-center ${className}`}>
{@render children()}
{#if loading}
<Spinner class="size-4"/>
{/if}
</button>
{#if href}
<a {href} class={combinedClasses} {...props}>
{@render children()}
{#if loading}
<Spinner class="size-4"/>
{/if}
</a>
{:else}
<button class={combinedClasses} {...props}>
{@render children()}
{#if loading}
<Spinner class="size-4"/>
{/if}
</button>
{/if}
94 changes: 33 additions & 61 deletions ceremony/src/lib/components/Ceremony.svelte
Original file line number Diff line number Diff line change
@@ -1,91 +1,63 @@
<script lang="ts">
import type { ContributorState } from "$lib/stores/state.svelte.ts"
import H1 from "$lib/components/typography/H1.svelte"
import H3 from "$lib/components/typography/H3.svelte"
import H2 from "$lib/components/typography/H2.svelte"
import { generateSecret, start } from "$lib/client"
import Reward from "$lib/components/Reward.svelte"
import Download from "$lib/components/Download.svelte"
import Queue from "$lib/components/Queue.svelte"
import Install from "$lib/components/Install.svelte"
import { start } from "$lib/client"
import { AddressForm, type ValidState } from "$lib/components/address"
import Blink from "$lib/components/Blink.svelte"
import Tweet from "$lib/components/Tweet.svelte"
import SwimLoad from "$lib/components/SwimLoad.svelte"
import { getNumberSuffix } from "$lib/utils/utils.ts"
import Text from "$lib/components/typography/Text.svelte"
import Status from "$lib/components/Status.svelte"
import Thanks from "$lib/components/Thanks.svelte"
import { user } from "$lib/stores/user.svelte.ts"

type Props = {
contributor: ContributorState
}

let { contributor }: Props = $props()

async function generate() {
const email = user.session?.user.email
await generateSecret(email)
}

$effect(() => {
if (contributor?.contributionState === "contribute" && contributor.state !== "contributing") {
console.log("Call client start")
start()
}

if (contributor.clientState !== "offline") {
generate()
}
})

let addressValidState: ValidState = $state("PENDING")
window.addEventListener("beforeunload", (e: BeforeUnloadEvent) => {
e.preventDefault()
e.returnValue = ""
})
</script>

<div class="p-8 w-full flex items-center justify-center flex-col">

{#if contributor.state === 'inQueue'}

<Status {contributor} />

<div class="border p-8 w-full max-w-4xl flex flex-col items-center">
<H1 class="mb-6">You are <span class="!text-union-accent-500">{contributor.queueState.position}<span
class="lowercase">{getNumberSuffix(contributor.queueState.position)}</span> </span> in queue</H1>

<SwimLoad max={contributor.queueState.count} current={contributor.queueState.position}/>

<div class="mb-4 text-center">
<H2>Queue length: <span class="text-union-accent-500">{contributor.queueState.count}</span></H2>
<H3>Waiting time: <span class="text-union-accent-500">{contributor.queueState.estimatedTime} minutes</span>
(est.).
</H3>
</div>

<div class="text-center">
<H2 class="mb-2">Get your nft</H2>
<AddressForm class="" onValidation={result => (addressValidState = result)}/>
</div>
</div>


{#if !contributor.userWallet}
<Reward {contributor}/>
{:else if contributor.clientState === 'offline'}
<Install {contributor}/>
{:else if !contributor.downloadedSecret}
<Download {contributor}/>
{:else if contributor.state === "inQueue"}
<Queue {contributor}/>
{:else if contributor.state === 'contribute'}
<Status {contributor} />
<H1>Starting contribution...</H1>

<H1>Starting contribution...</H1>
{:else if contributor.state === 'contributing'}
<Status {contributor} />
<H1>Contributing...</H1>

<H1>Contributing...</H1>
{:else if contributor.state === 'verifying'}
<Status {contributor} />
<H1>Verifying your contribution...</H1>

<H1>Verifying your contribution...</H1>
{:else if contributor.state === 'contributed'}

<div class="flex flex-col justify-center items-center gap-4">
<H1>Thank you! Your contribution is completed.</H1>
<Tweet/>
</div>

{:else if contributor.state === 'noClient'}

<Status {contributor} />
<H1>No client. Cannot start contribution.</H1>

<Thanks {contributor}/>
{:else}
<H1>Not able to contribute at this time</H1>

<H1>Not able to contribute at this time</H1>
{/if}

</div>


<div class="absolute bottom-10 flex flex-col px-8 text-center gap-4">
</div>
<div class="absolute bottom-10 flex flex-col px-8 text-center gap-4"></div>
42 changes: 36 additions & 6 deletions ceremony/src/lib/components/Code.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import Button from "$lib/components/Button.svelte"

type Props = {
contributor: ContributorState
secondary?: boolean
}

let { contributor }: Props = $props()
let { contributor, secondary = false }: Props = $props()

let words: Array<string> = $state(new Array(6).fill(""))
let code = $derived(normalizeString(words))
Expand Down Expand Up @@ -44,17 +45,39 @@ async function handleCodeJoin() {
toast.error("An error occurred while redeeming the code")
} finally {
codeLoading = false
words = new Array(6).fill("")
}
}
</script>

function handleKeyDown(event: KeyboardEvent, index: number) {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault()
if (index < words.length - 1) {
// Move to next input
const nextInput = document.querySelector(
`input:nth-child(${2 * index + 3})`
) as HTMLInputElement
nextInput?.focus()
} else if (event.key === "Enter") {
// On last input, trigger the USE CODE button only for Enter key
handleCodeJoin()
}
} else if (event.key === "Backspace" && words[index] === "" && index > 0) {
event.preventDefault()
// Move to previous input
const prevInput = document.querySelector(
`input:nth-child(${2 * index - 1})`
) as HTMLInputElement
prevInput?.focus()
}
}
</script>

<div class="flex gap-2 max-w-4xl flex-wrap justify-center mb-8">
{#each words as word, index}
<input
bind:value={words[index]}
onpaste={handlePaste}
onkeydown={(e) => handleKeyDown(e, index)}
class="bg-transparent border-b border-white w-20 text-center text-union-accent-500 outline-none focus:ring-0 focus:border-union-accent-500"
style="--tw-ring-color: transparent;"
/>
Expand All @@ -63,6 +86,13 @@ async function handleCodeJoin() {
{/if}
{/each}
</div>
<Button loading={codeLoading} type="button" onclick={handleCodeJoin}>
USE CODE
</Button>

{#if secondary}
<Button class="bg-transparent text-white hover:text-white border-2 border-white hover:bg-neutral-800" loading={codeLoading} type="button" onclick={handleCodeJoin}>
Redeem code
</Button>
{:else}
<Button loading={codeLoading} type="button" onclick={handleCodeJoin}>
Redeem code
</Button>
{/if}
15 changes: 0 additions & 15 deletions ceremony/src/lib/components/Counter/index.svelte
Original file line number Diff line number Diff line change
@@ -1,17 +1,4 @@
<script lang="ts">
// import Zero from "./numbers/zero.svelte"
// import One from "./numbers/one.svelte"
// import Two from "./numbers/two.svelte"
// import Three from "./numbers/three.svelte"
// import Four from "./numbers/four.svelte"
// import Five from "./numbers/five.svelte"
// import Six from "./numbers/six.svelte"
// import Seven from "./numbers/seven.svelte"
// import Eight from "./numbers/eight.svelte"
// import Nine from "./numbers/nine.svelte"
// import H4 from "$lib/components/typography/H4.svelte"
// import H1 from "$lib/components/typography/H1.svelte"
// import Text from "$lib/components/typography/Text.svelte"
import H2 from "$lib/components/typography/H2.svelte"
import H3 from "$lib/components/typography/H3.svelte"

Expand All @@ -25,8 +12,6 @@ let hours = $state("00")
let minutes = $state("00")
let seconds = $state("00")

// const components = [Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine]

let interval: ReturnType<typeof setInterval>

function updateCountdown(): void {
Expand Down
49 changes: 49 additions & 0 deletions ceremony/src/lib/components/Download.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script lang="ts">
import Button from "$lib/components/Button.svelte"
import H1 from "$lib/components/typography/H1.svelte"
import Text from "$lib/components/typography/Text.svelte"
import { ContributorState } from "$lib/stores/state.svelte.ts"
import { onMount } from "svelte"
import { user } from "$lib/stores/user.svelte.ts"
import { generateSecret } from "$lib/client"

type Props = {
contributor: ContributorState
}

let { contributor }: Props = $props()

let download = $state(false)

function handleDownload(event: MouseEvent) {
event.preventDefault()
const newUrl = "http://localhost:4919/secret_key"
window.open(newUrl, "_blank")
download = true
}

function setDownloadedSecret() {
localStorage.setItem("downloaded-secret", "true")
contributor.downloadedSecret = true
}
</script>

{#if !download}
<H1>Generate your PGP secret</H1>
<Text class="text-center mb-4">
The MPC client automatically uses this secret to sign your contribution.<br>
Your secret is locally generated through the MPC client.
</Text>
<Button variant="primary" onclick={handleDownload}>Generate secret</Button>
{:else}
<H1>Store your PGP secret</H1>
<Text class="text-center mb-4">
Please store your secret somewhere safe, such as in your password manager.
<br> Never share your secret.
<br> This secret key is the only way to prove that you have contributed.
</Text>
<div class="flex gap-4">
<Button variant="primary" onclick={setDownloadedSecret}>I've generated and stored my secret</Button>
<Button variant="secondary" href="http://localhost:4919/secret_key" target="_blank">Generate again</Button>
</div>
{/if}
Loading