Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
38 changes: 33 additions & 5 deletions src/routes/(public)/(guest)/login/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,33 @@
import { Layout } from '@appwrite.io/pink-svelte';
let mail: string, pass: string, disabled: boolean;
let showPasswordLogin: boolean = false;
export let data;
$: showPasswordLogin = pass && pass.length > 0;
async function sendSignInCode() {
try {
disabled = true;
// use createEmailToken for sign in with code
const sessionToken = await sdk.forConsole.account.createEmailToken({
userId: 'unique',
email: mail
});
await goto(
`${base}/login/email-otp?email=${encodeURIComponent(mail)}&userId=${sessionToken.userId}`
);
Comment on lines +34 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve existing query params when switching to the OTP flow

Right now we build the OTP URL with only email and userId, which strips any pre-existing query params (code, campaign, redirect, etc.). The new loader in email-otp/+page.ts expects those params to rehydrate data, so dropping them prevents us from ever hitting the coupon/campaign apply-credit paths and also loses any redirect target after a successful OTP login. Please carry forward the current search params before adding email/userId.

-            await goto(
-                `${base}/login/email-otp?email=${encodeURIComponent(mail)}&userId=${sessionToken.userId}`
-            );
+            const params = new URLSearchParams(window.location.search);
+            params.set('email', mail);
+            params.set('userId', sessionToken.userId);
+
+            await goto(`${base}/login/email-otp?${params.toString()}`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`${base}/login/email-otp?email=${encodeURIComponent(mail)}&userId=${sessionToken.userId}`
);
const params = new URLSearchParams(window.location.search);
params.set('email', mail);
params.set('userId', sessionToken.userId);
await goto(`${base}/login/email-otp?${params.toString()}`);
🤖 Prompt for AI Agents
In src/routes/(public)/(guest)/login/+page.svelte around lines 34 to 35, the OTP
redirect builds a URL with only email and userId which drops any existing query
params (code, campaign, redirect, etc.); update the URL construction to preserve
current search params by copying the existing location.search (or current
URLSearchParams) into a new URLSearchParams instance, set/overwrite the email
and userId keys, and then append that full query string to
`${base}/login/email-otp` so all original params are carried forward along with
email and userId.

} catch (error) {
disabled = false;
addNotification({
type: 'error',
message: error.message
});
}
}
async function login() {
try {
disabled = true;
Expand Down Expand Up @@ -52,7 +76,6 @@
return;
}
// no specific redirect, so redirect will happen through invalidating the account
await invalidate(Dependencies.ACCOUNT);
} catch (error) {
disabled = false;
Expand Down Expand Up @@ -92,22 +115,27 @@
<Unauthenticated coupon={data?.couponData} campaign={data?.campaign}>
<svelte:fragment slot="title">Sign in</svelte:fragment>
<svelte:fragment>
<Form onSubmit={login}>
<Form onSubmit={showPasswordLogin ? login : sendSignInCode}>
<Layout.Stack>
<InputEmail
id="email"
label="Email"
placeholder="Email"
autofocus={true}
required={true}
bind:value={mail} />
<InputPassword
id="password"
label="Password"
placeholder="Password"
required={true}
required={false}
bind:value={pass} />
<Button fullWidth submit {disabled}>Sign in</Button>

{#if showPasswordLogin}
<Button fullWidth submit {disabled}>Sign in</Button>
{:else}
<Button fullWidth submit {disabled}>Get sign in code</Button>
{/if}

{#if isCloud}
<span class="with-separators eyebrow-heading-3">or</span>
<Button secondary fullWidth on:click={onGithubLogin} {disabled}>
Expand Down
118 changes: 118 additions & 0 deletions src/routes/(public)/(guest)/login/email-otp/+page.svelte
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

svelte5 runes.

Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<script lang="ts">
import { goto, invalidate } from '$app/navigation';
import { base } from '$app/paths';
import { InputDigits, Button } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Unauthenticated } from '$lib/layout';
import { Dependencies } from '$lib/constants';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { redirectTo } from '$routes/store';
import { user } from '$lib/stores/user';
import { Layout, Typography, Link } from '@appwrite.io/pink-svelte';
import { page } from '$app/state';

let otpCode: string = '';
let disabled: boolean = false;
let email: string = '';
let userId: string = '';

export let data;

// Get email and userId from URL params
$: email = page.url.searchParams.get('email') || '';
$: userId = page.url.searchParams.get('userId') || '';

async function verifyOTP() {
try {
disabled = true;
// Use createSession with the userId from createEmailToken
await sdk.forConsole.account.createSession({ userId: userId, secret: otpCode });

if ($user) {
trackEvent(Submit.AccountLogin, { mfa_used: 'none' });
addNotification({
type: 'success',
message: 'Successfully logged in.'
});
}

if (data?.couponData?.code) {
trackEvent(Submit.AccountCreate, {
campaign_name: data?.couponData?.code,
email: email,
name: $user?.name
});
await goto(`${base}/apply-credit?code=${data?.couponData?.code}`);
return;
}
if (data?.campaign?.$id) {
await goto(`${base}/apply-credit?campaign=${data.campaign?.$id}`);
return;
}
if ($redirectTo) {
window.location.href = $redirectTo;
return;
}

await invalidate(Dependencies.ACCOUNT);
} catch (error) {
disabled = false;
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.AccountLogin);
}
}

async function resendCode() {
try {
disabled = true;
const sessionToken = await sdk.forConsole.account.createEmailToken({
userId: 'unique',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ID.unique()

email: email
});
userId = sessionToken.userId;

addNotification({
type: 'success',
message: 'New sign in code sent to your email.'
});
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
} finally {
disabled = false;
}
}
</script>

<svelte:head>
<title>Enter your sign-in code - Appwrite</title>
</svelte:head>

<Unauthenticated coupon={data?.couponData} campaign={data?.campaign} align="center">
<svelte:fragment slot="title">Enter your sign-in code</svelte:fragment>
<svelte:fragment>
<Layout.Stack gap="l" justifyContent="center" alignContent="center" alignItems="center">
<Typography.Text align="center">
We sent a 6-digit code to {email}
</Typography.Text>

<form on:submit|preventDefault={verifyOTP}>
<Layout.Stack align="center">
<InputDigits bind:value={otpCode} required />
<Button submit {disabled} class="verify-button">Verify</Button>
</Layout.Stack>
</form>

<Typography.Text align="center">
Didn't get it?
<Link.Button on:click={resendCode} {disabled}>Resend code</Link.Button>
</Typography.Text>
</Layout.Stack>
</svelte:fragment>
</Unauthenticated>
39 changes: 39 additions & 0 deletions src/routes/(public)/(guest)/login/email-otp/+page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { base } from '$app/paths';
import type { Campaign } from '$lib/stores/campaigns';
import { sdk } from '$lib/stores/sdk';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';

export const load: PageLoad = async ({ url }) => {
if (!url.searchParams.has('email')) {
redirect(303, `${base}/login`);
}

if (url.searchParams.has('code')) {
const code = url.searchParams.get('code');
let campaign: Campaign;
try {
const couponData = await sdk.forConsole.billing.getCoupon(code);
if (couponData.campaign) {
campaign = await sdk.forConsole.billing.getCampaign(couponData.campaign);
return {
couponData,
campaign
};
} else redirect(303, `${base}/login`);
} catch (e) {
redirect(303, `${base}/login`);
}
}
if (url.searchParams.has('campaign')) {
const campaignId = url.searchParams.get('campaign');
let campaign: Campaign;
try {
campaign = await sdk.forConsole.billing.getCampaign(campaignId);
return { campaign };
} catch (e) {
redirect(303, `${base}/login`);
}
}
return;
};