Skip to content

Commit

Permalink
Show notifications on registration failure (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
lasuillard authored Jul 19, 2024
1 parent 1f57ea6 commit 20d6e5b
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 8 deletions.
3 changes: 2 additions & 1 deletion .devcontainer/docker-compose.devcontainer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ services:
volumes:
- ..:/workspace
environment:
DISPLAY: novnc:0.0
DISPLAY: ${DISPLAY:-novnc:0.0}
healthcheck:
disable: true
cap_add:
- SYS_ADMIN
shm_size: 2gb

novnc:
image: theasp/novnc:latest
Expand Down
5 changes: 3 additions & 2 deletions src/components/Config.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import P from 'flowbite-svelte/P.svelte';
import { get } from 'svelte/store';
import Eye from '~/components/Eye.svelte';
import { putMessage } from '~/lib/messages';
import { launchWebAuthFlow as _launchWebAuthFlow } from '~/lib/raindrop/auth';
import { accessToken, clientID, clientSecret, refreshToken } from '~/lib/settings';
Expand All @@ -20,9 +21,10 @@
});
accessToken.set(result.accessToken);
refreshToken.set(result.refreshToken);
putMessage({ type: 'success', message: 'Successfully authorized app.' });
} catch (err) {
// TODO: Show error message as modal or toast
console.error('Failed to authorize app:', err);
putMessage({ type: 'error', message: String(err) });
}
};
</script>
Expand Down Expand Up @@ -80,7 +82,6 @@
classDiv="w-full mr-2"
id="access-token"
type={showAccessToken ? 'text' : 'password'}
disabled
bind:value={$accessToken}
>
Access Token
Expand Down
2 changes: 1 addition & 1 deletion src/components/Config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('access token', () => {
const { getByTestId } = render(Config);
const input = getByTestId('access-token/input') as HTMLInputElement;
expect(input).toBeTruthy();
expect(input.getAttributeNames()).toContain('disabled');
expect(input.getAttributeNames()).not.toContain('disabled');
});

it('is masked by default and able to toggle it', async () => {
Expand Down
42 changes: 42 additions & 0 deletions src/components/Message.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<script lang="ts">
import { Toast } from 'flowbite-svelte';
import {
CheckCircleSolid,
CloseCircleSolid,
ExclamationCircleSolid
} from 'flowbite-svelte-icons';
import type { ComponentType } from 'svelte';
import { dismissMessage, type Message } from '~/lib/messages';
const messageMapping: {
[type: string]: { icon: ComponentType; color: 'blue' | 'green' | 'red' };
} = {
success: {
icon: CheckCircleSolid,
color: 'green'
},
info: {
icon: ExclamationCircleSolid,
color: 'blue'
},
error: {
icon: CloseCircleSolid,
color: 'red'
}
};
export let message: Message;
</script>

{#if message}
{@const color = messageMapping[message.type].color}
{@const icon = messageMapping[message.type].icon}
<div {...$$restProps}>
<Toast {color} on:close={() => dismissMessage(message.id)}>
<svelte:fragment slot="icon">
<svelte:component this={icon} class="h-5 w-5" />
</svelte:fragment>
{message.message}
</Toast>
</div>
{/if}
9 changes: 9 additions & 0 deletions src/components/Message.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// @vitest-environment jsdom
import { render } from '@testing-library/svelte';
import { expect, it } from 'vitest';
import Message from './Message.svelte';

it('renders OK', () => {
const { container } = render(Message);
expect(container).toBeTruthy();
});
48 changes: 48 additions & 0 deletions src/lib/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { writable } from 'svelte/store';

export type Message = {
id?: string;
type: 'success' | 'info' | 'error';
message: string;
};

export type MessageBox = {
[id: string]: Message;
};

export const messageBox = writable<MessageBox>({});

/**
* Put message to message box.
* @param message Message to put. If `.id` is not set, random generated ID will be used.
* @returns ID of message.
*/
export function putMessage(message: Message): string {
const id = message.id ?? Math.random().toString(36).substring(2);
messageBox.update((box) => {
box[id] = { id, ...message };
return box;
});
return id;
}

/**
* Dismiss message with ID.
* @param id ID of message.
*/
export function dismissMessage(id?: string) {
if (!id) return;

messageBox.update((box) => {
delete box[id];
return box;
});
}

/* c8 ignore start */
if (import.meta.vitest) {
const { describe } = import.meta.vitest;

describe.todo('To Do');
}
/* c8 ignore stop */
17 changes: 13 additions & 4 deletions src/pages/options/Page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,49 @@
import About from '~/components/About.svelte';
import Bookmarks from '~/components/Bookmarks.svelte';
import Config from '~/components/Config.svelte';
import Message from '~/components/Message.svelte';
import TryIt from '~/components/TryIt.svelte';
import { messageBox } from '~/lib/messages';
</script>

<main class="mx-1 mt-4 self-center">
<Tabs style="underline">
<TabItem open>
<div slot="title" class="flex items-center gap-2">
<BookmarkOutline size="sm" />
<BookmarkOutline size="sm" class="focus:outline-none" />
Bookmarks
</div>
<Bookmarks />
</TabItem>
<TabItem>
<div slot="title" class="flex items-center gap-2">
<SearchOutline size="sm" />
<SearchOutline size="sm" class="focus:outline-none" />
Try It
</div>
<TryIt />
</TabItem>
<TabItem>
<div slot="title" class="flex items-center gap-2">
<UserSettingsOutline size="sm" />
<UserSettingsOutline size="sm" class="focus:outline-none" />
Settings
</div>
<Config />
</TabItem>
<TabItem>
<div slot="title" class="flex items-center gap-2">
<QuestionCircleOutline size="sm" />
<QuestionCircleOutline size="sm" class="focus:outline-none" />
About
</div>
<About />
</TabItem>
</Tabs>

<!-- Global message box -->
<div class="absolute right-6 top-6 space-y-2">
{#each Object.entries($messageBox) as [id, message] (id)}
<Message {message} />
{/each}
</div>
</main>

<style lang="postcss">
Expand Down

0 comments on commit 20d6e5b

Please sign in to comment.