Add badges discount

This commit is contained in:
tree🌴 2023-02-27 07:02:38 +01:00
parent 90b158861f
commit bb3194dce7
8 changed files with 7954 additions and 94 deletions

7817
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -27,8 +27,16 @@
"remove-markdown": "^0.5.0", "remove-markdown": "^0.5.0",
"svelte": "^3.54.0", "svelte": "^3.54.0",
"svelte-markdown": "^0.2.3", "svelte-markdown": "^0.2.3",
"svelte-web3": "^4.0.1",
"tailwindcss": "^3.2.6", "tailwindcss": "^3.2.6",
"vite": "^4.0.0" "vite": "^4.0.0"
}, },
"type": "module" "type": "module",
"dependencies": {
"@wagmi/core": "^0.9.7",
"@web3modal/ethereum": "^2.1.1",
"@web3modal/html": "^2.1.1",
"ethers": "^5.7.2",
"web3": "^1.8.2"
}
} }

View file

@ -33,14 +33,26 @@
} }
.button { .button {
@apply px-3 py-1.5 text-black bg-white border border-black; @apply px-3 py-1.5 text-black bg-white border border-black cursor-pointer;
} }
.button:hover { .button:hover, .button-inactive {
@apply text-white bg-black border border-white; @apply text-white bg-black border border-white;
} }
.button:hover.button-inactive {
@apply cursor-default text-gray-600 border-gray-600;
}
.button-inverse {
@apply px-3 py-1.5 text-white bg-black border border-black cursor-pointer;
}
.button-inverse:hover {
@apply text-black bg-white border border-black;
}
.section-header { .section-header {
@apply text-3xl md:text-5xl mb-8 md:mb-16 font-bold pt-10; @apply text-3xl md:text-5xl mb-8 md:mb-16 font-bold pt-10;
} }
.section-subheader {
@apply text-3xl font-bold mb-4 md:mb-8;
}
.text-mild { .text-mild {
@apply text-white/70; @apply text-white/70;
@ -89,4 +101,8 @@
.topic-item:hover .text-supermild { .topic-item:hover .text-supermild {
@apply text-black; @apply text-black;
} }
.eligible .text-mild {
@apply text-black;
}
} }

View file

@ -9,6 +9,7 @@
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
<script src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
<script <script
defer defer
data-domain="prague.web3privacy.info" data-domain="prague.web3privacy.info"

View file

@ -0,0 +1,176 @@
<script>
import { browser } from '$app/environment';
import { configureChains, createClient, disconnect, signMessage, getAccount } from '@wagmi/core'
import { arbitrum, mainnet, polygon, optimism, gnosis } from '@wagmi/core/chains'
import { EthereumClient, modalConnectors, walletConnectProvider } from '@web3modal/ethereum'
import { Web3Modal } from '@web3modal/html'
import { writable } from 'svelte/store';
import SvelteMarkdown from 'svelte-markdown';
import { animateText, animateSection } from '$lib/helpers';
export let data;
const projectId = '43a2f1e1b1753e7d4e628b5a1827d319'
const chains = [mainnet, arbitrum, optimism, polygon, gnosis]
const redeemUrl = 'https://tickets.web3privacy.info/w3ps1/redeem?voucher='
const badges = data.badges
let web3Modal
let enable = () => init()
let disable
let userBadges = null
let claimed = {}
const web3store = writable({})
$: connected = $web3store.isConnected
$: selectedAccount = $web3store.address
async function checkBadges (addr) {
const resp = await fetch(`${data.config.badgesApiUrl}/account/${addr}`)
return resp.json()
}
async function init () {
const { provider } = configureChains(chains, [walletConnectProvider({ projectId })])
const wagmiClient = createClient({
autoConnect: false,
connectors: [...modalConnectors({ appName: 'web3Modal', chains })],
provider
})
const ethereumClient = new EthereumClient(wagmiClient, chains)
disable = async () => {
await disconnect()
userBadges = {}
web3store.set({})
}
enable = async () => {
await web3Modal.openModal()
}
web3Modal = new Web3Modal({ projectId }, ethereumClient)
await web3Modal.openModal()
const unsubscribe = web3Modal.subscribeModal(async (newState) => {
const account = getAccount()
web3store.set(account)
claimed = {}
userBadges = await checkBadges(account.address)
});
}
function claimHandler (badgeId) {
return async () => {
const badge = badges.find(b => b.id === badgeId)
const addr = selectedAccount
claimed[badgeId] = {}
const msg = `I want to get a discount on a ticket to Web3Privacy Prague 2023. My address is "${addr}" and I own "${badge.name}". ${new Date().toISOString()}`
let signature;
try {
signature = await signMessage({ message: msg })
} catch (e) {
claimed[badgeId] = null;
return;
}
if (!signature) {
return null
}
claimed[badgeId] = { text: "Wait for the voucher to be generated from our ticketing system .." }
const query = {
addr,
badgeId,
msg,
signature
}
console.log(query)
const resp = await fetch(`${data.config.badgesApiUrl}/claim`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(query)
})
const res = await resp.json()
claimed[badgeId] = {
claimed: true,
voucher: res.voucher,
res
}
}
}
</script>
{#if !connected}
<div class="buttons">
<button class="button is-link is-light" on:click={enable} on:mouseenter={(ev) => animateText(ev, 15)}>Connect using Web3 wallet</button>
</div>
<div class="text-supermild mt-4 text-lg md:w-4/5 mx-auto">Once connected, you will see what discount you are entitled to - based on ownership of the specific ZK Badges or SBT tokens.</div>
{/if}
{#if connected}
<div>
Connected as: <span class="font-bold">{selectedAccount}</span>
<button class="button is-link is-light inline-block cursor-pointer" on:click={disable} on:mouseenter={animateText}>disconnect</button>
</div>
<!--<p>Connected chain: chainId = {$chainId}</p>
<p>Selected account: {$selectedAccount || 'not defined'</p>
<p>Wallet type: {$walletType || 'not defined'}</p>
<p>chainData = {JSON.stringify($chainData)}</p>
<p>Selected account balance = <Balance address={checkAccount} /> {$chainData.nativeCurrency?.symbol}</p>
-->
{/if}
<div class="w-full mt-16">
<div class="m-auto">
<div class="text-2xl font-bold mb-10" on:mouseenter={animateText}>Eligible ZK Badges / SBTs</div>
<div class="pb-8 lg:w-2/3 xl:w-2/3 mx-auto">
{#each badges as badge}
<div class="lg:flex justify-center mb-10 badge {userBadges?.badges && userBadges?.badges[badge?.id]?.eligible ? 'bg-white text-black eligible' : 'bg-[#0d1117]'} p-4" on:mouseenter={animateSection(30)}>
<div class="w-28 lg:w-40 mr-6 inline-block xl:block"><img src={badge.img} class="w-full {connected && userBadges && userBadges.badges[badge.id]?.eligible ? '' : 'grayscale'} badge-image" /></div>
<div class="lg:text-left w-full">
{#if badge.shortname}<div class="uppercase font-bold">{badge.shortname}</div>{/if}
<div class="{badge.shortname ? '' : 'font-bold'} animate-section">
<a href={badge.url} target="_blank" class="external">{badge.name}</a>
</div>
<div class="text-xl">Price: <span class="font-bold">{data.config.ticketBasePrice - data.config.ticketBasePrice*(badge.discount/100)}</span> ({badge.discount}% discount)</div>
{#if connected && userBadges}
<div class="pt-2">
{#if claimed[badge.id]}
{#if claimed[badge.id].voucher}
Your personal voucher: <a href="{redeemUrl+claimed[badge.id].voucher}" target="_blank" class="underline hover:no-underline external">{claimed[badge.id].voucher}</a>
<div class="pt-2">
<a href="{redeemUrl+claimed[badge.id].voucher}" target="_blank"><button class="button-inverse" on:mouseenter={(ev) => animateText(ev, 15)}>Buy ticket using voucher</button></a>
</div>
{:else}
{claimed[badge.id].text || 'Please sign message in your wallet ..'}
{/if}
{:else}
{#if userBadges.badges[badge.id]?.eligible}
<button class="button-inverse" on:click={claimHandler(badge.id)} on:mouseenter={(ev) => animateText(ev, 15)}>Eligible! Get discount!</button>
{:else}
<button class="button button-inactive text-gray-600 border-gray-600">Not eligible</button>
{/if}
{/if}
</div>
{/if}
{#if badge.text}
<div class="text-base pt-2 text-mild">{badge.text}</div>
{/if}
</div>
</div>
{/each}
</div>
<div class="text-2xl font-bold mb-8" on:mouseenter={animateText}>Other discounts (via form)</div>
<div class="text-lg md:w-4/5 mx-auto">
<SvelteMarkdown source={data.config.ticketsDiscounts} />
</div>
<div class="mt-8 mb-8">
<a href={data.config.ticketsDiscountForm}><button class="button" on:mouseenter={animateText}>Apply for a discount</button></a>
</div>
</div>
</div>

View file

@ -54,6 +54,10 @@ ticketsIntro: Be a part of the first Web3Privacy Summit experience…
ticketsNote: Tickets will go on sale in late February 2023. ticketsNote: Tickets will go on sale in late February 2023.
ticketing: false ticketing: false
ticketingUrl: https://tickets.web3privacy.info/w3ps1/ ticketingUrl: https://tickets.web3privacy.info/w3ps1/
ticketBasePrice: 99
ticketsDiscountForm: https://attend.web3privacy.info
ticketsDiscounts: |
We also offer discounts for other groups such as active open-source contributors to privacy protocols, privacy (lunarpunk) advocates, full-time students, attendees from OECD low income countries, independent developers, etc. Feel free to request your special discount using the form:
tickets: tickets:
- title: All-day Access - title: All-day Access
price: €99 price: €99
@ -64,7 +68,7 @@ tickets:
- Networking drinks with speakers & attendees - Networking drinks with speakers & attendees
- '#Lunarpunk party' - '#Lunarpunk party'
hint: | hint: |
[Apply for a discount →](https://attend.web3privacy.info)<br />(as independent developer, student, privacy advocate, open-source contributor..) Discounts: We offer various discounts up to 100%, see below
- title: '#Lunarpunk Party' - title: '#Lunarpunk Party'
price: €15 price: €15
includes: includes:
@ -201,3 +205,4 @@ program:
type: other type: other
- time: 16:10 - 18:30 - time: 16:10 - 18:30
title: Workshops IV. title: Workshops IV.
badgesApiUrl: https://badges-pretix-voucher-api.web3privacy.info

View file

@ -1,7 +1,12 @@
import config from '$lib/config.yaml'; import config from '$lib/config.yaml';
export async function load({ params, url, fetch }) { export async function load({ params, url, fetch }) {
const resp = await fetch(config.badgesApiUrl + "/badges")
const badges = await resp.json()
return { return {
config config,
badges
}; };
} }

View file

@ -3,6 +3,7 @@
import PeopleList from '$lib/components/PeopleList.svelte'; import PeopleList from '$lib/components/PeopleList.svelte';
import { animateText, animateSection } from '$lib/helpers'; import { animateText, animateSection } from '$lib/helpers';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import Web3Dialog from '$lib/components/Web3Dialog.svelte';
export let data; export let data;
</script> </script>
@ -134,7 +135,7 @@
</div> </div>
<div class="bg-black" id="ticket"> <div class="bg-black" id="ticket">
<div class="middle-pane-medium pt-16 text-xl text-center mx-auto pb-32"> <div class="middle-pane-medium pt-16 text-xl text-center mx-auto">
<div class="section-header" on:mouseenter={animateText}>Ticket</div> <div class="section-header" on:mouseenter={animateText}>Ticket</div>
<div class="mb-8 text-lg text-mild">{data.config.ticketsIntro}</div> <div class="mb-8 text-lg text-mild">{data.config.ticketsIntro}</div>
<div class="grid lg:grid-cols-2 gap-10 md:w-2/3 mx-auto"> <div class="grid lg:grid-cols-2 gap-10 md:w-2/3 mx-auto">
@ -181,7 +182,14 @@
<div class="mt-8 text-xl">{data.config.ticketsNote}</div> <div class="mt-8 text-xl">{data.config.ticketsNote}</div>
{/if} {/if}
</div> </div>
<div class="middle-pane-medium text-xl text-center mx-auto pt-10 pb-32">
<div class="section-subheader" on:mouseenter={animateText}>Get your discount</div>
<div>
<Web3Dialog {data} />
</div> </div>
</div>
</div>
<div class="mb-36" id="faq"> <div class="mb-36" id="faq">
<div class="middle-pane-medium pt-20 text-xl text-center mx-auto"> <div class="middle-pane-medium pt-20 text-xl text-center mx-auto">