@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.
Files changed (129) hide show
  1. package/.env.example +26 -0
  2. package/README.md +24 -0
  3. package/app/app.vue +7 -0
  4. package/app/assets/styles/animation.css +50 -0
  5. package/app/assets/styles/base.css +34 -0
  6. package/app/assets/styles/cards.css +20 -0
  7. package/app/assets/styles/custom-media.css +4 -0
  8. package/app/assets/styles/custom-selectors.css +1 -0
  9. package/app/assets/styles/forms.css +183 -0
  10. package/app/assets/styles/index.css +11 -0
  11. package/app/assets/styles/normalize.css +541 -0
  12. package/app/assets/styles/prose.css +166 -0
  13. package/app/assets/styles/scroll.css +13 -0
  14. package/app/assets/styles/text.css +14 -0
  15. package/app/assets/styles/utils.css +24 -0
  16. package/app/assets/styles/variables.css +195 -0
  17. package/app/assets/styles/web3-modals.css +26 -0
  18. package/app/components/Account.client.vue +20 -0
  19. package/app/components/Actions.vue +25 -0
  20. package/app/components/AppHeader.vue +99 -0
  21. package/app/components/Authenticated.client.vue +17 -0
  22. package/app/components/Avatar.vue +61 -0
  23. package/app/components/BlocksTimeAgo.client.vue +20 -0
  24. package/app/components/Breadcrumbs.vue +51 -0
  25. package/app/components/Button.vue +98 -0
  26. package/app/components/CardLink.vue +38 -0
  27. package/app/components/CheckSpinner.vue +39 -0
  28. package/app/components/Collection/Intro.vue +111 -0
  29. package/app/components/Collection/OverviewCard.vue +73 -0
  30. package/app/components/Collection/Withdraw.client.vue +61 -0
  31. package/app/components/CollectionsOverview.client.vue +58 -0
  32. package/app/components/Connect.client.vue +88 -0
  33. package/app/components/CountDown.vue +153 -0
  34. package/app/components/DialogFrame.vue +96 -0
  35. package/app/components/ExpandableText.vue +50 -0
  36. package/app/components/Form/Errors.vue +18 -0
  37. package/app/components/Form/Group.vue +57 -0
  38. package/app/components/Form/Input.vue +48 -0
  39. package/app/components/Form/SelectFile.vue +60 -0
  40. package/app/components/GasPrice.client.vue +9 -0
  41. package/app/components/HeaderSection.vue +18 -0
  42. package/app/components/Icon.vue +37 -0
  43. package/app/components/IconLink.vue +29 -0
  44. package/app/components/Image.client.vue +120 -0
  45. package/app/components/Loading.vue +79 -0
  46. package/app/components/MintGasPrice.client.vue +20 -0
  47. package/app/components/MintGasPricePopover.client.vue +69 -0
  48. package/app/components/MintToken.vue +89 -0
  49. package/app/components/MintTokenBar.vue +79 -0
  50. package/app/components/Modal.vue +36 -0
  51. package/app/components/Navbar.client.vue +86 -0
  52. package/app/components/Page/Frame.vue +77 -0
  53. package/app/components/Page/FrameSM.vue +33 -0
  54. package/app/components/Popover.client.vue +119 -0
  55. package/app/components/Profile/Header.client.vue +96 -0
  56. package/app/components/QueryDialog.vue +38 -0
  57. package/app/components/ToggleDarkMode.client.vue +58 -0
  58. package/app/components/Token/Detail.client.vue +194 -0
  59. package/app/components/Token/MintTimeline.client.vue +110 -0
  60. package/app/components/Token/MintTimelineItem.vue +33 -0
  61. package/app/components/Token/OverviewCard.vue +140 -0
  62. package/app/components/TransactionFlow.vue +225 -0
  63. package/app/components/Visual/ImagePreview.vue +8 -0
  64. package/app/composables/account.ts +21 -0
  65. package/app/composables/app.ts +15 -0
  66. package/app/composables/artistData.ts +22 -0
  67. package/app/composables/chainId.ts +25 -0
  68. package/app/composables/collections.ts +435 -0
  69. package/app/composables/darkMode.ts +1 -0
  70. package/app/composables/gasPrice.ts +46 -0
  71. package/app/composables/head.ts +29 -0
  72. package/app/composables/priceFeed.ts +80 -0
  73. package/app/composables/subdomain.ts +27 -0
  74. package/app/error.vue +31 -0
  75. package/app/layouts/default.vue +42 -0
  76. package/app/middleware/lowercaseId.ts +1 -0
  77. package/app/middleware/lowercaseProfileAddress.ts +1 -0
  78. package/app/middleware/redirectUserScope.ts +13 -0
  79. package/app/pages/[id]/[collection]/[tokenId]/index.vue +66 -0
  80. package/app/pages/[id]/[collection]/[tokenId].vue +25 -0
  81. package/app/pages/[id]/[collection]/index.vue +51 -0
  82. package/app/pages/[id]/[collection]/mint.vue +260 -0
  83. package/app/pages/[id]/[collection].vue +24 -0
  84. package/app/pages/[id]/add.vue +40 -0
  85. package/app/pages/[id]/create.vue +177 -0
  86. package/app/pages/[id]/index.vue +43 -0
  87. package/app/pages/[id].vue +9 -0
  88. package/app/pages/index.vue +47 -0
  89. package/app/pages/profile/[address]/index.vue +51 -0
  90. package/app/pages/profile/[address].vue +9 -0
  91. package/app/pages/profile/index.vue +28 -0
  92. package/app/plugins/1.polyfill.client.ts +12 -0
  93. package/app/plugins/2.wagmi.ts +57 -0
  94. package/app/router.options.ts +25 -0
  95. package/app/utils/abis.ts +77 -0
  96. package/app/utils/arrays.ts +1 -0
  97. package/app/utils/artifact.ts +21 -0
  98. package/app/utils/breakpoints.ts +11 -0
  99. package/app/utils/dates.ts +23 -0
  100. package/app/utils/format.ts +60 -0
  101. package/app/utils/images.ts +27 -0
  102. package/app/utils/ipfs.ts +13 -0
  103. package/app/utils/lowercaseRouteParam.ts +10 -0
  104. package/app/utils/serializer.ts +18 -0
  105. package/app/utils/strings.ts +30 -0
  106. package/app/utils/time.ts +23 -0
  107. package/app/utils/types.ts +62 -0
  108. package/app/utils/urls.ts +43 -0
  109. package/nuxt.config.ts +130 -0
  110. package/package.json +44 -0
  111. package/public/apple-touch-icon-512x512.png +0 -0
  112. package/public/example-contract-icon-original.svg +5 -0
  113. package/public/example-contract-icon.svg +5 -0
  114. package/public/favicon.ico +0 -0
  115. package/public/icon.svg +8 -0
  116. package/public/icons/check.svg +3 -0
  117. package/public/icons/opepen.svg +264 -0
  118. package/public/icons/wallets/coinbase.svg +4 -0
  119. package/public/icons/wallets/metamask.svg +1 -0
  120. package/public/icons/wallets/rainbow.svg +59 -0
  121. package/public/icons/wallets/walletconnect.svg +1 -0
  122. package/public/maskable-icon-512x512.png +0 -0
  123. package/public/pwa-192x192.png +0 -0
  124. package/public/pwa-512x512.png +0 -0
  125. package/public/pwa-64x64.png +0 -0
  126. package/server/middleware/log.ts +3 -0
  127. package/server/middleware/subdomain.ts +12 -0
  128. package/server/tsconfig.json +3 -0
  129. package/tsconfig.json +4 -0
@@ -0,0 +1,194 @@
1
+ <template>
2
+ <article class="token">
3
+ <div class="artifact">
4
+ <div :class="{ shaded }">
5
+ <Image :src="token.artifact" :alt="token.name" class="borderless" />
6
+ </div>
7
+ </div>
8
+
9
+ <MintToken
10
+ :token="token"
11
+ :mint-count="mintCount"
12
+ #default="{
13
+ displayPrice,
14
+ dollarPrice,
15
+ mintRequest,
16
+ minted,
17
+ mintOpen,
18
+ currentBlock,
19
+ blocksRemaining,
20
+ transactionFlowConfig
21
+ }"
22
+ >
23
+ <section class="details">
24
+ <header class="title">
25
+ <h1>{{ token.name }} <span class="muted-light">#{{ token.tokenId }}</span></h1>
26
+ <p v-if="token.description" class="muted-light">
27
+ <ExpandableText :text="token.description" :length="95" expand-text="Read More" />
28
+ </p>
29
+ <p class="muted-light">
30
+ By <NuxtLink :to="{ name: 'id', params: { id: collection.owner } }">{{ store.displayName(collection.owner) }}</NuxtLink>
31
+ </p>
32
+ </header>
33
+
34
+ <div v-if="mintOpen">
35
+ <MintTokenBar
36
+ v-model:mintCount="mintCount"
37
+ v-bind="{
38
+ token,
39
+ displayPrice,
40
+ dollarPrice,
41
+ mintRequest,
42
+ transactionFlowConfig,
43
+ minted,
44
+ }"
45
+ />
46
+ </div>
47
+
48
+ <div class="mint-status">
49
+ <p v-if="mintOpen">{{ blocksRemaining }} blocks remaining</p>
50
+ <p v-else-if="currentBlock" class="muted-light">Closed at block {{ token.untilBlock }}</p>
51
+ <p class="muted-light" v-if="ownedBalance">You own {{ ownedBalance }} {{ pluralize('token', Number(ownedBalance)) }}</p>
52
+ </div>
53
+
54
+ <TokenMintTimeline :token="token" :collection="collection" class="network-mints" />
55
+ </section>
56
+ </MintToken>
57
+ </article>
58
+ </template>
59
+
60
+ <script setup lang="ts">
61
+ import ExpandableText from '../ExpandableText.vue'
62
+
63
+ const { token } = defineProps<{
64
+ token: Token
65
+ }>()
66
+
67
+ const breakpoints = useBreakpoints()
68
+ const shaded = computed(() => breakpoints.greater('sm').value && ! isDark.value)
69
+
70
+ const store = useOnchainStore()
71
+ const collection = computed(() => store.collection(token.collection))
72
+
73
+ const mintCount = ref('1')
74
+ const ownedBalance = computed(() => store.tokenBalance(collection.value.address, token.tokenId))
75
+ </script>
76
+
77
+ <style scoped>
78
+ .token {
79
+ position: relative;
80
+ container-type: inline-size;
81
+
82
+ display: grid;
83
+ grid-auto-rows: min-content;
84
+ padding: 0 !important;
85
+
86
+ @media (--md) {
87
+ height: calc(100dvh - var(--navbar-height));
88
+ grid-template-columns: 60% 1fr;
89
+ grid-auto-rows: auto;
90
+ }
91
+ }
92
+
93
+ .artifact {
94
+ --padding-x: 0;
95
+ --padding-top: 0;
96
+ --padding-bottom: 0;
97
+ --width: 100cqw;
98
+ /* --height: calc(100cqh - var(--padding-top) - var(--padding-bottom)); */
99
+ --height: 100%;
100
+ --dimension: min(100cqw, 100cqh);
101
+
102
+ @media (--md) {
103
+ --width: 60cqw;
104
+ --height: calc(100cqh - var(--navbar-height));
105
+ --padding-top: var(--spacer-lg);
106
+ --padding-x: var(--spacer-lg);
107
+ --padding-bottom: calc(var(--spacer-lg) + var(--spacer));
108
+ --padding-bottom: var(--spacer-lg);
109
+
110
+ --dimension: min(
111
+ calc(60cqw - var(--padding-x)*2),
112
+ calc(100cqh - var(--padding-top) - var(--padding-bottom))
113
+ );
114
+ }
115
+
116
+ @media (--lg) {
117
+ --padding-top: var(--spacer-xl);
118
+ --padding-x: var(--spacer-xl);
119
+ --padding-bottom: calc(var(--spacer-xl) + var(--spacer-lg));
120
+ }
121
+
122
+ height: var(--height);
123
+ width: var(--width);
124
+ padding: var(--padding-top) var(--padding-x) var(--padding-bottom);
125
+
126
+ @media (--md) {
127
+ border-right: var(--border);
128
+ display: flex;
129
+ justify-content: center;
130
+ align-items: center;
131
+ }
132
+
133
+ > * {
134
+ width: var(--dimension);
135
+ height: auto;
136
+ border-bottom: var(--border) !important;
137
+ transition: transform var(--speed-slow), box-shadow var(--speed-slow);
138
+
139
+ @media (--md) {
140
+ border-bottom: none !important;
141
+ }
142
+
143
+ &.shaded {
144
+ box-shadow: var(--shadow-xl);
145
+ transform: translateY(calc(-1 * var(--size-2))) scale(1.001);
146
+ }
147
+ }
148
+ }
149
+
150
+ .details {
151
+
152
+ @media (--md) {
153
+ overflow-y: auto;
154
+ -webkit-overflow-scrolling: auto;
155
+ }
156
+
157
+ > * {
158
+ border-bottom: var(--border);
159
+ padding: var(--spacer);
160
+
161
+ @media (--md) {
162
+ padding: var(--spacer) var(--spacer-lg);
163
+ }
164
+ }
165
+
166
+ header {
167
+ z-index: 100;
168
+ display: grid;
169
+ gap: var(--spacer-sm);
170
+ background: var(--background-semi);
171
+ backdrop-filter: var(--blur);
172
+
173
+ h1 {
174
+ font-size: var(--font-lg);
175
+ }
176
+
177
+ @media (--md) {
178
+ padding: var(--spacer-lg);
179
+ position: sticky;
180
+ top: 0;
181
+ }
182
+ }
183
+ }
184
+
185
+ .mint-status {
186
+ display: flex;
187
+ gap: var(--spacer);
188
+ flex-wrap: wrap;
189
+
190
+ @media (--md) {
191
+ justify-content: space-between;
192
+ }
193
+ }
194
+ </style>
@@ -0,0 +1,110 @@
1
+ <template>
2
+ <section>
3
+ <h1>Mint Timeline</h1>
4
+
5
+ <div v-if="currentBlock" class="table">
6
+ <TokenMintTimelineItem
7
+ v-for="mint of mints"
8
+ :mint="mint"
9
+ :key="mint.tx"
10
+ :block="currentBlock"
11
+ />
12
+ <div v-if="! loading || mints.length">
13
+ <span>
14
+ <Account :address="collection.owner" />
15
+ </span>
16
+
17
+ <span class="right">1<span class="muted-light">×</span></span>
18
+ <span class="right">Artist Mint</span>
19
+
20
+ <span class="right"><BlocksTimeAgo v-if="currentBlock" :blocks="currentBlock - (token.untilBlock - 7200n)" /></span>
21
+
22
+ <span class="right muted-light">
23
+ <NuxtLink :to="`${config.public.blockExplorer}/nft/${token.collection}/${token.tokenId}`" target="_blank">
24
+ <Icon type="link" />
25
+ </NuxtLink>
26
+ </span>
27
+ </div>
28
+ </div>
29
+
30
+ <Loading v-if="loading || ! currentBlock" txt="Mint History..." />
31
+ </section>
32
+ </template>
33
+
34
+ <script setup>
35
+ import { useBlockNumber } from '@wagmi/vue'
36
+
37
+ const config = useRuntimeConfig()
38
+ const { data: currentBlock } = useBlockNumber({ chainId: config.public.chainId })
39
+
40
+ const { token, collection } = defineProps({
41
+ token: Object,
42
+ collection: Object,
43
+ })
44
+
45
+ const state = useOnchainStore()
46
+
47
+ const mints = computed(() => state.tokenMints(token.collection, token.tokenId))
48
+
49
+ const loading = ref(false)
50
+ onMounted(async () => {
51
+ loading.value = true
52
+ try {
53
+ await state.fetchTokenMints(token)
54
+ await state.backfillTokenMints(token)
55
+ } catch (e) {
56
+ console.error(e)
57
+ }
58
+ loading.value = false
59
+ })
60
+
61
+ watch(currentBlock, () => state.fetchTokenMints(token))
62
+ </script>
63
+
64
+ <style scoped>
65
+ section {
66
+ padding-top: var(--spacer-lg) !important;
67
+ padding-bottom: var(--spacer-lg) !important;
68
+ container-type: inline-size;
69
+ }
70
+
71
+ h1 {
72
+ margin-bottom: var(--spacer);
73
+ font-size: var(--font-base);
74
+ border-bottom: var(--border);
75
+ padding: 0 0 var(--spacer-sm);
76
+ margin: 0 0 var(--spacer-lg);
77
+ }
78
+
79
+ .table {
80
+ display: grid;
81
+ gap: var(--spacer);
82
+
83
+ :deep(> div) {
84
+ display: grid;
85
+ gap: var(--spacer);
86
+
87
+ .right {
88
+ text-align: right;
89
+ }
90
+
91
+ span {
92
+ white-space: nowrap;
93
+
94
+ &:nth-child(3) {
95
+ display: none;
96
+
97
+ @container (min-width: 30rem) {
98
+ display: inline;
99
+ }
100
+ }
101
+ }
102
+
103
+ grid-template-columns: 6rem 3rem 1fr 3rem;
104
+
105
+ @container (min-width: 30rem) {
106
+ grid-template-columns: 6rem 3rem 1fr 6rem 1rem;
107
+ }
108
+ }
109
+ }
110
+ </style>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <div>
3
+ <span>
4
+ <Account :address="mint.address" />
5
+ </span>
6
+
7
+ <span class="right">{{ mint.amount.toString() }}<span class="muted-light">×</span></span>
8
+
9
+ <span class="right">{{ formattedPrice.value }} {{ formattedPrice.format }}</span>
10
+
11
+ <span class="right"><BlocksTimeAgo :blocks="block - mint.block" /></span>
12
+
13
+ <span class="right muted-light">
14
+ <NuxtLink :to="`${config.public.blockExplorer}/tx/${mint.tx}`" target="_blank">
15
+ <Icon type="link" />
16
+ </NuxtLink>
17
+ </span>
18
+ </div>
19
+ </template>
20
+
21
+ <script setup>
22
+ const config = useRuntimeConfig()
23
+
24
+ const props = defineProps({
25
+ mint: Object,
26
+ block: BigInt,
27
+ })
28
+
29
+ const formattedPrice = computed(() => customFormatEther(props.mint.price))
30
+ </script>
31
+
32
+ <style scoped>
33
+ </style>
@@ -0,0 +1,140 @@
1
+ <template>
2
+ <MintToken
3
+ :token="token"
4
+ :mintCount="mintCount"
5
+ #default="{
6
+ displayPrice,
7
+ dollarPrice,
8
+ mintRequest,
9
+ minted,
10
+ mintOpen,
11
+ blocksRemaining,
12
+ transactionFlowConfig
13
+ }"
14
+ >
15
+ <article class="token">
16
+ <div class="content">
17
+ <header>
18
+ <h1>
19
+ <span>{{ token.name }} <span class="muted">#{{ token.tokenId }}</span></span>
20
+ <span v-if="token.description">{{ shortString(token.description, 40, 30) }}</span>
21
+ </h1>
22
+ <p v-if="mintOpen" class="muted">Closes in {{ blocksRemaining }} {{ pluralize('block', Number(blocksRemaining))}}</p>
23
+ <p v-else class="muted">Closed at block {{ token.untilBlock }}</p>
24
+ </header>
25
+ <Image :src="token.artifact" :alt="token.name" />
26
+ <CardLink :to="{
27
+ name: 'id-collection-tokenId',
28
+ params: { id: collection.owner, collection: token.collection, tokenId: `${token.tokenId}` }
29
+ }">View {{ token.name }}</CardLink>
30
+ </div>
31
+ <footer>
32
+ <MintTokenBar
33
+ v-if="mintOpen"
34
+ v-model:mintCount="mintCount"
35
+ v-bind="{
36
+ token,
37
+ displayPrice,
38
+ dollarPrice,
39
+ mintRequest,
40
+ transactionFlowConfig,
41
+ minted,
42
+ }"
43
+ />
44
+ <p class="muted" v-if="ownedBalance">You own {{ ownedBalance }} "{{ token.name }}" {{ pluralize('token', Number(ownedBalance)) }}</p>
45
+ </footer>
46
+ </article>
47
+ </MintToken>
48
+ </template>
49
+
50
+ <script setup lang="ts">
51
+ const { token } = defineProps<{
52
+ token: Token
53
+ }>()
54
+
55
+ const store = useOnchainStore()
56
+ const collection = computed(() => store.collection(token.collection)) as ComputedRef<Collection>
57
+
58
+ const mintCount = ref('1')
59
+ const ownedBalance = computed(() => store.tokenBalance(collection.value.address, token.tokenId))
60
+ </script>
61
+
62
+ <style scoped>
63
+ .token,
64
+ .token > .content {
65
+ display: flex;
66
+ flex-direction: column;
67
+ justify-content: center;
68
+ gap: var(--spacer);
69
+ }
70
+
71
+ .token {
72
+ padding: var(--spacer-xl) var(--spacer) !important;
73
+ border: 0 !important;
74
+
75
+ /* Tokens should be at min the screen height. */
76
+ &:not(:first-of-type) {
77
+ min-height: min(60rem, calc(100dvh - 2*var(--navbar-height)));
78
+
79
+ @media (--md) {
80
+ min-height: min(60rem, calc(100dvh - var(--navbar-height)));
81
+ }
82
+ }
83
+
84
+ > p {
85
+ color: var(--muted-light);
86
+ font-size: var(--font-sm);
87
+ text-align: left;
88
+ }
89
+ }
90
+
91
+ .token > .content {
92
+ display: flex;
93
+ flex-direction: column;
94
+ justify-content: center;
95
+ gap: var(--spacer);
96
+
97
+ header {
98
+ display: flex;
99
+ flex-direction: column;
100
+ gap: var(--spacer-xs);
101
+
102
+ @media (--sm) {
103
+ flex-direction: row;
104
+ justify-content: space-between;
105
+ align-items: center;
106
+ gap: var(--spacer);
107
+ }
108
+ }
109
+
110
+ h1 {
111
+ span:last-of-type:not(:first-child) {
112
+ color: var(--muted-light);
113
+ white-space: wrap;
114
+ display: none;
115
+
116
+ @media (--md) {
117
+ display: block;
118
+ }
119
+ }
120
+
121
+ + p {
122
+ @media (--md) {
123
+ text-align: right;
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ footer {
130
+ width: 100%;
131
+ display: flex;
132
+ gap: var(--spacer);
133
+ justify-content: space-between;
134
+ align-items: baseline;
135
+
136
+ > *:first-child:last-child {
137
+ margin-left: auto;
138
+ }
139
+ }
140
+ </style>
@@ -0,0 +1,225 @@
1
+ <template>
2
+ <slot :start="start" name="start"></slot>
3
+
4
+ <Modal
5
+ :open="open"
6
+ @close="cancel"
7
+ :x-close="false"
8
+ class="transaction-flow"
9
+ >
10
+ <div class="text">
11
+ <CheckSpinner class="spinner" />
12
+ <h1 v-if="text.title[step]">{{ text.title[step] }}</h1>
13
+ <p class="muted-light">{{ text.lead[step] }}</p>
14
+ <p v-if="error">{{ error }}</p>
15
+ </div>
16
+
17
+ <slot :name="step" :cancel="cancel"></slot>
18
+
19
+ <Button v-if="step === 'waiting'" :to="txLink" target="_blank" class="block-explorer">
20
+ <Icon type="loader" class="spin" />
21
+ <span>View on Etherscan</span>
22
+ </Button>
23
+
24
+ <Actions v-if="step === 'chain'" class="centered">
25
+ <Button @click="cancel" class="secondary">Cancel</Button>
26
+ </Actions>
27
+ <Actions v-if="step === 'confirm' || step === 'error'">
28
+ <Button @click="cancel" class="secondary">Cancel</Button>
29
+ <Button @click="() => initializeRequest()">{{ text.action[step] || 'Execute' }}</Button>
30
+ </Actions>
31
+ </Modal>
32
+ </template>
33
+
34
+ <script setup>
35
+ import { useChainId } from '@wagmi/vue'
36
+ import { waitForTransactionReceipt } from '@wagmi/core'
37
+ const checkChain = useEnsureChainIdCheck()
38
+ const chainId = useChainId()
39
+
40
+ const { $wagmi } = useNuxtApp()
41
+ const config = useRuntimeConfig()
42
+ const props = defineProps({
43
+ text: {
44
+ type: Object,
45
+ default: {
46
+ title: {
47
+ confirm: '[[Really?]]',
48
+ },
49
+ lead: {
50
+ confirm: '[[Do you really?]]'
51
+ },
52
+ action: {
53
+ confirm: '[[Do!]]',
54
+ }
55
+ },
56
+ },
57
+ request: Function,
58
+ delayAfter: {
59
+ type: Number,
60
+ default: 2_000,
61
+ },
62
+ skipConfirmation: Boolean,
63
+ autoCloseSuccess: Boolean,
64
+ })
65
+
66
+ const emit = defineEmits(['complete', 'cancel'])
67
+
68
+ const open = ref(false)
69
+
70
+ const switchChain = ref(false)
71
+ watch(chainId, async () => {
72
+ if (! switchChain.value) return
73
+
74
+ if (await checkChain()) {
75
+ switchChain.value = false
76
+ initializeRequest()
77
+ } else {
78
+ switchChain.value = true
79
+ }
80
+ })
81
+
82
+ const requesting = ref(false)
83
+ const waiting = ref(false)
84
+ const complete = ref(false)
85
+ const error = ref('')
86
+ const tx = ref(null)
87
+ const receipt = ref(null)
88
+ const txLink = computed(() => `${config.public.blockExplorer}/tx/${tx.value}`)
89
+
90
+ const step = computed(() => {
91
+ if (
92
+ open.value &&
93
+ !requesting.value &&
94
+ !switchChain.value &&
95
+ !waiting.value &&
96
+ !complete.value
97
+ ) {
98
+ return 'confirm'
99
+ }
100
+
101
+ if (switchChain.value) {
102
+ return 'chain'
103
+ }
104
+
105
+ if (requesting.value) {
106
+ return 'requesting'
107
+ }
108
+
109
+ if (waiting.value) {
110
+ return 'waiting'
111
+ }
112
+
113
+ if (complete.value) {
114
+ return 'complete'
115
+ }
116
+
117
+ return 'error'
118
+ })
119
+
120
+ const initializeRequest = async (request = props.request) => {
121
+ complete.value = false
122
+ open.value = true
123
+ error.value = ''
124
+ tx.value = null
125
+ receipt.value = null
126
+
127
+ if (! await checkChain()) {
128
+ switchChain.value = true
129
+ return
130
+ } else {
131
+ switchChain.value = false
132
+ }
133
+
134
+ if (requesting.value) return
135
+
136
+ try {
137
+ requesting.value = true
138
+ tx.value = await request()
139
+ requesting.value = false
140
+ waiting.value = true
141
+ const [receiptObject] = await Promise.all([
142
+ waitForTransactionReceipt($wagmi, { hash: tx.value }),
143
+ delay(6_000),
144
+ ])
145
+ await delay(props.delayAfter)
146
+ receipt.value = receiptObject
147
+ emit('complete', receiptObject)
148
+ complete.value = true
149
+ } catch (e) {
150
+ if (e?.cause?.code === 4001) {
151
+ open.value = false
152
+ } else {
153
+ error.value = e.shortMessage || 'Error submitting transaction request.'
154
+ }
155
+ console.log(e)
156
+ }
157
+
158
+ requesting.value = false
159
+ waiting.value = false
160
+
161
+ if (props.autoCloseSuccess) {
162
+ await delay(2_000)
163
+ open.value = false
164
+ await delay(300) // Animations...
165
+ }
166
+
167
+ return receipt.value
168
+ }
169
+
170
+ const start = () => {
171
+ if (props.skipConfirmation && !open.value) {
172
+ initializeRequest()
173
+ }
174
+
175
+ open.value = true
176
+ }
177
+
178
+ const cancel = () => {
179
+ open.value = false
180
+
181
+ emit('cancel')
182
+ }
183
+
184
+ defineExpose({
185
+ initializeRequest,
186
+ })
187
+ </script>
188
+
189
+ <style>
190
+ .transaction-flow {
191
+ display: grid;
192
+ justify-content: center;
193
+ gap: var(--spacer);
194
+
195
+ .spinner {
196
+ width: var(--size-7);
197
+ height: var(--size-7);
198
+ margin: calc(-1 * var(--size-4)) auto var(--size-3);
199
+ }
200
+
201
+ .text {
202
+ height: min-content;
203
+ }
204
+
205
+ h1 {
206
+ font-size: var(--font-lg);
207
+ margin-bottom: var(--size-4);
208
+ text-align: center;
209
+ }
210
+
211
+ p {
212
+ white-space: pre-wrap;
213
+ width: 100%;
214
+ text-align: center;
215
+
216
+ a {
217
+ text-decoration: underline;
218
+ }
219
+ }
220
+
221
+ .block-explorer {
222
+ justify-self: center;
223
+ }
224
+ }
225
+ </style>
@@ -0,0 +1,8 @@
1
+ <template>
2
+ <svg viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
3
+ <rect width="64" height="64" fill="var(--gray-z-0)"/>
4
+ <path d="M39 23H25C23.8954 23 23 23.8954 23 25V39C23 40.1046 23.8954 41 25 41H39C40.1046 41 41 40.1046 41 39V25C41 23.8954 40.1046 23 39 23Z" stroke="var(--gray-z-2)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
5
+ <path d="M28.5 30C29.3284 30 30 29.3284 30 28.5C30 27.6716 29.3284 27 28.5 27C27.6716 27 27 27.6716 27 28.5C27 29.3284 27.6716 30 28.5 30Z" stroke="var(--gray-z-2)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
6
+ <path d="M41 35L36 30L25 41" stroke="var(--gray-z-2)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
7
+ </svg>
8
+ </template>