Skip to content

Commit 888e37d

Browse files
authored
Feature/successful modal (#1680)
* Add modal component and enhance checkout UI Introduced a new `Modal` component to be used in the checkout process for displaying detailed views. Enhanced `Checkout.vue` and `Result.vue` to integrate the modal functionality, allowing better visualization of certain elements. Added Japanese translations for "Passport" and "Home". * Refactor modal logic and remove unused image Renamed the function responsible for opening the modal to 'modalOpen' for clarity. Removed the initialization and usage of the unused 'img2' image. Ensured the modal opens immediately after image loading, and updated the content binding of the modal to use dynamic attributes.
1 parent 1f9a077 commit 888e37d

File tree

5 files changed

+303
-5
lines changed

5 files changed

+303
-5
lines changed

src/ui/components/Checkout/Checkout.vue

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ onUnmounted(() => {
581581
<div v-if="props.accessControlUrl" class="grid gap-4 p-5 lg:gap-8">
582582
<!-- Access control section -->
583583
<span>
584-
<p class="mb-2 flex items-center gap-2 font-bold text-dp-white-600">
584+
<p class="text-dp-white-600 mb-2 flex items-center gap-2 font-bold">
585585
<svg
586586
xmlns="http://www.w3.org/2000/svg"
587587
fill="none"
@@ -674,7 +674,7 @@ onUnmounted(() => {
674674

675675
<section
676676
v-if="!useInjectedTransactionForm && isPriced"
677-
class="sticky bottom-0 flex grow flex-col gap-5 rounded-b-xl border-t border-dp-white-300 bg-white p-5"
677+
class="border-dp-white-300 sticky bottom-0 flex grow flex-col gap-5 rounded-b-xl border-t bg-white p-5"
678678
v-bind:class="
679679
!props.accessControlUrl || (props.accessControlUrl && accessAllowed)
680680
? 'lg:static lg:border-0 lg:bg-transparent'
@@ -728,7 +728,7 @@ onUnmounted(() => {
728728
>
729729
<div
730730
role="presentation"
731-
class="mx-auto h-16 w-16 animate-spin rounded-full border-l border-r border-t border-native-blue-300"
731+
class="border-native-blue-300 mx-auto h-16 w-16 animate-spin rounded-full border-l border-r border-t"
732732
/>
733733
<p v-if="isApproving">{{ i18n('ApprovalPending') }}</p>
734734
<p v-if="isStaking && !isWaitingForStaked">
@@ -742,7 +742,15 @@ onUnmounted(() => {
742742
</section>
743743
</div>
744744

745-
<Result :id="mintedId?.toString()" :rpc-url="rpcUrl" v-if="stakeSuccessful">
745+
<Result
746+
v-if="stakeSuccessful"
747+
:id="mintedId?.toString()"
748+
:rpc-url="rpcUrl"
749+
:stake-successful="stakeSuccessful"
750+
:name="previewName"
751+
:description="props.description"
752+
:image-src="previewImageSrc"
753+
>
746754
<template #before:preview>
747755
<slot name="result:before:preview" />
748756
</template>
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<script setup lang="ts">
2+
import { i18nFactory } from '../../../i18n'
3+
import { Strings } from './i18n'
4+
import Skeleton from '../Skeleton/Skeleton.vue'
5+
import { ProseTextInherit } from '../../../constants'
6+
7+
defineProps<{
8+
name: string | undefined
9+
description: string | undefined
10+
image: HTMLImageElement | undefined
11+
}>()
12+
13+
const i18nBase = i18nFactory(Strings)
14+
let i18n = i18nBase(['en'])
15+
</script>
16+
17+
<style>
18+
.bg-color-navy {
19+
background-color: #0d1426;
20+
}
21+
22+
.w-h-screen {
23+
height: 200vw;
24+
width: 200vw;
25+
}
26+
@media (min-width: 1024px) {
27+
.w-h-screen {
28+
height: 100vw;
29+
width: 100vw;
30+
}
31+
}
32+
33+
/* background focus lines */
34+
.origin-zero {
35+
transform-origin: 0 0;
36+
}
37+
38+
.bg-focus-lines {
39+
background-image: repeating-conic-gradient(
40+
rgba(255, 255, 255, 0) 0,
41+
rgba(255, 255, 255, 0) 13deg,
42+
rgba(255, 255, 255, 0.1) 13deg,
43+
rgba(255, 255, 255, 0.1) 16deg
44+
),
45+
repeating-conic-gradient(
46+
rgba(255, 255, 255, 0) 0,
47+
rgba(255, 255, 255, 0) 20deg,
48+
rgba(255, 255, 255, 0.1) 20deg,
49+
rgba(255, 255, 255, 0.1) 23deg
50+
),
51+
repeating-conic-gradient(
52+
rgba(255, 255, 255, 0) 0,
53+
rgba(255, 255, 255, 0) 5deg,
54+
rgba(255, 255, 255, 0.1) 5deg,
55+
rgba(255, 255, 255, 0.1) 8deg
56+
),
57+
repeating-conic-gradient(
58+
rgba(255, 255, 255, 0) 0,
59+
rgba(255, 255, 255, 0) 2deg,
60+
rgba(255, 255, 255, 0.1) 2deg,
61+
rgba(255, 255, 255, 0.1) 3deg
62+
);
63+
}
64+
65+
.animate-spin-slow {
66+
animation: spin-slow 300s linear infinite;
67+
}
68+
69+
@keyframes spin-slow {
70+
from {
71+
transform: rotate(0deg) translate(-50%, -50%);
72+
}
73+
to {
74+
transform: rotate(360deg) translate(-50%, -50%);
75+
}
76+
}
77+
78+
.mask {
79+
mask-image: radial-gradient(
80+
transparent 0%,
81+
transparent 20%,
82+
white 50%,
83+
white
84+
);
85+
}
86+
</style>
87+
88+
<template>
89+
<div>
90+
<div
91+
class="bg-color-navy relative mb-2 flex h-auto min-h-52 w-full flex-col items-center justify-center overflow-hidden rounded-md border border-black p-2 lg:mb-6 lg:h-96 lg:min-h-0 lg:p-8"
92+
>
93+
<div
94+
class="w-h-screen bg-focus-lines origin-zero animate-spin-slow mask absolute inset-2/4 -translate-x-1/2 -translate-y-1/2 bg-center bg-no-repeat"
95+
></div>
96+
<h3 class="mb-1 text-white">
97+
<span v-if="name" class="text-xl italic lg:text-3xl">{{ name }}</span>
98+
</h3>
99+
<!-- image -->
100+
<img
101+
:src="image?.src"
102+
:width="image?.width"
103+
:height="image?.height"
104+
class="z-10 max-h-60 min-h-full max-w-60 object-contain lg:max-h-none lg:max-w-xl"
105+
/>
106+
</div>
107+
<div class="flex flex-col gap-4 px-0 lg:px-52">
108+
<!-- description -->
109+
<aside
110+
v-if="description"
111+
v-html="description"
112+
class="break-all text-base"
113+
:class="ProseTextInherit"
114+
></aside>
115+
<Skeleton
116+
v-if="description === undefined"
117+
class="mx-auto h-full w-full"
118+
/>
119+
120+
<!-- slots -->
121+
<slot name="after:description" />
122+
123+
<div
124+
class="flex w-full flex-col gap-4 lg:flex-row lg:justify-between lg:gap-0"
125+
>
126+
<a
127+
href="/passport/0x262A038D0bc05B4112c7D58BBfd407810bcfE2aB"
128+
class="hs-button is-filled rounded-lg border px-0 py-4 text-base lg:px-12 lg:py-6"
129+
>
130+
{{ i18n('Passport') }}
131+
</a>
132+
<a
133+
href="/"
134+
class="hs-button is-filled rounded-lg border px-12 py-4 text-base lg:py-6"
135+
>
136+
{{ i18n('Home') }}
137+
</a>
138+
</div>
139+
</div>
140+
</div>
141+
</template>

src/ui/components/Checkout/Result.vue

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@ import { ProseTextInherit } from '../../../constants/class-names'
1212
import { Strings } from './i18n'
1313
import { i18nFactory } from '../../../i18n'
1414
import { markdownToHtml } from '../../../markdown'
15+
import Modal from '../Modal.vue'
16+
import ModalCheckout from './ModalCheckout.vue'
1517
1618
const i18nBase = i18nFactory(Strings)
1719
let i18n = i18nBase(['en'])
1820
1921
type Props = {
2022
id?: number | string
2123
rpcUrl: string
24+
stakeSuccessful: boolean
25+
name: string | undefined
26+
description: string | undefined
27+
imageSrc: string | undefined
2228
}
2329
const props = defineProps<Props>()
2430
@@ -51,6 +57,14 @@ const htmlDescription: ComputedRef<UndefinedOr<string>> = computed(() => {
5157
)
5258
})
5359
60+
// modal visibility
61+
const modalVisible = ref(false)
62+
63+
// open modal
64+
const modalOpen = () => {
65+
modalVisible.value = true
66+
}
67+
5468
onMounted(async () => {
5569
const provider = new JsonRpcProvider(props.rpcUrl)
5670
i18n = i18nBase(navigator.languages)
@@ -69,6 +83,9 @@ onMounted(async () => {
6983
img.src = src
7084
})
7185
console.log({ metadata })
86+
87+
// Modal Open
88+
modalOpen()
7289
})
7390
</script>
7491

@@ -77,13 +94,28 @@ onMounted(async () => {
7794
<div class="mx-auto grid gap-8 md:max-w-lg">
7895
<slot name="before:preview" />
7996
<div
80-
class="-mx-8 grid gap-8 bg-dp-white-300 p-6 md:mx-auto md:w-full md:rounded-md"
97+
class="bg-dp-white-300 -mx-8 grid gap-8 p-6 md:mx-auto md:w-full md:rounded-md"
8198
>
8299
<div class="flex flex-col gap-6">
83100
<p class="font-mono font-bold">
84101
{{ i18n('Minted') }} <span class="text-black/50">#{{ id }}</span>
85102
</p>
86103

104+
<!-- me -->
105+
<Modal
106+
:is-visible="modalVisible"
107+
:modal-content="ModalCheckout"
108+
:attrs="{
109+
name: tokenURI?.name,
110+
description: htmlDescription,
111+
image: image,
112+
}"
113+
>
114+
<template #after:description>
115+
<slot name="before:preview" />
116+
</template>
117+
</Modal>
118+
87119
<div class="rounded-lg border border-black/20 bg-black/10 p-4">
88120
<img
89121
v-if="image"

src/ui/components/Checkout/i18n/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,12 @@ export const Strings = {
5959
ja: ([amount, currency]) =>
6060
`${amount} ${currency} はオーナーに直接支払われます!`,
6161
},
62+
Passport: {
63+
en: 'Passport',
64+
ja: 'Passport',
65+
},
66+
Home: {
67+
en: 'Home',
68+
ja: 'Home',
69+
},
6270
} satisfies ClubsI18nParts

src/ui/components/Modal.vue

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<script setup lang="ts">
2+
import { Component as VueComponent } from 'vue'
3+
4+
defineProps<{
5+
isVisible: boolean
6+
modalContent: VueComponent
7+
attrs: { [key: string]: any }
8+
}>()
9+
</script>
10+
11+
<style>
12+
.modal-container {
13+
position: fixed;
14+
top: 0;
15+
right: 0;
16+
bottom: 0;
17+
left: 0;
18+
display: flex;
19+
justify-content: center;
20+
align-items: flex-end;
21+
}
22+
23+
@media (min-width: 1024px) {
24+
.modal-container {
25+
align-items: center;
26+
}
27+
}
28+
29+
.modal-overlay {
30+
position: fixed;
31+
top: 0;
32+
right: 0;
33+
bottom: 0;
34+
left: 0;
35+
background: rgba(0, 0, 0, 0.6);
36+
z-index: -1;
37+
}
38+
39+
.modal-content {
40+
display: flex;
41+
flex-direction: column;
42+
padding: 1rem;
43+
width: 100%;
44+
max-width: 64rem;
45+
background: white;
46+
border-top-left-radius: 0.5rem;
47+
border-top-right-radius: 0.5rem;
48+
border-bottom-left-radius: 0;
49+
border-bottom-right-radius: 0;
50+
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2);
51+
52+
max-height: 80vh;
53+
overflow-y: auto;
54+
}
55+
56+
@media (min-width: 1024px) {
57+
.modal-content {
58+
border-bottom-left-radius: 0.5rem;
59+
border-bottom-right-radius: 0.5rem;
60+
}
61+
}
62+
63+
.v-enter-active {
64+
transition: transform 600ms cubic-bezier(0.07, 1.28, 0.5, 1);
65+
}
66+
67+
.v-leave-active {
68+
transition: transform 600ms linear;
69+
}
70+
71+
.v-enter-from {
72+
transform: translate(0, 100%);
73+
}
74+
75+
.v-leave-to {
76+
transform: translate(0, 0);
77+
}
78+
79+
html:has(#modal-container[data-active='true']) {
80+
overflow: hidden;
81+
}
82+
</style>
83+
84+
<template>
85+
<div>
86+
<Teleport to="body">
87+
<div
88+
id="modal-container"
89+
v-show="isVisible"
90+
class="modal-container"
91+
:data-active="isVisible"
92+
>
93+
<div class="modal-overlay"></div>
94+
<Transition>
95+
<component
96+
v-show="isVisible"
97+
class="modal-content"
98+
:is="modalContent"
99+
v-bind="attrs"
100+
>
101+
<template #after:description>
102+
<slot name="after:description" />
103+
</template>
104+
</component>
105+
</Transition>
106+
</div>
107+
</Teleport>
108+
</div>
109+
</template>

0 commit comments

Comments
 (0)