Skip to content

Commit

Permalink
Merge branch 'main' into feat(frontend)/add-lamports-type
Browse files Browse the repository at this point in the history
  • Loading branch information
loki344 authored Jan 7, 2025
2 parents 8947742 + ba23c18 commit 168c025
Show file tree
Hide file tree
Showing 19 changed files with 82 additions and 62 deletions.
91 changes: 63 additions & 28 deletions src/frontend/src/lib/services/reward-code.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,48 @@ import {
} from '$lib/api/reward.api';
import { i18n } from '$lib/stores/i18n.store';
import { toastsError } from '$lib/stores/toasts.store';
import { AlreadyClaimedError, InvalidCodeError, UserNotVipError } from '$lib/types/errors';
import type { ResultSuccess } from '$lib/types/utils';
import type { Identity } from '@dfinity/agent';
import { fromNullable } from '@dfinity/utils';
import { get } from 'svelte/store';

const queryVipUser = async ({
identity,
certified
}: {
const queryVipUser = async (params: {
identity: Identity;
certified: boolean;
}): Promise<ResultSuccess> => {
const userData = await getUserInfoApi({
identity,
certified,
...params,
nullishIdentityErrorMessage: get(i18n).auth.error.no_internet_identity
});

return { success: fromNullable(userData.is_vip) === true };
};

export const isVipUser = async ({ identity }: { identity: Identity }): Promise<ResultSuccess> => {
/**
* Checks if a user is a VIP user.
*
* This function performs **always** a query (not certified) to determine the VIP status of a user.
*
* @async
* @param {Object} params - The parameters required to check VIP status.
* @param {Identity} params.identity - The user's identity for authentication.
* @returns {Promise<ResultSuccess>} - Resolves with the result indicating if the user is a VIP.
*
* @throws {Error} Displays an error toast and logs the error if the query fails.
*/
export const isVipUser = async (params: { identity: Identity }): Promise<ResultSuccess> => {
try {
return await queryVipUser({ identity, certified: false });
} catch (err) {
return await queryVipUser({ ...params, certified: false });
} catch (err: unknown) {
const { vip } = get(i18n);
toastsError({
msg: { text: vip.reward.error.loading_user_data },
err
});

return { success: false, err };
}
return { success: false };
};

const updateReward = async (identity: Identity): Promise<VipReward> => {
Expand All @@ -50,22 +60,34 @@ const updateReward = async (identity: Identity): Promise<VipReward> => {
return response.VipReward;
}
if ('NotImportantPerson' in response) {
throw new Error('User is not VIP');
throw new UserNotVipError();
}
throw new Error('Unknown error');
};

// The call to generate a new reward code will always be an update call and cannot be a query.
/**
* Generates a new VIP reward code.
*
* This function **always** makes an **update** call and cannot be a query.
*
* @async
* @param {Identity} identity - The user's identity for authentication.
* @returns {Promise<VipReward | undefined>} - Resolves with the generated VIP reward or `undefined` if the operation fails.
*
* @throws {Error} Displays an error toast and logs the error if the update call fails.
*/
export const getNewReward = async (identity: Identity): Promise<VipReward | undefined> => {
try {
return await updateReward(identity);
} catch (err) {
} catch (err: unknown) {
const { vip } = get(i18n);
toastsError({
msg: { text: vip.reward.error.loading_reward },
err
});
}

// TODO: should return a ResultSuccess
};

const updateVipReward = async ({
Expand All @@ -74,41 +96,54 @@ const updateVipReward = async ({
}: {
identity: Identity;
code: string;
}): Promise<ResultSuccess> => {
}): Promise<void> => {
const response = await claimVipRewardApi({
identity,
vipReward: { code },
nullishIdentityErrorMessage: get(i18n).auth.error.no_internet_identity
});

if ('Success' in response) {
return { success: true };
return;
}

if ('InvalidCode' in response) {
throw new InvalidCodeError();
}
if ('InvalidCode' in response || 'AlreadyClaimed' in response) {
return { success: false };

if ('AlreadyClaimed' in response) {
throw new AlreadyClaimedError();
}

throw new Error('Unknown error');
};

// The call to claim a reward with a reward code will always be an update call and cannot be a query.
export const claimVipReward = async ({
identity,
code
}: {
/**
* Claims a VIP reward using a provided reward code.
*
* This function **always** makes an **update** call and cannot be a query.
*
* @async
* @param {Object} params - The parameters required to claim the reward.
* @param {Identity} params.identity - The user's identity for authentication.
* @param {string} params.code - The reward code to claim the VIP reward.
* @returns {Promise<ResultSuccess>} - Resolves with the result of the claim if successful.
*
* @throws {Error} Throws an error if the update call fails or the reward cannot be claimed.
*/
export const claimVipReward = async (params: {
identity: Identity;
code: string;
}): Promise<ResultSuccess> => {
try {
return await updateVipReward({
identity,
code
});
} catch (err) {
await updateVipReward(params);
return { success: true };
} catch (err: unknown) {
const { vip } = get(i18n);
toastsError({
msg: { text: vip.reward.error.claiming_reward },
err
});
return { success: false, err };
}
return { success: false };
};
4 changes: 4 additions & 0 deletions src/frontend/src/lib/types/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import type { TokenId } from '$lib/types/token';

export class UserProfileNotFoundError extends Error {}

export class UserNotVipError extends Error {}
export class InvalidCodeError extends Error {}
export class AlreadyClaimedError extends Error {}

export class LoadIdbAddressError extends Error {
constructor(private readonly _tokenId: TokenId) {
super();
Expand Down
17 changes: 1 addition & 16 deletions src/frontend/src/sol/providers/sol-rpc.providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@ import {
SOLANA_RPC_HTTP_URL_MAINNET,
SOLANA_RPC_HTTP_URL_TESTNET
} from '$env/networks/networks.sol.env';
import { i18n } from '$lib/stores/i18n.store';
import { replacePlaceholders } from '$lib/utils/i18n.utils';
import {
SolanaNetworks,
type SolRpcConnectionConfig,
type SolanaNetworkType
} from '$sol/types/network';
import { assertNonNullish } from '@dfinity/utils';
import { createSolanaRpc } from '@solana/rpc';
import { get } from 'svelte/store';

const rpcs: Record<SolanaNetworkType, SolRpcConnectionConfig> = {
[SolanaNetworks.mainnet]: {
Expand All @@ -30,18 +26,7 @@ const rpcs: Record<SolanaNetworkType, SolRpcConnectionConfig> = {
}
};

const solanaRpcConfig = (network: SolanaNetworkType): SolRpcConnectionConfig => {
const solRpc = rpcs[network];

assertNonNullish(
solRpc,
replacePlaceholders(get(i18n).init.error.no_solana_rpc, {
$network: network.toString()
})
);

return solRpc;
};
const solanaRpcConfig = (network: SolanaNetworkType): SolRpcConnectionConfig => rpcs[network];

export const solanaHttpRpc = (network: SolanaNetworkType): ReturnType<typeof createSolanaRpc> => {
const rpc = solanaRpcConfig(network);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { HERO_CONTEXT_KEY } from '$lib/stores/hero.store';
import { mockValidIcCkToken } from '$tests/mocks/ic-tokens.mock';
import { render } from '@testing-library/svelte';
import { readable } from 'svelte/store';
import { expect } from 'vitest';

describe('ConvertToCkBTC', () => {
const buttonId = 'convert-to-ckbtc-button';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { HttpAgent } from '@dfinity/agent';
import { BitcoinCanister, type BitcoinNetwork } from '@dfinity/ckbtc';
import { jsonReplacer } from '@dfinity/utils';
import { waitFor } from '@testing-library/svelte';
import { type MockInstance } from 'vitest';
import type { MockInstance } from 'vitest';
import { mock } from 'vitest-mock-extended';

describe('btc-wallet.worker', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { ethTransactionsStore } from '$eth/stores/eth-transactions.store';
import { bn3 } from '$tests/mocks/balances.mock';
import { createMockEthTransactions } from '$tests/mocks/eth-transactions.mock';
import { get } from 'svelte/store';
import { expect } from 'vitest';

describe('eth-transactions.store', () => {
const tokenId = ETHEREUM_TOKEN_ID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as toastsStore from '$lib/stores/toasts.store';
import { bn1 } from '$tests/mocks/balances.mock';
import { createMockIcTransactionsUi } from '$tests/mocks/ic-transactions.mock';
import { get } from 'svelte/store';
import { expect, type MockInstance } from 'vitest';
import type { MockInstance } from 'vitest';

describe('ic-transactions.services', () => {
describe('onLoadTransactionsError', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/tests/icp/services/icrc.services.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { IcrcLedgerCanister } from '@dfinity/ledger-icrc';
import { Principal } from '@dfinity/principal';
import { fromNullable, nonNullish } from '@dfinity/utils';
import { get } from 'svelte/store';
import { type MockInstance } from 'vitest';
import type { MockInstance } from 'vitest';
import { mock } from 'vitest-mock-extended';

describe('icrc.services', () => {
Expand Down
1 change: 0 additions & 1 deletion src/frontend/src/tests/lib/api/idb.api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
} from '$lib/api/idb.api';
import { mockPrincipal } from '$tests/mocks/identity.mock';
import * as idbKeyval from 'idb-keyval';
import { beforeEach, describe, expect, it, vi } from 'vitest';

vi.mock('idb-keyval', () => ({
createStore: vi.fn(() => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { mockEthAddress } from '$tests/mocks/eth.mocks';
import { mockValidIcCkToken, mockValidIcToken } from '$tests/mocks/ic-tokens.mock';
import { mockValidToken } from '$tests/mocks/tokens.mock';
import { get } from 'svelte/store';
import { beforeEach } from 'vitest';

describe('all-tokens.derived', () => {
const mockIcrcToken: IcrcCustomToken = {
Expand Down
1 change: 0 additions & 1 deletion src/frontend/src/tests/lib/derived/network.derived.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { mockIdentity } from '$tests/mocks/identity.mock';
import { mockPage } from '$tests/mocks/page.store.mock';
import { encodeIcrcAccount } from '@dfinity/ledger-icrc';
import { get } from 'svelte/store';
import { beforeEach, describe, expect, test, vi } from 'vitest';

describe('network.derived', () => {
beforeEach(() => {
Expand Down
1 change: 0 additions & 1 deletion src/frontend/src/tests/lib/derived/token.derived.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { parseTokenId } from '$lib/validation/token.validation';
import { mockValidErc20Token } from '$tests/mocks/erc20-tokens.mock';
import { mockValidIcToken } from '$tests/mocks/ic-tokens.mock';
import { get } from 'svelte/store';
import { expect } from 'vitest';

describe('token.derived', () => {
const mockEr20UserToken: Erc20UserToken = {
Expand Down
1 change: 0 additions & 1 deletion src/frontend/src/tests/lib/services/auth.services.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { signOut } from '$lib/services/auth.services';
import { authStore } from '$lib/stores/auth.store';
import { vi } from 'vitest';

const rootLocation = 'https://oisy.com/';
const activityLocation = 'https://oisy.com/activity';
Expand Down
1 change: 0 additions & 1 deletion src/frontend/src/tests/lib/services/batch.services.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { batch } from '$lib/services/batch.services';
import { expect } from 'vitest';

describe('batch.services', () => {
describe('batch', () => {
Expand Down
1 change: 0 additions & 1 deletion src/frontend/src/tests/lib/services/rest.services.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { retry } from '$lib/services/rest.services';
import { expect } from 'vitest';

describe('rest.services', () => {
describe('retry', () => {
Expand Down
14 changes: 11 additions & 3 deletions src/frontend/src/tests/lib/services/reward-code.services.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ import * as rewardApi from '$lib/api/reward.api';
import { claimVipReward, getNewReward, isVipUser } from '$lib/services/reward-code.services';
import { i18n } from '$lib/stores/i18n.store';
import * as toastsStore from '$lib/stores/toasts.store';
import { AlreadyClaimedError, InvalidCodeError } from '$lib/types/errors';
import en from '$tests/mocks/i18n.mock';
import { mockIdentity } from '$tests/mocks/identity.mock';
import { get } from 'svelte/store';
import { vi } from 'vitest';

const nullishIdentityErrorMessage = en.auth.error.no_internet_identity;

describe('reward-code', () => {
beforeEach(() => {
vi.spyOn(console, 'error').mockImplementation(() => {});
});

describe('isVip', () => {
const mockedUserData: UserData = {
is_vip: [true],
Expand Down Expand Up @@ -122,7 +126,9 @@ describe('reward-code', () => {
vipReward: { code: '1234567890' },
nullishIdentityErrorMessage
});
expect(result).toEqual({ success: false });
expect(result.success).toBeFalsy();
expect(result.err).not.toBeUndefined();
expect(result.err).toBeInstanceOf(InvalidCodeError);
});

it('should return false if an already used vip reward code is used', async () => {
Expand All @@ -138,7 +144,9 @@ describe('reward-code', () => {
vipReward: { code: '1234567890' },
nullishIdentityErrorMessage
});
expect(result).toEqual({ success: false });
expect(result.success).toBeFalsy();
expect(result.err).not.toBeUndefined();
expect(result.err).toBeInstanceOf(AlreadyClaimedError);
});
});
});
1 change: 0 additions & 1 deletion src/frontend/src/tests/lib/utils/convert.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { validateConvertAmount } from '$lib/utils/convert.utils';
import { BigNumber } from 'alchemy-sdk';
import { describe } from 'vitest';

describe('validateConvertAmount', () => {
const userAmount = BigNumber.from(200000n);
Expand Down
1 change: 0 additions & 1 deletion src/frontend/src/tests/lib/utils/nav.utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
type RouteParams
} from '$lib/utils/nav.utils';
import type { LoadEvent, NavigationTarget, Page } from '@sveltejs/kit';
import { describe, expect } from 'vitest';

describe('nav.utils', () => {
const mockGoTo = vi.fn();
Expand Down
1 change: 0 additions & 1 deletion src/frontend/src/tests/lib/utils/onramper.utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
import { mockBtcAddress } from '$tests/mocks/btc.mock';
import { mockEthAddress } from '$tests/mocks/eth.mocks';
import { mockAccountIdentifierText } from '$tests/mocks/identity.mock';
import { describe } from 'vitest';

describe('onramper.utils', () => {
describe('buildOnramperLink', () => {
Expand Down

0 comments on commit 168c025

Please sign in to comment.