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(deprecate): redo invitation code #722

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 6 additions & 7 deletions scripts/console.invitation.mjs
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
#!/usr/bin/env node

import { nanoid } from 'nanoid';
import { consoleActorIC } from './actor.mjs';
import { consoleActorLocal } from './actor.mjs';

(async () => {
const actor = await consoleActorIC();
const actor = await consoleActorLocal();

const invitationCode = nanoid();
const invitationCode = nanoid();

await actor.add_invitation_code(invitationCode);
await actor.add_invitation_code(invitationCode);

console.log('Invitation code:', invitationCode);
})();
console.log('🏷️ Invitation code:', invitationCode);
console.log('🔗 Redeem URL:', `https://console.juno.build/join?invite=${invitationCode}`);
2 changes: 1 addition & 1 deletion src/console/console.did
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ service : () -> {
) query;
init_asset_upload : (InitAssetKey, nat) -> (InitUploadResult);
init_proposal : (ProposalType) -> (nat, Proposal);
init_user_mission_control_center : () -> (MissionControl);
init_user_mission_control_center : (opt text) -> (MissionControl);
list_assets : (text, ListParams) -> (ListResults) query;
list_custom_domains : () -> (vec record { text; CustomDomain }) query;
list_payments : () -> (vec record { nat64; Payment }) query;
Expand Down
16 changes: 11 additions & 5 deletions src/console/src/factory/mission_control.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::controllers::update_mission_control_controllers;
use crate::store::heap::increment_mission_controls_rate;
use crate::store::heap::{increment_mission_controls_rate, redeem_invitation_code};
use crate::store::stable::{
add_mission_control, delete_mission_control, get_mission_control, init_empty_mission_control,
};
Expand All @@ -13,17 +13,23 @@ use junobuild_shared::types::state::UserId;
pub async fn init_user_mission_control(
console: &Principal,
caller: &Principal,
invitation_code: &Option<String>,
) -> Result<MissionControl, String> {
let result = get_mission_control(caller);
match result {
Err(error) => Err(error.to_string()),
Ok(mission_control) => match mission_control {
Some(mission_control) => Ok(mission_control),
None => {
// Guard too many requests
increment_mission_controls_rate()?;
None => match invitation_code {
None => Err("No invitation code provided.".to_string()),
Some(invitation_code) => {
// Guard too many requests
increment_mission_controls_rate()?;

create_mission_control(caller, console).await
redeem_invitation_code(caller, invitation_code)?;

create_mission_control(caller, console).await
}
}
},
}
Expand Down
4 changes: 2 additions & 2 deletions src/console/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,11 @@ fn list_user_mission_control_centers() -> MissionControls {
}

#[update]
async fn init_user_mission_control_center() -> MissionControl {
async fn init_user_mission_control_center(invitation_code: Option<String>) -> MissionControl {
let caller = caller();
let console = id();

init_user_mission_control(&console, &caller)
init_user_mission_control(&console, &caller, &invitation_code)
.await
.unwrap_or_else(|e| trap(&e))
}
Expand Down
2 changes: 1 addition & 1 deletion src/declarations/console/console.did.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ export interface _SERVICE {
>;
init_asset_upload: ActorMethod<[InitAssetKey, bigint], InitUploadResult>;
init_proposal: ActorMethod<[ProposalType], [bigint, Proposal]>;
init_user_mission_control_center: ActorMethod<[], MissionControl>;
init_user_mission_control_center: ActorMethod<[[] | [string]], MissionControl>;
list_assets: ActorMethod<[string, ListParams], ListResults>;
list_custom_domains: ActorMethod<[], Array<[string, CustomDomain]>>;
list_payments: ActorMethod<[], Array<[bigint, Payment]>>;
Expand Down
2 changes: 1 addition & 1 deletion src/declarations/console/console.factory.did.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ export const idlFactory = ({ IDL }) => {
),
init_asset_upload: IDL.Func([InitAssetKey, IDL.Nat], [InitUploadResult], []),
init_proposal: IDL.Func([ProposalType], [IDL.Nat, Proposal], []),
init_user_mission_control_center: IDL.Func([], [MissionControl], []),
init_user_mission_control_center: IDL.Func([IDL.Opt(IDL.Text)], [MissionControl], []),
list_assets: IDL.Func([IDL.Text, ListParams], [ListResults], ['query']),
list_custom_domains: IDL.Func([], [IDL.Vec(IDL.Tuple(IDL.Text, CustomDomain))], ['query']),
list_payments: IDL.Func([], [IDL.Vec(IDL.Tuple(IDL.Nat64, Payment))], ['query']),
Expand Down
2 changes: 1 addition & 1 deletion src/declarations/console/console.factory.did.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ export const idlFactory = ({ IDL }) => {
),
init_asset_upload: IDL.Func([InitAssetKey, IDL.Nat], [InitUploadResult], []),
init_proposal: IDL.Func([ProposalType], [IDL.Nat, Proposal], []),
init_user_mission_control_center: IDL.Func([], [MissionControl], []),
init_user_mission_control_center: IDL.Func([IDL.Opt(IDL.Text)], [MissionControl], []),
list_assets: IDL.Func([IDL.Text, ListParams], [ListResults], ['query']),
list_custom_domains: IDL.Func([], [IDL.Vec(IDL.Tuple(IDL.Text, CustomDomain))], ['query']),
list_payments: IDL.Func([], [IDL.Vec(IDL.Tuple(IDL.Nat64, Payment))], ['query']),
Expand Down
13 changes: 10 additions & 3 deletions src/frontend/src/lib/api/console.api.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import type { MissionControl } from '$declarations/console/console.did';
import type { OptionInvitationCode } from '$lib/types/console';
import type { OptionIdentity } from '$lib/types/itentity';
import { getConsoleActor } from '$lib/utils/actor.juno.utils';
import type { Principal } from '@dfinity/principal';
import { fromNullable, isNullish } from '@dfinity/utils';
import { fromNullable, isNullish, toNullable } from '@dfinity/utils';

export const initMissionControl = async (identity: OptionIdentity): Promise<MissionControl> => {
export const initMissionControl = async ({
identity,
invitationCode
}: {
identity: OptionIdentity;
invitationCode: OptionInvitationCode;
}): Promise<MissionControl> => {
const actor = await getConsoleActor(identity);

const existingMissionControl: MissionControl | undefined = fromNullable<MissionControl>(
await actor.get_user_mission_control_center()
);

if (!existingMissionControl) {
return await actor.init_user_mission_control_center();
return await actor.init_user_mission_control_center(toNullable(invitationCode));
}

return existingMissionControl;
Expand Down
14 changes: 3 additions & 11 deletions src/frontend/src/lib/components/core/DeprecatedSignIn.svelte
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
<script lang="ts">
import { signIn } from '$lib/services/auth.services';
import { isBusy } from '$lib/stores/busy.store';
import { createEventDispatcher } from 'svelte';

const dispatch = createEventDispatcher();

const doSignIn = async (domain: 'internetcomputer.org' | 'ic0.app') => {
dispatch('junoSignIn');
await signIn({ domain });
};
</script>

<p class="sign-in-now">
Juno defaults to
<button
class="text action"
on:click={async () => await doSignIn('internetcomputer.org')}
disabled={$isBusy}>internetcomputer.org</button
<button class="text action" on:click={() => dispatch('junoSignIn')} disabled={$isBusy}
>internetcomputer.org</button
> for authentication.
</p>

<p>
Alternatively, use the legacy method at
<button class="text action" on:click={async () => await doSignIn('ic0.app')} disabled={$isBusy}
<button class="text action" on:click={() => dispatch('junoSignInDeprecated')} disabled={$isBusy}
>ic0.app</button
>.
</p>
Expand Down
66 changes: 16 additions & 50 deletions src/frontend/src/lib/components/core/SignIn.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import DeprecatedSignIn from '$lib/components/core/DeprecatedSignIn.svelte';
import IconICMonochrome from '$lib/components/icons/IconICMonochrome.svelte';
import { signIn } from '$lib/services/auth.services';
import Container from '$lib/components/ui/Container.svelte';

let quotes: string[];
$: quotes = [
Expand All @@ -21,59 +22,24 @@

let title: string;
$: title = quotes[Math.floor(Math.random() * quotes.length)];
</script>

<div class="container">
<div class="overview">
<h1>{title}</h1>

<p>{$i18n.sign_in.future}</p>
</div>
const doSignIn = async (domain?: 'internetcomputer.org' | 'ic0.app') => {
await signIn({ domain });
};
</script>

<div class="sign-in">
<button on:click={async () => await signIn({})} disabled={$isBusy}
<Container>
{$i18n.sign_in.deprecated}
<svelte:fragment slot="title">{title}</svelte:fragment>
<svelte:fragment slot="actions">
<button on:click={async () => await doSignIn()} disabled={$isBusy}
><IconICMonochrome size="20px" />
<span>{$i18n.sign_in.internet_identity}</span></button
>

<DeprecatedSignIn />
</div>
</div>

<style lang="scss">
@use '../../../lib/styles/mixins/media';

.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;

text-align: center;

min-height: calc(100vh - var(--header-height) - var(--footer-height) - var(--padding-8x));
}

h1 {
color: var(--color-primary);
padding: var(--padding-2x) 0 var(--padding);

--bigger-title: 1;
font-size: calc(var(--font-size-h1) * var(--bigger-title));

max-width: 470px;

@include media.min-width(large) {
--bigger-title: 1.4;
margin-top: var(--padding-8x);
}
}

.sign-in {
padding: var(--padding) 0 0;
}

button {
font-size: var(--font-size-small);
}
</style>
<DeprecatedSignIn
on:junoSignIn={async () => await doSignIn('internetcomputer.org')}
on:junoSignInDeprecated={async () => await doSignIn('ic0.app')}
/>
</svelte:fragment>
</Container>
84 changes: 84 additions & 0 deletions src/frontend/src/lib/components/core/SignInRedeem.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script lang="ts">
import { signIn } from '$lib/services/auth.services.js';
import DeprecatedSignIn from '$lib/components/core/DeprecatedSignIn.svelte';
import IconSignIn from '$lib/components/icons/IconSignIn.svelte';
import { isBusy } from '$lib/stores/busy.store';
import Popover from '$lib/components/ui/Popover.svelte';
import Container from '$lib/components/ui/Container.svelte';
import { replaceHistory } from '$lib/utils/route.utils';
import { onMount } from 'svelte';

export let invitationCode: string;

const cleanUpInviteUrl = () => {
const url: URL = new URL(window.location.href);

url.searchParams.delete('invite');

replaceHistory(url);
};

onMount(cleanUpInviteUrl);

const redeemSignIn = async (domain?: 'internetcomputer.org' | 'ic0.app') => {
// Close popover to prevent glitch on successful login
visible = false;

const { success } = await signIn({
domain,
invitationCode
});

if (success === 'ok') {
return;
}

// In case logging was aborted or failed, reopen redeem popover since we hide it to avoid a glitch
visible = true;
};

let visible = false;
</script>

<Container>
Enter your code to join Juno and start building.
<svelte:fragment slot="title">Unlock Your Invitation</svelte:fragment>
<svelte:fragment slot="actions">
<button on:click={() => (visible = true)} disabled={$isBusy}
><IconSignIn size="20px" />
<span>Redeem invitation code</span></button
>
</svelte:fragment>
</Container>

<Popover bind:visible center={true} backdrop="dark">
<div class="content">
<h3>Redeem code</h3>

<p>Enter your code below, and sign in to join Juno.</p>

<input
bind:value={invitationCode}
aria-label="Invitation code"
name="invitation-code"
placeholder="Invitation code"
type="text"
required
/>

<button type="submit" disabled={$isBusy} on:click={async () => await redeemSignIn()}>
<IconSignIn size="20px" /> <span>Redeem by signing in</span>
</button>

<DeprecatedSignIn
on:junoSignIn={async () => await redeemSignIn('internetcomputer.org')}
on:junoSignInDeprecated={async () => await redeemSignIn('ic0.app')}
/>
</div>
</Popover>

<style lang="scss">
.content {
padding: var(--padding-2x);
}
</style>
58 changes: 58 additions & 0 deletions src/frontend/src/lib/components/ui/Container.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<div class="container">
<div class="overview">
<h1><slot name="title" /></h1>

<p><slot /></p>
</div>

<div class="sign-in">
<slot name="actions" />
</div>
</div>

<style lang="scss">
@use '../../../lib/styles/mixins/media';

.overview {
display: flex;
flex-direction: column;
align-items: center;
}

.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;

text-align: center;

min-height: calc(100vh - var(--header-height) - var(--footer-height) - var(--padding-8x));
}

h1 {
color: var(--color-primary);
padding: var(--padding-2x) 0 var(--padding);

--bigger-title: 1;
font-size: calc(var(--font-size-h1) * var(--bigger-title));

@include media.min-width(large) {
--bigger-title: 1.4;
margin-top: var(--padding-8x);
}
}

h1,
p {
max-width: 470px;
}

.sign-in {
padding: var(--padding) 0 0;
}

button {
font-size: var(--font-size-small);
}
</style>
Loading
Loading