diff --git a/README.md b/README.md index 19beafc..e1a2e61 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,5 @@ You can preview the production build with `pnpm run preview`. - `VITE_GA_TAG` for Google Analytics tag - `VITE_DB_NAME` the pouchdb database name - `VITE_REMOTE_DB` remote pouchdb/couchdb database url. Required `VITE_SYNCED` to be `true` -- `VITE_REMOTE_DB_USERNAME` remote pouchdb/couchdb database username. Required `VITE_SYNCED` to be `true` -- `VITE_REMOTE_DB_PASSWORD` remote pouchdb/couchdb database password. Required `VITE_SYNCED` to be `true` - `VITE_AUTH0_CLIENT` [Auth0](https://auth0.com/) client id - `VITE_AUTH0_DOMAIN` [Auth0](https://auth0.com/) domain diff --git a/env.sample b/env.sample index 905c105..34d0b67 100644 --- a/env.sample +++ b/env.sample @@ -1,7 +1,5 @@ VITE_DB_NAME=database_name VITE_REMOTE_DB=remote_database_url -VITE_REMOTE_DB_USERNAME=database_username -VITE_REMOTE_DB_PASSWORD=database_password VITE_GA_TAG=google_analytics_tag VITE_SYNCED=true_or_false_to_toggle_remote_sync VITE_AUTH0_DOMAIN=auth0_domain diff --git a/src/App.svelte b/src/App.svelte index 23c61fc..d5eb9c9 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -13,11 +13,11 @@ import { pwaInfo } from 'virtual:pwa-info'; import ExportImport from './components/ExportImport.svelte'; import Toast from './components/Toast.svelte'; + import ToggleTheme from './components/ToggleTheme.svelte'; import { toastActions, toastMessage } from './stores'; let auth0: Auth0Client; let wrapper: HTMLElement; - let theme = $state(null); let menuItems = $derived( tabs.map((item) => ({ @@ -46,26 +46,6 @@ }); }; - const toggleTheme = () => { - const html = document.querySelector('html'); - const themeData = html?.getAttribute('data-theme'); - // const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)'); - let nextTheme; - switch (themeData) { - case 'dark': - nextTheme = 'light'; - break; - case 'light': - nextTheme = null; - break; - default: - nextTheme = 'dark'; - break; - } - theme = nextTheme; - nextTheme ? html?.setAttribute('data-theme', nextTheme) : html?.removeAttribute('data-theme'); - }; - onMount(async () => { handleBackToTop(); if (import.meta.env.VITE_SYNCED === 'true' && navigator.onLine) { @@ -93,6 +73,7 @@ {#if import.meta.env.VITE_SYNCED === 'true' && !$isLoggedin}
+
{:else} @@ -103,11 +84,13 @@

+ +
{#if $isLoggedin}
- {$user.name} + {$user.nickname}
{/if} @@ -126,48 +109,7 @@ > {/if} - + { @@ -197,16 +139,14 @@ min-height: 100vh; } - :global(.ColorToggle) { - margin: auto auto 2rem 1rem; - position: sticky; - bottom: 2rem; - } - .Login { padding: 2rem; gap: 2rem; align-items: flex-start; + + :global(.ColorToggle) { + margin-left: auto; + } } .Menu { @@ -217,6 +157,8 @@ .Header { align-self: flex-end; + display: flex; + flex-direction: row-reverse; } .Logo { @@ -241,6 +183,11 @@ margin: 2rem; justify-content: end; align-items: center; + + img { + inline-size: 3rem; + border-radius: 100%; + } } [data-syncing] { diff --git a/src/auth.ts b/src/auth.ts index b11a35d..b78b4b2 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -25,12 +25,12 @@ export const checkAuth = async (auth0: Auth0Client) => { // if code then login success if (params.has('code')) { // Let the Auth0 SDK do it's stuff - save some state, etc. - await auth0?.handleRedirectCallback(); + await auth0.handleRedirectCallback(); // Can be smart here and redirect to original path instead of root window.history.replaceState({}, document.title, '/'); } - const _isAuthenticated = await auth0?.isAuthenticated(); + const _isAuthenticated = await auth0.isAuthenticated(); isLoggedin.set(_isAuthenticated); if (_isAuthenticated) { @@ -39,13 +39,25 @@ export const checkAuth = async (auth0: Auth0Client) => { // Get the access token. Make sure to supply audience property // in Auth0 config, otherwise you will soon start throwing stuff! - const _token = await auth0.getTokenSilently(); - token.set(_token); + const { id_token } = await auth0.getTokenSilently({ + authorizationParams: { + redirect_uri: window.location.origin + }, + detailedResponse: true + }); + + token.set(id_token); // refresh token after specific period or things will stop // working. Useful for long-lived apps like dashboards. intervalId = setInterval(async () => { - token.set(await auth0.getTokenSilently()); + const { id_token } = await auth0.getTokenSilently({ + authorizationParams: { + redirect_uri: window.location.origin + }, + detailedResponse: true + }); + token.set(id_token); }, refreshRate); } // }); diff --git a/src/components/ToggleTheme.svelte b/src/components/ToggleTheme.svelte new file mode 100644 index 0000000..7da33c4 --- /dev/null +++ b/src/components/ToggleTheme.svelte @@ -0,0 +1,67 @@ + + + diff --git a/src/db.ts b/src/db.ts index 6f855be..c7220eb 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,12 +1,9 @@ import { addRxPlugin, type RxDatabase, type RxJsonSchema } from 'rxdb'; import { RxDBDevModePlugin } from 'rxdb/plugins/dev-mode'; import { RxDBUpdatePlugin } from 'rxdb/plugins/update'; -import { - getFetchWithCouchDBAuthorization, - replicateCouchDB -} from 'rxdb/plugins/replication-couchdb'; +import { replicateCouchDB } from 'rxdb/plugins/replication-couchdb'; import { RxDBQueryBuilderPlugin } from 'rxdb/plugins/query-builder'; -import { SyncStatus, status } from './stores'; +import { SyncStatus, status, token } from './stores'; import { createCollection, createDatabase } from './lib/rxdb'; export type Todo = { @@ -26,6 +23,20 @@ addRxPlugin(RxDBQueryBuilderPlugin); const dbName = import.meta.env.VITE_DB_NAME || 'Todone'; +// Custom fetch with Auth token +const fetchWithAuth = (url: RequestInfo | URL, options: any) => { + let authToken; + const optionsWithAuth = Object.assign({}, options); + if (!optionsWithAuth.headers) { + optionsWithAuth.headers = {}; + } + + token.subscribe((v) => (authToken = v)); + optionsWithAuth.headers['Authorization'] = `Bearer ${authToken}`; + + return fetch(url, optionsWithAuth); +}; + // Schema const todoSchema: RxJsonSchema = { version: 0, @@ -47,52 +58,49 @@ const getDB = async () => { try { // Create database instance if (!db) { - db = await createDatabase(dbName) + db = await createDatabase(dbName); } else { - return db + return db; } // Setup collections - await createCollection(db!, 'todos', todoSchema) + await createCollection(db!, 'todos', todoSchema).then(() => { + if (import.meta.env.VITE_SYNCED === 'true') { + setupReplication(); + } + }); // Setup replication const setupReplication = () => { const url = import.meta.env.VITE_REMOTE_DB; - const username = import.meta.env.VITE_REMOTE_DB_USERNAME; - const password = import.meta.env.VITE_REMOTE_DB_PASSWORD; - + const replicationState = replicateCouchDB({ replicationIdentifier: 'couchdb-replication', collection: db?.todos, live: true, url, - fetch: getFetchWithCouchDBAuthorization(username, password), + fetch: fetchWithAuth, pull: {}, push: {} }); - + replicationState.active$.subscribe(() => status.set(SyncStatus.ACTIVE)); replicationState.error$.subscribe(() => status.set(SyncStatus.ERROR)); }; - if (import.meta.env.VITE_SYNCED === 'true') { - setupReplication(); - } - return db; - } catch (e) { - console.error(`Error initializing database`, e) + console.error(`Error initializing database`, e); } -} +}; export const getTodos = async () => { - const db = await getDB() + const db = await getDB(); return db!.todos.find().sort({ updated: 'desc' }).$; }; export const add = async (data: { title: string; value: string }) => { - const db = await getDB() + const db = await getDB(); const now = new Date().toISOString(); return db!.todos.insert({ ...data, updated: now, id: now }); @@ -109,7 +117,7 @@ export const update = async ({ value: string; completed: boolean; }) => { - const db = await getDB() + const db = await getDB(); const now = new Date().toISOString(); const todo = db!.todos.findOne({ @@ -127,7 +135,7 @@ export const update = async ({ }; export const remove = async (id: string) => { - const db = await getDB() + const db = await getDB(); await db!.todos .findOne({ selector: { @@ -138,7 +146,7 @@ export const remove = async (id: string) => { }; export const setCompleted = async (id: string, completed: boolean) => { - const db = await getDB() + const db = await getDB(); return await db!.todos .findOne({ selector: { @@ -153,12 +161,11 @@ export const setCompleted = async (id: string, completed: boolean) => { }; export const exportTodos = async () => { - const db = await getDB() + const db = await getDB(); return db!.todos.find().sort({ updated: 'desc' }).exec(); }; export const importTodos = async (data: Todo[]) => { - const db = await getDB() + const db = await getDB(); return db!.todos.bulkUpsert(data); }; - diff --git a/src/stores.ts b/src/stores.ts index 64e462f..26572c2 100644 --- a/src/stores.ts +++ b/src/stores.ts @@ -20,7 +20,7 @@ export const status = writable>(SyncStatus.NOT_SYNCE export const isLoggedin = writable(false); -export const user = writable<{ name?: string }>({}); +export const user = writable<{ name?: string; nickname?: string; picture?: string }>({}); export const token = writable(''); export const toastMessage = writable(null); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 1545b6b..9f7308f 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -8,8 +8,6 @@ interface ImportMetaEnv { readonly VITE_GA_TAG: string; readonly VITE_DB_NAME: string; readonly VITE_REMOTE_DB: string; - readonly VITE_REMOTE_DB_USERNAME: string; - readonly VITE_REMOTE_DB_PASSWORD: string; readonly VITE_AUTH0_CLIENT: string; readonly VITE_AUTH0_DOMAIN: string; readonly VITE_SENTRY_DSN: string;