Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(frontend): adds vip qr code modal #3991

Merged
merged 135 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 130 commits
Commits
Show all changes
135 commits
Select commit Hold shift + click to select a range
01eb37a
implements VipQrCodeModal
BonomoAlessandro Dec 16, 2024
cee9183
adds invitation link address field
BonomoAlessandro Dec 16, 2024
a4db206
adds generate new link button
BonomoAlessandro Dec 16, 2024
cd54a85
adds countdown to regenerate qr code
BonomoAlessandro Dec 16, 2024
3580e27
rearranges code
BonomoAlessandro Dec 16, 2024
7ca84a2
adds vip qr code item to the menu
BonomoAlessandro Dec 16, 2024
64cfb2e
adds alt text to button
BonomoAlessandro Dec 16, 2024
e12f611
removes unused import
BonomoAlessandro Dec 16, 2024
1d7f8c1
resolves lint issue
BonomoAlessandro Dec 16, 2024
1bb1789
resolves lint issue
BonomoAlessandro Dec 16, 2024
872a275
🤖 Apply formatting changes
github-actions[bot] Dec 16, 2024
011897c
Merge branch 'feature(frontend)/displays-vip-qr-codes-option-in-menu'…
BonomoAlessandro Dec 16, 2024
f97e89b
opens qr code modal on menu click
BonomoAlessandro Dec 16, 2024
25f50e2
🤖 Updated i18n files
github-actions[bot] Dec 16, 2024
8c3e70a
🤖 Apply formatting changes
github-actions[bot] Dec 16, 2024
b2b46e9
🤖 Apply bindings changes
github-actions[bot] Dec 16, 2024
401fe5f
makes vip in settings optional
BonomoAlessandro Dec 16, 2024
3db52c7
Merge remote-tracking branch 'origin/feature(frontend)/displays-vip-q…
BonomoAlessandro Dec 16, 2024
cb60999
🤖 Apply bindings changes
github-actions[bot] Dec 16, 2024
0ed7b08
tries to fix tests
BonomoAlessandro Dec 16, 2024
903d651
🤖 Apply formatting changes
github-actions[bot] Dec 16, 2024
446e377
🤖 Apply bindings changes
github-actions[bot] Dec 16, 2024
b7c0133
implements tests for vip menu item
BonomoAlessandro Dec 17, 2024
55f3efd
reformat code
BonomoAlessandro Dec 17, 2024
6da0dea
reformat code
BonomoAlessandro Dec 17, 2024
ba5e7aa
implements reward canister api
BonomoAlessandro Dec 17, 2024
1fd8a1f
🤖 Apply formatting changes
github-actions[bot] Dec 17, 2024
ba49b3b
resolves lint issue
BonomoAlessandro Dec 17, 2024
7ab28ed
🤖 Apply formatting changes
github-actions[bot] Dec 17, 2024
0cec4de
Merge remote-tracking branch 'origin/feature(frontend)/displays-vip-q…
BonomoAlessandro Dec 17, 2024
27b4373
updates backend.did
BonomoAlessandro Dec 17, 2024
ebe723e
implements reward canister tests
BonomoAlessandro Dec 17, 2024
d0725a5
🤖 Apply formatting changes
github-actions[bot] Dec 17, 2024
983518f
Merge remote-tracking branch 'origin/main' into feature(frontend)/dis…
BonomoAlessandro Dec 17, 2024
3c63f59
reorders buttons in modal on mobile view
BonomoAlessandro Dec 17, 2024
af68afa
add is_vip function to reward canister
BonomoAlessandro Dec 17, 2024
f58477b
🤖 Apply formatting changes
github-actions[bot] Dec 17, 2024
93addf3
Merge remote-tracking branch 'origin/main' into feature(frontend)/imp…
BonomoAlessandro Dec 18, 2024
7560091
implements rewards api
BonomoAlessandro Dec 18, 2024
6d394e9
updates reward canister tests
BonomoAlessandro Dec 18, 2024
63d18aa
🤖 Apply formatting changes
github-actions[bot] Dec 18, 2024
a05b56c
implements reward canister
BonomoAlessandro Dec 18, 2024
0b4e930
🤖 Apply formatting changes
github-actions[bot] Dec 18, 2024
6e66f5c
Merge branch 'feature(frontend)/implement-reward-canister' into featu…
BonomoAlessandro Dec 18, 2024
9a1e806
implements reward canister service test cases
BonomoAlessandro Dec 18, 2024
e24552c
🤖 Apply formatting changes
github-actions[bot] Dec 18, 2024
063b64e
resolves lint issues
BonomoAlessandro Dec 18, 2024
b4f8a50
🤖 Apply formatting changes
github-actions[bot] Dec 18, 2024
056ad5f
resolves lint issues
BonomoAlessandro Dec 18, 2024
516e50c
Merge branch 'main' into feature(frontend)/implements-reward-canister…
BonomoAlessandro Dec 18, 2024
04a52c0
Merge branch 'feature(frontend)/implements-reward-canister-api' into …
BonomoAlessandro Dec 18, 2024
da24239
loads vip status from reward canister
BonomoAlessandro Dec 18, 2024
228348c
🤖 Apply formatting changes
github-actions[bot] Dec 18, 2024
a33c282
renames getUserInfo endpoint
BonomoAlessandro Dec 18, 2024
9d4ae15
renames getUserInfo endpoint
BonomoAlessandro Dec 18, 2024
c876584
uses updates reward-code service
BonomoAlessandro Dec 18, 2024
4e22b1f
🤖 Apply formatting changes
github-actions[bot] Dec 18, 2024
9aea3d7
🤖 Apply formatting changes
github-actions[bot] Dec 18, 2024
bb5470d
removes unused imports
BonomoAlessandro Dec 18, 2024
e8fbeaf
resolves lint issues
BonomoAlessandro Dec 18, 2024
6c76bd2
🤖 Apply formatting changes
github-actions[bot] Dec 18, 2024
411aba2
removes unused function
BonomoAlessandro Dec 18, 2024
cfa31b6
updates Vip qr code modal
BonomoAlessandro Dec 18, 2024
978428f
🤖 Apply formatting changes
github-actions[bot] Dec 18, 2024
7d56943
Merge remote-tracking branch 'origin/main' into feature(frontend)/imp…
BonomoAlessandro Dec 18, 2024
128805b
adds certified to reward code service
BonomoAlessandro Dec 18, 2024
15e6a1f
🤖 Apply formatting changes
github-actions[bot] Dec 18, 2024
5b0643c
adds rewards canister id
BonomoAlessandro Dec 18, 2024
49d0f2b
changes order of code
BonomoAlessandro Dec 18, 2024
60c50d3
updates reward service test cases
BonomoAlessandro Dec 18, 2024
cd4ee86
Merge branch 'main' into feature(frontend)/implements-reward-canister…
BonomoAlessandro Dec 18, 2024
33b3124
fixes service implementation
BonomoAlessandro Dec 18, 2024
067779d
🤖 Apply formatting changes
github-actions[bot] Dec 18, 2024
a8b9ef5
renames getVipStatus to isVipUser
BonomoAlessandro Dec 18, 2024
5d1a290
improves service code
BonomoAlessandro Dec 18, 2024
e365bef
improves service code
BonomoAlessandro Dec 18, 2024
c82c209
implements error handling in service
BonomoAlessandro Jan 6, 2025
61d03a8
reformat code
BonomoAlessandro Jan 6, 2025
7164bc1
handles certified in reward service
BonomoAlessandro Jan 6, 2025
dcc7665
reformat code
BonomoAlessandro Jan 6, 2025
25a91e9
🤖 Updated i18n files
github-actions[bot] Jan 6, 2025
5bf2b59
resolves lint issue
BonomoAlessandro Jan 6, 2025
4b8fc89
Merge branch 'main' into feature(frontend)/implements-reward-canister…
BonomoAlessandro Jan 6, 2025
61de068
Merge branch 'main' into feature(frontend)/displays-vip-qr-codes-opti…
BonomoAlessandro Jan 6, 2025
06b40fc
removes redundant data-tid definition
BonomoAlessandro Dec 17, 2024
fb48e03
Merge branch 'feature(frontend)/implements-reward-canister-api' into …
BonomoAlessandro Jan 6, 2025
4f93f50
fixes Menu
BonomoAlessandro Jan 6, 2025
220af6d
🤖 Apply formatting changes
github-actions[bot] Jan 6, 2025
e143ab7
fixes lint issue
BonomoAlessandro Jan 6, 2025
ef35d7f
fixes return values
BonomoAlessandro Jan 6, 2025
362862e
🤖 Apply formatting changes
github-actions[bot] Jan 6, 2025
3623b6b
Merge branch 'feature(frontend)/implements-reward-canister-api' into …
BonomoAlessandro Jan 6, 2025
be16955
adds Settings type
BonomoAlessandro Jan 6, 2025
ecf90d2
removes unused variables
BonomoAlessandro Jan 6, 2025
886864b
Merge remote-tracking branch 'origin/main' into feature(frontend)/add…
BonomoAlessandro Jan 6, 2025
f12318b
🤖 Apply formatting changes
github-actions[bot] Jan 6, 2025
ce19d3e
Merge branch 'feature(frontend)/implements-reward-canister-api' into …
BonomoAlessandro Jan 6, 2025
5fcc81b
🤖 Updated i18n files
github-actions[bot] Jan 6, 2025
9cdb053
adds source to IconVipQr.svelte
BonomoAlessandro Jan 6, 2025
bae43d8
🤖 Apply formatting changes
github-actions[bot] Jan 6, 2025
d66eeb2
resolves lint issues
BonomoAlessandro Jan 6, 2025
5e1b2f1
🤖 Apply formatting changes
github-actions[bot] Jan 6, 2025
16cf4e3
resolves lint issues
BonomoAlessandro Jan 6, 2025
3fd3317
🤖 Apply formatting changes
github-actions[bot] Jan 6, 2025
67d0479
Merge branch 'feature(frontend)/displays-vip-qr-codes-option-in-menu'…
BonomoAlessandro Jan 6, 2025
1bcb260
Merge remote-tracking branch 'origin/feature(frontend)/adds-vip-qr-co…
BonomoAlessandro Jan 6, 2025
8f84839
resolves lint issues
BonomoAlessandro Jan 6, 2025
1acf902
🤖 Apply formatting changes
github-actions[bot] Jan 6, 2025
5833036
reformat code
BonomoAlessandro Jan 6, 2025
93e784f
Merge remote-tracking branch 'origin/main' into feature(frontend)/add…
BonomoAlessandro Jan 6, 2025
7766f8e
🤖 Apply formatting changes
github-actions[bot] Jan 6, 2025
03d2249
resolves lint issue
BonomoAlessandro Jan 6, 2025
dac8cf2
🤖 Apply formatting changes
github-actions[bot] Jan 6, 2025
709372f
implements test cases for the vip qr code modal
BonomoAlessandro Jan 7, 2025
fee9a2e
🤖 Apply formatting changes
github-actions[bot] Jan 7, 2025
3cb6545
Merge remote-tracking branch 'origin/main' into feature(frontend)/add…
BonomoAlessandro Jan 7, 2025
7499b43
Merge branch 'main' into feature(frontend)/adds-vip-qr-code-modal
BonomoAlessandro Jan 7, 2025
f25aea8
resolves review feedback
BonomoAlessandro Jan 8, 2025
d38c314
resolves review feedback
BonomoAlessandro Jan 8, 2025
6dbf8fb
only tries to generate code 3 times
BonomoAlessandro Jan 8, 2025
c40dea1
resolves review feedback
BonomoAlessandro Jan 8, 2025
c0ae70e
🤖 Apply formatting changes
github-actions[bot] Jan 8, 2025
10c3fe0
displays "generating new code" while generating a new code
BonomoAlessandro Jan 8, 2025
9125b9e
Merge remote-tracking branch 'origin/main' into feature(frontend)/add…
BonomoAlessandro Jan 8, 2025
3b67b73
adds visibilityChange event listener
BonomoAlessandro Jan 8, 2025
d085779
🤖 Apply formatting changes
github-actions[bot] Jan 8, 2025
5861429
removes unused fragment
BonomoAlessandro Jan 8, 2025
c35b523
resolves review feedback
BonomoAlessandro Jan 9, 2025
81adf43
solves lint issue
BonomoAlessandro Jan 9, 2025
ea258c4
Merge branch 'main' into feature(frontend)/adds-vip-qr-code-modal
BonomoAlessandro Jan 9, 2025
9ada8ae
aligns modal buttons next to each other
BonomoAlessandro Jan 9, 2025
5230570
Merge branch 'main' into feature(frontend)/adds-vip-qr-code-modal
BonomoAlessandro Jan 9, 2025
075c640
fixes the breaking of the displayed url
BonomoAlessandro Jan 9, 2025
643d0ac
Merge branch 'main' into feature(frontend)/adds-vip-qr-code-modal
BonomoAlessandro Jan 9, 2025
71ec264
Merge branch 'main' into feature(frontend)/adds-vip-qr-code-modal
BonomoAlessandro Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/frontend/src/lib/components/core/Menu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import IconlyUfo from '$lib/components/icons/iconly/IconlyUfo.svelte';
import LicenseLink from '$lib/components/license-agreement/LicenseLink.svelte';
import ChangelogLink from '$lib/components/navigation/ChangelogLink.svelte';
import VipQrCodeModal from '$lib/components/qr/VipQrCodeModal.svelte';
import ButtonIcon from '$lib/components/ui/ButtonIcon.svelte';
import ButtonMenu from '$lib/components/ui/ButtonMenu.svelte';
import ExternalLink from '$lib/components/ui/ExternalLink.svelte';
Expand All @@ -31,9 +32,11 @@
NAVIGATION_MENU_VIP_BUTTON
} from '$lib/constants/test-ids.constants';
import { authIdentity } from '$lib/derived/auth.derived';
import { modalVipQrCode } from '$lib/derived/modal.derived';
import { networkId } from '$lib/derived/network.derived';
import { isVipUser } from '$lib/services/reward-code.services';
import { i18n } from '$lib/stores/i18n.store';
import { modalStore } from '$lib/stores/modal.store';
import {
isRouteActivity,
isRouteDappExplorer,
Expand Down Expand Up @@ -158,11 +161,10 @@
{/if}

{#if isVip}
<!-- TODO: implements on:click function -->
<ButtonMenu
ariaLabel={$i18n.navigation.alt.vip_qr_code}
testId={NAVIGATION_MENU_VIP_BUTTON}
on:click={() => {}}
on:click={modalStore.openVipQrCode}
>
<IconVipQr size="20" />
{$i18n.navigation.text.vip_qr_code}
Expand Down Expand Up @@ -204,3 +206,7 @@
</span>
</div>
</Popover>

{#if $modalVipQrCode}
<VipQrCodeModal />
{/if}
134 changes: 134 additions & 0 deletions src/frontend/src/lib/components/qr/VipQrCodeModal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<script lang="ts">
import { Modal, QRCode } from '@dfinity/gix-components';
import { isNullish, nonNullish } from '@dfinity/utils';
import { onDestroy, onMount } from 'svelte';
import IconAstronautHelmet from '$lib/components/icons/IconAstronautHelmet.svelte';
import ReceiveCopy from '$lib/components/receive/ReceiveCopy.svelte';
import Button from '$lib/components/ui/Button.svelte';
import ButtonCloseModal from '$lib/components/ui/ButtonCloseModal.svelte';
import ButtonGroup from '$lib/components/ui/ButtonGroup.svelte';
import ContentWithToolbar from '$lib/components/ui/ContentWithToolbar.svelte';
import SkeletonText from '$lib/components/ui/SkeletonText.svelte';
import { VIP_CODE_REGENERATE_INTERVAL_IN_SECONDS } from '$lib/constants/app.constants';
import {
VIP_CODE_REGENERATE_BUTTON,
VIP_QR_CODE_COPY_BUTTON
} from '$lib/constants/test-ids.constants';
import { authIdentity } from '$lib/derived/auth.derived';
import { nullishSignOut } from '$lib/services/auth.services';
import { getNewReward } from '$lib/services/reward-code.services';
import { i18n } from '$lib/stores/i18n.store';
import { modalStore } from '$lib/stores/modal.store';
import { replacePlaceholders } from '$lib/utils/i18n.utils';

let counter = VIP_CODE_REGENERATE_INTERVAL_IN_SECONDS;
let countdown: NodeJS.Timeout | undefined;
const maxRetriesToGetRewardCode = 3;
let retriesToGetRewardCode = 0;

let code: string;
const generateCode = async () => {
if (isNullish($authIdentity)) {
await nullishSignOut();
return;
}

const vipReward = await getNewReward($authIdentity);
if (nonNullish(vipReward)) {
code = vipReward.code;
} else {
retriesToGetRewardCode++;
}
};

const regenerateCode = async () => {
clearInterval(countdown);

if (retriesToGetRewardCode >= maxRetriesToGetRewardCode) {
return;
}

await generateCode();
Copy link
Member

@peterpeterparker peterpeterparker Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't the retries be asserted here? With the current implementation, we might indeed prevent any further calls, but the countdown would still proceed. Not a strong opinion, just a thought.

counter = VIP_CODE_REGENERATE_INTERVAL_IN_SECONDS;
countdown = setInterval(intervalFunction, 1000);
};

const intervalFunction = async () => {
counter--;

if (counter === 0) {
peterpeterparker marked this conversation as resolved.
Show resolved Hide resolved
await regenerateCode();
}
};

const onVisibilityChange = () => {
if (document.hidden) {
clearInterval(countdown);
} else {
countdown = setInterval(intervalFunction, 1000);
}
};

onMount(regenerateCode);
onDestroy(() => clearInterval(countdown));

let qrCodeUrl;
$: qrCodeUrl = `${window.location.origin}/?code=${code}`;
peterpeterparker marked this conversation as resolved.
Show resolved Hide resolved
</script>

<svelte:window on:visibilitychange={onVisibilityChange} />

<Modal on:nnsClose={modalStore.close}>
<svelte:fragment slot="title"
><span class="text-xl">{$i18n.vip.invitation.text.title}</span>
</svelte:fragment>

<ContentWithToolbar>
<div class="mx-auto mb-4 aspect-square h-80 max-h-[44vh] max-w-full p-4">
{#if nonNullish(code)}
<QRCode value={qrCodeUrl}>
<div slot="logo" class="flex items-center justify-center rounded-lg bg-white p-2">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't slot="logo" go in the parent div two levels above?

<div slot="logo" class="mx-auto mb-4 aspect-square h-80 max-h-[44vh] max-w-full p-4">
  {#if nonNullish(code)}
    <QRCode value={qrCodeUrl}>
	    <div class="flex items-center justify-center rounded-lg bg-white p-2">

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AntonioVentilii No it is a part of the QRCode

<IconAstronautHelmet />
</div>
</QRCode>
{/if}
</div>

{#if nonNullish(code)}
<div class="flex items-center justify-between gap-4 rounded-lg bg-brand-subtle px-3 py-2">
<output>{qrCodeUrl}</output>
<ReceiveCopy
address={qrCodeUrl}
copyAriaLabel={$i18n.vip.invitation.text.invitation_link_copied}
testId={VIP_QR_CODE_COPY_BUTTON}
/>
</div>

<span class="mb-4 block w-full pt-3 text-center text-sm text-tertiary">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think block is default, no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's not default

{#if 0 >= counter}
<span class="animate-pulse">{$i18n.vip.invitation.text.generating_new_code}</span>
{:else}
{replacePlaceholders($i18n.vip.invitation.text.regenerate_countdown_text, {
$counter: counter.toString()
})}
{/if}
</span>
{:else}
<span class="w-full"><SkeletonText /></span>
{/if}

<ButtonGroup styleClass="flex-col sm:flex-row" slot="toolbar">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really sure about this: we don't do it in any of the modals, nor in any usage of the Buttons... if we are going to change it, better to do it in another PR for all the ButtonGroup components, for consistency

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AntonioVentilii What exactly do you mean with

for all the ButtonGroup components, for consistency

I mean like this we just enable to add some custom styling to the element

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed, but why only for this modal? why not for all?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's defined like this in the design. The buttons should swap the alignment if the page gets smaller. To achieve this on this modal we need something like this.
Other modals do not need to swap the alignment

Copy link
Collaborator

@AntonioVentilii AntonioVentilii Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still not convinced about this... may you please double-check with design (especially if it's ONLY for this modal)? furthermore, will the "Close" button go up or down when it is vertical (i.e. flex-col-reverse)?

Screenshot 2025-01-09 at 14 45 45

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AntonioVentilii @artkorotkikh-dfinity just wrote me that he updated the designs.

<ButtonCloseModal />
<Button
paddingSmall
colorStyle="primary"
type="button"
fullWidth
on:click={regenerateCode}
testId={VIP_CODE_REGENERATE_BUTTON}
>
{$i18n.vip.invitation.text.generate_new_link}
</Button>
</ButtonGroup>
</ContentWithToolbar>
</Modal>
3 changes: 2 additions & 1 deletion src/frontend/src/lib/components/ui/ButtonGroup.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script lang="ts">
export let testId: string | undefined = undefined;
export let styleClass = '';
</script>

<div class="mb-2 flex w-full gap-3" data-tid={testId}>
<div class={`mb-2 flex w-full gap-3 ${styleClass}`} data-tid={testId}>
<slot />
</div>
3 changes: 3 additions & 0 deletions src/frontend/src/lib/constants/app.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,6 @@ export const ZERO = BigNumber.from(0n);
// Wallets
export const WALLET_TIMER_INTERVAL_MILLIS = (SECONDS_IN_MINUTE / 2) * 1000; // 30 seconds in milliseconds
export const WALLET_PAGINATION = 10n;

// VIP
export const VIP_CODE_REGENERATE_INTERVAL_IN_SECONDS = 45;
3 changes: 3 additions & 0 deletions src/frontend/src/lib/constants/test-ids.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@ export const TOKEN_MENU_ETH = 'token-menu-eth';
export const TOKEN_MENU_ETH_BUTTON = 'token-menu-eth-button';
export const TOKEN_MENU_BTC = 'token-menu-btc';
export const TOKEN_MENU_BTC_BUTTON = 'token-menu-btc-button';

export const VIP_QR_CODE_COPY_BUTTON = 'vip-qr-code-copy-button';
export const VIP_CODE_REGENERATE_BUTTON = 'vip-code-regenerate-button';
4 changes: 4 additions & 0 deletions src/frontend/src/lib/derived/modal.derived.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ export const modalAboutWhyOisy: Readable<boolean> = derived(
modalStore,
($modalStore) => $modalStore?.type === 'about-why-oisy'
);
export const modalVipQrCode: Readable<boolean> = derived(
modalStore,
($modalStore) => $modalStore?.type === 'vip-qr-code'
);
export const modalDAppDetails: Readable<boolean> = derived(
modalStore,
($modalStore) => $modalStore?.type === 'dapp-details'
Expand Down
9 changes: 9 additions & 0 deletions src/frontend/src/lib/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,15 @@
"loading_user_data": "Failed to load user data from reward canister.",
"claiming_reward": "Error while claiming reward."
}
},
"invitation": {
"text": {
"title": "Generate invitation link",
"invitation_link_copied": "Invitation link copied to clipboard.",
"generate_new_link": "Generate new link",
"generating_new_code": "Generating new code",
"regenerate_countdown_text": "New link will be generated in $counter sec"
}
}
},
"signer": {
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/src/lib/stores/modal.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface Modal<T> {
| 'ic-token'
| 'receive-bitcoin'
| 'about-why-oisy'
| 'vip-qr-code'
| 'dapp-details'
| 'successful-reward'
| 'failed-reward';
Expand Down Expand Up @@ -71,6 +72,7 @@ export interface ModalStore<T> extends Readable<ModalData<T>> {
openIcToken: () => void;
openReceiveBitcoin: () => void;
openAboutWhyOisy: () => void;
openVipQrCode: () => void;
openDappDetails: <D extends T>(data: D) => void;
openSuccessfulReward: () => void;
openFailedReward: () => void;
Expand Down Expand Up @@ -119,6 +121,7 @@ const initModalStore = <T>(): ModalStore<T> => {
openIcToken: setType('ic-token'),
openReceiveBitcoin: setType('receive-bitcoin'),
openAboutWhyOisy: setType('about-why-oisy'),
openVipQrCode: setType('vip-qr-code'),
openDappDetails: setTypeWithData('dapp-details'),
openSuccessfulReward: setType('successful-reward'),
openFailedReward: setType('failed-reward'),
Expand Down
9 changes: 9 additions & 0 deletions src/frontend/src/lib/types/i18n.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,15 @@ interface I18nVip {
};
error: { loading_reward: string; loading_user_data: string; claiming_reward: string };
};
invitation: {
text: {
title: string;
invitation_link_copied: string;
generate_new_link: string;
generating_new_code: string;
regenerate_countdown_text: string;
};
};
}

interface I18nSigner {
Expand Down
106 changes: 106 additions & 0 deletions src/frontend/src/tests/lib/components/qr/VipQrCodeModal.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { NewVipRewardResponse } from '$declarations/rewards/rewards.did';
import * as rewardApi from '$lib/api/reward.api';
import VipQrCodeModal from '$lib/components/qr/VipQrCodeModal.svelte';
import {
VIP_CODE_REGENERATE_BUTTON,
VIP_QR_CODE_COPY_BUTTON
} from '$lib/constants/test-ids.constants';
import * as authStore from '$lib/derived/auth.derived';
import { mockIdentity } from '$tests/mocks/identity.mock';
import type { Identity } from '@dfinity/agent';
import { render, waitFor } from '@testing-library/svelte';
import { readable } from 'svelte/store';
import { vi } from 'vitest';

describe('VipQrCodeModal', () => {
const qrCodeSelector = `div[data-tid="qr-code"]`;
const urlSelector = `output`;
const copyButtonSelector = `button[data-tid=${VIP_QR_CODE_COPY_BUTTON}]`;
const regenerateButtonSelector = `button[data-tid=${VIP_CODE_REGENERATE_BUTTON}]`;

const mockAuthStore = (value: Identity | null = mockIdentity) =>
vi.spyOn(authStore, 'authIdentity', 'get').mockImplementation(() => readable(value));

const mockedNewRewardResponse: NewVipRewardResponse = {
VipReward: {
code: '1234567890'
}
};

it('should render the vip qr code modal items', async () => {
mockAuthStore();
vi.spyOn(rewardApi, 'getNewVipReward').mockResolvedValue(mockedNewRewardResponse);

const { container } = render(VipQrCodeModal);

await waitFor(() => {
const qrCode: HTMLDivElement | null = container.querySelector(qrCodeSelector);
const qrCodeURL: HTMLOutputElement | null = container.querySelector(urlSelector);
const copyButton: HTMLButtonElement | null = container.querySelector(copyButtonSelector);
const regenerateButton: HTMLButtonElement | null =
container.querySelector(regenerateButtonSelector);

if (
qrCode === null ||
qrCodeURL === null ||
copyButton === null ||
regenerateButton === null
) {
throw new Error('one of the elements is not yet loaded.');
}

expect(qrCode).toBeInTheDocument();

expect(qrCodeURL).toBeInTheDocument();
expect(qrCodeURL?.textContent?.includes(mockedNewRewardResponse.VipReward.code));

expect(copyButton).toBeInTheDocument();

expect(regenerateButton).toBeInTheDocument();
});
});

it('should regenerate reward code', async () => {
const regeneratedNewRewardResponse: NewVipRewardResponse = {
VipReward: {
code: '0987654321'
}
};

mockAuthStore();
vi.spyOn(rewardApi, 'getNewVipReward').mockResolvedValue(mockedNewRewardResponse);

const { container } = render(VipQrCodeModal);

await waitFor(() => {
const qrCodeURL: HTMLOutputElement | null = container.querySelector(urlSelector);
const regenerateButton: HTMLButtonElement | null =
container.querySelector(regenerateButtonSelector);

if (qrCodeURL === null || regenerateButton === null) {
throw new Error('one of the elements is not yet loaded.');
}

expect(qrCodeURL).toBeInTheDocument();
expect(qrCodeURL?.textContent?.includes(mockedNewRewardResponse.VipReward.code));

expect(regenerateButton).toBeInTheDocument();

vi.spyOn(rewardApi, 'getNewVipReward').mockResolvedValue(regeneratedNewRewardResponse);
regenerateButton.click();
});

await waitFor(() => {
const reloadedQrCodeUrl: HTMLOutputElement | null = container.querySelector(urlSelector);

if (
reloadedQrCodeUrl === null ||
!reloadedQrCodeUrl?.textContent?.includes(regeneratedNewRewardResponse.VipReward.code)
) {
throw new Error('reward code not yet reloaded.');
}

expect(reloadedQrCodeUrl?.textContent?.includes(regeneratedNewRewardResponse.VipReward.code));
});
});
});
Loading