@visualizevalue/mint-app-base 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +26 -0
- package/README.md +24 -0
- package/app/app.vue +7 -0
- package/app/assets/styles/animation.css +50 -0
- package/app/assets/styles/base.css +34 -0
- package/app/assets/styles/cards.css +20 -0
- package/app/assets/styles/custom-media.css +4 -0
- package/app/assets/styles/custom-selectors.css +1 -0
- package/app/assets/styles/forms.css +183 -0
- package/app/assets/styles/index.css +11 -0
- package/app/assets/styles/normalize.css +541 -0
- package/app/assets/styles/prose.css +166 -0
- package/app/assets/styles/scroll.css +13 -0
- package/app/assets/styles/text.css +14 -0
- package/app/assets/styles/utils.css +24 -0
- package/app/assets/styles/variables.css +195 -0
- package/app/assets/styles/web3-modals.css +26 -0
- package/app/components/Account.client.vue +20 -0
- package/app/components/Actions.vue +25 -0
- package/app/components/AppHeader.vue +99 -0
- package/app/components/Authenticated.client.vue +17 -0
- package/app/components/Avatar.vue +61 -0
- package/app/components/BlocksTimeAgo.client.vue +20 -0
- package/app/components/Breadcrumbs.vue +51 -0
- package/app/components/Button.vue +98 -0
- package/app/components/CardLink.vue +38 -0
- package/app/components/CheckSpinner.vue +39 -0
- package/app/components/Collection/Intro.vue +111 -0
- package/app/components/Collection/OverviewCard.vue +73 -0
- package/app/components/Collection/Withdraw.client.vue +61 -0
- package/app/components/CollectionsOverview.client.vue +58 -0
- package/app/components/Connect.client.vue +88 -0
- package/app/components/CountDown.vue +153 -0
- package/app/components/DialogFrame.vue +96 -0
- package/app/components/ExpandableText.vue +50 -0
- package/app/components/Form/Errors.vue +18 -0
- package/app/components/Form/Group.vue +57 -0
- package/app/components/Form/Input.vue +48 -0
- package/app/components/Form/SelectFile.vue +60 -0
- package/app/components/GasPrice.client.vue +9 -0
- package/app/components/HeaderSection.vue +18 -0
- package/app/components/Icon.vue +37 -0
- package/app/components/IconLink.vue +29 -0
- package/app/components/Image.client.vue +120 -0
- package/app/components/Loading.vue +79 -0
- package/app/components/MintGasPrice.client.vue +20 -0
- package/app/components/MintGasPricePopover.client.vue +69 -0
- package/app/components/MintToken.vue +89 -0
- package/app/components/MintTokenBar.vue +79 -0
- package/app/components/Modal.vue +36 -0
- package/app/components/Navbar.client.vue +86 -0
- package/app/components/Page/Frame.vue +77 -0
- package/app/components/Page/FrameSM.vue +33 -0
- package/app/components/Popover.client.vue +119 -0
- package/app/components/Profile/Header.client.vue +96 -0
- package/app/components/QueryDialog.vue +38 -0
- package/app/components/ToggleDarkMode.client.vue +58 -0
- package/app/components/Token/Detail.client.vue +194 -0
- package/app/components/Token/MintTimeline.client.vue +110 -0
- package/app/components/Token/MintTimelineItem.vue +33 -0
- package/app/components/Token/OverviewCard.vue +140 -0
- package/app/components/TransactionFlow.vue +225 -0
- package/app/components/Visual/ImagePreview.vue +8 -0
- package/app/composables/account.ts +21 -0
- package/app/composables/app.ts +15 -0
- package/app/composables/artistData.ts +22 -0
- package/app/composables/chainId.ts +25 -0
- package/app/composables/collections.ts +435 -0
- package/app/composables/darkMode.ts +1 -0
- package/app/composables/gasPrice.ts +46 -0
- package/app/composables/head.ts +29 -0
- package/app/composables/priceFeed.ts +80 -0
- package/app/composables/subdomain.ts +27 -0
- package/app/error.vue +31 -0
- package/app/layouts/default.vue +42 -0
- package/app/middleware/lowercaseId.ts +1 -0
- package/app/middleware/lowercaseProfileAddress.ts +1 -0
- package/app/middleware/redirectUserScope.ts +13 -0
- package/app/pages/[id]/[collection]/[tokenId]/index.vue +66 -0
- package/app/pages/[id]/[collection]/[tokenId].vue +25 -0
- package/app/pages/[id]/[collection]/index.vue +51 -0
- package/app/pages/[id]/[collection]/mint.vue +260 -0
- package/app/pages/[id]/[collection].vue +24 -0
- package/app/pages/[id]/add.vue +40 -0
- package/app/pages/[id]/create.vue +177 -0
- package/app/pages/[id]/index.vue +43 -0
- package/app/pages/[id].vue +9 -0
- package/app/pages/index.vue +47 -0
- package/app/pages/profile/[address]/index.vue +51 -0
- package/app/pages/profile/[address].vue +9 -0
- package/app/pages/profile/index.vue +28 -0
- package/app/plugins/1.polyfill.client.ts +12 -0
- package/app/plugins/2.wagmi.ts +57 -0
- package/app/router.options.ts +25 -0
- package/app/utils/abis.ts +77 -0
- package/app/utils/arrays.ts +1 -0
- package/app/utils/artifact.ts +21 -0
- package/app/utils/breakpoints.ts +11 -0
- package/app/utils/dates.ts +23 -0
- package/app/utils/format.ts +60 -0
- package/app/utils/images.ts +27 -0
- package/app/utils/ipfs.ts +13 -0
- package/app/utils/lowercaseRouteParam.ts +10 -0
- package/app/utils/serializer.ts +18 -0
- package/app/utils/strings.ts +30 -0
- package/app/utils/time.ts +23 -0
- package/app/utils/types.ts +62 -0
- package/app/utils/urls.ts +43 -0
- package/nuxt.config.ts +130 -0
- package/package.json +44 -0
- package/public/apple-touch-icon-512x512.png +0 -0
- package/public/example-contract-icon-original.svg +5 -0
- package/public/example-contract-icon.svg +5 -0
- package/public/favicon.ico +0 -0
- package/public/icon.svg +8 -0
- package/public/icons/check.svg +3 -0
- package/public/icons/opepen.svg +264 -0
- package/public/icons/wallets/coinbase.svg +4 -0
- package/public/icons/wallets/metamask.svg +1 -0
- package/public/icons/wallets/rainbow.svg +59 -0
- package/public/icons/wallets/walletconnect.svg +1 -0
- package/public/maskable-icon-512x512.png +0 -0
- package/public/pwa-192x192.png +0 -0
- package/public/pwa-512x512.png +0 -0
- package/public/pwa-64x64.png +0 -0
- package/server/middleware/log.ts +3 -0
- package/server/middleware/subdomain.ts +12 -0
- package/server/tsconfig.json +3 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M36 16C38.8457 16 41.3379 17.5098 42.7207 19.7715C45.2979 19.1484 48.1299 19.8457 50.1416 21.8574C52.1543 23.8691 52.8486 26.7012 52.2266 29.2793C54.4893 30.6621 56 33.1543 56 36C56 38.8457 54.4893 41.3379 52.2266 42.7207C52.8496 45.2988 52.1543 48.1289 50.1426 50.1426C48.1299 52.1543 45.2988 52.8516 42.7207 52.2285C41.8545 53.6445 40.5537 54.7656 39.0039 55.4062C38.0781 55.7891 37.0635 56 36 56C33.1543 56 30.6611 54.4902 29.2783 52.2266C26.7012 52.8477 23.8701 52.1543 21.8574 50.1426C19.8457 48.1309 19.1504 45.2988 19.7725 42.7207C17.5098 41.3379 16 38.8457 16 36C16 33.1543 17.5107 30.6621 19.7734 29.2793C19.1514 26.7012 19.8457 23.8711 21.8584 21.8594C23.8701 19.8477 26.7012 19.1523 29.2783 19.7734C30.6611 17.5098 33.1543 16 36 16Z" fill="currentColor"/>
|
|
4
|
+
<path d="M43.9359 29.5111L34.811 43.197C34.3261 43.9227 33.3467 44.1237 32.6193 43.6349L28.2769 39.3362C26.8372 37.8964 29.0747 35.6606 30.5087 37.1044L33.2509 39.8408L41.3041 27.7551C42.4333 26.0612 45.0673 27.8188 43.9359 29.5111Z" fill="var(--background)"/>
|
|
5
|
+
</svg>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup>
|
|
9
|
+
const { fill } = defineProps({
|
|
10
|
+
fill: {
|
|
11
|
+
type: String,
|
|
12
|
+
default: 'currentColor',
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<style scoped>
|
|
18
|
+
svg {
|
|
19
|
+
height: var(--size-5);
|
|
20
|
+
|
|
21
|
+
:not(.clear) {
|
|
22
|
+
background-color: var(--background);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
path:first-child {
|
|
26
|
+
animation: spin 9s infinite linear;
|
|
27
|
+
transform-origin: center;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@keyframes spin {
|
|
32
|
+
from {
|
|
33
|
+
transform: rotate(0deg);
|
|
34
|
+
}
|
|
35
|
+
to {
|
|
36
|
+
transform: rotate(360deg);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<header>
|
|
3
|
+
<figure v-if="collection.image">
|
|
4
|
+
<Image :src="collection.image" :alt="collection.name" />
|
|
5
|
+
</figure>
|
|
6
|
+
<div class="text">
|
|
7
|
+
<div>
|
|
8
|
+
<h1>{{ collection.name }} <small>({{ collection.symbol }})</small></h1>
|
|
9
|
+
<p v-if="collection.description" class="muted-light">
|
|
10
|
+
<ExpandableText :text="collection.description" />
|
|
11
|
+
</p>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div>
|
|
15
|
+
<p v-if="id" class="muted-light">
|
|
16
|
+
<span>
|
|
17
|
+
By <NuxtLink :to="{ name: 'id', params: { id } }">{{ store.displayName(id) }}</NuxtLink>
|
|
18
|
+
</span>
|
|
19
|
+
<span>
|
|
20
|
+
{{ collection.latestTokenId }} {{ pluralize('token', Number(collection.latestTokenId)) }}
|
|
21
|
+
</span>
|
|
22
|
+
<span>
|
|
23
|
+
Created at Block {{ collection.initBlock }}
|
|
24
|
+
</span>
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<menu v-if="ownedByMe">
|
|
29
|
+
<CollectionWithdraw :collection="collection" />
|
|
30
|
+
<Button
|
|
31
|
+
:to="{ name: 'id-collection-mint', params: { id, collection: collection.address } }"
|
|
32
|
+
id="mint-new"
|
|
33
|
+
class="small"
|
|
34
|
+
>Mint New</Button>
|
|
35
|
+
</menu>
|
|
36
|
+
</div>
|
|
37
|
+
</header>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
const { collection } = defineProps<{
|
|
42
|
+
collection: Collection
|
|
43
|
+
}>()
|
|
44
|
+
|
|
45
|
+
const id = useArtistId()
|
|
46
|
+
const store = useOnchainStore()
|
|
47
|
+
|
|
48
|
+
const ownedByMe = useIsMeCheck(collection.owner)
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<style scoped>
|
|
52
|
+
header {
|
|
53
|
+
display: grid;
|
|
54
|
+
gap: var(--spacer);
|
|
55
|
+
|
|
56
|
+
figure {
|
|
57
|
+
max-width: var(--size-10);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@media (--sm) {
|
|
61
|
+
max-width: 100%;
|
|
62
|
+
|
|
63
|
+
&:has(> figure) {
|
|
64
|
+
grid-template-columns: 20% 1fr;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.text {
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-direction: column;
|
|
71
|
+
justify-content: center;
|
|
72
|
+
gap: var(--spacer);
|
|
73
|
+
position: relative;
|
|
74
|
+
|
|
75
|
+
> div {
|
|
76
|
+
display: grid;
|
|
77
|
+
gap: var(--spacer-sm);
|
|
78
|
+
|
|
79
|
+
&:nth-child(2) {
|
|
80
|
+
span {
|
|
81
|
+
&:not(:last-child):after {
|
|
82
|
+
content: ' · ';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
h1 {
|
|
89
|
+
small {
|
|
90
|
+
color: var(--muted-light);
|
|
91
|
+
font-size: var(--font-base);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
menu {
|
|
96
|
+
margin: 0;
|
|
97
|
+
display: flex;
|
|
98
|
+
padding: 0;
|
|
99
|
+
gap: var(--spacer-sm);
|
|
100
|
+
|
|
101
|
+
button {
|
|
102
|
+
width: auto;
|
|
103
|
+
|
|
104
|
+
@media (--md) {
|
|
105
|
+
width: fit-content;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
</style>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<article>
|
|
3
|
+
<Image v-if="collection.image" :src="collection.image" :alt="collection.name" />
|
|
4
|
+
<div class="text">
|
|
5
|
+
<div>
|
|
6
|
+
<h1>{{ collection.name }} <small>({{ collection.symbol }})</small></h1>
|
|
7
|
+
<p v-if="collection.description" class="muted-light">{{ shortDescription }}</p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div>
|
|
11
|
+
<p>{{ collection.latestTokenId }} {{ pluralize('token', Number(collection.latestTokenId)) }} · Created at Block {{ collection.initBlock }}</p>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
<CardLink
|
|
15
|
+
:to="{ name: 'id-collection', params: { id: collection.owner, collection: collection.address } }"
|
|
16
|
+
:title="`View ${collection.name}`"
|
|
17
|
+
/>
|
|
18
|
+
</article>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
const { collection } = defineProps<{
|
|
23
|
+
collection: Collection
|
|
24
|
+
}>()
|
|
25
|
+
|
|
26
|
+
const shortDescription = computed(() => shortString(collection.description, 40, 30))
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<style scoped>
|
|
30
|
+
article {
|
|
31
|
+
display: grid;
|
|
32
|
+
padding: var(--spacer);
|
|
33
|
+
gap: var(--spacer);
|
|
34
|
+
transition: all var(--speed);
|
|
35
|
+
|
|
36
|
+
&:has(> a:--highlight) {
|
|
37
|
+
background: var(--gray-z-0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@media (--sm) {
|
|
41
|
+
&:has(> .image) {
|
|
42
|
+
grid-template-columns: 20% 1fr;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
img {
|
|
47
|
+
width: 100%;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.text {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
justify-content: center;
|
|
54
|
+
gap: var(--spacer);
|
|
55
|
+
|
|
56
|
+
> div {
|
|
57
|
+
display: grid;
|
|
58
|
+
gap: var(--spacer-sm);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
> div:last-child {
|
|
62
|
+
color: var(--muted-light);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
h1 {
|
|
66
|
+
small {
|
|
67
|
+
color: var(--muted-light);
|
|
68
|
+
font-size: var(--font-base);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<TransactionFlow v-if="showWithdrawButton" :request="withdraw" :text="{
|
|
3
|
+
title: {
|
|
4
|
+
chain: 'Switch Chain',
|
|
5
|
+
requesting: 'Confirm In Wallet',
|
|
6
|
+
waiting: 'Transaction Submitted',
|
|
7
|
+
complete: 'Success!'
|
|
8
|
+
},
|
|
9
|
+
lead: {
|
|
10
|
+
chain: 'Requesting to switch chain...',
|
|
11
|
+
requesting: 'Requesting Signature...',
|
|
12
|
+
waiting: 'Checking withdraw transaction...',
|
|
13
|
+
complete: `Contract balance withdrawn...`,
|
|
14
|
+
},
|
|
15
|
+
action: {
|
|
16
|
+
confirm: 'Withdraw',
|
|
17
|
+
error: 'Retry',
|
|
18
|
+
complete: 'OK',
|
|
19
|
+
},
|
|
20
|
+
}" skip-confirmation auto-close-success @complete="onComplete">
|
|
21
|
+
<template #start="{ start }">
|
|
22
|
+
<Button @click="start" class="small">
|
|
23
|
+
Withdraw ({{ balance.value }} {{ balance.format }})
|
|
24
|
+
</Button>
|
|
25
|
+
</template>
|
|
26
|
+
</TransactionFlow>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup>
|
|
30
|
+
const { $wagmi } = useNuxtApp()
|
|
31
|
+
const chainId = useMainChainId()
|
|
32
|
+
|
|
33
|
+
const { collection } = defineProps({
|
|
34
|
+
collection: Object,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const forceHide = ref(false)
|
|
38
|
+
const showWithdrawButton = computed(() => collection.balance && ! forceHide.value)
|
|
39
|
+
const balance = computed(() => customFormatEther(collection.balance))
|
|
40
|
+
|
|
41
|
+
const withdraw = computed(() => async () => {
|
|
42
|
+
return await writeContract($wagmi, {
|
|
43
|
+
abi: MINT_ABI,
|
|
44
|
+
chainId,
|
|
45
|
+
address: collection.address,
|
|
46
|
+
functionName: 'withdraw',
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const store = useOnchainStore()
|
|
51
|
+
const onComplete = async () => {
|
|
52
|
+
forceHide.value = true
|
|
53
|
+
await store.fetchCollectionBalance(collection.address)
|
|
54
|
+
await delay(1000)
|
|
55
|
+
forceHide.value = false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onMounted(async () => {
|
|
59
|
+
await store.fetchCollectionBalance(collection.address)
|
|
60
|
+
})
|
|
61
|
+
</script>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section v-if="collections.length" class="collections borderless">
|
|
3
|
+
<slot name="before" :collections="collections" />
|
|
4
|
+
|
|
5
|
+
<CollectionOverviewCard
|
|
6
|
+
v-for="collection in collections"
|
|
7
|
+
:key="collection.address"
|
|
8
|
+
:collection="collection"
|
|
9
|
+
/>
|
|
10
|
+
</section>
|
|
11
|
+
<section v-else-if="! loading" class="centered borderless">
|
|
12
|
+
<template v-if="isMe">
|
|
13
|
+
<p>It looks like you haven't deployed any collections.</p>
|
|
14
|
+
<div>
|
|
15
|
+
<Button :to="{ name: `id-create`, params: { id } }">
|
|
16
|
+
<Icon type="plus" />
|
|
17
|
+
<span>Create Your First</span>
|
|
18
|
+
</Button>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
<template v-else>
|
|
22
|
+
<p>It looks like this account hasn't deployed any collections.</p>
|
|
23
|
+
</template>
|
|
24
|
+
</section>
|
|
25
|
+
<Loading v-if="loading" txt="Querying the blockchain..." />
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup>
|
|
29
|
+
const store = useOnchainStore()
|
|
30
|
+
|
|
31
|
+
const props = defineProps({
|
|
32
|
+
id: String,
|
|
33
|
+
})
|
|
34
|
+
const id = computed(() => props.id)
|
|
35
|
+
|
|
36
|
+
const isMe = useIsMeCheck(id)
|
|
37
|
+
|
|
38
|
+
const { loading } = useLoadArtistData(id)
|
|
39
|
+
const collections = computed(() => isMe.value
|
|
40
|
+
? store.forArtist(id.value)
|
|
41
|
+
: store.forArtistOnlyMinted(id.value)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
// Force update collections with no mints
|
|
45
|
+
if (store.forArtist(id.value).length !== collections.length) {
|
|
46
|
+
store.forArtist(id.value)
|
|
47
|
+
.filter(c => c.latestTokenId === 0n)
|
|
48
|
+
.forEach(c => store.fetchCollection(c.address))
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<style scoped>
|
|
53
|
+
.collections {
|
|
54
|
+
display: grid;
|
|
55
|
+
gap: var(--spacer-lg);
|
|
56
|
+
padding: var(--spacer-lg) var(--spacer) !important;
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Button v-if="showConnect" @click="chooseModalOpen = true" :class="class">
|
|
3
|
+
<slot>Connect</slot>
|
|
4
|
+
</Button>
|
|
5
|
+
<slot v-else name="connected">
|
|
6
|
+
<Account :address="address" />
|
|
7
|
+
</slot>
|
|
8
|
+
|
|
9
|
+
<Teleport to="body">
|
|
10
|
+
<Modal
|
|
11
|
+
v-if="showConnect"
|
|
12
|
+
:open="chooseModalOpen"
|
|
13
|
+
@close="closeModal"
|
|
14
|
+
>
|
|
15
|
+
<div class="wallet-options">
|
|
16
|
+
<Button
|
|
17
|
+
v-for="connector in shownConnectors"
|
|
18
|
+
@click="() => login(connector)"
|
|
19
|
+
class="choose-connector-button"
|
|
20
|
+
>
|
|
21
|
+
<img v-if="ICONS[connector.name]" :src="connector.icon || `/icons/wallets/${ICONS[connector.name]}`" alt="">
|
|
22
|
+
{{ connector.name }}
|
|
23
|
+
</Button>
|
|
24
|
+
</div>
|
|
25
|
+
</Modal>
|
|
26
|
+
</Teleport>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup>
|
|
30
|
+
import { useAccount, useConnect, useChainId } from '@wagmi/vue'
|
|
31
|
+
|
|
32
|
+
const ICONS = {
|
|
33
|
+
'Coinbase Wallet': 'coinbase.svg',
|
|
34
|
+
'MetaMask': 'metamask.svg',
|
|
35
|
+
'WalletConnect': 'walletconnect.svg',
|
|
36
|
+
'Rainbow': 'rainbow.svg',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const props = defineProps(['class'])
|
|
40
|
+
const emit = defineEmits(['connected', 'disconnected'])
|
|
41
|
+
|
|
42
|
+
const chainId = useChainId()
|
|
43
|
+
const { connectors, connect } = useConnect()
|
|
44
|
+
const { address, isConnected } = useAccount()
|
|
45
|
+
|
|
46
|
+
const showConnect = computed(() => !isConnected.value)
|
|
47
|
+
const shownConnectors = computed(() => {
|
|
48
|
+
const unique = Array.from(
|
|
49
|
+
new Map(
|
|
50
|
+
connectors.map(connector => [connector.name, connector])
|
|
51
|
+
).values()
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return unique.length > 1
|
|
55
|
+
? unique.filter(c => c.id !== 'injected')
|
|
56
|
+
: unique
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const chooseModalOpen = ref(false)
|
|
60
|
+
const closeModal = () => {
|
|
61
|
+
chooseModalOpen.value = false
|
|
62
|
+
}
|
|
63
|
+
const login = async (connector) => {
|
|
64
|
+
connect({ connector, chainId })
|
|
65
|
+
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
chooseModalOpen.value = false
|
|
68
|
+
}, 100)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const check = () => isConnected.value ? emit('connected', { address: address.value }) : emit('disconnected')
|
|
72
|
+
watch(isConnected, () => check())
|
|
73
|
+
onMounted(() => check())
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<style scoped>
|
|
77
|
+
.wallet-options {
|
|
78
|
+
display: flex;
|
|
79
|
+
gap: var(--spacer);
|
|
80
|
+
flex-wrap: wrap;
|
|
81
|
+
justify-content: center;
|
|
82
|
+
}
|
|
83
|
+
.choose-connector-button {
|
|
84
|
+
img {
|
|
85
|
+
width: var(--size-5);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
</style>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="countDownComplete" class="complete countdown">
|
|
3
|
+
<slot name="complete"></slot>
|
|
4
|
+
</div>
|
|
5
|
+
<div v-else class="countdown">
|
|
6
|
+
<slot name="header"></slot>
|
|
7
|
+
<span v-if="timeUntil">{{ countdown }}</span>
|
|
8
|
+
<span v-else>_d _h _m _s</span>
|
|
9
|
+
<slot />
|
|
10
|
+
<slot name="footer" />
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
export default {
|
|
16
|
+
emits: ['start', 'complete'],
|
|
17
|
+
|
|
18
|
+
props: {
|
|
19
|
+
adjustSeconds: {
|
|
20
|
+
type: Number,
|
|
21
|
+
default: 0,
|
|
22
|
+
},
|
|
23
|
+
until: {
|
|
24
|
+
type: Number,
|
|
25
|
+
default: 1632552931,
|
|
26
|
+
},
|
|
27
|
+
minimal: {
|
|
28
|
+
type: Boolean,
|
|
29
|
+
default: false,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
data () {
|
|
34
|
+
return {
|
|
35
|
+
timeUntil: null,
|
|
36
|
+
interval: null,
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
computed: {
|
|
41
|
+
countDownComplete () {
|
|
42
|
+
return this.timeUntil <= 0
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
years () {
|
|
46
|
+
return Math.floor(this.timeUntil / 60 / 60 / 24 / 365)
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
accountedForYears () {
|
|
50
|
+
return this.years * 60 * 60 * 24 * 365
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
days () {
|
|
54
|
+
return Math.floor((this.timeUntil - this.accountedForYears) / 60 / 60 / 24)
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
accountedForDays () {
|
|
58
|
+
return this.days * 60 * 60 * 24
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
hours () {
|
|
62
|
+
return Math.floor((this.timeUntil - this.accountedForYears - this.accountedForDays) / 60 / 60)
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
accountedForHours () {
|
|
66
|
+
return this.hours * 60 * 60
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
minutes () {
|
|
70
|
+
return Math.floor((this.timeUntil - this.accountedForYears - this.accountedForDays - this.accountedForHours) / 60)
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
totalSeconds () {
|
|
74
|
+
return this.timeUntil / 60
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
seconds () {
|
|
78
|
+
return this.timeUntil % 60
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
countdown () {
|
|
82
|
+
const yearsString = this.years + 'y'
|
|
83
|
+
const daysString = this.days + 'd'
|
|
84
|
+
const hoursString = this.hours + 'h'
|
|
85
|
+
const minutesString = this.minutes + 'm'
|
|
86
|
+
const secondsString = this.seconds + 's'
|
|
87
|
+
|
|
88
|
+
return [
|
|
89
|
+
this.years && yearsString,
|
|
90
|
+
this.days && daysString,
|
|
91
|
+
this.hours && hoursString,
|
|
92
|
+
minutesString,
|
|
93
|
+
(!this.minimal || this.totalSeconds < 600) && secondsString,
|
|
94
|
+
].filter(i => !!i).join(' ')
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
mounted () {
|
|
99
|
+
this.update()
|
|
100
|
+
this.interval = setInterval(() => this.update(), 1000)
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
beforeUnmount () {
|
|
104
|
+
clearInterval(this.interval)
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
methods: {
|
|
108
|
+
update () {
|
|
109
|
+
let start = this.until
|
|
110
|
+
|
|
111
|
+
start += this.adjustSeconds
|
|
112
|
+
|
|
113
|
+
this.timeUntil = start - nowInSeconds()
|
|
114
|
+
|
|
115
|
+
if (this.countDownComplete) {
|
|
116
|
+
this.$emit('complete')
|
|
117
|
+
clearInterval(this.interval)
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
}
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<style scoped>
|
|
126
|
+
.countdown {
|
|
127
|
+
text-align: center;
|
|
128
|
+
|
|
129
|
+
small,
|
|
130
|
+
span {
|
|
131
|
+
display: block;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
> *:first-child {
|
|
135
|
+
margin-top: 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
> *:last-child {
|
|
139
|
+
margin-bottom: 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
&.inline,
|
|
143
|
+
&.inline * {
|
|
144
|
+
display: inline !important;
|
|
145
|
+
text-align: left !important;
|
|
146
|
+
font-size: 1em !important;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.complete {
|
|
151
|
+
padding: 0;
|
|
152
|
+
}
|
|
153
|
+
</style>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<dialog ref="dialog">
|
|
3
|
+
<slot />
|
|
4
|
+
</dialog>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
const dialog = ref()
|
|
9
|
+
|
|
10
|
+
const close = () => {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
const keyFrame = new KeyframeEffect(
|
|
13
|
+
dialog.value,
|
|
14
|
+
[{ translate: '0 var(--spacer)', opacity: '0' }],
|
|
15
|
+
{ duration: 300, easing: 'ease', direction: 'normal' }
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const animation = new Animation(keyFrame, document.timeline)
|
|
19
|
+
animation.play()
|
|
20
|
+
animation.onfinish = () => {
|
|
21
|
+
dialog.value.close()
|
|
22
|
+
resolve()
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const open = () => {
|
|
28
|
+
dialog.value.showModal()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
defineExpose({
|
|
32
|
+
close,
|
|
33
|
+
open,
|
|
34
|
+
})
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<style>
|
|
38
|
+
dialog {
|
|
39
|
+
position: fixed;
|
|
40
|
+
padding: calc(var(--spacer)*2);
|
|
41
|
+
max-width: var(--dialog-width);
|
|
42
|
+
width: 100%;
|
|
43
|
+
background: var(--background);
|
|
44
|
+
color: var(--color);
|
|
45
|
+
border: var(--border);
|
|
46
|
+
overscroll-behavior: contain;
|
|
47
|
+
height: min-content;
|
|
48
|
+
opacity: 0;
|
|
49
|
+
pointer-events: none;
|
|
50
|
+
align-content: center;
|
|
51
|
+
|
|
52
|
+
&[open] {
|
|
53
|
+
animation: fade-in var(--speed);
|
|
54
|
+
opacity: 1;
|
|
55
|
+
pointer-events: all;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&::backdrop {
|
|
59
|
+
background-image: linear-gradient(
|
|
60
|
+
45deg,
|
|
61
|
+
var(--gray-z-0-semi),
|
|
62
|
+
var(--gray-z-1-semi),
|
|
63
|
+
var(--gray-z-2-semi)
|
|
64
|
+
);
|
|
65
|
+
backdrop-filter: var(--blur);
|
|
66
|
+
pointer-events: none;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
> .close {
|
|
70
|
+
position: absolute;
|
|
71
|
+
top: var(--spacer);
|
|
72
|
+
right: var(--spacer);
|
|
73
|
+
width: min-content;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
> h1 {
|
|
77
|
+
padding-right: var(--size-6);
|
|
78
|
+
font-family: var(--font-family-ui);
|
|
79
|
+
font-size: var(--font-lg);
|
|
80
|
+
text-transform: var(--text-transform);
|
|
81
|
+
margin-bottom: var(--size-3);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
> .actions {
|
|
85
|
+
margin-top: var(--spacer);
|
|
86
|
+
display: flex;
|
|
87
|
+
gap: var(--spacer);
|
|
88
|
+
justify-content: flex-end;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
html:has(dialog[open]),
|
|
93
|
+
body:has(dialog[open]) {
|
|
94
|
+
overflow: hidden;
|
|
95
|
+
}
|
|
96
|
+
</style>
|