Skip to content

Commit 3a091c5

Browse files
Web3NLclaude
andcommitted
fix: add agree-to-terms step to install page (#81)
Users must now accept a disclaimer before proceeding with canister creation. Adds a new "Agree to Terms" step as the first step in the install flow with an MIT-style "as-is" no-warranty disclaimer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2983dd1 commit 3a091c5

3 files changed

Lines changed: 130 additions & 16 deletions

File tree

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())}

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)