Skip to content

Commit

Permalink
feat(ceremony): save progress
Browse files Browse the repository at this point in the history
  • Loading branch information
Swepool committed Sep 19, 2024
1 parent e8b9711 commit 6417a61
Showing 6 changed files with 146 additions and 104 deletions.
85 changes: 85 additions & 0 deletions ceremony/src/lib/components/Contribution.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script lang="ts">
import { getUserContribution } from "$lib/supabase"
import Spinner from "$lib/components/Spinner.svelte"
import H2 from "$lib/components/typography/H2.svelte"
import Button from "$lib/components/Button.svelte"
import { toast } from "svelte-sonner"
import { page } from "$app/stores"
type Props = {
hash: string
}
let { hash }: Props = $props()
function hexToUint8Array(hexString: string) {
return new Uint8Array(hexString.match(/.{1,2}/g)?.map(byte => Number.parseInt(byte, 16)) || [])
}
function uint8ArrayToUtf8(bytes: Uint8Array) {
return new TextDecoder().decode(bytes)
}
function decodeHexString(hexString: string) {
return uint8ArrayToUtf8(hexToUint8Array(hexString))
}
async function copyToClipboard(text: string, label: string) {
try {
await navigator.clipboard.writeText(text)
toast.success(`Copied ${label}!`)
} catch (err) {
console.error("Failed to copy text: ", err)
toast.error(`Failed to copy ${label} to clipboard.`)
}
}
const imagePath = "/images/ceremony.png"
let imageUrl = $derived(new URL(imagePath, $page.url.origin).href)
</script>

<svelte:head>
<meta property="og:type" content="Website"/>
<meta property="og:site_name" content="Union Ceremony"/>
<meta property="og:locale" content="en"/>
<meta property="og:image:type" content="image/png"/>
<meta property="og:image:width" content="1200"/>
<meta property="og:image:height" content="675"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:site" content="@union_build"/>
<meta name="twitter:creator" content="@union_build"/>
<meta property="og:image" content={imageUrl}/>
<meta property="og:image:secure_url" content={imageUrl}/>
<meta name="twitter:image" content={imageUrl}/>
</svelte:head>

{#await getUserContribution(hash)}
<Spinner class="size-5 text-union-accent-500"/>
{:then contribution}
{#if contribution}
<div class="flex flex-col items-start gap-1 px-3 py-2">
<div>
<H2>Contributor: <span class="!text-union-accent-500">{contribution.user_name}</span></H2>
</div>

<div class="flex flex-col gap-4">
<div>
<H2 class="mb-2">Public key</H2>
<pre class="text-white whitespace-pre-wrap bg-neutral-800 p-4 mb-4">{decodeHexString(contribution.public_key)}</pre>
<Button onclick={() => copyToClipboard(decodeHexString(contribution.public_key), "public key")}>Copy
Public
key
</Button>
</div>

<div>
<H2 class="mb-2">Signature</H2>
<pre class="text-white whitespace-pre-wrap bg-neutral-800 p-4 mb-4">{decodeHexString(contribution.signature)}</pre>
<Button onclick={() => copyToClipboard(decodeHexString(contribution.signature), "signature")}>Copy
Signature
</Button>
</div>
</div>
</div>
{/if}
{/await}
48 changes: 48 additions & 0 deletions ceremony/src/lib/components/Contributions.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<script lang="ts">
import Spinner from "$lib/components/Spinner.svelte"
import H4 from "$lib/components/typography/H4.svelte"
import Blink from "$lib/components/Blink.svelte"
import Text from "$lib/components/typography/Text.svelte"
import {getContributions} from "$lib/supabase"
let intervalId: NodeJS.Timeout | number
let contributions = $state()
async function loadContributions() {
contributions = await getContributions()
}
$effect(() => {
loadContributions()
intervalId = setInterval(loadContributions, 1000 * 5)
return () => {
if (intervalId) clearInterval(intervalId)
}
})
</script>
{#if contributions}

<div class="flex flex-col items-center h-svh overflow-y-auto pb-24 pt-36 w-full">
<div class="w-full h-48 bg-gradient-to-b via-black from-black to-transparent absolute top-0"></div>
<div class="flex flex-col items-center max-w-md">
<div class="rounded-full border-[2px] h-8 w-8"></div>
<div class="h-24 w-[2px] bg-white"></div>
{#each contributions as contribution, index }
<a href="/contributions?hash={contribution.public_key_hash}" class="flex items-center gap-4 w-full">

<Text>{(index + 1) * 10}M</Text>
<div class="text-white flex gap-1 items-center border-white border px-3 py-2 w-full">
<img class="size-7" src={contribution.avatar_url} alt="">
<Text class="uppercase max-w-48 truncate">{contribution.user_name}</Text>
</div>
</a>
{#if index !== contributions.length - 1}
<div class="h-12 w-[2px] bg-white"></div>
{/if}
{/each}
</div>
</div>
{:else}
<Spinner class="size-5 text-union-accent-500"/>
{/if}
4 changes: 4 additions & 0 deletions ceremony/src/lib/layout/Navbar/index.svelte
Original file line number Diff line number Diff line change
@@ -54,10 +54,12 @@ let loggedIn = $derived(!!user.session?.user.id)
<div class="hidden md:block">
{#if user.session}
<div class="flex items-center gap-4">
<NavLink class="p-2" href="/contributions">Contributions</NavLink>
<Button onclick={logout}>Log out</Button>
</div>
{:else}
<div class="flex items-center gap-4">
<NavLink class="p-2" href="/contributions">Contributions</NavLink>
<NavLink href="/auth/dive">Dive in</NavLink>
</div>
{/if}
@@ -74,8 +76,10 @@ let loggedIn = $derived(!!user.session?.user.id)
<div class="md:hidden mt-4 w-full bg-black">
<div class="flex flex-col divide-y divide-white/50">
{#if user.session}
<NavLink class="p-2" href="/contributions">Contributions</NavLink>
<Button class="py-2" onclick={logout}>Log out</Button>
{:else}
<NavLink class="p-2" href="/contributions">Contributions</NavLink>
<NavLink class="p-2" href="/auth/dive">Dive in</NavLink>
{/if}
</div>
1 change: 1 addition & 0 deletions ceremony/src/lib/supabase/queries.ts
Original file line number Diff line number Diff line change
@@ -71,6 +71,7 @@ export const queryContributions = async () => {
const { data, error } = await supabase
.from("users_contribution")
.select("public_key_hash, user_name, avatar_url")
.order('time_verified', { ascending: false })

return { data, error }
}
109 changes: 5 additions & 104 deletions ceremony/src/routes/contributions/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,112 +1,13 @@
<script lang="ts">
import { getContributions, getUserContribution } from "$lib/supabase"
import Spinner from "$lib/components/Spinner.svelte"
import Text from "$lib/components/typography/Text.svelte"
import Blink from "$lib/components/Blink.svelte"
import H4 from "$lib/components/typography/H4.svelte"
import { page } from "$app/stores"
import H2 from "$lib/components/typography/H2.svelte"
import Button from "$lib/components/Button.svelte"
import { toast } from "svelte-sonner"
import Contribution from "$lib/components/Contribution.svelte"
import Contributions from "$lib/components/Contributions.svelte"
let hash = $derived($page.url.searchParams.get("hash"))
function hexToUint8Array(hexString: string) {
return new Uint8Array(hexString.match(/.{1,2}/g)?.map(byte => Number.parseInt(byte, 16)) || [])
}
function uint8ArrayToUtf8(bytes: Uint8Array) {
return new TextDecoder().decode(bytes)
}
function decodeHexString(hexString: string) {
return uint8ArrayToUtf8(hexToUint8Array(hexString))
}
async function copyToClipboard(text: string, label: string) {
try {
await navigator.clipboard.writeText(text)
toast.success(`Copied ${label}!`)
} catch (err) {
console.error("Failed to copy text: ", err)
toast.error(`Failed to copy ${label} to clipboard.`)
}
}
let intervalId: NodeJS.Timeout | number
let contributions = $state()
async function loadContributions() {
contributions = await getContributions()
}
$effect(() => {
loadContributions()
intervalId = setInterval(loadContributions, 1000 * 5)
return () => {
if (intervalId) clearInterval(intervalId)
}
})
</script>

{#if hash}
{#await getUserContribution(hash)}
<Spinner class="size-5 text-union-accent-500"/>
{:then contribution}
{#if contribution}
<div class="flex flex-col items-start gap-1 px-3 py-2">
<div>
<H2>Contributor: <span class="!text-union-accent-500">{contribution.user_name}</span></H2>
</div>

<div class="flex flex-col gap-4">
<div>
<H2 class="mb-2">Public key</H2>
<pre class="text-white whitespace-pre-wrap bg-neutral-800 p-4 mb-4">{decodeHexString(contribution.public_key)}</pre>
<Button onclick={() => copyToClipboard(decodeHexString(contribution.public_key), "public key")}>Copy
Public
key
</Button>
</div>

<div>
<H2 class="mb-2">Signature</H2>
<pre class="text-white whitespace-pre-wrap bg-neutral-800 p-4 mb-4">{decodeHexString(contribution.signature)}</pre>
<Button onclick={() => copyToClipboard(decodeHexString(contribution.signature), "signature")}>Copy
Signature
</Button>
</div>
</div>
</div>
{/if}
{/await}
{:else}
{#if contributions}
<div class="flex flex-col-reverse items-center h-svh overflow-y-auto pb-24 pt-36 w-full">
<div class="w-full h-48 bg-gradient-to-b via-black from-black to-transparent absolute top-0"></div>
<div class="flex flex-col items-center max-w-sm">
{#each contributions as contribution, index }
{#if index !== 0}
<div class="h-4 w-[2px] bg-white"></div>
{/if}
<a href="/contributions?hash={contribution.public_key_hash}"
class="text-white flex gap-1 items-center border-white border px-3 py-2 w-full">
<img class="size-7" src={contribution.avatar_url} alt="">
<Text class="uppercase max-w-48 truncate">{contribution.user_name}</Text>
</a>
{/each}
<div class="h-4 w-[2px] bg-white"></div>
<div class="text-white flex gap-2 items-center border-white border px-3 py-2 mb-16">
<Spinner class="size-7 text-union-accent-500"/>
<span>Next contribution...</span>
</div>
<H4>
<Blink/>
</H4>
</div>
</div>
{:else}
<Spinner class="size-5 text-union-accent-500"/>
{/if}
<Contribution {hash}/>
{:else}
<Contributions/>
{/if}
3 changes: 3 additions & 0 deletions ceremony/static/images/ceremony.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 6417a61

Please sign in to comment.