Skip to content

Commit 5acdf56

Browse files
authored
Merge pull request #229 from Web3NL/fix/81-agree-to-terms
fix: add agree-to-terms step to install page
2 parents 2983dd1 + d891e14 commit 5acdf56

5 files changed

Lines changed: 148 additions & 32 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<script lang="ts">
2+
import { Card, Checkbox } from '@dfinity/gix-components';
3+
4+
export let onAgree: () => void;
5+
export let dappName: string | null = null;
6+
7+
let checked = false;
8+
</script>
9+
10+
<Card>
11+
<h3>Terms &amp; Disclaimer</h3>
12+
13+
<p>
14+
{#if dappName}
15+
You are about to install <strong>{dappName}</strong> as an Internet Computer
16+
canister.
17+
{:else}
18+
You are about to install a custom dapp as an Internet Computer canister.
19+
{/if}
20+
Please read and accept the following disclaimer before proceeding.
21+
</p>
22+
23+
<div class="disclaimer">
24+
<p>
25+
This software is provided <strong>&ldquo;as is&rdquo;</strong>, without
26+
warranty of any kind, express or implied, including but not limited to the
27+
warranties of merchantability, fitness for a particular purpose, and
28+
noninfringement.
29+
</p>
30+
<p>
31+
In no event shall the authors, contributors, or copyright holders be
32+
liable for any claim, damages, or other liability, whether in an action of
33+
contract, tort, or otherwise, arising from, out of, or in connection with
34+
the software or the use or other dealings in the software.
35+
</p>
36+
<p>By proceeding, you acknowledge that:</p>
37+
<ul>
38+
<li>
39+
You are solely responsible for any ICP tokens spent during canister
40+
creation.
41+
</li>
42+
<li>
43+
The installed dapp code is provided without any guarantee of
44+
correctness, security, or fitness for any purpose.
45+
</li>
46+
<li>
47+
You assume all risks associated with installing and running this
48+
canister on the Internet Computer.
49+
</li>
50+
</ul>
51+
</div>
52+
53+
<div class="agree-checkbox">
54+
<Checkbox
55+
inputId="agree-terms"
56+
{checked}
57+
on:nnsChange={() => (checked = !checked)}
58+
>
59+
I have read and agree to the terms and disclaimer
60+
</Checkbox>
61+
</div>
62+
63+
<button class="primary" on:click={onAgree} disabled={!checked}>
64+
Continue
65+
</button>
66+
</Card>
67+
68+
<style>
69+
.disclaimer {
70+
margin: 1rem 0;
71+
padding: 1rem;
72+
border: 1px solid var(--input-border-color, #ccc);
73+
border-radius: var(--border-radius, 4px);
74+
background: var(--input-background, rgba(0, 0, 0, 0.05));
75+
font-size: 0.9rem;
76+
max-height: 200px;
77+
overflow-y: auto;
78+
}
79+
80+
.disclaimer ul {
81+
padding-left: 1.5rem;
82+
margin: 0.5rem 0;
83+
}
84+
85+
.disclaimer li {
86+
margin-bottom: 0.5rem;
87+
}
88+
89+
.agree-checkbox {
90+
margin: 1rem 0 1.5rem 0;
91+
}
92+
</style>

canisters/my-canister-app/src/routes/(authed)/install/+page.svelte

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import GoodNewsCard from '$lib/components/install/GoodNewsCard.svelte';
2525
import ConnectIICard from '$lib/components/install/ConnectIICard.svelte';
2626
import FundAccountCard from '$lib/components/install/FundAccountCard.svelte';
27+
import AgreeTermsCard from '$lib/components/install/AgreeTermsCard.svelte';
2728
import { goto, beforeNavigate } from '$app/navigation';
2829
import { fetchWasmModule, type WasmSource } from '$lib/utils/fetch';
2930
@@ -91,7 +92,7 @@
9192
if (requiredBalanceE8s > 0n) {
9293
if (currentBalance >= requiredBalanceE8s) {
9394
stopBalanceTimer();
94-
advanceToStep(2);
95+
advanceToStep(3);
9596
} else if (!lowDepositWarnShown) {
9697
showWarnToast('Balance too low.');
9798
lowDepositWarnShown = true;
@@ -129,10 +130,15 @@
129130
});
130131
131132
let steps: [ProgressStep, ...ProgressStep[]] = [
133+
{
134+
step: 'agree',
135+
text: 'Agree to Terms',
136+
state: 'in_progress',
137+
},
132138
{
133139
step: 'fund',
134140
text: 'Fund Account',
135-
state: 'in_progress',
141+
state: 'next',
136142
},
137143
{
138144
step: 'create',
@@ -154,12 +160,17 @@
154160
$: currentStep =
155161
steps.findIndex(step => step.state === 'in_progress') + 1 || steps.length;
156162
157-
function advanceToStep(targetStep: 2 | 3 | 4) {
158-
// targetStep is 1-based for the step the user is entering (or completing if 4)
163+
function handleAgreeToTerms() {
164+
advanceToStep(2);
165+
startBalanceTimer();
166+
}
167+
168+
function advanceToStep(targetStep: 2 | 3 | 4 | 5) {
169+
// targetStep is 1-based for the step the user is entering (or completing if 5)
159170
steps = steps.map((s, idx) => {
160171
const stepIndex = idx + 1; // 1-based
161172
let state: ProgressStep['state'];
162-
if (targetStep === 4) {
173+
if (targetStep === 5) {
163174
// All steps completed
164175
state = 'completed';
165176
} else if (stepIndex < targetStep) {
@@ -193,11 +204,11 @@
193204
function retryFailedStep() {
194205
if (lastFailedStep === 'create') {
195206
clearFailedState();
196-
advanceToStep(2);
207+
advanceToStep(3);
197208
createCanister();
198209
} else if (lastFailedStep === 'connect-ii') {
199210
clearFailedState();
200-
advanceToStep(3);
211+
advanceToStep(4);
201212
takeControlOfCanister();
202213
}
203214
}
@@ -220,7 +231,7 @@
220231
localStorage.setItem(CANISTER_STORAGE_KEY, canisterPrincipal.toText());
221232
}
222233
223-
advanceToStep(3);
234+
advanceToStep(4);
224235
} catch (err) {
225236
console.error('Failed to create canister', err);
226237
setFailed('create');
@@ -257,7 +268,7 @@
257268
localStorage.removeItem(CANISTER_STORAGE_KEY);
258269
}
259270
260-
advanceToStep(4);
271+
advanceToStep(5);
261272
} catch (err) {
262273
console.error('Failed to install code / connect II', err);
263274
setFailed('connect-ii');
@@ -323,20 +334,24 @@
323334
if (pendingFromInstall) {
324335
canisterPrincipal = pendingFromInstall;
325336
recoveredCanister = true;
326-
// Stay on step 2 to retry - createNewCanister will detect and retry install
327-
advanceToStep(2);
337+
// Stay on step 3 to retry - createNewCanister will detect and retry install
338+
advanceToStep(3);
328339
} else {
329340
// Check for pending canister from successful create (waiting for II connect)
330341
const storedCanisterId = localStorage.getItem(CANISTER_STORAGE_KEY);
331342
if (storedCanisterId != null) {
332343
canisterPrincipal = Principal.fromText(storedCanisterId);
333344
recoveredCanister = true;
334-
advanceToStep(3);
345+
advanceToStep(4);
335346
}
336347
}
337348
}
338349
339-
startBalanceTimer();
350+
// Only start balance timer immediately when recovering a previous install
351+
// Otherwise, it starts when the user agrees to terms via handleAgreeToTerms()
352+
if (recoveredCanister) {
353+
startBalanceTimer();
354+
}
340355
});
341356
342357
onDestroy(() => {
@@ -406,7 +421,9 @@
406421
</p>
407422
<button class="primary" on:click={retryFailedStep}>Retry</button>
408423
</div>
409-
{:else if currentStep === 1 || currentStep === 2}
424+
{:else if currentStep === 1}
425+
<AgreeTermsCard onAgree={handleAgreeToTerms} dappName={dappNameText} />
426+
{:else if currentStep === 2 || currentStep === 3}
410427
<FundAccountCard
411428
{principalText}
412429
{minimumBalance}
@@ -418,9 +435,9 @@
418435
onCreate={createCanister}
419436
onRefreshBalance={loadBalance}
420437
/>
421-
{:else if currentStep === 3}
438+
{:else if currentStep === 4}
422439
<ConnectIICard onConnect={takeControlOfCanister} />
423-
{:else if currentStep === 4 && canisterPrincipal}
440+
{:else if currentStep === 5 && canisterPrincipal}
424441
<GoodNewsCard
425442
frontPageUrl={createFrontpageUrl(canisterPrincipal.toText())}
426443
dashboardUrl={createDashboardUrl(canisterPrincipal.toText())}

packages-rs/my-canister-frontend/src/asset_router.rs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use flate2::write::GzEncoder;
21
use flate2::Compression;
2+
use flate2::write::GzEncoder;
33
use ic_asset_certification::{Asset, AssetConfig, AssetEncoding, AssetFallbackConfig, AssetRouter};
44
use ic_http_certification::StatusCode;
55
use include_dir::Dir;
@@ -12,8 +12,8 @@ use std::io::Write;
1212
/// Files with extensions not in this list will be rejected unless
1313
/// explicitly allowed via [`FrontendConfig::extra_allowed_extensions`].
1414
pub const DEFAULT_ALLOWED_EXTENSIONS: &[&str] = &[
15-
"html", "js", "mjs", "css", "png", "jpg", "jpeg", "gif", "webp", "svg", "ico", "avif",
16-
"woff", "woff2", "ttf", "otf", "eot", "json", "xml", "txt", "wasm", "map",
15+
"html", "js", "mjs", "css", "png", "jpg", "jpeg", "gif", "webp", "svg", "ico", "avif", "woff",
16+
"woff2", "ttf", "otf", "eot", "json", "xml", "txt", "wasm", "map",
1717
];
1818

1919
/// Maximum file size in bytes (2 MB).
@@ -158,7 +158,14 @@ fn process_dir_recursive(
158158
} else {
159159
format!("{base_path}/{subdir_name}")
160160
};
161-
process_dir_recursive(subdir, &new_base_path, assets, asset_configs, seen_paths, config)?;
161+
process_dir_recursive(
162+
subdir,
163+
&new_base_path,
164+
assets,
165+
asset_configs,
166+
seen_paths,
167+
config,
168+
)?;
162169
}
163170
Ok(())
164171
}
@@ -276,17 +283,13 @@ fn create_default_headers(_content_type: &str) -> Vec<(String, String)> {
276283
),
277284
(
278285
"Permissions-Policy".into(),
279-
"accelerometer=(), camera=(), geolocation=(), microphone=(), payment=(), usb=()"
280-
.into(),
286+
"accelerometer=(), camera=(), geolocation=(), microphone=(), payment=(), usb=()".into(),
281287
),
282288
(
283289
"Cross-Origin-Opener-Policy".into(),
284290
"same-origin-allow-popups".into(),
285291
),
286-
(
287-
"Cross-Origin-Resource-Policy".into(),
288-
"same-origin".into(),
289-
),
292+
("Cross-Origin-Resource-Policy".into(), "same-origin".into()),
290293
]
291294
}
292295

@@ -562,12 +565,10 @@ mod tests {
562565
#[test]
563566
fn includes_cross_origin_opener_policy() {
564567
let headers = create_default_headers("text/html");
565-
assert!(
566-
headers.contains(&(
567-
"Cross-Origin-Opener-Policy".into(),
568-
"same-origin-allow-popups".into(),
569-
))
570-
);
568+
assert!(headers.contains(&(
569+
"Cross-Origin-Opener-Policy".into(),
570+
"same-origin-allow-popups".into(),
571+
)));
571572
}
572573

573574
#[test]

tests/my-canister-app/install-dapp.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ test('My Canister App E2E Suite', async ({ page }) => {
6262
.waitFor({ state: 'visible' });
6363
await helloWorldCard.getByRole('button', { name: 'Install' }).click();
6464

65+
// Accept terms and disclaimer
66+
await page.locator('[data-tid="checkbox"]').waitFor({ state: 'visible' });
67+
await page.locator('[data-tid="checkbox"]').click();
68+
await page.getByRole('button', { name: 'Continue' }).click();
69+
6570
// Wait until minimum deposit amount (number) is loaded instead of placeholder '...'
6671
const depositLocator = page
6772
.locator('p')

0 commit comments

Comments
 (0)