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

Fix pincode on Android #5965

Merged
merged 8 commits into from
Jun 30, 2024
Merged
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
3 changes: 1 addition & 2 deletions frontend/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,9 @@
"svelte-easy-crop": "^2.0.1",
"svelte-i18n": "^3.7.0",
"svelte-material-icons": "^3.0.5",
"svelte-pincode": "^2.2.0",
"svelte-qrcode-image": "^1.0.0-rc.2",
"usergeek-ic-js": "git+https://github.com/hpeebles/usergeek-ic-js.git#b7e56319447c26e4f8f18779859435137aafc4c2",
"uuid": "^9.0.0",
"wavesurfer.js": "^7.7.5"
}
}
}
50 changes: 5 additions & 45 deletions frontend/app/src/components/SignInWithMagicLink.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
import FancyLoader from "./icons/FancyLoader.svelte";
import ModalContent from "./ModalContent.svelte";
import { pageReplace } from "../routes";
import { Pincode, PincodeInput } from "svelte-pincode";
import ButtonGroup from "./ButtonGroup.svelte";
import Button from "./Button.svelte";
import page from "page";
import Pincode from "./pincode/Pincode.svelte";

const client = getContext<OpenChat>("client");
const dispatch = createEventDispatcher();
Expand All @@ -19,24 +17,13 @@
let status: string | undefined = undefined;
let message = "magicLink.closeMessage";
let busy = false;
let value: string;
let code: string[];

onMount(() => {
pageReplace("/home");
});

function submit() {
if (!isCodeComplete(code)) {
return;
}

if (!isCodeValid(code)) {
status = "magicLink.code_invalid";
return;
}

qs += "&u=" + value;
function onCodeEntered(ev: CustomEvent<{ code: string[]; value: string }>) {
qs += "&u=" + ev.detail.value;

busy = true;

Expand All @@ -59,21 +46,13 @@
.finally(() => (busy = false));
}

function isCodeComplete(code: string[]): boolean {
return code.filter((c) => c.length > 0).length === 3;
}

function isCodeValid(code: string[]): boolean {
return code.filter((c) => /^[0-9]$/.test(c)).length === 3;
}

function close() {
dispatch("close");
}
</script>

<div class="magic-link">
<ModalContent>
<ModalContent hideFooter>
<div class="header" slot="header">
<Translatable resourceKey={i18nKey("magicLink.title")} />
</div>
Expand All @@ -86,36 +65,17 @@
{:else if status === undefined}
<p><Translatable resourceKey={i18nKey("magicLink.enterCode")} /></p>

<Pincode bind:value bind:code on:complete={submit}>
<PincodeInput />
<PincodeInput />
<PincodeInput />
</Pincode>
<Pincode type="numeric" length={3} on:complete={onCodeEntered} />
{:else}
<p class="status"><Translatable resourceKey={i18nKey(status)} /></p>
<p class="message"><Translatable resourceKey={i18nKey(message)} /></p>
{/if}
</div>
</div>
<div class="footer" slot="footer">
<ButtonGroup align="center">
<Button disabled={busy} on:click={close} secondary>
{$_("cancel")}
</Button>
<Button loading={busy} disabled={busy} on:click={submit}>
{$_("submit")}
</Button>
</ButtonGroup>
</div>
</ModalContent>
</div>

<style lang="scss">
:global([data-pincode]) {
gap: $sp3;
border: none !important;
}

:global(.magic-link .modal-content) {
min-width: 576px;
color: var(--txt);
Expand Down
28 changes: 2 additions & 26 deletions frontend/app/src/components/home/PinNumberModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Translatable from "../Translatable.svelte";
import { i18nKey } from "../../i18n/i18n";
import ModalContent from "../ModalContent.svelte";
import { Pincode, PincodeInput } from "svelte-pincode";
import Pincode from "../pincode/Pincode.svelte";

const dispatch = createEventDispatcher();

Expand All @@ -13,25 +13,8 @@
let showError = false;

function onPinComplete(ev: CustomEvent<{ code: string[]; value: string }>) {
if (!isPinComplete(ev.detail.code)) {
return;
}

if (!isPinValid(ev.detail.code)) {
showError = true;
return;
}

dispatch("complete", ev.detail.value);
}

function isPinValid(pin: string[]): boolean {
return pin.filter((c) => /^[0-9]$/.test(c)).length === 6;
}

function isPinComplete(pin: string[]): boolean {
return pin.filter((c) => c.length > 0).length === 6;
}
</script>

<ModalContent closeIcon hideFooter fitToContent fixedWidth={false} on:close>
Expand All @@ -44,14 +27,7 @@
<Translatable resourceKey={i18nKey(message)} />
</p>
{/if}
<Pincode bind:value={pin} on:complete={onPinComplete}>
<PincodeInput />
<PincodeInput />
<PincodeInput />
<PincodeInput />
<PincodeInput />
<PincodeInput />
</Pincode>
<Pincode type="numeric" length={6} bind:value={pin} on:complete={onPinComplete} />
{#if showError}
<div class="error">
<Translatable resourceKey={i18nKey("pinNumber.invalid")} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import Translatable from "../../Translatable.svelte";
import { i18nKey } from "../../../i18n/i18n";
import ModalContent from "../../ModalContent.svelte";
import { Pincode, PincodeInput } from "svelte-pincode";
import ButtonGroup from "../../ButtonGroup.svelte";
import Button from "../../Button.svelte";
import { pinNumberFailureStore, type OpenChat } from "openchat-client";
import ErrorMessage from "../../ErrorMessage.svelte";
import { toastStore } from "../../../stores/toast";
import { pinNumberErrorMessageStore } from "../../../stores/pinNumber";
import Pincode from "../../pincode/Pincode.svelte";

const client = getContext<OpenChat>("client");
const dispatch = createEventDispatcher();
Expand Down Expand Up @@ -76,14 +76,7 @@
{#if type === "change"}
<div><Translatable resourceKey={i18nKey("pinNumber.currentPin")} /></div>
{/if}
<Pincode bind:code={currPinArray}>
<PincodeInput />
<PincodeInput />
<PincodeInput />
<PincodeInput />
<PincodeInput />
<PincodeInput />
</Pincode>
<Pincode type="numeric" length={6} bind:code={currPinArray} />
</div>
{/if}
{#if type !== "clear"}
Expand All @@ -92,14 +85,7 @@
<div><Translatable resourceKey={i18nKey("pinNumber.newPin")} /></div>
<!-- <Legend label={i18nKey("pinNumber.newPin")}></Legend> -->
{/if}
<Pincode bind:code={newPinArray}>
<PincodeInput />
<PincodeInput />
<PincodeInput />
<PincodeInput />
<PincodeInput />
<PincodeInput />
</Pincode>
<Pincode type="numeric" length={6} bind:code={newPinArray} />
</div>
{/if}
{#if errorMessage !== undefined}
Expand All @@ -119,11 +105,6 @@
</ModalContent>

<style lang="scss">
:global([data-pincode]) {
gap: $sp3;
border: none !important;
}

.header {
text-align: center;
}
Expand Down
107 changes: 107 additions & 0 deletions frontend/app/src/components/pincode/Pincode.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<script lang="ts" context="module">
export type PincodeType = "alphanumeric" | "numeric";
export type PincodeChar = {
idx: number;
value: string;
};
</script>

<script lang="ts">
import { createEventDispatcher, afterUpdate } from "svelte";
import PincodeInput from "./PincodeInput.svelte";

export let length: number;
export let code: string[] = [];
export let value: string = "";
export let type: PincodeType = "alphanumeric";
export let complete: boolean = false;
export let selectTextOnFocus: boolean = false;

function initialise(length: number): PincodeChar[] {
return [...Array(length).keys()].map((idx) => ({ idx, value: "" }));
}

const dispatch = createEventDispatcher();
$: characters = initialise(length);

let ref: HTMLDivElement | undefined = undefined;
let prevValue = value;

function setCode() {
code = characters.map((char) => char.value || "");
}

function focusNextInput(idx: number) {
const inputs = ref?.querySelectorAll("input");
if (inputs) {
const nextInput = inputs[idx + 1];
nextInput?.focus();
}
}

function clear(ev: CustomEvent<PincodeChar>) {
if (!characters[ev.detail.idx].value) {
const inputs = ref?.querySelectorAll("input");
if (inputs) {
const prevInput = inputs[ev.detail.idx - 1];
prevInput?.focus();
prevInput?.select();
}
}
characters = characters.map((char, i) => {
if (i === ev.detail.idx) return { ...char, value: "" };
return char;
});
setCode();
}

function update(ev: CustomEvent<PincodeChar>) {
characters = characters.map((char, idx) => {
if (idx === ev.detail.idx) return ev.detail;
return char;
});
setCode();
focusNextInput(ev.detail.idx);
}

afterUpdate(() => {
if (complete) dispatch("complete", { code, value });
});

function handlePaste(e: ClipboardEvent) {
e.preventDefault();
code =
e.clipboardData
?.getData("text")
.split("")
.filter((it) => it !== " ") ?? [];
}

$: value = code.join("");
$: complete = code.length > 0 && code.filter(Boolean).length === characters.length;
$: if (code) {
characters = characters.map((char, i) => ({ ...char, value: code[i] }));
}
$: if (code.length === 0) {
characters = characters.map((char) => ({ ...char, value: "" }));
}
$: {
if (prevValue !== value && value.length === 0) {
dispatch("clear");
}
prevValue = value;
}
</script>

<div class="pincode" bind:this={ref} on:paste={handlePaste}>
{#each characters as char}
<PincodeInput on:update={update} on:clear={clear} {type} {selectTextOnFocus} bind:char />
{/each}
</div>

<style lang="scss">
.pincode {
display: inline-flex;
gap: $sp3;
}
</style>
Loading
Loading