Skip to content

feat(launchpad2): DRAFT #7023

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

Draft
wants to merge 179 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
179 commits
Select commit Hold shift + click to select a range
6b855e9
Add canister method to store favorit projects
mstrasinskis Jun 19, 2025
3740cdb
Add tests
mstrasinskis Jun 19, 2025
65681d4
Add related types
mstrasinskis Jun 20, 2025
1bea82c
Add getFavProjects/setFavProjects api
mstrasinskis Jun 20, 2025
a6bf493
test: set/get fav projects
mstrasinskis Jun 20, 2025
2f480e1
Add CardList component
mstrasinskis Jun 27, 2025
14e9363
cleanup
mstrasinskis Jun 27, 2025
39115fc
Add Launchpad2 page
mstrasinskis Jun 27, 2025
1dd7c2d
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jun 27, 2025
c447bb3
i18n
mstrasinskis Jun 27, 2025
3847a62
Add testId to CardList
mstrasinskis Jun 27, 2025
b2e5e19
Launchpad2Po
mstrasinskis Jun 27, 2025
54c0821
refactor: Move store logic outside
mstrasinskis Jun 27, 2025
cf9f8ca
refactor: extract UpcomingLaunchesCards
mstrasinskis Jun 27, 2025
e079d0b
Add getUpcomingLaunchesCards util
mstrasinskis Jun 28, 2025
f4bc07c
refactor: move AdoptedSnsProposals
mstrasinskis Jun 29, 2025
36565be
cleanup
mstrasinskis Jun 29, 2025
9dcfa5a
test: getUpcomingLaunchesCards
mstrasinskis Jun 29, 2025
7e01d35
getLaunchedSnsProjectCards util
mstrasinskis Jun 30, 2025
60bd8fc
test: getLaunchedSnsProjectCards
mstrasinskis Jun 30, 2025
3883403
Revert "test: getLaunchedSnsProjectCards"
mstrasinskis Jun 30, 2025
081089f
Revert "getLaunchedSnsProjectCards util"
mstrasinskis Jun 30, 2025
63b53f2
test: Launchpad2
mstrasinskis Jun 30, 2025
0476d32
refactor: Launchpad2
mstrasinskis Jul 1, 2025
d2cf70a
CardList mobileHorizontalScroll style
mstrasinskis Jul 1, 2025
ed88602
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 1, 2025
15a4658
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 1, 2025
3762062
Update title mobile styles
mstrasinskis Jul 1, 2025
e8988a2
IconCoin
mstrasinskis Jul 2, 2025
5407904
IconSearch
mstrasinskis Jul 2, 2025
b90d4d1
IconStar
mstrasinskis Jul 2, 2025
0a32d7e
LogoWrapper --logo-size
mstrasinskis Jul 2, 2025
fec069f
CardFrame style component
mstrasinskis Jul 2, 2025
81e7f46
Remove UpcomingLaunchesCards
mstrasinskis Jul 2, 2025
523aae1
launchpad mixins
mstrasinskis Jul 2, 2025
e7adc69
labels
mstrasinskis Jul 2, 2025
65b5a65
OngoingProjectCard (new styles/outdated values)
mstrasinskis Jul 2, 2025
cb75cb0
Update ongoingProject card logic
mstrasinskis Jul 3, 2025
bc861e3
remove local icons
mstrasinskis Jul 3, 2025
fae857c
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 3, 2025
4f59735
cleanup imports
mstrasinskis Jul 3, 2025
6a145e8
replace icon to gix
mstrasinskis Jul 3, 2025
0c7804c
Fix minIcp source
mstrasinskis Jul 3, 2025
074ee81
Add OngoingProjectCardPo
mstrasinskis Jul 3, 2025
5fc6aa6
test: OngoingProjectCard
mstrasinskis Jul 3, 2025
2d2a99c
Tag colour
mstrasinskis Jul 4, 2025
b57b4f0
Update layout css
mstrasinskis Jul 4, 2025
92bb8e8
Avoid carousel for a single entry
mstrasinskis Jul 4, 2025
e5720d6
fix footer aligment
mstrasinskis Jul 4, 2025
f75a1a7
Add UpcomingProjectCard component
mstrasinskis Jul 4, 2025
a9a82f1
Add CreateSnsProposalCard component
mstrasinskis Jul 4, 2025
9adeb3b
Mobile/desktop layout
mstrasinskis Jul 4, 2025
d587e3d
Add deprecation warning
mstrasinskis Jul 4, 2025
7e2fcef
Use 3 column only for xlarge
mstrasinskis Jul 5, 2025
649ea8d
Add ProjectCard2 component
mstrasinskis Jul 5, 2025
4af442b
relaRepFix imports
mstrasinskis Jul 6, 2025
a0f7fa8
test: UpcomingProjectCard
mstrasinskis Jul 6, 2025
f8e5a3a
test: CreateSnsProposalCard
mstrasinskis Jul 6, 2025
2ccde58
ProjectCard2 / bind new fields
mstrasinskis Jul 7, 2025
cddc003
ProjectCard2 labels and styles
mstrasinskis Jul 7, 2025
acc486f
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 7, 2025
062e637
Fix labels duplication
mstrasinskis Jul 7, 2025
541427c
Fix labels
mstrasinskis Jul 7, 2025
db13c4d
refactro: abstract CardFramePo
mstrasinskis Jul 8, 2025
23453b2
fix h3
mstrasinskis Jul 8, 2025
debe95a
test background icon
mstrasinskis Jul 8, 2025
6772846
test: ProjectCard2
mstrasinskis Jul 8, 2025
ab2429c
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 8, 2025
e3d801a
Linkable cards on mobile
mstrasinskis Jul 8, 2025
902035b
fix imports
mstrasinskis Jul 8, 2025
6ee3eae
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 8, 2025
60424a8
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 8, 2025
d4c3239
fix imports
mstrasinskis Jul 8, 2025
81f0b13
fix imports
mstrasinskis Jul 8, 2025
d236968
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 8, 2025
95be377
cleanup: remove test data
mstrasinskis Jul 8, 2025
56a99f2
Apply formatUsdValue
mstrasinskis Jul 8, 2025
6e5bfb3
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 8, 2025
fbe40d1
reset test changes
mstrasinskis Jul 8, 2025
fb8ca69
Add skeletons to launchpad2
mstrasinskis Jul 8, 2025
1024042
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 8, 2025
e8308f5
isLoading prop
mstrasinskis Jul 8, 2025
b5c011e
cleanup labels
mstrasinskis Jul 8, 2025
dacebbe
fix: cap.icp field source
mstrasinskis Jul 9, 2025
eaa848f
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 9, 2025
6a2b4df
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 9, 2025
bad0f36
test: launchpad page
mstrasinskis Jul 9, 2025
23f7dcc
cleanup test data
mstrasinskis Jul 9, 2025
131ca56
Update launchpad utils test
mstrasinskis Jul 9, 2025
86aefe5
Redesign desktop voting results
mstrasinskis Jul 9, 2025
704052f
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 9, 2025
286494c
Update description field styles
mstrasinskis Jul 9, 2025
a8c7bc5
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 10, 2025
9b0d597
display cards from the launchpad2
mstrasinskis Jul 10, 2025
6798312
Adapt card size
mstrasinskis Jul 10, 2025
2cb3b3e
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 10, 2025
cb083e2
Fix imports
mstrasinskis Jul 10, 2025
013d3e2
cleanup
mstrasinskis Jul 10, 2025
8563f62
H5->H3
mstrasinskis Jul 10, 2025
55ebb80
test: Launchpad cards on portfolio page
mstrasinskis Jul 10, 2025
d35cc24
fix sns projects source
mstrasinskis Jul 11, 2025
4f6f439
Add new DTO types
mstrasinskis Jul 11, 2025
2dee4b0
Update MetricsDto types
mstrasinskis Jul 11, 2025
672560b
Add metrics to mock data
mstrasinskis Jul 11, 2025
2e104fc
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 11, 2025
59221fa
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 11, 2025
6535795
Add reward event derived store
mstrasinskis Jul 11, 2025
7452266
Update sns metrics store
mstrasinskis Jul 11, 2025
03217fe
cleanup
mstrasinskis Jul 11, 2025
ab5fbd1
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 13, 2025
65bb73c
refactor: rename latest reward event
mstrasinskis Jul 13, 2025
29448de
Add metrics and latest reward event to snsFull project
mstrasinskis Jul 13, 2025
94c1855
cleanup
mstrasinskis Jul 13, 2025
8e2abd2
Revert "cleanup"
mstrasinskis Jul 13, 2025
38c66ba
test: metrics and last reward event in full project store
mstrasinskis Jul 13, 2025
3c01c30
New util snsProjectWeeklyProposalActivity
mstrasinskis Jul 13, 2025
f01c3ce
Display projects' weekly activity
mstrasinskis Jul 13, 2025
28468af
Make no mocked metrics by default
mstrasinskis Jul 13, 2025
033f9e4
test: snsProjectWeeklyProposalActivity
mstrasinskis Jul 13, 2025
9de8942
Update test ids
mstrasinskis Jul 13, 2025
1a603e1
fix imports
mstrasinskis Jul 13, 2025
a1fe8fb
Update tests
mstrasinskis Jul 13, 2025
dad6d7f
refactoring
mstrasinskis Jul 14, 2025
5ac77fb
New snsProjectIcpInTreasuryPercentage util
mstrasinskis Jul 14, 2025
b2ad79b
test: snsProjectIcpInTreasuryPercentage
mstrasinskis Jul 14, 2025
6a86ee1
Display icp treasury
mstrasinskis Jul 14, 2025
6d9a943
test: icp treasury display
mstrasinskis Jul 14, 2025
0597b0a
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 14, 2025
f4a4bd4
Use formatPercentage
mstrasinskis Jul 15, 2025
dda3fda
Update percentage tests
mstrasinskis Jul 15, 2025
d09eba2
Replace token price with market cap
mstrasinskis Jul 15, 2025
ec28f6d
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 15, 2025
1a34233
Restore formatCurrencyNumber
mstrasinskis Jul 15, 2025
af094a3
Hide fav buttons
mstrasinskis Jul 15, 2025
1595ccd
Hide with css
mstrasinskis Jul 15, 2025
6982e47
use PRICE_NOT_AVAILABLE_PLACEHOLDER
mstrasinskis Jul 15, 2025
6e4cebb
Add snsProjectMarketCap util
mstrasinskis Jul 15, 2025
329c83f
show navigation link on ProjectCard
mstrasinskis Jul 15, 2025
6e8d552
Add compareLaunchpadSnsProjects util
mstrasinskis Jul 15, 2025
0f8fb3a
Remove mocking Adapted projects
mstrasinskis Jul 16, 2025
7d67c0e
Update sorting to use directly marketCup util
mstrasinskis Jul 16, 2025
d994c6b
sorting: change the root canister id to simplify testing
mstrasinskis Jul 16, 2025
233c6ca
Refactor comparators
mstrasinskis Jul 16, 2025
21eb560
Add ledgerCanisterId to mock sns summary params
mstrasinskis Jul 16, 2025
2e98308
test: launchpad comparators
mstrasinskis Jul 16, 2025
57e457b
fix typo
mstrasinskis Jul 16, 2025
7617ddd
test: Sorting on launchpad2
mstrasinskis Jul 16, 2025
3c4c63e
Add compareSnsProjectsByIcpTreasury
mstrasinskis Jul 16, 2025
dff6329
Separate mockIcpTreasuryMetrics
mstrasinskis Jul 16, 2025
5cfbd5e
Test compareLaunchpadSnsProjects
mstrasinskis Jul 16, 2025
9050c35
Merge branch 'main' into launchpad2/page-layout
mstrasinskis Jul 16, 2025
7c6dd99
Merge branch 'main' into fav-projects-api
mstrasinskis Jul 17, 2025
b98f734
Merge branch 'main' into fav-projects-api
mstrasinskis Jul 17, 2025
fb1edca
remove duplication
mstrasinskis Jul 17, 2025
84ab4e4
Merge branch 'fav-projects-api' of github.com:dfinity/nns-dapp into f…
mstrasinskis Jul 17, 2025
30d1ba0
cleanup
mstrasinskis Jul 17, 2025
464920b
Add compareSnsProjectsAbandonedLast
mstrasinskis Jul 17, 2025
01d4cdc
test abandoned projects
mstrasinskis Jul 17, 2025
9c8a3fe
Merge branch 'fav-projects-api' into launchpad2/page-layout
mstrasinskis Jul 17, 2025
645f1bf
Add snsFavProjectsStore
mstrasinskis Jul 17, 2025
6ece27c
Add MAX_SNS_FAV_PROJECTS const
mstrasinskis Jul 17, 2025
a7a1bcc
Add sns-fav-projects.utils
mstrasinskis Jul 17, 2025
9b87116
Add sns fav projects services
mstrasinskis Jul 17, 2025
3c80c0c
test: sns fav projects services
mstrasinskis Jul 17, 2025
0b14637
Add tmp IconStarFill
mstrasinskis Jul 17, 2025
3d2bc08
cleanup
mstrasinskis Jul 17, 2025
17f8450
remove underline from buttons
mstrasinskis Jul 17, 2025
aa1a423
preload fav projects on app init
mstrasinskis Jul 17, 2025
ccb63fe
todo
mstrasinskis Jul 17, 2025
fb3eedd
Labels
mstrasinskis Jul 17, 2025
2a2f56a
Toggle sns project favorit state
mstrasinskis Jul 17, 2025
2a3b171
New snsFavProjectsToggleVisibleStore
mstrasinskis Jul 17, 2025
a8e1675
New snsFavProjectsVisibilityStore
mstrasinskis Jul 17, 2025
e4f27fc
Constant for snsFavProjectsVisibilityStore
mstrasinskis Jul 17, 2025
71103a7
Update FavProjectButton
mstrasinskis Jul 17, 2025
c266a03
Fav button disabled style
mstrasinskis Jul 17, 2025
b110532
New FavProjectOnlyToggle
mstrasinskis Jul 17, 2025
88fff8b
Display set/unset fav buttons
mstrasinskis Jul 17, 2025
bfc4e5c
Filter by favorites
mstrasinskis Jul 17, 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
39 changes: 39 additions & 0 deletions frontend/src/lib/api/fav-projects.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { nnsDappCanister } from "$lib/api/nns-dapp.api";
import type {
FavProject,
FavProjects,
} from "$lib/canisters/nns-dapp/nns-dapp.types";
import { logWithTimestamp } from "$lib/utils/dev.utils";
import type { Identity } from "@dfinity/agent";

export const getFavProjects = async ({
identity,
certified,
}: {
identity: Identity;
certified: boolean;
}): Promise<FavProjects> => {
logWithTimestamp(`Getting favorite projects:${certified} call...`);

const { canister: nnsDapp } = await nnsDappCanister({ identity });
const response = await nnsDapp.getFavProjects({ certified });

logWithTimestamp(`Getting favorite projects:${certified} complete`);

return response;
};

export const setFavProjects = async ({
identity,
favProjects,
}: {
identity: Identity;
favProjects: Array<FavProject>;
}): Promise<void> => {
logWithTimestamp("Setting favorite projects call...");

const { canister: nnsDapp } = await nnsDappCanister({ identity });
await nnsDapp.setFavProjects(favProjects);

logWithTimestamp("Setting favorite projects call complete.");
};
40 changes: 40 additions & 0 deletions frontend/src/lib/canisters/nns-dapp/nns-dapp.canister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ProposalPayloadNotFoundError,
ProposalPayloadTooLargeError,
SubAccountLimitExceededError,
TooManyFavProjectsError,
TooManyImportedTokensError,
UnknownProposalPayloadError,
} from "$lib/canisters/nns-dapp/nns-dapp.errors";
Expand All @@ -21,6 +22,8 @@ import type {
AccountDetails,
CanisterDetails,
CreateSubAccountResponse,
FavProject,
FavProjects,
GetAccountResponse,
ImportedToken,
ImportedTokens,
Expand Down Expand Up @@ -373,4 +376,41 @@ export class NNSDappCanister {
`Error setting imported tokens ${JSON.stringify(response)}`
);
};

public getFavProjects = async ({
certified,
}: {
certified: boolean;
}): Promise<FavProjects> => {
const response = await this.getNNSDappService(certified).get_fav_projects();
if ("Ok" in response) {
return response.Ok;
}
if ("AccountNotFound" in response) {
throw new AccountNotFoundError("error__account.not_found");
}
// Edge case
throw new Error(`Error getting fav projects ${JSON.stringify(response)}`);
};

public setFavProjects = async (
favProjects: Array<FavProject>
): Promise<void> => {
const response = await this.certifiedService.set_fav_projects({
fav_projects: favProjects,
});
if ("Ok" in response) {
return;
}
if ("AccountNotFound" in response) {
throw new AccountNotFoundError("error__account.not_found");
}
if ("TooManyFavProjects" in response) {
throw new TooManyFavProjectsError("error__fav_projects.too_many", {
$limit: response.TooManyFavProjects?.limit.toString(),
});
}
// Edge case
throw new Error(`Error setting fav projects ${JSON.stringify(response)}`);
};
}
13 changes: 13 additions & 0 deletions frontend/src/lib/canisters/nns-dapp/nns-dapp.certified.idl.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ export const idlFactory = ({ IDL }) => {
canister_id: IDL.Principal,
block_index: IDL.Opt(IDL.Nat64),
});
const FavProject = IDL.Record({ root_canister_id: IDL.Principal });
const FavProjects = IDL.Record({ fav_projects: IDL.Vec(FavProject) });
const GetFavProjectsResponse = IDL.Variant({
Ok: FavProjects,
AccountNotFound: IDL.Null,
});
const ImportedToken = IDL.Record({
index_canister_id: IDL.Opt(IDL.Principal),
ledger_canister_id: IDL.Principal,
Expand Down Expand Up @@ -120,6 +126,11 @@ export const idlFactory = ({ IDL }) => {
SubAccountNotFound: IDL.Null,
NameTooLong: IDL.Null,
});
const SetFavProjectsResponse = IDL.Variant({
Ok: IDL.Null,
AccountNotFound: IDL.Null,
TooManyFavProjects: IDL.Record({ limit: IDL.Int32 }),
});
const SetImportedTokensResponse = IDL.Variant({
Ok: IDL.Null,
AccountNotFound: IDL.Null,
Expand All @@ -146,6 +157,7 @@ export const idlFactory = ({ IDL }) => {
),
get_account: IDL.Func([], [GetAccountResponse], []),
get_canisters: IDL.Func([], [IDL.Vec(CanisterDetails)], []),
get_fav_projects: IDL.Func([], [GetFavProjectsResponse], []),
get_imported_tokens: IDL.Func([], [GetImportedTokensResponse], []),
get_proposal_payload: IDL.Func(
[IDL.Nat64],
Expand All @@ -164,6 +176,7 @@ export const idlFactory = ({ IDL }) => {
[RenameSubAccountResponse],
[]
),
set_fav_projects: IDL.Func([FavProjects], [SetFavProjectsResponse], []),
set_imported_tokens: IDL.Func(
[ImportedTokens],
[SetImportedTokensResponse],
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/lib/canisters/nns-dapp/nns-dapp.errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ export class TooManyImportedTokensError extends AccountTranslateError {
}
}

export class TooManyFavProjectsError extends AccountTranslateError {
constructor(message: string, substitutions?: I18nSubstitutions) {
super(message);

this.substitutions = substitutions;
}
}

export class SubAccountLimitExceededError extends Error {}

export class NameTooLongError extends AccountTranslateError {
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/lib/canisters/nns-dapp/nns-dapp.idl.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ export const idlFactory = ({ IDL }) => {
canister_id: IDL.Principal,
block_index: IDL.Opt(IDL.Nat64),
});
const FavProject = IDL.Record({ root_canister_id: IDL.Principal });
const FavProjects = IDL.Record({ fav_projects: IDL.Vec(FavProject) });
const GetFavProjectsResponse = IDL.Variant({
Ok: FavProjects,
AccountNotFound: IDL.Null,
});
const ImportedToken = IDL.Record({
index_canister_id: IDL.Opt(IDL.Principal),
ledger_canister_id: IDL.Principal,
Expand Down Expand Up @@ -120,6 +126,11 @@ export const idlFactory = ({ IDL }) => {
SubAccountNotFound: IDL.Null,
NameTooLong: IDL.Null,
});
const SetFavProjectsResponse = IDL.Variant({
Ok: IDL.Null,
AccountNotFound: IDL.Null,
TooManyFavProjects: IDL.Record({ limit: IDL.Int32 }),
});
const SetImportedTokensResponse = IDL.Variant({
Ok: IDL.Null,
AccountNotFound: IDL.Null,
Expand All @@ -146,6 +157,7 @@ export const idlFactory = ({ IDL }) => {
),
get_account: IDL.Func([], [GetAccountResponse], ["query"]),
get_canisters: IDL.Func([], [IDL.Vec(CanisterDetails)], ["query"]),
get_fav_projects: IDL.Func([], [GetFavProjectsResponse], ["query"]),
get_imported_tokens: IDL.Func([], [GetImportedTokensResponse], ["query"]),
get_proposal_payload: IDL.Func(
[IDL.Nat64],
Expand All @@ -164,6 +176,7 @@ export const idlFactory = ({ IDL }) => {
[RenameSubAccountResponse],
[]
),
set_fav_projects: IDL.Func([FavProjects], [SetFavProjectsResponse], []),
set_imported_tokens: IDL.Func(
[ImportedTokens],
[SetImportedTokensResponse],
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/lib/canisters/nns-dapp/nns-dapp.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export type DetachCanisterResponse = { Ok: null } | { CanisterNotFound: null };
export type GetAccountResponse =
| { Ok: AccountDetails }
| { AccountNotFound: null };
export type GetFavProjectsResponse =
| { Ok: FavProjects }
| { AccountNotFound: null };
export type GetImportedTokensResponse =
| { Ok: ImportedTokens }
| { AccountNotFound: null };
Expand Down Expand Up @@ -66,6 +69,13 @@ export interface ImportedToken {
export interface ImportedTokens {
imported_tokens: Array<ImportedToken>;
}
export interface FavProject {
root_canister_id: Principal;
}
export interface FavProjects {
fav_projects: Array<FavProject>;
}

export interface RegisterHardwareWalletRequest {
principal: Principal;
name: string;
Expand Down Expand Up @@ -99,6 +109,10 @@ export type SetImportedTokensResponse =
| { Ok: null }
| { AccountNotFound: null }
| { TooManyImportedTokens: { limit: number } };
export type SetFavProjectsResponse =
| { Ok: null }
| { AccountNotFound: null }
| { TooManyFavProjects: { limit: number } };
export interface Stats {
seconds_since_last_ledger_sync: bigint;
sub_accounts_count: bigint;
Expand Down Expand Up @@ -132,6 +146,7 @@ export default interface _SERVICE {
get_account: () => Promise<GetAccountResponse>;
get_canisters: () => Promise<Array<CanisterDetails>>;
get_imported_tokens: () => Promise<GetImportedTokensResponse>;
get_fav_projects: () => Promise<GetFavProjectsResponse>;
get_proposal_payload: (arg_0: bigint) => Promise<GetProposalPayloadResponse>;
get_stats: () => Promise<Stats>;
http_request: (arg_0: HttpRequest) => Promise<HttpResponse>;
Expand All @@ -142,4 +157,5 @@ export default interface _SERVICE {
arg_0: RenameSubAccountRequest
) => Promise<RenameSubAccountResponse>;
set_imported_tokens: ActorMethod<[ImportedTokens], SetImportedTokensResponse>;
set_fav_projects: ActorMethod<[FavProjects], SetFavProjectsResponse>;
}
91 changes: 91 additions & 0 deletions frontend/src/lib/components/launchpad/FavProjectButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<script lang="ts">
import TestIdWrapper from "$lib/components/common/TestIdWrapper.svelte";
import { MAX_SNS_FAV_PROJECTS } from "$lib/constants/sns.constants";
import type { SnsFullProject } from "$lib/derived/sns/sns-projects.derived";
import {
addSnsFavProject,
removeSnsFavProject,
} from "$lib/services/sns.fav-projects.services";
import { i18n } from "$lib/stores/i18n";
import { snsFavProjectsStore } from "$lib/stores/sns-fav-projects.store";
import { replacePlaceholders } from "$lib/utils/i18n.utils";
import { isSnsProjectFavorite } from "$lib/utils/sns-fav-projects.utils";
import { getCommitmentE8s } from "$lib/utils/sns.utils";
import { Tooltip } from "@dfinity/gix-components";
import { nonNullish } from "@dfinity/utils";
import type { Snippet } from "svelte";

type Props = {
project: SnsFullProject;
children: Snippet;
};

const { project, children }: Props = $props();

const loaded = $derived(nonNullish($snsFavProjectsStore.rootCanisterIds));
const maxReached = $derived(
($snsFavProjectsStore.rootCanisterIds?.length ?? 0) >= MAX_SNS_FAV_PROJECTS
);
const userHasParticipated = $derived(
(getCommitmentE8s(project.swapCommitment) ?? 0n) > 0n
);
const isFavorite = $derived(
isSnsProjectFavorite({
project,
favProjects: $snsFavProjectsStore.rootCanisterIds,
})
);
// Even when the maximum is reached, the button should be enabled for favorite projects
// so that the user can remove them from favorites.
const disabled = $derived(userHasParticipated || (maxReached && !isFavorite));
const toggleFavorite = async (event: MouseEvent) => {
// Avoid unwanted navigation on mobile
event.preventDefault();

if (isFavorite) {
await removeSnsFavProject(project.rootCanisterId);
} else {
await addSnsFavProject(project.rootCanisterId);
}

// TODO(launchpad2): Add analytics event for this toggle
// analytics.event("fav-projects-update-state", {
// value: isFavorite ? "remove" : "add",
// });
};
</script>

<TestIdWrapper testId="fav-project-button-component">
{#if loaded}
<button
onclick={toggleFavorite}
aria-label={$i18n.launchpad_cards.project_card_watch}
{disabled}
>
{#if maxReached}
<Tooltip
id="max-fav-projects-reached-tooltip"
text={replacePlaceholders(
$i18n.fav_projects.maximum_reached_tooltip,
{
$max: `${MAX_SNS_FAV_PROJECTS}`,
}
)}
>
{@render children()}
</Tooltip>
{:else}
{@render children()}
{/if}
</button>
{/if}
</TestIdWrapper>

<style lang="scss">
button {
color: var(--primary);
&:disabled {
color: var(--tooltip-background);
}
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script lang="ts">
import IconStarFill from "$lib/components/ui/icons/IconStarFill.svelte";
import { snsFavProjectsToggleVisibleStore } from "$lib/derived/sns-fav-projects.derived";
import { i18n } from "$lib/stores/i18n";
import { snsFavProjectsVisibilityStore } from "$lib/stores/sns-fav-projects-visibility.store";
import { IconStar } from "@dfinity/gix-components";

let checked = $derived($snsFavProjectsVisibilityStore === "fav");
const Icon = $derived(checked ? IconStarFill : IconStar);

const toggle = () => {
const newState = checked ? "all" : "fav";
snsFavProjectsVisibilityStore.set(newState);
// TODO(launchpad2): Add analytics event for this toggle
// analytics.event("fav-projects-only-toggle", {
// value: newState,
// });
};
</script>

{#if $snsFavProjectsToggleVisibleStore}
<button
class="wrapper"
onclick={toggle}
aria-label={$i18n.launchpad.toggle_fav_only}
title={$i18n.launchpad.toggle_fav_only}
data-tid="toggle-fav-projects-only-component"
>
<Icon />
</button>
{/if}

<style lang="scss">
button {
min-width: 40px;
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
border: 1.5px solid var(--primary);
color: var(--primary);
}
</style>
Loading
Loading