Skip to content

Commit 4c87762

Browse files
authored
Merge pull request #72 from lnbits/payment_success
feat: simple payment success page
2 parents d9a1c26 + c9ec730 commit 4c87762

3 files changed

Lines changed: 392 additions & 0 deletions

File tree

src/pages/PaymentSuccess.vue

Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
<template>
2+
<q-layout view="lHh Lpr lFf" class="pricing-shell">
3+
<q-header class="bg-transparent" style="color: inherit">
4+
<q-toolbar class="p-toolbar">
5+
<router-link :to="isLoggedIn ? '/' : '/login'" class="p-logo">
6+
<img src="/profile.svg" alt="LNbits" class="p-logo__img" />
7+
<span class="p-logo__text">
8+
<span class="text-italic" style="font-weight: 300; font-size: 0.88rem"
9+
>my</span
10+
><span class="q-ml-xs" style="font-weight: 700">LNbits</span>
11+
</span>
12+
</router-link>
13+
<q-space />
14+
<nav class="p-nav">
15+
<template v-if="isLoggedIn">
16+
<router-link to="/" class="p-nav__link gt-xs">Dashboard</router-link>
17+
<q-btn
18+
to="/instances"
19+
label="My Instances"
20+
no-caps
21+
unelevated
22+
size="md"
23+
class="p-nav__cta"
24+
/>
25+
</template>
26+
<template v-else>
27+
<router-link to="/login" class="p-nav__link gt-xs">Sign in</router-link>
28+
<q-btn
29+
to="/login"
30+
label="Get Started"
31+
no-caps
32+
unelevated
33+
size="md"
34+
class="p-nav__cta"
35+
/>
36+
</template>
37+
</nav>
38+
</q-toolbar>
39+
</q-header>
40+
41+
<q-page-container>
42+
<section class="p-hero p-hero--success">
43+
<div class="p-hero__glow p-hero__glow--a" aria-hidden="true" />
44+
<div class="p-hero__glow p-hero__glow--b" aria-hidden="true" />
45+
46+
<div class="p-hero__inner">
47+
<div class="p-success">
48+
<div class="p-success__icon">
49+
<q-icon name="check" size="32px" />
50+
</div>
51+
<h1 class="p-hero__h1">Payment Successful</h1>
52+
<p class="p-hero__sub">
53+
Please close this tab and return to the previous tab.
54+
</p>
55+
</div>
56+
</div>
57+
</section>
58+
59+
<footer class="p-foot">
60+
<div class="p-foot__inner">
61+
<div class="p-foot__brand">
62+
<img src="/profile.svg" alt="" class="p-foot__logo" />
63+
<span>
64+
<span
65+
class="text-italic"
66+
style="font-weight: 300; font-size: 0.82rem"
67+
>my</span
68+
><span class="q-ml-xs" style="font-weight: 700">LNbits</span>
69+
</span>
70+
</div>
71+
<nav class="p-foot__nav">
72+
<a href="https://lnbits.com" target="_blank" rel="noopener"
73+
>LNbits.com</a
74+
>
75+
<a href="https://docs.lnbits.com" target="_blank" rel="noopener"
76+
>Docs</a
77+
>
78+
<a
79+
href="https://github.com/lnbits/lnbits"
80+
target="_blank"
81+
rel="noopener"
82+
>GitHub</a
83+
>
84+
<router-link to="/terms-of-service">Terms</router-link>
85+
</nav>
86+
</div>
87+
</footer>
88+
</q-page-container>
89+
</q-layout>
90+
</template>
91+
92+
<script setup>
93+
import {computed, onMounted, ref} from 'vue'
94+
import {useQuasar} from 'quasar'
95+
import {saas} from 'boot/saas'
96+
97+
const q = useQuasar()
98+
const darkMode = ref(false)
99+
const isLoggedIn = computed(() => !!saas.email)
100+
101+
onMounted(() => {
102+
darkMode.value = localStorage.getItem('darkMode') === 'true'
103+
q.dark.set(darkMode.value)
104+
})
105+
</script>
106+
107+
<style scoped>
108+
.pricing-shell {
109+
min-height: 100vh;
110+
background:
111+
radial-gradient(circle at top left, rgba(178, 56, 255, 0.18), transparent 30%),
112+
radial-gradient(circle at bottom right, rgba(255, 79, 216, 0.14), transparent 28%),
113+
linear-gradient(180deg, #12071b 0%, #0d0917 48%, #09060f 100%);
114+
color: rgba(244, 238, 255, 0.88);
115+
}
116+
117+
.p-toolbar {
118+
padding: 0.75rem 1.5rem;
119+
}
120+
121+
.p-logo {
122+
display: flex;
123+
align-items: center;
124+
gap: 0.6rem;
125+
text-decoration: none;
126+
color: inherit;
127+
transition: opacity 150ms;
128+
}
129+
130+
.p-logo:hover {
131+
opacity: 0.85;
132+
}
133+
134+
.p-logo__img {
135+
width: 36px;
136+
height: 36px;
137+
object-fit: contain;
138+
}
139+
140+
.p-nav {
141+
display: flex;
142+
align-items: center;
143+
gap: 1rem;
144+
}
145+
146+
.p-nav__link {
147+
color: rgba(244, 238, 255, 0.5);
148+
text-decoration: none;
149+
font-size: 0.88rem;
150+
font-weight: 500;
151+
transition: color 150ms;
152+
}
153+
154+
.p-nav__link:hover {
155+
color: #fff;
156+
}
157+
158+
.p-nav__cta {
159+
background: linear-gradient(
160+
135deg,
161+
var(--q-primary, #b238ff),
162+
var(--q-secondary, #6d3cff)
163+
) !important;
164+
border-radius: 10px !important;
165+
font-weight: 700;
166+
letter-spacing: 0.01em;
167+
box-shadow: 0 4px 18px rgba(178, 56, 255, 0.2);
168+
transition: box-shadow 200ms, transform 150ms;
169+
}
170+
171+
.p-nav__cta:hover {
172+
box-shadow: 0 6px 26px rgba(178, 56, 255, 0.35);
173+
transform: translateY(-1px);
174+
}
175+
176+
.p-hero {
177+
position: relative;
178+
display: flex;
179+
align-items: center;
180+
justify-content: center;
181+
text-align: center;
182+
min-height: calc(100vh - 152px);
183+
padding: 3.2rem 2rem 4rem;
184+
overflow: hidden;
185+
}
186+
187+
.p-hero--success {
188+
min-height: calc(100vh - 137px);
189+
}
190+
191+
.p-hero__glow {
192+
position: absolute;
193+
border-radius: 50%;
194+
filter: blur(90px);
195+
opacity: 0.25;
196+
pointer-events: none;
197+
will-change: transform;
198+
}
199+
200+
.p-hero__glow--a {
201+
width: 520px;
202+
height: 520px;
203+
top: -12%;
204+
left: 12%;
205+
background: radial-gradient(
206+
circle,
207+
rgba(178, 56, 255, 0.55),
208+
transparent 70%
209+
);
210+
animation: p-glow-drift 18s ease-in-out infinite alternate;
211+
}
212+
213+
.p-hero__glow--b {
214+
width: 420px;
215+
height: 420px;
216+
bottom: -8%;
217+
right: 8%;
218+
background: radial-gradient(
219+
circle,
220+
rgba(255, 79, 216, 0.45),
221+
transparent 70%
222+
);
223+
animation: p-glow-drift 22s ease-in-out infinite alternate-reverse;
224+
}
225+
226+
@keyframes p-glow-drift {
227+
from {
228+
transform: translate(0, 0);
229+
}
230+
231+
to {
232+
transform: translate(30px, -20px);
233+
}
234+
}
235+
236+
@media (prefers-reduced-motion: reduce) {
237+
.p-hero__glow {
238+
animation: none;
239+
}
240+
}
241+
242+
.p-hero__inner {
243+
position: relative;
244+
z-index: 1;
245+
width: 100%;
246+
max-width: 720px;
247+
animation: p-fade-up 600ms ease-out;
248+
}
249+
250+
@keyframes p-fade-up {
251+
from {
252+
opacity: 0;
253+
transform: translateY(16px);
254+
}
255+
256+
to {
257+
opacity: 1;
258+
transform: translateY(0);
259+
}
260+
}
261+
262+
.p-success {
263+
padding: 3rem 2.25rem;
264+
border-radius: 24px;
265+
background: rgba(255, 255, 255, 0.04);
266+
border: 1px solid rgba(255, 255, 255, 0.08);
267+
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.28);
268+
backdrop-filter: blur(16px);
269+
}
270+
271+
.p-success__icon {
272+
width: 72px;
273+
height: 72px;
274+
margin: 0 auto 1.5rem;
275+
border-radius: 999px;
276+
display: flex;
277+
align-items: center;
278+
justify-content: center;
279+
color: #fff;
280+
background: linear-gradient(135deg, #41c98d, #2ea66f);
281+
box-shadow: 0 12px 36px rgba(65, 201, 141, 0.28);
282+
}
283+
284+
.p-hero__h1 {
285+
margin: 0;
286+
font-size: clamp(2rem, 4.5vw, 3.2rem);
287+
font-weight: 700;
288+
line-height: 1.12;
289+
letter-spacing: -0.025em;
290+
color: #fff;
291+
}
292+
293+
.p-hero__sub {
294+
margin: 1.25rem auto 0;
295+
max-width: 36rem;
296+
font-size: 1.05rem;
297+
line-height: 1.65;
298+
color: rgba(244, 238, 255, 0.65);
299+
}
300+
301+
.p-foot {
302+
padding: 2rem;
303+
border-top: 1px solid rgba(255, 255, 255, 0.06);
304+
}
305+
306+
.p-foot__inner {
307+
max-width: 1060px;
308+
margin: 0 auto;
309+
display: flex;
310+
align-items: center;
311+
justify-content: space-between;
312+
}
313+
314+
.p-foot__brand {
315+
display: flex;
316+
align-items: center;
317+
gap: 0.5rem;
318+
color: rgba(244, 238, 255, 0.5);
319+
}
320+
321+
.p-foot__logo {
322+
width: 28px;
323+
height: 28px;
324+
object-fit: contain;
325+
}
326+
327+
.p-foot__nav {
328+
display: flex;
329+
gap: 1.5rem;
330+
}
331+
332+
.p-foot__nav a {
333+
font-size: 0.82rem;
334+
color: rgba(244, 238, 255, 0.35);
335+
text-decoration: none;
336+
transition: color 150ms;
337+
}
338+
339+
.p-foot__nav a:hover {
340+
color: rgba(244, 238, 255, 0.75);
341+
}
342+
343+
@media (max-width: 599px) {
344+
.p-toolbar {
345+
padding: 0.5rem 1rem;
346+
}
347+
348+
.p-hero {
349+
min-height: calc(100vh - 144px);
350+
padding: 2.75rem 1.25rem 3rem;
351+
}
352+
353+
.p-hero__glow {
354+
display: none;
355+
}
356+
357+
.p-success {
358+
padding: 2.25rem 1.4rem;
359+
border-radius: 20px;
360+
}
361+
362+
.p-success__icon {
363+
width: 64px;
364+
height: 64px;
365+
margin-bottom: 1.25rem;
366+
}
367+
368+
.p-hero__h1 {
369+
font-size: 1.8rem;
370+
}
371+
372+
.p-hero__sub {
373+
font-size: 0.95rem;
374+
}
375+
376+
.p-foot__inner {
377+
flex-direction: column;
378+
gap: 1rem;
379+
text-align: center;
380+
}
381+
382+
.p-foot__nav {
383+
flex-wrap: wrap;
384+
justify-content: center;
385+
}
386+
}
387+
</style>

src/router/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export default route(function (/* { store, ssrContext } */) {
4343
to.path !== '/pricing' &&
4444
to.path !== '/terms-of-service' &&
4545
to.path !== '/payment-confirmation' &&
46+
to.path !== '/paid' &&
4647
to.path !== '/confirm' &&
4748
!saas.email
4849
) {

0 commit comments

Comments
 (0)