diff --git a/ceremony/src/lib/components/Terminal/Activity.svelte b/ceremony/src/lib/components/Terminal/Activity.svelte index 09fbcee2c5..8bc027e568 100644 --- a/ceremony/src/lib/components/Terminal/Activity.svelte +++ b/ceremony/src/lib/components/Terminal/Activity.svelte @@ -21,25 +21,26 @@ function formatTimestamp(timestamp: string): string { </script> {#if activity.data} - {#each activity.data as item, i (item)} - {@const type = item.message.type} - {@const user = item.message.user} - <Print> - {formatTimestamp(item.created_at)} - - {#if type === "join_waitlist"} - {user} joined the waitlist - {:else if type === "redeem"} - {user} have redeemed a code - {:else if type === "join_queue"} - {user} joined the queue - {:else if type === "contribution_started"} - {user} have started their contribution - {:else if type === "contribution_submitted"} - {user} has submitted their contribution - {:else if type === "contribution_verified"} - {user} contribution just verified - {/if} - </Print> - - {/each} + <div class="flex flex-col-reverse"> + {#each activity.data as item, i (item)} + {@const type = item.message.type} + {@const user = item.message.user} + <Print> + {formatTimestamp(item.created_at)} - + {#if type === "join_waitlist"} + {user} joined the waitlist + {:else if type === "redeem"} + {user} has redeemed a code + {:else if type === "join_queue"} + {user} joined the queue + {:else if type === "contribution_started"} + {user} has started their contribution + {:else if type === "contribution_submitted"} + {user} has submitted their contribution + {:else if type === "contribution_verified"} + {user}'s contribution has just been verified + {/if} + </Print> + {/each} + </div> {/if} \ No newline at end of file diff --git a/ceremony/src/lib/components/Terminal/Authenticate.svelte b/ceremony/src/lib/components/Terminal/Authenticate.svelte index 61843e52ae..e2f1ba24dd 100644 --- a/ceremony/src/lib/components/Terminal/Authenticate.svelte +++ b/ceremony/src/lib/components/Terminal/Authenticate.svelte @@ -1,68 +1,39 @@ <script lang="ts"> -import { type AuthProviders, Terminal } from "$lib/state/terminal.svelte.ts" import { supabase } from "$lib/supabase/client.ts" import { onDestroy, onMount } from "svelte" -import Button from "$lib/components/Terminal/Button.svelte" -import { cn } from "$lib/utils/utils.ts" -import { on } from "svelte/events" +import { sleep } from "$lib/utils/utils.ts" import Print from "$lib/components/Terminal/Print.svelte" +import Buttons from "$lib/components/Terminal/Install/Buttons.svelte" +import { getState } from "$lib/state/index.svelte.ts" -type Props = { - terminal: Terminal -} - -let { terminal }: Props = $props() - -const providers: Array<AuthProviders> = ["GitHub", "Google"] - -let focusedIndex = $state(0) let redirecting = $state(false) +const { terminal } = getState() -async function logIn(provider: AuthProviders) { - //@ts-ignore - const thisProvider: "github" | "google" = provider.toLowerCase() +onMount(() => { + terminal.updateHistory({ text: "Unauthenticated user", replace: true }) + terminal.updateHistory({ text: "Please authenticate with one of the following", replace: true }) +}) + +async function logIn(provider: "github" | "google") { + terminal.updateHistory({ text: `Signing in with ${provider}`, replace: true }) + await sleep(2000) const { data, error } = await supabase.auth.signInWithOAuth({ - provider: thisProvider, + provider: provider, options: { redirectTo: `/` } }) if (error || !data) { - terminal.updateHistory(`Error signing in using ${provider}`) + terminal.updateHistory({ text: `Error signing in using ${provider}`, type: "warning" }) } else { redirecting = true - terminal.updateHistory(`Redirecting to ${provider}`) + terminal.updateHistory({ text: `Redirecting to ${provider}` }) } } -let unsubscribe: (() => void) | undefined -let subscriptionTimeout: NodeJS.Timeout | undefined -onMount(() => { - terminal.setStep(1) - terminal.updateHistory("Please authenticate using one of the following") - subscriptionTimeout = setTimeout(() => { - unsubscribe = terminal.keys.subscribe(event => { - if (event) { - if (event.type !== "keydown") return - if (event.key === "ArrowUp") { - focusedIndex = (focusedIndex - 1 + providers.length) % providers.length - } else if (event.key === "ArrowDown") { - focusedIndex = (focusedIndex + 1) % providers.length - } else if (event.key === "Enter") { - logIn(providers[focusedIndex]) - } - } - }) - }, 200) - return () => { - if (subscriptionTimeout) { - clearTimeout(subscriptionTimeout) - } - if (unsubscribe) { - unsubscribe() - } - } -}) +function trigger(value: "github" | "google") { + logIn(value) +} onDestroy(() => { terminal.clearHistory() @@ -70,17 +41,9 @@ onDestroy(() => { </script> {#if !redirecting} - - {#each providers as provider, index} - <Button - onmouseenter={() => focusedIndex = index} - class={cn(index === focusedIndex ? "bg-union-accent-500 text-black" : "", "capitalize")} - onclick={() => logIn(provider)} - > - > {provider} - </Button> - {/each} + <Buttons + data={[{text: "GitHub", action: "github"}, {text: "Google", action: "google"}]} + trigger={(value: 'github' | 'google') => trigger(value)} /> <Print><br></Print> - <Print class="uppercase !text-[#FD6363]">By logging in, I acknowledge that my name, email address, and optional wallet address will be part of the publicly viewable MPC ceremony data. I agree that this data will never be deleted as it is encoded in my contribution.</Print> - + <Print class="uppercase !text-[#FD6363]">By signing in, I acknowledge that my public GPG key and signature will be permanently publicly available as it is cryptographically part of the MPC ceremony data. I am aware that my GPG key contains the email address I use to sign in.</Print> {/if} \ No newline at end of file diff --git a/ceremony/src/lib/components/Terminal/Button.svelte b/ceremony/src/lib/components/Terminal/Button.svelte index 56ae3f3347..f18c461a5f 100644 --- a/ceremony/src/lib/components/Terminal/Button.svelte +++ b/ceremony/src/lib/components/Terminal/Button.svelte @@ -1,14 +1,28 @@ <script lang="ts"> -let { children, class: className = "", value = $bindable(), ...rest } = $props() +let { children, class: className = "", focus = undefined, value = $bindable(), ...rest } = $props() </script> <button bind:this={value} - class="px-2 focus:blink block outline-none focus:ring-transparent focus:border-none text-sm sm:text-base {className}" + class="px-2 block outline-none focus:ring-transparent focus:border-none text-sm sm:text-base {className}" + class:terminal-blink={focus} {...rest}> {@render children()} </button> <style> + .terminal-blink { + animation: blink 1.35s step-end infinite; + } + @keyframes blink { + 0%, 100% { + background-color: #A0ECFD; + color: black; + } + 50% { + background-color: transparent; + color: white; + } + } </style> \ No newline at end of file diff --git a/ceremony/src/lib/components/Terminal/Ceremony.svelte b/ceremony/src/lib/components/Terminal/Ceremony.svelte index 049fb5ce1f..ff258df1f1 100644 --- a/ceremony/src/lib/components/Terminal/Ceremony.svelte +++ b/ceremony/src/lib/components/Terminal/Ceremony.svelte @@ -29,7 +29,7 @@ onDestroy(() => { {:else if contributor.state === "verifying"} {terminal.setStep(9)} - {terminal.updateHistory("Verifying your contribution...", {replace: true})} + {terminal.updateHistory({text:"Verifying your contribution...", replace: true})} {:else if contributor.clientState === "offline" || contributor.clientState === undefined} {terminal.setStep(5)} @@ -41,11 +41,11 @@ onDestroy(() => { {:else if contributor.state === 'contribute'} {terminal.setStep(8)} - {terminal.updateHistory("Starting contribution...", {replace: true})} + {terminal.updateHistory({text: "Starting contribution...", replace: true})} {:else if contributor.state === "contributing"} {terminal.setStep(9)} - {terminal.updateHistory("Contributing...", {replace: true})} + {terminal.updateHistory({text: "Contributing...", replace: true})} {:else} <Print>Not able to contribute at this time</Print> diff --git a/ceremony/src/lib/components/Terminal/Code.svelte b/ceremony/src/lib/components/Terminal/Code.svelte index cd0be1716c..8c648cf3d1 100644 --- a/ceremony/src/lib/components/Terminal/Code.svelte +++ b/ceremony/src/lib/components/Terminal/Code.svelte @@ -2,9 +2,11 @@ import { getState } from "$lib/state/index.svelte.ts" import { onDestroy, onMount } from "svelte" import Print from "$lib/components/Terminal/Print.svelte" -import Confirm from "$lib/components/Terminal/Confirm.svelte" +import Buttons from "$lib/components/Terminal/Install/Buttons.svelte" +import { sleep } from "$lib/utils/utils.ts" +import { callJoinQueue } from "$lib/supabase" -const { terminal } = getState() +const { terminal, contributor } = getState() let inputCode: string = $state("") let normalizedCode = $derived(normalizeString(inputCode)) @@ -12,15 +14,57 @@ let inputElement: HTMLInputElement let showConfirm = $state(false) let showInput = $state(true) -function normalizeString(input: string): string { - return input.toLowerCase().replace(/[^a-z0-9]/gi, "") -} - function handleKeyDown(event: KeyboardEvent) { if (event.key === "Enter") { + console.log("XX keydown") event.preventDefault() - showConfirm = true + terminal.updateHistory({ text: `Entered code: ${inputCode}`, duplicate: true }) + terminal.updateHistory({ + text: "If you enter the queue then you must have your browser and terminal open when it is your turn. you cannot leave the queue, and when it is your turn you need to contribute", + type: "warning", + duplicate: true + }) + terminal.updateHistory({ text: "", lineBreak: true }) showInput = false + showConfirm = true + } +} + +onMount(() => { + if (inputElement) { + inputElement.focus() + } +}) + +onDestroy(() => { + if (inputElement) { + inputElement.blur() + } + terminal.clearHistory() +}) + +function normalizeString(input: string): string { + return input.toLowerCase().replace(/[^a-z0-9]/gi, "") +} + +async function handleCodeJoin() { + try { + terminal.updateHistory({ text: "Checking code...", duplicate: true }) + console.log("code: ", normalizedCode) + await sleep(1000) + const codeOk = await callJoinQueue(normalizedCode) + if (codeOk) { + contributor.setAllowanceState("hasRedeemed") + terminal.updateHistory({ text: "Code successfully redeemed" }) + } else { + terminal.updateHistory({ text: "The code is not valid", duplicate: true }) + terminal.updateHistory({ text: "", lineBreak: true, duplicate: true }) + onCancel() + } + } catch (error) { + console.error("Error redeeming code:", error) + terminal.updateHistory({ text: "An error occurred while redeeming the code" }) + onCancel() } } @@ -29,9 +73,14 @@ function onCancel() { showConfirm = false } -onDestroy(() => { - terminal.clearHistory() -}) +function trigger(value: "enter" | "cancel") { + console.log("XX trigger 2") + if (value === "enter") { + handleCodeJoin() + } else if (value === "cancel") { + onCancel() + } +} </script> {#if showInput} @@ -50,6 +99,10 @@ onDestroy(() => { </div> {/if} -{#if showConfirm} - <Confirm normalized={normalizedCode} code={inputCode} {onCancel}/> +{#if showConfirm && !showInput} + <Buttons + index={1} + data={[{text: "Enter the queue", action: "enter"}, {text: "Cancel", action: "cancel"}]} + trigger={(value: 'enter' | 'cancel') => trigger(value)} + /> {/if} diff --git a/ceremony/src/lib/components/Terminal/Confirm.svelte b/ceremony/src/lib/components/Terminal/Confirm.svelte deleted file mode 100644 index 5d3ef8002b..0000000000 --- a/ceremony/src/lib/components/Terminal/Confirm.svelte +++ /dev/null @@ -1,95 +0,0 @@ -<script lang="ts"> -import { cn, sleep } from "$lib/utils/utils.ts" -import { callJoinQueue } from "$lib/supabase" -import Print from "$lib/components/Terminal/Print.svelte" -import { getState } from "$lib/state/index.svelte.ts" -import Button from "$lib/components/Terminal/Button.svelte" -import type { KeyEvent } from "$lib/state/terminal.svelte.ts" -import { onDestroy, onMount } from "svelte" - -const { terminal, contributor } = getState() - -type Props = { - code: string - onCancel: () => void - normalized: string -} - -let { code, onCancel, normalized, ...props }: Props = $props() - -let buttons = [{ text: "Enter the queue" }, { text: "Cancel" }] -let focusedIndex = $state(2) - -onMount(() => { - focusedIndex = 2 -}) -async function handleCodeJoin(i: number) { - try { - terminal.updateHistory("Checking code...", { duplicate: true }) - console.log("code: ", normalized) - await sleep(1000) - const codeOk = await callJoinQueue(normalized) - if (codeOk) { - contributor.setAllowanceState("hasRedeemed") - terminal.updateHistory("Code successfully redeemed") - } else { - terminal.updateHistory("The code is not valid", { duplicate: true }) - onCancel() - } - } catch (error) { - console.error("Error redeeming code:", error) - terminal.updateHistory("An error occurred while redeeming the code") - onCancel() - } -} - -const handleKeydown = (event: KeyEvent) => { - if (event.type === "keydown") { - if (event.key === "ArrowDown" || event.key === "ArrowUp") { - const direction = event.key === "ArrowDown" ? 1 : -1 - focusedIndex = (focusedIndex + direction + buttons.length) % buttons.length - buttons[focusedIndex].focus() - } - } -} - -const unsubscribe = terminal.keys.subscribe(event => { - if (event) { - handleKeydown(event) - } -}) - -onDestroy(() => { - unsubscribe() -}) -</script> - -<Print class="!text-[#FD6363]">IF YOU ENTER THE QUEUE THEN YOU MUST HAVE YOUR BROWSER AND TERMINAL OPEN WHEN IT IS YOUR - TURN. - YOU CANNOT LEAVE THE QUEUE, AND WHEN IT IS YOUR TURN YOU NEED TO CONTRIBUTE -</Print> -<Print><br></Print> -<Button - bind:value={buttons[0]} - onmouseenter={() => focusedIndex = 0} - class={cn(focusedIndex === 0 ? "bg-union-accent-500 text-black" : "")} - onclick={handleCodeJoin} -> - > Enter the queue -</Button> -<Button - bind:value={buttons[1]} - onmouseenter={() => focusedIndex = 1} - class={cn(focusedIndex === 1 ? "bg-union-accent-500 text-black" : "")} - onclick={onCancel} -> - > Cancel -</Button> -<Button - bind:value={buttons[2]} - onmouseenter={() => focusedIndex = 2} - class={cn(focusedIndex === 2 ? "bg-white text-black" : "")} -> - > Select one of the above -</Button> - diff --git a/ceremony/src/lib/components/Terminal/Contributors.svelte b/ceremony/src/lib/components/Terminal/Contributors.svelte index b008a7febd..8c2d89da50 100644 --- a/ceremony/src/lib/components/Terminal/Contributors.svelte +++ b/ceremony/src/lib/components/Terminal/Contributors.svelte @@ -1,63 +1,34 @@ <script lang="ts"> import { getState } from "$lib/state/index.svelte.ts" import { goto } from "$app/navigation" -import Print from "$lib/components/Terminal/Print.svelte" -import { cn } from "$lib/utils/utils.ts" -import Button from "$lib/components/Terminal/Button.svelte" import { onMount } from "svelte" +import Buttons from "$lib/components/Terminal/Install/Buttons.svelte" +import type { Contributions } from "$lib/state/contributions.svelte.ts" +import type { Terminal } from "$lib/state/terminal.svelte.ts" -const { contributions, terminal } = getState() +type State = { + contributions: Contributions + terminal: Terminal +} + +const { contributions, terminal }: State = getState() let focusedIndex = $state(0) let buttons: Array<HTMLButtonElement> = [] +let data = $state([]) +onMount(() => { + contributions.data.map(contribution => { + data.push({ text: contribution.payload_id, action: contribution.public_key_hash }) + }) +}) -function handleClick(contributor: any) { - goto(`/contributions/${contributor.public_key_hash}`) +function trigger(value: any) { + goto(`/contributions/${value}`) terminal.setTab(4) - terminal.setHash(contributor.public_key_hash) + terminal.setHash(value) } - -let unsubscribe: (() => void) | undefined -let subscriptionTimeout: NodeJS.Timeout | undefined -onMount(() => { - subscriptionTimeout = setTimeout(() => { - unsubscribe = terminal.keys.subscribe(event => { - if (event) { - if (event.type === "keydown") { - if (event.key === "ArrowUp") { - focusedIndex = - (focusedIndex - 1 + contributions.data.length) % contributions.data.length - buttons[focusedIndex]?.focus() - } else if (event.key === "ArrowDown") { - focusedIndex = (focusedIndex + 1) % contributions.data.length - buttons[focusedIndex]?.focus() - } else if (event.key === "Enter") { - if (buttons[focusedIndex]) { - handleClick(contributions.data[focusedIndex]) - } - } - } - } - }) - }, 200) - return () => { - if (subscriptionTimeout) { - clearTimeout(subscriptionTimeout) - } - if (unsubscribe) { - unsubscribe() - } - } -}) </script> - - {#each contributions.data as contributor, index} - <Button - bind:value={buttons[index]} - onmouseenter={() => focusedIndex = index} - class={cn(index === focusedIndex ? "bg-union-accent-500 text-black" : "", "whitespace-nowrap text-start w-fit max-w-5xl truncate")} - onclick={() => handleClick(contributor)} - > - > {contributor.payload_id} - </Button> - {/each} \ No newline at end of file +<Buttons + {data} + trigger={(value) => trigger(value)} +/> diff --git a/ceremony/src/lib/components/Terminal/Install/Buttons.svelte b/ceremony/src/lib/components/Terminal/Install/Buttons.svelte new file mode 100644 index 0000000000..b209d73d62 --- /dev/null +++ b/ceremony/src/lib/components/Terminal/Install/Buttons.svelte @@ -0,0 +1,62 @@ +<script lang="ts"> +import { cn, sleep } from "$lib/utils/utils.ts" +import Button from "$lib/components/Terminal/Button.svelte" +import { onDestroy, onMount } from "svelte" + +type Props = { + trigger: (value: any) => void + index?: number + data: Array<{ + text: string + action: string + }> +} + +let { trigger, data, index = 0, ...props }: Props = $props() + +let buttons = $state<Array<HTMLButtonElement | null>>([]) +let focusedIndex = $state(index) + +const handleKeydown = (event: KeyboardEvent) => { + if (event.key === "ArrowDown" || event.key === "ArrowUp") { + event.preventDefault() + const direction = event.key === "ArrowDown" ? 1 : -1 + focusedIndex = (focusedIndex + direction + buttons.length) % buttons.length + const button = buttons[focusedIndex] + if (button && typeof button.focus === "function") { + button.focus() + } + } else if (event.key === "Enter") { + event.preventDefault() + const button = buttons[focusedIndex] + if (button && typeof button.click === "function") { + button.click() + } + } +} + +onMount(async () => { + focusedIndex = index + await sleep(300) + window.addEventListener("keydown", handleKeydown) +}) + +onDestroy(() => { + window.removeEventListener("keydown", handleKeydown) +}) +</script> + +{#each data as btn, index} + {#key btn} + <Button + {...props} + bind:value={buttons[index]} + onmouseenter={() => focusedIndex = index} + class={cn(focusedIndex === index ? "bg-union-accent-500 text-black" : "")} + onclick={() => trigger(btn.action)} + focus={focusedIndex === index} + > + > {btn.text} + </Button> + {/key} +{/each} \ No newline at end of file diff --git a/ceremony/src/lib/components/Terminal/Install/Linux.svelte b/ceremony/src/lib/components/Terminal/Install/Linux.svelte index 0ef7006e1b..af39ff6fab 100644 --- a/ceremony/src/lib/components/Terminal/Install/Linux.svelte +++ b/ceremony/src/lib/components/Terminal/Install/Linux.svelte @@ -1,9 +1,8 @@ <script lang="ts"> import { onDestroy, onMount } from "svelte" import { getState } from "$lib/state/index.svelte.ts" -import { cn, sleep } from "$lib/utils/utils.ts" -import type { KeyEvent } from "$lib/state/terminal.svelte.ts" -import Button from "$lib/components/Terminal/Button.svelte" +import { sleep } from "$lib/utils/utils.ts" +import Buttons from "$lib/components/Terminal/Install/Buttons.svelte" type Props = { change: () => void @@ -13,47 +12,37 @@ const { terminal } = getState() let { change }: Props = $props() let showButtons = $state(true) -let buttons = $state<Array<HTMLButtonElement>>([]) -let focusedIndex = $state(0) let command = "mkdir -p ceremony && docker pull ghcr.io/unionlabs/union/mpc-client:latest && docker run -v $(pwd)/ceremony:/ceremony -w /ceremony -p 4919:4919 --rm -it ghcr.io/unionlabs/union/mpc-client:latest" onMount(() => { const messages = [ - { text: "---", options: { duplicate: true } }, + { text: "---", duplicate: true }, { - text: "You must have docker installed and running in order to contribute. Once you have docker running, copy the following command in your terminal:", - options: {} - }, - { text: "---", options: { duplicate: true } }, - { text: command, options: { duplicate: true } }, - { text: "---", options: { duplicate: true } }, - { - text: "Once the MPC client is running you can return to this page.", - options: { duplicate: true } + text: "You must have docker installed and running in order to contribute. Once you have docker running, copy the following command in your terminal:" }, + { text: "---", duplicate: true }, + { text: command, duplicate: true }, + { text: "---", duplicate: true }, + { text: "Once the MPC client is running you can return to this page.", duplicate: true }, { text: "If the MPC client is running but you still see this page, ensure that you are using either Chrome, FireFox or Brave. For Brave, disable the shields in the address bar.", - options: { duplicate: true } + duplicate: true } ] messages.forEach(msg => { - terminal.updateHistory(msg.text, msg.options) + terminal.updateHistory(msg) }) - - if (buttons.length > 0) { - buttons[0].focus() - } }) const copy = async () => { showButtons = false - terminal.updateHistory("Copying command...", { duplicate: true }) + terminal.updateHistory({ text: "Copying command...", duplicate: true }) await sleep(500) await navigator.clipboard.writeText(command) - terminal.updateHistory("Command copied!", { duplicate: true }) + terminal.updateHistory({ text: "Command copied!", duplicate: true }) await sleep(500) showButtons = true } @@ -63,45 +52,22 @@ const selectDifferentOs = () => { change() } -const handleKeydown = (event: KeyEvent) => { - if (event.type === "keydown") { - if (event.key === "ArrowDown" || event.key === "ArrowUp") { - const direction = event.key === "ArrowDown" ? 1 : -1 - focusedIndex = (focusedIndex + direction + buttons.length) % buttons.length - buttons[focusedIndex].focus() - } - } -} - -const unsubscribe = terminal.keys.subscribe(event => { - if (event) { - handleKeydown(event) - } -}) - onDestroy(() => { - unsubscribe() terminal.clearHistory() }) + +function trigger(value: "copy" | "select") { + if (value === "copy") { + copy() + } else if (value === "select") { + selectDifferentOs() + } +} </script> {#if showButtons} - <Button - autofocus - bind:value={buttons[0]} - onmouseenter={() => focusedIndex = 0} - class={cn(focusedIndex === 0 ? "bg-union-accent-500 text-black" : "")} - onclick={copy} - > - > Copy command - </Button> - <Button - bind:value={buttons[1]} - onmouseenter={() => focusedIndex = 1} - class={cn(focusedIndex === 1 ? "bg-union-accent-500 text-black" : "")} - onclick={selectDifferentOs} - > - > Select different OS - </Button> + <Buttons + data={[{text: "Copy command", action: "copy"}, {text: "Select different OS", action: "select"}]} + trigger={(value) => trigger(value)}/> {/if} diff --git a/ceremony/src/lib/components/Terminal/Install/MacOS.svelte b/ceremony/src/lib/components/Terminal/Install/MacOS.svelte index 84eb72a02b..870b3dd143 100644 --- a/ceremony/src/lib/components/Terminal/Install/MacOS.svelte +++ b/ceremony/src/lib/components/Terminal/Install/MacOS.svelte @@ -1,9 +1,8 @@ <script lang="ts"> import { onDestroy, onMount } from "svelte" import { getState } from "$lib/state/index.svelte.ts" -import { cn, sleep } from "$lib/utils/utils.ts" -import type { KeyEvent } from "$lib/state/terminal.svelte.ts" -import Button from "$lib/components/Terminal/Button.svelte" +import { sleep } from "$lib/utils/utils.ts" +import Buttons from "$lib/components/Terminal/Install/Buttons.svelte" type Props = { change: () => void @@ -13,59 +12,50 @@ const { terminal } = getState() let { change }: Props = $props() let showButtons = $state(true) -let buttons = $state<Array<HTMLButtonElement>>([]) -let focusedIndex = $state(0) let command = "mkdir -p ceremony && docker pull ghcr.io/unionlabs/union/mpc-client:latest && docker run -v $(pwd)/ceremony:/ceremony -w /ceremony -p 4919:4919 --rm -it ghcr.io/unionlabs/union/mpc-client:latest" onMount(() => { const messages = [ - { text: "---", options: { duplicate: true } }, + { text: "---" }, { - text: "You must have OrbStack installed in order to contribute, because Docker Desktop is too slow. If you use Docker Desktop it is extremely likely that you will lose your contribution slot.", - options: {} + text: "You must have OrbStack installed in order to contribute, because Docker Desktop is too slow. If you use Docker Desktop it is extremely likely that you will lose your contribution slot." }, - { text: "---", options: { duplicate: true } }, - { text: "1. Install OrbStack", options: { duplicate: true } }, - { text: "2. Open OrbStack from the Applications/ folder", options: { duplicate: true } }, - { text: "3. Click allow on the OrbStack popups", options: { duplicate: true } }, + { text: "---" }, { - text: "4. Open Terminal from the Applications/Utilities/ folder", - options: { duplicate: true } + text: '1. <a class="underline-offset-4 decoration-union-accent-500 underline" href="https://orbstack.dev/ ">Install OrbStack</a>' }, + { text: "2. Open OrbStack from the Applications/ folder" }, + { text: "3. Click allow on the OrbStack popups" }, { - text: "5. Paste the following command in Terminal to start the MPC client:", - options: { duplicate: true } + text: "4. Open Terminal from the Applications/Utilities/ folder" }, - { text: "---", options: { duplicate: true } }, - { text: command, options: { duplicate: true } }, - { text: "---", options: { duplicate: true } }, { - text: "Once the MPC client is running you can return to this page.", - options: { duplicate: true } + text: "5. Paste the following command in Terminal to start the MPC client:" }, + { text: "---", duplicate: true }, + { text: command }, + { text: "---", duplicate: true }, { - text: "If the MPC client is running but you still see this page, ensure that you are using either Chrome, FireFox or Brave. For Brave, disable the shields in the address bar.", - options: { duplicate: true } + text: "Once the MPC client is running you can return to this page." + }, + { + text: "If the MPC client is running but you still see this page, ensure that you are using either Chrome, FireFox or Brave. For Brave, disable the shields in the address bar." } ] messages.forEach(msg => { - terminal.updateHistory(msg.text, msg.options) + terminal.updateHistory(msg) }) - - if (buttons.length > 0) { - buttons[0].focus() - } }) const copy = async () => { showButtons = false - terminal.updateHistory("Copying command...", { duplicate: true }) + terminal.updateHistory({ text: "Copying command...", duplicate: true }) await sleep(500) await navigator.clipboard.writeText(command) - terminal.updateHistory("Command copied!", { duplicate: true }) + terminal.updateHistory({ text: "Command copied!", duplicate: true }) await sleep(500) showButtons = true } @@ -75,44 +65,22 @@ const selectDifferentOs = () => { change() } -const handleKeydown = (event: KeyEvent) => { - if (event.type === "keydown") { - if (event.key === "ArrowDown" || event.key === "ArrowUp") { - const direction = event.key === "ArrowDown" ? 1 : -1 - focusedIndex = (focusedIndex + direction + buttons.length) % buttons.length - buttons[focusedIndex].focus() - } +function trigger(value: "copy" | "select") { + if (value === "copy") { + copy() + } else if (value === "select") { + selectDifferentOs() } } -const unsubscribe = terminal.keys.subscribe(event => { - if (event) { - handleKeydown(event) - } -}) - onDestroy(() => { - unsubscribe() terminal.clearHistory() }) </script> {#if showButtons} - <Button - bind:value={buttons[0]} - onmouseenter={() => focusedIndex = 0} - class={cn(focusedIndex === 0 ? "bg-union-accent-500 text-black" : "")} - onclick={copy} - > - > Copy command - </Button> - <Button - bind:value={buttons[1]} - onmouseenter={() => focusedIndex = 1} - class={cn(focusedIndex === 1 ? "bg-union-accent-500 text-black" : "")} - onclick={selectDifferentOs} - > - > Select different OS - </Button> + <Buttons + data={[{text: "Copy command", action: "copy"}, {text: "Select different OS", action: "select"}]} + trigger={(value) => trigger(value)}/> {/if} diff --git a/ceremony/src/lib/components/Terminal/Install/SelectOS.svelte b/ceremony/src/lib/components/Terminal/Install/SelectOS.svelte index 87da3cf84b..8a8a79bd00 100644 --- a/ceremony/src/lib/components/Terminal/Install/SelectOS.svelte +++ b/ceremony/src/lib/components/Terminal/Install/SelectOS.svelte @@ -1,12 +1,11 @@ <script lang="ts"> -import { cn, type DetectedOS } from "$lib/utils/utils.ts" +import { type DetectedOS } from "$lib/utils/utils.ts" import { getState } from "$lib/state/index.svelte.ts" -import { onDestroy, onMount } from "svelte" -import type { KeyEvent } from "$lib/state/terminal.svelte.ts" -import Button from "$lib/components/Terminal/Button.svelte" +import { onMount } from "svelte" +import Buttons from "$lib/components/Terminal/Install/Buttons.svelte" type Props = { - select: (os: DetectedOS) => void + select: (os: string) => void } const { terminal } = getState() @@ -14,45 +13,25 @@ const { terminal } = getState() let { select }: Props = $props() let showButtons = $state(true) -const selections: Array<DetectedOS> = ["Linux", "macOS"] -let focusedIndex = $state(0) +const selections = [ + { text: "Linux", action: "linux" }, + { text: "macOS", action: "macos" } +] onMount(() => { - terminal.updateHistory("No MPC client detected", { duplicate: true }) - terminal.updateHistory("Select your OS for instructions", { duplicate: true }) + terminal.updateHistory({ text: "No MPC client detected", duplicate: true }) + terminal.updateHistory({ text: "Select your OS for instructions", duplicate: true }) }) -function handleKeyDown(event: KeyEvent) { - if (event.type === "keydown") { - if (event.key === "ArrowUp") { - focusedIndex = (focusedIndex - 1 + selections.length) % selections.length - } else if (event.key === "ArrowDown") { - focusedIndex = (focusedIndex + 1) % selections.length - } else if (event.key === "Enter") { - showButtons = false - select(selections[focusedIndex]) - } - } +function trigger(value: "linux" | "macos") { + select(value) } - -const unsubscribe = terminal.keys.subscribe(event => { - if (event) { - handleKeyDown(event) - } -}) - -onDestroy(unsubscribe) </script> - {#if showButtons} - {#each selections as os, index} - <Button - onmouseenter={() => focusedIndex = index} - class={cn(index === focusedIndex ? "bg-union-accent-500 text-black" : "")} - onclick={() => select(os)} - > - > {os} - </Button> - {/each} + <Buttons + data={selections} + trigger={(value) => trigger(value)}/> + {/if} + diff --git a/ceremony/src/lib/components/Terminal/Install/index.svelte b/ceremony/src/lib/components/Terminal/Install/index.svelte index 9774d11d43..63a322fb10 100644 --- a/ceremony/src/lib/components/Terminal/Install/index.svelte +++ b/ceremony/src/lib/components/Terminal/Install/index.svelte @@ -7,7 +7,7 @@ import MacOS from "$lib/components/Terminal/Install/MacOS.svelte" import Linux from "$lib/components/Terminal/Install/Linux.svelte" let os = $state<DetectedOS | undefined>(undefined) -let selectedOs = $state<DetectedOS | undefined>(undefined) +let selectedOs = $state<string | undefined>(undefined) const { terminal } = getState() @@ -16,15 +16,16 @@ onMount(async () => { }) let change = async () => { - terminal.updateHistory(`Loading supported os..`, { duplicate: true }) + terminal.updateHistory({ text: `Loading supported os..`, duplicate: true }) await sleep(500) selectedOs = undefined } -let select = async (value: DetectedOS) => { - terminal.updateHistory(`Loading ${value} instructions..`, { duplicate: true }) +let select = async (value: string) => { + terminal.updateHistory({ text: `Loading ${value} instructions..`, duplicate: true }) await sleep(500) selectedOs = value + console.log("XX", value) } onDestroy(() => { @@ -34,9 +35,9 @@ onDestroy(() => { {#if !selectedOs} <SelectOS {select}/> -{:else if selectedOs === "macOS"} +{:else if selectedOs === "macos"} <MacOS {change}/> -{:else if selectedOs === "Linux"} +{:else if selectedOs === "linux"} <Linux {change}/> {/if} diff --git a/ceremony/src/lib/components/Terminal/Join.svelte b/ceremony/src/lib/components/Terminal/Join.svelte index 804afd1298..ef001d9040 100644 --- a/ceremony/src/lib/components/Terminal/Join.svelte +++ b/ceremony/src/lib/components/Terminal/Join.svelte @@ -6,28 +6,24 @@ import { onDestroy, onMount } from "svelte" import { cn, sleep } from "$lib/utils/utils.ts" import Code from "$lib/components/Terminal/Code.svelte" import Button from "$lib/components/Terminal/Button.svelte" +import Buttons from "$lib/components/Terminal/Install/Buttons.svelte" const { contributor, terminal } = getState() -const buttons = [ - { - label: "I have an invitation code", - action: "code" - }, - { - label: "I want to join the waitlist", - action: "waitlist" - } -] - let isOpenToPublic = $state(false) -let waitlistLoading = $state(false) let selected = $state(false) let code = $state(false) -let focusedIndex = $state(0) + +onMount(() => { + terminal.updateHistory({ text: "Access the ceremony", replace: true }) + terminal.setStep(2) +}) + +onDestroy(() => { + terminal.clearHistory() +}) async function handleWaitlistJoin() { - waitlistLoading = true try { await callJoinQueue(null) if (isOpenToPublic) { @@ -37,78 +33,38 @@ async function handleWaitlistJoin() { } } catch (error) { console.error("Error joining waitlist:", error) - } finally { - waitlistLoading = false } } -let unsubscribe: (() => void) | undefined -let subscriptionTimeout: NodeJS.Timeout | undefined -onMount(() => { - terminal.setStep(2) - terminal.updateHistory("Please authenticate using one of the following") - subscriptionTimeout = setTimeout(() => { - unsubscribe = terminal.keys.subscribe(event => { - if (event) { - if (code) return - if (event.type === "keydown") { - if (event.key === "ArrowUp") { - focusedIndex = (focusedIndex - 1 + buttons.length) % buttons.length - } else if (event.key === "ArrowDown") { - focusedIndex = (focusedIndex + 1) % buttons.length - } else if (event.key === "Enter") { - handleAction(buttons[focusedIndex].action) - } - } - } - }) - }, 200) - return () => { - if (subscriptionTimeout) { - clearTimeout(subscriptionTimeout) - } - if (unsubscribe) { - unsubscribe() - } - } -}) - -async function handleAction(action: string) { - if (action === "waitlist") { +async function trigger(value: string) { + if (value === "waitlist") { selected = true - terminal.updateHistory("Adding user to the waitlist...") + terminal.updateHistory({ text: "Adding user to the waitlist..." }) await sleep(1000) handleWaitlistJoin() - } else if (action === "code") { + } else if (value === "code") { code = true } } -onDestroy(() => { - if (unsubscribe) { - unsubscribe() +const buttons = [ + { + text: "I have an invitation code", + action: "code" + }, + { + text: "I want to join the waitlist", + action: "waitlist" } - terminal.clearHistory() -}) +] </script> -{terminal.updateHistory("Access the ceremony")} - {#if !selected} {#if code } <Code /> {:else } - {#each buttons as button, index} - <Button - onmouseenter={() => focusedIndex = index} - class={cn(index === focusedIndex ? "bg-union-accent-500 text-black" : "")} - onclick={() => handleAction(button.action)} - > - > {button.label} - </Button> - {/each} + <Buttons data={buttons} trigger={(value: 'code' | 'waitlist') => trigger(value)}/> {/if} - {/if} diff --git a/ceremony/src/lib/components/Terminal/Printer.svelte b/ceremony/src/lib/components/Terminal/Printer.svelte new file mode 100644 index 0000000000..0f4fbf0076 --- /dev/null +++ b/ceremony/src/lib/components/Terminal/Printer.svelte @@ -0,0 +1,22 @@ +<script lang="ts"> +import Print from "$lib/components/Terminal/Print.svelte" +import { getState } from "$lib/state/index.svelte.ts" +import { cn } from "$lib/utils/utils.ts" + +const { terminal } = getState() +</script> + +<div class="flex flex-col"> + {#each terminal.history as content} + {#if content.lineBreak} + <Print><br></Print> + {:else} + <p class={cn( + "w-fit text-neutral-300 text-sm sm:text-base", + content.type === "warning" ? "!text-[#FD6363]" : "", + content.type === "info" ? "!text-union-accent-500" : "", + content.uppercase ? "uppercase" : "", + )}>{@html content.text}</p> + {/if} + {/each} +</div> \ No newline at end of file diff --git a/ceremony/src/lib/components/Terminal/Queue.svelte b/ceremony/src/lib/components/Terminal/Queue.svelte index 30a2e9c336..347ec78ea9 100644 --- a/ceremony/src/lib/components/Terminal/Queue.svelte +++ b/ceremony/src/lib/components/Terminal/Queue.svelte @@ -7,7 +7,7 @@ import LoadingBar from "$lib/components/Terminal/LoadingBar.svelte" const { contributor, terminal } = getState() onMount(() => { - terminal.updateHistory("You are in queue") + terminal.updateHistory({ text: "You are in queue" }) }) onDestroy(() => { diff --git a/ceremony/src/lib/components/Terminal/Reward.svelte b/ceremony/src/lib/components/Terminal/Reward.svelte index 2b69f52861..7be63c7963 100644 --- a/ceremony/src/lib/components/Terminal/Reward.svelte +++ b/ceremony/src/lib/components/Terminal/Reward.svelte @@ -13,8 +13,12 @@ let validation = (val: ValidState) => { } onMount(() => { - terminal.updateHistory("Add an address, you may receive rewards for successful contributions.") - terminal.updateHistory('You can enter your union or any cosmos address, or type "skip".') + terminal.updateHistory({ + text: "Add an address, you may receive rewards for successful contributions." + }) + terminal.updateHistory({ + text: 'You can enter your union or any cosmos address, or type "skip".' + }) }) onDestroy(() => { diff --git a/ceremony/src/lib/components/Terminal/Secret.svelte b/ceremony/src/lib/components/Terminal/Secret.svelte index 6e0a94280e..d952455a06 100644 --- a/ceremony/src/lib/components/Terminal/Secret.svelte +++ b/ceremony/src/lib/components/Terminal/Secret.svelte @@ -1,9 +1,9 @@ <script lang="ts"> import { getState } from "$lib/state/index.svelte.ts" -import { cn, sleep } from "$lib/utils/utils.ts" +import { sleep } from "$lib/utils/utils.ts" import { generateSecret } from "$lib/client" -import Button from "$lib/components/Terminal/Button.svelte" -import { onDestroy, onMount } from "svelte" +import { onDestroy } from "svelte" +import Buttons from "$lib/components/Terminal/Install/Buttons.svelte" const { contributor, terminal, user } = getState() @@ -17,7 +17,7 @@ function handleDownload() { window.open(newUrl, "_blank") } -function setDownloadedSecret() { +function stored() { localStorage.setItem("downloaded-secret", "true") contributor.downloadedSecret = true } @@ -25,10 +25,10 @@ function setDownloadedSecret() { async function generate() { if (contributor.state !== "noClient") { generating = true - terminal.updateHistory("Generating secret...") + terminal.updateHistory({ text: "Generating secret..." }) await sleep(3000) generateSecret(user.session?.user.email) - terminal.updateHistory("Initialize saving...") + terminal.updateHistory({ text: "Initialize saving..." }) await sleep(1000) handleDownload() generating = false @@ -38,16 +38,16 @@ async function generate() { $effect(() => { if (generated) { - terminal.updateHistory( - "Please store your secret somewhere safe, such as in your password manager. There's no need to open the file and remember to never share a secret. This secret key is the only way to prove that you have contributed." - ) + terminal.updateHistory({ + text: "Please store your secret somewhere safe, such as in your password manager. There's no need to open the file and remember to never share a secret. This secret key is the only way to prove that you have contributed." + }) } else { - terminal.updateHistory("Client detected") - terminal.updateHistory("Generate your PGP secret") - terminal.updateHistory( - "The MPC client automatically uses this secret to sign your contribution." - ) - terminal.updateHistory("Your secret is locally generated through the MPC client.") + terminal.updateHistory({ text: "Client detected" }) + terminal.updateHistory({ text: "Generate your PGP secret" }) + terminal.updateHistory({ + text: "The MPC client automatically uses this secret to sign your contribution." + }) + terminal.updateHistory({ text: "Your secret is locally generated through the MPC client." }) } }) @@ -55,52 +55,21 @@ onDestroy(() => { terminal.clearHistory() }) -let unsubscribe: (() => void) | undefined -let subscriptionTimeout: NodeJS.Timeout | undefined -onMount(() => { - subscriptionTimeout = setTimeout(() => { - unsubscribe = terminal.keys.subscribe(event => { - if (event) { - if (event.type === "keydown") { - if (event.key === "ArrowDown" || event.key === "ArrowUp") { - const direction = event.key === "ArrowDown" ? 1 : -1 - focusedIndex = (focusedIndex + direction + buttons.length) % buttons.length - buttons[focusedIndex].focus() - } - } - } - }) - }, 200) - return () => { - if (subscriptionTimeout) { - clearTimeout(subscriptionTimeout) - } - if (unsubscribe) { - unsubscribe() - } +function trigger(value: "generate" | "stored") { + if (value === "generate") { + generate() + } else if (value === "stored") { + stored() } -}) +} </script> {#if !generating} {#if !generated} - <Button bind:value={buttons[0]} - onmouseenter={() => focusedIndex = 0} - class={cn(focusedIndex === 0 ? "bg-union-accent-500 text-black" : "")} - onclick={generate}>> Generate secret - </Button> + <Buttons data={[{text: "Generate secret", action: "generate"}]} trigger={(value) => trigger(value)}/> {:else} - <Button - bind:value={buttons[0]} - onmouseenter={() => focusedIndex = 0} - class={cn(focusedIndex === 0 ? "bg-union-accent-500 text-black" : "")} - onclick={setDownloadedSecret}>> I've generated and stored my secret - </Button> - <Button - bind:value={buttons[1]} - onmouseenter={() => focusedIndex = 1} - class={cn(focusedIndex === 1 ? "bg-union-accent-500 text-black" : "")} - onclick={generate}>> Generate again - </Button> + <Buttons + data={[{text: "Generate secret again", action: "generate"}, {text: "I've stored my secret", action: "stored"}]} + trigger={(value) => trigger(value)}/> {/if} {/if} diff --git a/ceremony/src/lib/components/Terminal/Thanks.svelte b/ceremony/src/lib/components/Terminal/Thanks.svelte index e280ccda53..7667b02397 100644 --- a/ceremony/src/lib/components/Terminal/Thanks.svelte +++ b/ceremony/src/lib/components/Terminal/Thanks.svelte @@ -1,82 +1,38 @@ <script lang="ts"> import { getPublicHash } from "$lib/supabase" import { getState } from "$lib/state/index.svelte.ts" -import { cn, sleep } from "$lib/utils/utils.ts" +import { sleep } from "$lib/utils/utils.ts" import { onDestroy, onMount } from "svelte" -import Button from "$lib/components/Terminal/Button.svelte" -import { beforeNavigate } from "$app/navigation" +import Buttons from "$lib/components/Terminal/Install/Buttons.svelte" const { terminal } = getState() -let focusedIndex = $state(0) -let showButtons = $state(true) - -const buttons = $state([ - { text: "Tweet your attestation", action: "tweet" }, - { text: "View contributions", action: "view" } -]) - -beforeNavigate(() => { - if (unsubscribe) { - unsubscribe() - } -}) - -let unsubscribe: (() => void) | undefined -let subscriptionTimeout: NodeJS.Timeout | undefined - onMount(() => { - terminal.updateHistory("Thank you!", { replace: true }) - terminal.updateHistory("-------------") - terminal.updateHistory( - "Your contribution is complete. Thank you for securing the Union network. Tweet your cryptographic attestation for extra transparency.", - { replace: true } - ) - - subscriptionTimeout = setTimeout(() => { - unsubscribe = terminal.keys.subscribe(event => { - if (event) { - if (event.type === "keydown" && terminal.tab === 1) { - if (event.key === "ArrowUp") { - focusedIndex = (focusedIndex - 1 + buttons.length) % buttons.length - } else if (event.key === "ArrowDown") { - focusedIndex = (focusedIndex + 1) % buttons.length - } else if (event.key === "Enter") { - triggerAction(focusedIndex) - } - } - } - }) - }, 200) - return () => { - if (subscriptionTimeout) { - clearTimeout(subscriptionTimeout) - } - if (unsubscribe) { - unsubscribe() - } - } + terminal.updateHistory({ text: "Thank you!", replace: true }) + terminal.updateHistory({ text: "-------------" }) + terminal.updateHistory({ + text: "Your contribution is complete. Thank you for securing the Union network. Tweet your cryptographic attestation for extra transparency.", + replace: true + }) }) async function shareOnTwitter() { - showButtons = false - terminal.updateHistory("Preparing tweet...", { duplicate: true }) + terminal.updateHistory({ text: "Preparing tweet...", duplicate: true }) let hash = await getPublicHash() await sleep(2000) - terminal.updateHistory("Opening X (twitter)...", { duplicate: true }) + terminal.updateHistory({ text: "Opening X (twitter)...", duplicate: true }) await sleep(2000) let url = `https://ceremony.union.build/contributions/${hash}` const tweetText = `I just contributed to the @union_build Trusted Setup Ceremony, to secure its ZK circuit for trustless, decentralized interoperability. \n\nI attest to my contribution. My public key hash is: \n\n${url}\n\n#JoinTheUnion` const twitterIntentUrl = new URL("https://twitter.com/intent/tweet") twitterIntentUrl.searchParams.append("text", tweetText) window.open(twitterIntentUrl.toString(), "_blank") - showButtons = true } -function triggerAction(index: number) { - if (buttons[index].action === "tweet") { +function trigger(value: "tweet" | "view") { + if (value === "tweet") { shareOnTwitter() - } else if (buttons[index].action === "view") { + } else if (value === "view") { terminal.setTab(3) } } @@ -86,14 +42,4 @@ onDestroy(() => { }) </script> -{#if showButtons} - {#each buttons as btn, index} - <Button - onmouseenter={() => focusedIndex = index} - class={cn(index === focusedIndex ? "bg-union-accent-500 text-black" : "")} - onclick={() => triggerAction(index)} - > - > {btn.text} - </Button> - {/each} -{/if} \ No newline at end of file +<Buttons data={[{text: "Tweet your attestation", action: "tweet"},{text: "View contributions", action: "view"}]} trigger={(value: 'tweet' | 'view') => trigger(value)}/> diff --git a/ceremony/src/lib/components/Terminal/Waitlist.svelte b/ceremony/src/lib/components/Terminal/Waitlist.svelte index 8270db1166..d24775b9d9 100644 --- a/ceremony/src/lib/components/Terminal/Waitlist.svelte +++ b/ceremony/src/lib/components/Terminal/Waitlist.svelte @@ -1,19 +1,23 @@ <script lang="ts"> -import { getNumberSuffix } from "$lib/utils/utils.ts" +import { getNumberSuffix, sleep } from "$lib/utils/utils.ts" import { getState } from "$lib/state/index.svelte.ts" import Code from "$lib/components/Terminal/Code.svelte" import { onDestroy, onMount } from "svelte" const { contributor, terminal } = getState() -onMount(() => { +onMount(async () => { terminal.setStep(3) - terminal.updateHistory("You're on the waitlist") - terminal.updateHistory( - `When the ceremony opens to the public, you will be ${contributor.waitListPosition}${getNumberSuffix(contributor.waitListPosition)} in the public queue.` - ) - terminal.updateHistory("You will receive an email 12-18 hours before the public phase begins.") - terminal.updateHistory("Received an invite? You can skip the waitlist and join now.") + terminal.updateHistory({ text: "You're on the waitlist" }) + terminal.updateHistory({ + text: `When the ceremony opens to the public, you will be ${contributor.waitListPosition}${getNumberSuffix(contributor.waitListPosition)} in the public queue.` + }) + terminal.updateHistory({ + text: "You will receive an email 12-18 hours before the public phase begins." + }) + terminal.updateHistory({ text: "Received an invite? You can skip the waitlist and join now." }) + await sleep(500) + await contributor.checkWaitListPosition() }) onDestroy(() => { diff --git a/ceremony/src/lib/components/Terminal/index.svelte b/ceremony/src/lib/components/Terminal/index.svelte index cee1c51246..bca497351e 100644 --- a/ceremony/src/lib/components/Terminal/index.svelte +++ b/ceremony/src/lib/components/Terminal/index.svelte @@ -3,14 +3,14 @@ import { getState } from "$lib/state/index.svelte.ts" import { logout, user } from "$lib/state/session.svelte.ts" import Contributions from "./Contributors.svelte" import { goto } from "$app/navigation" -import { onDestroy } from "svelte" import Print from "$lib/components/Terminal/Print.svelte" import Activity from "$lib/components/Terminal/Activity.svelte" import { cn } from "$lib/utils/utils.ts" import Button from "$lib/components/Terminal/Button.svelte" import TaskBar from "$lib/components/Terminal/TaskBar.svelte" +import Printer from "$lib/components/Terminal/Printer.svelte" -const { terminal, contributor } = getState() +const { terminal } = getState() let { children } = $props() @@ -24,26 +24,6 @@ const changeTab = async (tab: number) => { } } -// const unsubscribe = terminal.keys.subscribe(event => { -// if (event) { -// if (event.type === "keydown" && (event.shiftKey || event.ctrlKey)) { -// if (event.key === "!") { -// changeTab(1) -// } else if (event.key === "@") { -// changeTab(2) -// } else if (event.key === "#") { -// changeTab(3) -// } else if (event.key === "$") { -// changeTab(4) -// } else if (event.key === "X") { -// logout(terminal) -// } -// } -// } -// }) -// -// onDestroy(unsubscribe) - function autoScroll(node: HTMLElement) { const scroll = () => { node.scrollTop = node.scrollHeight @@ -61,7 +41,7 @@ function autoScroll(node: HTMLElement) { </script> -<div class="flex flex-col w-full h-full sm:max-h-[700px] bg-black/50 backdrop-blur-lg max-w-4xl sm:border border-[#48494C] rounded-lg overflow-hidden drop-shadow-2xl shadow-black"> +<div class="flex flex-col w-full h-full sm:max-h-[700px] bg-black/50 backdrop-blur-lg max-w-4xl sm:border border-[#48494C] sm:rounded-lg overflow-hidden drop-shadow-2xl shadow-black"> <!--TOP BAR--> <div class="w-full flex justify-between bg-neutral-800/80 px-4 py-2"> @@ -103,11 +83,7 @@ function autoScroll(node: HTMLElement) { <div class="overflow-y-scroll h-full p-4" use:autoScroll> <div class="text-union-accent-50 w-full"> {#if terminal.tab === 1} - <div class="flex flex-col"> - {#each terminal.history as text} - <Print>{text}</Print> - {/each} - </div> + <Printer /> <Print><br></Print> {@render children()} {:else if terminal.tab === 2} diff --git a/ceremony/src/lib/components/address/AddressForm.svelte b/ceremony/src/lib/components/address/AddressForm.svelte index 2856195bf9..39a7173177 100644 --- a/ceremony/src/lib/components/address/AddressForm.svelte +++ b/ceremony/src/lib/components/address/AddressForm.svelte @@ -31,7 +31,7 @@ const onAddressSubmit = async (event: Event) => { } validation("PENDING") - terminal.updateHistory("Checking address") + terminal.updateHistory({ text: "Checking address" }) await sleep(1000) const addressValidation = isValidBech32Address(inputText) validState = addressValidation ? "VALID" : "INVALID" @@ -47,25 +47,25 @@ const onAddressSubmit = async (event: Event) => { wallet: inputText }) if (result) { - terminal.updateHistory("Saving address...") + terminal.updateHistory({ text: "Saving address..." }) await sleep(2000) - terminal.updateHistory("Wallet address saved successfully") + terminal.updateHistory({ text: "Wallet address saved successfully" }) await sleep(2000) contributor.checkUserWallet(user.session?.user.id) } else { - terminal.updateHistory("Failed to save wallet address") + terminal.updateHistory({ text: "Failed to save wallet address" }) } } catch (error) { console.error("Error saving wallet address:", error) - terminal.updateHistory("An error occurred while saving the wallet address") + terminal.updateHistory({ text: "An error occurred while saving the wallet address" }) } } else if (validState === "INVALID") { - terminal.updateHistory("Wallet address not valid, try again..", { duplicate: true }) + terminal.updateHistory({ text: "Wallet address not valid, try again..", duplicate: true }) } } const skip = async () => { - terminal.updateHistory("Skipping reward step") + terminal.updateHistory({ text: "Skipping reward step" }) validation("SKIPPED") try { if (!contributor.userId) return @@ -74,15 +74,18 @@ const skip = async () => { wallet: "SKIPPED" }) if (result) { - terminal.updateHistory("Saving to db...") + terminal.updateHistory({ text: "Saving to db..." }) await sleep(2000) contributor.userWallet = "SKIPPED" } else { - terminal.updateHistory("Failed to save wallet address", { duplicate: true }) + terminal.updateHistory({ text: "Failed to save wallet address", duplicate: true }) } } catch (error) { console.error("Error saving wallet address:", error) - terminal.updateHistory("An error occurred while saving the wallet address", { duplicate: true }) + terminal.updateHistory({ + text: "An error occurred while saving the wallet address", + duplicate: true + }) } } diff --git a/ceremony/src/lib/layout/Navbar/NavLink.svelte b/ceremony/src/lib/layout/Navbar/NavLink.svelte deleted file mode 100644 index 9b7a592c32..0000000000 --- a/ceremony/src/lib/layout/Navbar/NavLink.svelte +++ /dev/null @@ -1,7 +0,0 @@ -<script lang="ts"> -let { children, class: className = "", ...props } = $props() -</script> - -<a {...props} class={`text-union-text-primary hover:underline underline-offset-4 decoration-union-accent-500 uppercase font-bold ${className}`}> - {@render children()} -</a> \ No newline at end of file diff --git a/ceremony/src/lib/layout/Navbar/index.svelte b/ceremony/src/lib/layout/Navbar/index.svelte deleted file mode 100644 index 657f2c7709..0000000000 --- a/ceremony/src/lib/layout/Navbar/index.svelte +++ /dev/null @@ -1,84 +0,0 @@ -<script lang="ts"> -import { supabase } from "$lib/supabase/client.ts" -import { invalidateAll } from "$app/navigation" -import Button from "$lib/components/Button.svelte" -import NavLink from "$lib/layout/Navbar/NavLink.svelte" -import Badge from "$lib/components/Badge.svelte" -import { user } from "$lib/state/session.svelte.ts" - -let isOpen = $state(false) - -function toggleMenu() { - isOpen = !isOpen -} - -async function logout() { - const { error } = await supabase.auth.signOut() - if (error) { - console.error("Error logging out:", error.message) - } else { - user.session = null - invalidateAll() - } -} - -let loggedIn = $derived(!!user.session?.user.id) -</script> - -<header class="fixed bg-black top-0 inset-x-0 flex items-center justify-between gap-4 px-2 md:h-16 md:px-4 z-50"> - <nav class=" w-full p-4"> - <div class="flex justify-between items-center"> - <div class="mr-auto flex flex-1 flex-shrink-0 items-center justify-start gap-3"> - {#if loggedIn} - <a href="/contributions" class="inline-flex flex-shrink-0 items-center text-white"> - <img - src="/union-logo-supermolot.svg" - alt="Union Logo" - class="size-full max-w-32 h-12 select-none" - /> - </a> - {:else} - <a href="/" class="inline-flex flex-shrink-0 items-center text-white"> - <img - src="/union-logo-supermolot.svg" - alt="Union Logo" - class="size-full max-w-32 h-12 select-none" - /> - </a> - {/if} - <Badge>Ceremony</Badge> - </div> - - <div class="hidden md:block"> - {#if user.session} - <div class="flex items-center gap-4"> - <Button variant="secondary" onclick={logout}>Log out</Button> - </div> - {:else} - <div class="flex items-center gap-4"> - <NavLink class="p-2" href="/contributions">Contributions</NavLink> - </div> - {/if} - </div> - - <Button onclick={toggleMenu} class="md:hidden text-black focus:outline-none"> - <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7"></path> - </svg> - </Button> - </div> - - {#if isOpen} - <div class="md:hidden mt-4 w-full bg-black"> - <div class="flex flex-col divide-y divide-white/50"> - {#if user.session} - <Button variant="secondary" onclick={logout}>Log out</Button> - {:else} - <NavLink class="p-2" href="/contributions">Contributions</NavLink> - {/if} - </div> - </div> - {/if} - - </nav> -</header> \ No newline at end of file diff --git a/ceremony/src/lib/state/contributions.svelte.ts b/ceremony/src/lib/state/contributions.svelte.ts index 529677fe5e..2689092275 100644 --- a/ceremony/src/lib/state/contributions.svelte.ts +++ b/ceremony/src/lib/state/contributions.svelte.ts @@ -1,7 +1,12 @@ import { getContributions } from "$lib/supabase" +type ContributionsData = { + public_key_hash: string + payload_id: string +} + export class Contributions { - data = $state() + data = $state<Array<ContributionsData>>([]) private readonly intervalId: NodeJS.Timeout | null = null constructor() { @@ -11,7 +16,7 @@ export class Contributions { async loadContributions() { try { - this.data = await getContributions() + this.data = (await getContributions()) ?? [] } catch (error) { console.error("Failed to load contributions:", error) } diff --git a/ceremony/src/lib/state/contributor.svelte.ts b/ceremony/src/lib/state/contributor.svelte.ts index fa1d40a335..5156f6e8ae 100644 --- a/ceremony/src/lib/state/contributor.svelte.ts +++ b/ceremony/src/lib/state/contributor.svelte.ts @@ -109,14 +109,14 @@ export class Contributor { if (this.userId === undefined && userId) { this.userId = userId this.loggedIn = true - this.checkWaitListPosition(userId) + this.checkWaitListPosition() this.checkUserWallet(userId) this.checkCurrentUserState(userId) this.startPolling() } } - async checkWaitListPosition(_userId: string | undefined): Promise<number | undefined> { + async checkWaitListPosition(): Promise<number | undefined> { this.waitListPosition = await getWaitListPosition() return this.waitListPosition } diff --git a/ceremony/src/lib/state/live.svelte.ts b/ceremony/src/lib/state/live.svelte.ts index c81b9410a3..7923e448a0 100644 --- a/ceremony/src/lib/state/live.svelte.ts +++ b/ceremony/src/lib/state/live.svelte.ts @@ -19,7 +19,7 @@ export class Activity { .from("log") .select("message, created_at") .order("created_at", { ascending: false }) - .limit(10) + .limit(50) if (error) { console.error("Error fetching initial data:", error) diff --git a/ceremony/src/lib/state/session.svelte.ts b/ceremony/src/lib/state/session.svelte.ts index 36eb33ec7d..d81518d723 100644 --- a/ceremony/src/lib/state/session.svelte.ts +++ b/ceremony/src/lib/state/session.svelte.ts @@ -36,11 +36,11 @@ export async function logout(terminal: Terminal): Promise<void> { await goto("/") if (!user.session) { - terminal.updateHistory("User already logged out", { duplicate: true }) + terminal.updateHistory({ text: "User already logged out", duplicate: true }) return } - terminal.updateHistory("Logging out user...") + terminal.updateHistory({ text: "Logging out user..." }) await sleep(1000) try { @@ -49,7 +49,7 @@ export async function logout(terminal: Terminal): Promise<void> { terminal.setHash(undefined) await invalidateAll() } catch (error) { - terminal.updateHistory(`error logging out`) + terminal.updateHistory({ text: "Error logging out" }) terminal.setHash(undefined) terminal.setTab(1) diff --git a/ceremony/src/lib/state/terminal.svelte.ts b/ceremony/src/lib/state/terminal.svelte.ts index 9f5686efaf..0185c3d46a 100644 --- a/ceremony/src/lib/state/terminal.svelte.ts +++ b/ceremony/src/lib/state/terminal.svelte.ts @@ -4,9 +4,13 @@ import { readable } from "svelte/store" export type AuthProviders = "GitHub" | "Google" export type State = "hasRedeemed" | "inQueue" | "inWaitlist" | "join" | undefined -interface UpdateHistoryOptions { - duplicate?: boolean - replace?: boolean +interface UpdateHistory { + text: string + type?: "warning" | "info" | undefined + uppercase?: boolean | undefined + lineBreak?: boolean | undefined + duplicate?: boolean | undefined + replace?: boolean | undefined } export type KeyEvent = { @@ -18,7 +22,7 @@ export type KeyEvent = { export class Terminal { state = $state<State>(undefined) - history = $state<Array<string>>([]) + history = $state<Array<UpdateHistory>>([]) tab = $state<1 | 2 | 3 | number>(1) hash = $state<string | undefined>(undefined) currentStep = $state<number>(0) @@ -51,18 +55,15 @@ export class Terminal { console.log("Creating terminal state") } - updateHistory(text: string, options: UpdateHistoryOptions = {}) { - const { duplicate = false, replace = false } = options + updateHistory(content: UpdateHistory) { + const index = this.history.findIndex(item => item.text === content.text) - const index = this.history.indexOf(text) - - if (duplicate) { - this.history.push(text) - } else if (replace && index !== -1) { - this.history.splice(index, 1) - this.history.push(text) - } else if (!this.history.includes(text)) { - this.history.push(text) + if (content.duplicate) { + this.history.push(content) + } else if (content.replace && index !== -1) { + this.history[index] = content + } else if (index === -1) { + this.history.push(content) } } diff --git a/ceremony/src/routes/+layout.svelte b/ceremony/src/routes/+layout.svelte index 3e5352ee22..40aaf29534 100644 --- a/ceremony/src/routes/+layout.svelte +++ b/ceremony/src/routes/+layout.svelte @@ -9,16 +9,15 @@ import Timer from "$lib/components/Terminal/Timer.svelte" import "../styles/tailwind.css" import { onMount } from "svelte" -import { getAverageTimes } from "$lib/supabase" let { children } = $props() -let { user, contributor, terminal } = createState() +let { user, contributor } = createState() $effect(() => { const { data: { subscription } - } = supabase.auth.onAuthStateChange((event, session) => { + } = supabase.auth.onAuthStateChange(() => { checkAuth() }) return () => { @@ -49,12 +48,15 @@ $effect(() => { document.getElementById("glitch-video").play() } }) -let showBootSequence = $state(true) +let showBootSequence = $state(localStorage?.getItem("ceremony:show-boot-sequence") !== "false") let bootSequenceVideoElement = $state<HTMLVideoElement | null>(null) onMount(() => bootSequenceVideoElement?.play()) -const hideBootSequenceVideo = () => (showBootSequence = false) +const hideBootSequenceVideo = () => { + showBootSequence = false + localStorage?.setItem("ceremony:show-boot-sequence", "false") +} </script> {#if showBootSequence} @@ -106,15 +108,15 @@ const hideBootSequenceVideo = () => (showBootSequence = false) {/if} <style lang="postcss"> - video[data-video] { - right: 0; - bottom: 0; - z-index: -1; - width: 100vw; - height: 100vh; - min-width: 100%; - position: fixed; - min-height: 100%; - object-fit: cover; - } + video[data-video] { + right: 0; + bottom: 0; + z-index: -1; + width: 100vw; + height: 100vh; + min-width: 100%; + position: fixed; + min-height: 100%; + object-fit: cover; + } </style> diff --git a/ceremony/src/routes/+page.svelte b/ceremony/src/routes/+page.svelte index be2065a484..86e7099d9f 100644 --- a/ceremony/src/routes/+page.svelte +++ b/ceremony/src/routes/+page.svelte @@ -26,6 +26,6 @@ onMount(() => { <Join/> {/if} {:else if user.session === null && terminal.tab === 1} - <Authenticate {terminal} /> + <Authenticate /> {/if} {/if} diff --git a/ceremony/src/routes/contributions/[hash]/+page.svelte b/ceremony/src/routes/contributions/[hash]/+page.svelte index 6b645ac1db..2b68117947 100644 --- a/ceremony/src/routes/contributions/[hash]/+page.svelte +++ b/ceremony/src/routes/contributions/[hash]/+page.svelte @@ -1,52 +1,23 @@ <script lang="ts"> import { page } from "$app/stores" -import { getState } from "$lib/state/index.svelte.ts" import { getUserContribution } from "$lib/supabase" import Print from "$lib/components/Terminal/Print.svelte" -import Button from "$lib/components/Terminal/Button.svelte" -import { cn, sleep } from "$lib/utils/utils.ts" - -const { terminal } = getState() +import { sleep } from "$lib/utils/utils.ts" +import Buttons from "$lib/components/Terminal/Install/Buttons.svelte" +import { onMount } from "svelte" +import { getState } from "$lib/state/index.svelte.ts" let hash = $derived($page.params.hash) - -let focusedIndex = $state(0) -let buttons: Array<HTMLButtonElement> = [] let prints = $state<Array<string>>([]) -let showButtons = $state(true) -let unsubscribe: (() => void) | undefined -let subscriptionTimeout: NodeJS.Timeout | undefined -$effect(() => { +const { terminal } = getState() +let contribution = $state() + +onMount(async () => { + terminal.clearHistory() + contribution = await getUserContribution(hash) terminal.setTab(4) terminal.setHash(hash) - subscriptionTimeout = setTimeout(() => { - unsubscribe = terminal.keys.subscribe(event => { - if (event) { - if (event.type === "keydown") { - if (event.key === "ArrowUp") { - focusedIndex -= 1 - buttons[focusedIndex]?.focus() - } else if (event.key === "ArrowDown") { - focusedIndex += 1 - buttons[focusedIndex]?.focus() - } else if (event.key === "Enter") { - if (buttons[focusedIndex]) { - buttons[focusedIndex].click() - } - } - } - } - }) - }, 200) - return () => { - if (subscriptionTimeout) { - clearTimeout(subscriptionTimeout) - } - if (unsubscribe) { - unsubscribe() - } - } }) function hexToUint8Array(hexString: string) { @@ -62,15 +33,21 @@ function decodeHexString(hexString: string) { } async function copyToClipboard(text: string, type: string) { - showButtons = false prints.push(`Copying ${type}..`) await sleep(1000) await navigator.clipboard.writeText(text) prints.push(`Successfully copied ${type}`) - showButtons = true } const imagePath = "https://ceremony.union.build/images/ceremony.png" + +function trigger(value: "key" | "signature") { + if (value === "key") { + copyToClipboard(decodeHexString(contribution.public_key), "public key") + } else if (value === "signature") { + copyToClipboard(decodeHexString(contribution.signature), "signature") + } +} </script> <svelte:head> @@ -104,33 +81,20 @@ const imagePath = "https://ceremony.union.build/images/ceremony.png" </svelte:head> -{#await getUserContribution(hash)} +{#if contribution} + <pre class="text-white whitespace-pre-wrap text-sm sm:text-base">{decodeHexString(contribution?.public_key)}</pre> + <pre class="text-white whitespace-pre-wrap text-sm sm:text-base">{decodeHexString(contribution?.signature)}</pre> + {#each prints as print} + <Print>{print}</Print> + {/each} + <Print><br></Print> + <Buttons + data={[{text: "Copy public key", action: 'key'}, {text: "Copy signature", action: 'signature'}]} + trigger={(value: 'key' | 'signature') => trigger(value)} + /> +{:else} <Print>Loading</Print> -{:then contribution} - {#if contribution} - <pre class="text-white whitespace-pre-wrap text-sm sm:text-base">{decodeHexString(contribution.public_key)}</pre> - <pre class="text-white whitespace-pre-wrap text-sm sm:text-base">{decodeHexString(contribution.signature)}</pre> - {#each prints as print} - <Print>{print}</Print> - {/each} - {#if showButtons} - <Print><br></Print> - <Button bind:value={buttons[0]} - onmouseenter={() => focusedIndex = 0} - class={cn(focusedIndex === 0 ? "bg-union-accent-500 text-black" : "")} - onclick={() => copyToClipboard(decodeHexString(contribution.public_key), "public key")}>> - Copy public key - </Button> - <Button bind:value={buttons[1]} - onmouseenter={() => focusedIndex = 1} - class={cn(focusedIndex === 1 ? "bg-union-accent-500 text-black" : "")} - onclick={() => copyToClipboard(decodeHexString(contribution.signature), "signature")}>> - Copy signature - </Button> - {/if} - {/if} -{/await} - +{/if} <style> pre {