Skip to content

Commit

Permalink
feat: view email login flow (#28)
Browse files Browse the repository at this point in the history
* feat: add email input

* feat: add otp verification ui
  • Loading branch information
teodorus-nathaniel authored Dec 30, 2024
1 parent 38c2b8d commit 45e5daa
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"vite": "^5.0.0"
},
"dependencies": {
"@k4ung/svelte-otp": "^0.0.9",
"@tanstack/svelte-query": "^5.62.2",
"bits-ui": "^0.21.16",
"clsx": "^2.1.1",
Expand Down
52 changes: 52 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import { WalletConnect } from './steps/wallet-connect/index.js';
import Thirdweb from './components/thirdweb.svelte';
import type { ConnectWalletModalProps } from './index.js';
import { OtpVerification } from './steps/otp-verification/index.js';
export let step: ConnectWalletModalStep;
export let setStep: ConnectWalletModalStepProps<'provider-selector'>['setStep'];
Expand Down Expand Up @@ -43,7 +44,8 @@
};
};
$: hideFooter = step === 'oauth-error' || step === 'oauth-loading' || step === 'wallet-connect';
const hideFooterSteps = ['otp-verification', 'oauth-error', 'oauth-loading', 'wallet-connect'];
$: hideFooter = hideFooterSteps.includes(step);
</script>

<div
Expand All @@ -63,6 +65,18 @@
{setModalOpen}
{setCustomBackClick}
/>
{:else if step === 'otp-verification'}
<OtpVerification
{wallets}
{chains}
{walletConnect}
{setStep}
{onFinishConnect}
{chain}
{additionalProps}
{setModalOpen}
{setCustomBackClick}
/>
{:else if step === 'wallet-selector'}
<WalletSelector
{wallets}
Expand Down
4 changes: 3 additions & 1 deletion src/lib/components/connect-wallet-modal/steps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export const connectWalletModalSteps = [
'oauth-loading',
'oauth-error',
'wallet-selector',
'wallet-connect'
'wallet-connect',
'otp-verification'
] as const;
export type ConnectWalletModalStep = (typeof connectWalletModalSteps)[number];

Expand All @@ -22,6 +23,7 @@ type ConnectWalletModalStepsAdditionalProps = {
'wallet-connect': {
wallet: Wallet;
};
'otp-verification': { email: string };
};

export type ConnectWalletModalStepProps<CurrentStep extends ConnectWalletModalStep> = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import OtpVerification from './otp-verification.svelte';

export { OtpVerification };
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script lang="ts">
import { Spinner } from '$/components/ui/spinner/index.js';
import { onMount } from 'svelte';
import type { ConnectWalletModalStepProps } from '../index.js';
import SvelteOtp from '@k4ung/svelte-otp';
import { cn } from '$/utils.js';
import { Button } from '$/components/ui/button/index.js';
type $$Props = ConnectWalletModalStepProps<'otp-verification'>;
export let additionalProps: $$Props['additionalProps'];
type AccountStatus = 'sending' | 'sent' | 'error';
let accountStatus: AccountStatus = 'sending';
let otp = '';
onMount(async () => {
// TODO: send otp
await new Promise((resolve) => setTimeout(resolve, 500));
accountStatus = 'sent';
});
</script>

{#if accountStatus === 'sending'}
<div class="twsv-flex twsv-flex-col twsv-items-center twsv-gap-8 twsv-py-12 twsv-text-center">
<Spinner />
</div>
{:else}
<div class="twsv-flex twsv-flex-col twsv-items-center twsv-gap-8 twsv-pt-8 twsv-text-center">
<div class="twsv-flex twsv-flex-col twsv-gap-1 twsv-font-medium">
<span class="twsv-text-muted-foreground">Enter the verification code sent to</span>
<span>{additionalProps.email}</span>
</div>
<SvelteOtp
numOfInputs={6}
numberOnly
inputClass={cn(
'!twsv-w-10 !twsv-h-10 twsv-rounded-xl !twsv-border-2 !twsv-border-secondary !twsv-bg-transparent focus-visible:twsv-outline-none focus-visible:twsv-ring-2 focus-visible:twsv-ring-accent'
)}
bind:value={otp}
/>
<Button variant="accent" size="lg" class="twsv-w-full">Verify</Button>
<div
class="-twsv-ml-6 -twsv-mr-6 twsv-self-stretch twsv-border-t twsv-border-border twsv-pb-1 twsv-pt-4"
>
<Button variant="link" size="auto">Resend verification code</Button>
</div>
</div>
{/if}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<script lang="ts">
import { Button } from '$/components/ui/button/index.js';
import { Input, type FormInputEvent } from '$/components/ui/input/index.js';
import { cn } from '$/utils.js';
import { validateEmail } from '$/utils/validation.js';
import ArrowRight from 'lucide-svelte/icons/arrow-right';
import { onMount } from 'svelte';
import type { ConnectWalletModalStepProps } from '../index.js';
export let setStep: ConnectWalletModalStepProps<'provider-selector'>['setStep'];
let value = '';
let error = '';
let showError = false;
$: shownError = showError && error;
onMount(() => {
showError = false;
});
const handleChange: (e: FormInputEvent<KeyboardEvent>) => void = (event) => {
if (event.key === 'Enter') {
showError = true;
}
const input = event.target as HTMLInputElement;
value = input.value;
if (!validateEmail(value)) {
error = 'Invalid email address';
} else {
error = '';
}
};
const handleSubmit = (event: SubmitEvent) => {
event.preventDefault();
showError = true;
setStep('otp-verification', { email: value });
};
</script>

<form class="twsv-relative" on:submit={handleSubmit}>
<Input
{value}
on:keypress={handleChange}
placeholder="Email address"
class={cn('twsv-pr-16 twsv-text-base', shownError && '!twsv-border-red-500')}
/>
<div
class="twsv-absolute twsv-right-0 twsv-top-1/2 twsv-h-12 twsv-w-12 -twsv-translate-y-1/2 twsv-p-px"
>
<Button
size="auto"
variant="ghost"
class={cn(
'twsv-h-full twsv-w-full twsv-rounded-l-none twsv-p-2 twsv-text-muted-foreground !twsv-outline-none !twsv-ring-0 !twsv-ring-offset-0 focus-visible:twsv-bg-secondary/50 focus-visible:twsv-text-secondary-foreground'
)}
type="submit"
on:click={() => {
showError = true;
}}
>
<ArrowRight />
</Button>
</div>
</form>
{#if shownError}
<p class="twsv-mt-1 twsv-text-sm twsv-text-red-500">{shownError}</p>
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { Separator } from '$/components/ui/separator/index.js';
import { SocialIcon } from '../../components/social-icon/index.js';
import type { ConnectWalletModalStepProps } from '../index.js';
import EmailInput from './email-input.svelte';
import { SUPPORTED_SOCIAL_PROVIDERS } from './index.js';
import SocialProviderButton from './social-provider-button.svelte';
Expand All @@ -17,6 +18,9 @@
<SocialProviderButton {chain} {setStep} {provider} {onFinishConnect} />
{/each}
</div>
<div class="twsv-mt-4">
<EmailInput {setStep} />
</div>
<div class="twsv-relative">
<Separator orientation="horizontal" class="twsv-relative twsv-my-6" />
<span
Expand Down
29 changes: 29 additions & 0 deletions src/lib/components/ui/input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Root from "./input.svelte";

export type FormInputEvent<T extends Event = Event> = T & {
currentTarget: EventTarget & HTMLInputElement;
};
export type InputEvents = {
blur: FormInputEvent<FocusEvent>;
change: FormInputEvent<Event>;
click: FormInputEvent<MouseEvent>;
focus: FormInputEvent<FocusEvent>;
focusin: FormInputEvent<FocusEvent>;
focusout: FormInputEvent<FocusEvent>;
keydown: FormInputEvent<KeyboardEvent>;
keypress: FormInputEvent<KeyboardEvent>;
keyup: FormInputEvent<KeyboardEvent>;
mouseover: FormInputEvent<MouseEvent>;
mouseenter: FormInputEvent<MouseEvent>;
mouseleave: FormInputEvent<MouseEvent>;
mousemove: FormInputEvent<MouseEvent>;
paste: FormInputEvent<ClipboardEvent>;
input: FormInputEvent<InputEvent>;
wheel: FormInputEvent<WheelEvent>;
};

export {
Root,
//
Root as Input,
};
Loading

0 comments on commit 45e5daa

Please sign in to comment.