Skip to content

Commit c5ed225

Browse files
authored
feat: Add Sharable Links and Enhanced Success Dialog to Invoice Form (#314)
1 parent 5193365 commit c5ed225

File tree

4 files changed

+244
-16
lines changed

4 files changed

+244
-16
lines changed

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/create-invoice-form/src/lib/create-invoice-form.svelte

+228-14
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@
2323
initializeCreateInvoiceCurrencyManager,
2424
} from "@requestnetwork/shared-utils/index";
2525
// Components
26+
import Toaster from "@requestnetwork/shared-components/sonner.svelte";
27+
import Share from "@requestnetwork/shared-icons/share.svelte";
2628
import { InvoiceForm, InvoiceView } from "./invoice";
2729
import Button from "@requestnetwork/shared-components/button.svelte";
2830
import Status from "@requestnetwork/shared-components/status.svelte";
2931
import Modal from "@requestnetwork/shared-components/modal.svelte";
3032
import { EncryptionTypes, CipherProviderTypes } from "@requestnetwork/types";
3133
import { onDestroy, onMount, tick } from "svelte";
3234
import { CurrencyManager } from "@requestnetwork/currency";
35+
import { toast } from "svelte-sonner";
3336
3437
interface CipherProvider extends CipherProviderTypes.ICipherProvider {
3538
disconnectWallet: () => void;
@@ -39,6 +42,7 @@
3942
export let wagmiConfig: WagmiConfig;
4043
export let requestNetwork: RequestNetwork | null | undefined;
4144
export let currencies: string[] = [];
45+
export let singleInvoicePath = "/invoice";
4246
let cipherProvider: CipherProvider | undefined;
4347
4448
let account: GetAccountReturnType | undefined =
@@ -61,6 +65,9 @@
6165
6266
let defaultCurrencies: any[] = [];
6367
68+
let showSuccessDialog = false;
69+
let createdRequestId = "";
70+
6471
onMount(async () => {
6572
currencyManager = await initializeCreateInvoiceCurrencyManager(currencies);
6673
@@ -283,6 +290,7 @@
283290
284291
const hanldeCreateNewInvoice = () => {
285292
removeAllStatuses();
293+
handleCloseSuccessDialog();
286294
formData = getInitialFormData();
287295
};
288296
@@ -340,6 +348,11 @@
340348
addToStatus(APP_STATUS.PERSISTING_ON_CHAIN);
341349
await request.waitForConfirmation();
342350
addToStatus(APP_STATUS.REQUEST_CONFIRMED);
351+
352+
// Show success dialog after confirmation
353+
createdRequestId = request.requestId;
354+
removeAllStatuses();
355+
showSuccessDialog = true;
343356
} catch (error: any) {
344357
if (error.message.includes("Transaction confirmation not received")) {
345358
isTimeout = true;
@@ -351,6 +364,10 @@
351364
}
352365
}
353366
};
367+
368+
const handleCloseSuccessDialog = () => {
369+
showSuccessDialog = false;
370+
};
354371
</script>
355372

356373
<div
@@ -392,19 +409,75 @@
392409
onClose={handleCloseInvoiceModal}
393410
>
394411
<Status config={activeConfig} statuses={appStatus} />
395-
<div class="modal-footer">
396-
<Button
397-
type="button"
398-
onClick={() => handleGoToDashboard(activeConfig.dashboardLink)}
399-
text="Go to dashboard"
400-
disabled={!appStatus.includes(APP_STATUS.REQUEST_CONFIRMED)}
401-
/>
402-
<Button
403-
type="button"
404-
onClick={hanldeCreateNewInvoice}
405-
text="Create a new invoice"
406-
disabled={!appStatus.includes(APP_STATUS.REQUEST_CONFIRMED)}
407-
/>
412+
</Modal>
413+
<Modal
414+
title="Invoice Created Successfully!"
415+
config={activeConfig}
416+
isOpen={showSuccessDialog}
417+
onClose={handleCloseSuccessDialog}
418+
>
419+
<div class="success-modal">
420+
<div class="checkmark-container">
421+
<svg
422+
class="checkmark"
423+
xmlns="http://www.w3.org/2000/svg"
424+
viewBox="0 0 52 52"
425+
>
426+
<circle
427+
class="checkmark__circle"
428+
cx="26"
429+
cy="26"
430+
r="25"
431+
fill="none"
432+
/>
433+
<path
434+
class="checkmark__check"
435+
fill="none"
436+
d="M14.1 27.2l7.1 7.2 16.7-16.8"
437+
/>
438+
</svg>
439+
</div>
440+
<p class="success-message">
441+
Your invoice has been sent to<br />
442+
{formData.payeeAddress}
443+
</p>
444+
<p class="share-tip">
445+
Did you know that sharing your invoice via link cuts payment times by
446+
77%? Give it a try.
447+
</p>
448+
449+
<div class="share-buttons">
450+
<div
451+
class="copy-button-wrapper"
452+
on:click={() => {
453+
const shareUrl = `${window.location.origin}${singleInvoicePath}/${createdRequestId}`;
454+
navigator.clipboard.writeText(shareUrl);
455+
toast.success("Share link copied to clipboard!");
456+
}}
457+
>
458+
Copy the Link
459+
<Share />
460+
</div>
461+
</div>
462+
463+
<div class="action-buttons">
464+
<button
465+
class="primary-button"
466+
on:click={() => handleGoToDashboard(activeConfig.dashboardLink)}
467+
>
468+
Back to Dashboard
469+
</button>
470+
<button class="primary-button" on:click={hanldeCreateNewInvoice}>
471+
Create New Invoice
472+
</button>
473+
<button
474+
class="primary-button"
475+
on:click={() =>
476+
(window.location.href = `${window.location.origin}${singleInvoicePath}/${createdRequestId}`)}
477+
>
478+
View Invoice
479+
</button>
480+
</div>
408481
</div>
409482
</Modal>
410483
<Modal
@@ -437,9 +510,10 @@
437510
/>
438511
</div>
439512
</Modal>
513+
<Toaster />
440514
</div>
441515

442-
<style>
516+
<style lang="scss">
443517
@font-face {
444518
font-family: "Montserrat";
445519
src: url("./fonts/Montserrat-VariableFont_wght.ttf") format("truetype");
@@ -500,4 +574,144 @@
500574
width: fit-content !important;
501575
height: fit-content !important;
502576
}
577+
578+
.success-modal {
579+
display: flex;
580+
flex-direction: column;
581+
align-items: center;
582+
justify-content: center;
583+
width: 100%;
584+
max-width: 600px;
585+
padding: 32px;
586+
text-align: center;
587+
background: white;
588+
border-radius: 8px;
589+
}
590+
591+
.success-modal h2 {
592+
font-size: 24px;
593+
margin-bottom: 24px;
594+
color: #333;
595+
}
596+
597+
.checkmark-container {
598+
width: 80px;
599+
height: 80px;
600+
display: flex;
601+
justify-content: center;
602+
align-items: center;
603+
background-color: #4caf50;
604+
border-radius: 50%;
605+
margin-bottom: 26px;
606+
}
607+
608+
.checkmark {
609+
width: 56px;
610+
height: 56px;
611+
border-radius: 50%;
612+
display: block;
613+
stroke-width: 3;
614+
stroke: #fff;
615+
stroke-miterlimit: 10;
616+
animation: scale 0.3s ease-in-out 0.9s both;
617+
}
618+
619+
.checkmark__circle {
620+
stroke-dasharray: 166;
621+
stroke-dashoffset: 166;
622+
stroke-width: 3;
623+
stroke-miterlimit: 10;
624+
stroke: #fff;
625+
fill: none;
626+
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
627+
}
628+
629+
.checkmark__check {
630+
transform-origin: 50% 50%;
631+
stroke-dasharray: 48;
632+
stroke-dashoffset: 48;
633+
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
634+
}
635+
636+
.success-message {
637+
margin-bottom: 24px;
638+
color: #666;
639+
}
640+
641+
.share-tip {
642+
margin-bottom: 32px;
643+
color: #666;
644+
font-size: 14px;
645+
}
646+
647+
.share-buttons {
648+
display: flex;
649+
justify-content: center;
650+
margin-bottom: 32px;
651+
}
652+
653+
.copy-button-wrapper {
654+
display: flex;
655+
justify-content: space-between;
656+
align-items: center;
657+
cursor: pointer;
658+
background: #f6f6f7;
659+
padding: 12px 24px;
660+
border-radius: 8px;
661+
width: 180px;
662+
font-size: 14px;
663+
}
664+
665+
.action-buttons {
666+
display: flex;
667+
gap: 16px;
668+
justify-content: center;
669+
}
670+
671+
.action-button {
672+
padding: 12px 24px;
673+
border: none;
674+
border-radius: 8px;
675+
font-weight: 500;
676+
cursor: pointer;
677+
transition: all 0.2s;
678+
}
679+
680+
.primary-button {
681+
border: none;
682+
background: transparent;
683+
width: fit-content;
684+
color: var(--mainColor);
685+
cursor: pointer;
686+
}
687+
688+
.primary-button:hover {
689+
opacity: 0.9;
690+
}
691+
692+
@media (max-width: 600px) {
693+
.success-modal {
694+
padding: 24px;
695+
}
696+
697+
.action-buttons {
698+
flex-direction: column;
699+
}
700+
}
701+
702+
@keyframes stroke {
703+
100% {
704+
stroke-dashoffset: 0;
705+
}
706+
}
707+
708+
@keyframes scale {
709+
0%,
710+
100% {
711+
transform: none;
712+
}
713+
50% {
714+
transform: scale3d(1.1, 1.1, 1);
715+
}
716+
}
503717
</style>

packages/create-invoice-form/src/lib/react/CreateInvoiceForm.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface CreateInvoiceFormProps {
88
config: IConfig;
99
wagmiConfig: WagmiConfig;
1010
requestNetwork: RequestNetwork | null | undefined;
11+
singleInvoicePath: string;
1112
currencies?: string[];
1213
}
1314

@@ -25,6 +26,7 @@ export interface CreateInvoiceFormProps {
2526
* config={config}
2627
* wagmiConfig={wagmiConfig}
2728
* requestNetwork={requestNetwork}
29+
* singleInvoicePath={'/invoice'}
2830
* currencies={['ETH-MAINNET', 'USDC-MAINNET', 'USDC-MATIC']}
2931
* />
3032
*/

packages/single-invoice/src/lib/single-invoice.svelte

+12
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828
import Button from "@requestnetwork/shared-components/button.svelte";
2929
import Tooltip from "@requestnetwork/shared-components/tooltip.svelte";
3030
import Modal from "@requestnetwork/shared-components/modal.svelte";
31+
import Toaster from "@requestnetwork/shared-components/sonner.svelte";
3132
// Icons
3233
import Download from "@requestnetwork/shared-icons/download.svelte";
34+
import Share from "@requestnetwork/shared-icons/share.svelte";
3335
// Utils
3436
import {
3537
formatDate,
@@ -1045,6 +1047,15 @@
10451047
}}
10461048
/>
10471049
</Tooltip>
1050+
<Tooltip text="Share Invoice">
1051+
<Share
1052+
onClick={() => {
1053+
const shareUrl = `${window.location.origin}${window.location.pathname}`;
1054+
navigator.clipboard.writeText(shareUrl);
1055+
toast.success("Share link copied to clipboard!");
1056+
}}
1057+
/>
1058+
</Tooltip>
10481059
</h2>
10491060
<div class="invoice-address">
10501061
<h2>From:</h2>
@@ -1353,6 +1364,7 @@
13531364
</div>
13541365
</Modal>
13551366
{/if}
1367+
<Toaster />
13561368

13571369
<style>
13581370
.innerDrawer {

0 commit comments

Comments
 (0)