more things
This commit is contained in:
parent
3e6bed20a6
commit
cdb8ccbf97
6 changed files with 193 additions and 6 deletions
File diff suppressed because one or more lines are too long
|
@ -144,6 +144,35 @@ app.get("/entry/list", {
|
|||
},
|
||||
})
|
||||
|
||||
app.post("/auth/heirs", {
|
||||
async handler(request, reply) {
|
||||
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
||||
let { payload } = await auth.verifyJwt(jwt);
|
||||
|
||||
if (payload.uid == null) {
|
||||
reply.status(401);
|
||||
return;
|
||||
}
|
||||
|
||||
let user = await auth.user(payload.uid);
|
||||
user.heirs = request.body;
|
||||
|
||||
await auth.updateUser(payload.uid, user);
|
||||
},
|
||||
schema: {
|
||||
headers: {
|
||||
type: "object",
|
||||
properties: {
|
||||
authorization: { type: "string" },
|
||||
},
|
||||
required: ["authorization"],
|
||||
},
|
||||
body: {
|
||||
type: "array",
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.get("/auth/genkey", {
|
||||
async handler(request) {
|
||||
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
|
||||
|
|
|
@ -7,9 +7,16 @@ export type Credentials = {
|
|||
token: string,
|
||||
}
|
||||
|
||||
export type AccountHeir = {
|
||||
contactMethod: "email",
|
||||
name: string,
|
||||
value: string,
|
||||
}
|
||||
|
||||
export type Account = {
|
||||
uid: string,
|
||||
name: string,
|
||||
heirs: AccountHeir[],
|
||||
}
|
||||
|
||||
function sendRequest(path: string, credentials?: Credentials, request: RequestInit = {}, params: string = "") {
|
||||
|
@ -88,6 +95,16 @@ export async function deleteEntry(credentials: Credentials, entry_id: string): P
|
|||
await sendRequest('/entry', credentials, { method: 'DELETE' }, `?entry_id=${entry_id}`)
|
||||
}
|
||||
|
||||
export async function updateHeirs(credentials: Credentials, heirs: AccountHeir[]): Promise<void> {
|
||||
await sendRequest('/auth/heirs', credentials, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(heirs),
|
||||
});
|
||||
}
|
||||
|
||||
export async function uploadAsset(session_key: string, file: File): Promise<string> {
|
||||
let url = new URL('/asset', ASSET_API_ENDPOINT);
|
||||
url.search = `?session_key=${session_key}`
|
||||
|
|
|
@ -3,10 +3,14 @@ import { accountData, assetEndpoint, genSessionKey, type Account, type Credentia
|
|||
|
||||
const CREDENTIALS_KEY = 'v0:credentials'
|
||||
|
||||
let _credentials: Credentials | null = null
|
||||
export const credentials = writable<Credentials | null>()
|
||||
credentials.subscribe((value) => {
|
||||
if (value != null) {
|
||||
localStorage.setItem(CREDENTIALS_KEY, JSON.stringify(value))
|
||||
_credentials = value;
|
||||
localStorage.setItem( CREDENTIALS_KEY, JSON.stringify(value))
|
||||
} else {
|
||||
_credentials = null;
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -45,3 +49,18 @@ export async function initializeStores() {
|
|||
asset_endpoint.set(asset_result)
|
||||
}
|
||||
}
|
||||
|
||||
export async function refreshAccount() {
|
||||
if (_credentials == null) {
|
||||
console.warn("Requested to refresh the user account but credentials are null.")
|
||||
return;
|
||||
}
|
||||
|
||||
let refreshedAccount = await accountData(_credentials)
|
||||
if ('error' in refreshedAccount) {
|
||||
console.warn("Failed to refresh the user account.")
|
||||
return;
|
||||
}
|
||||
|
||||
account.set(refreshedAccount)
|
||||
}
|
|
@ -1,14 +1,119 @@
|
|||
<script lang="ts">
|
||||
import { account, credentials } from "$lib/stores";
|
||||
import { createForm } from "felte";
|
||||
import { account, credentials, refreshAccount } from "$lib/stores";
|
||||
import { type AccountHeir, updateHeirs } from "$lib/api";
|
||||
|
||||
credentials.subscribe((v) => v == null && (setTimeout(() => window.location.pathname = '/auth/login', 200)))
|
||||
|
||||
let heirWizard = false;
|
||||
|
||||
const { form, errors } = createForm({
|
||||
onSubmit: async (values) => {
|
||||
let heir: AccountHeir = {
|
||||
contactMethod: values.contactMethod,
|
||||
name: values.name,
|
||||
value: values.contactDetails,
|
||||
};
|
||||
|
||||
let currentHeirs = structuredClone($account!.heirs)
|
||||
let updatedHeirs = [heir, ...currentHeirs];
|
||||
|
||||
await updateHeirs($credentials!, updatedHeirs);
|
||||
await refreshAccount();
|
||||
|
||||
heirWizard = false;
|
||||
},
|
||||
validate: (values) => {
|
||||
let errors = {}
|
||||
|
||||
if (values.contactMethod == null || values.contactMethod.length === 0) {
|
||||
errors['contactMethod'] = 'Must choose a contact method'
|
||||
}
|
||||
|
||||
if (values.name == null || values.name.length === 0) {
|
||||
errors['name'] = 'Must not be empty'
|
||||
}
|
||||
|
||||
if (values.contactDetails == null || values.contactDetails.length === 0) {
|
||||
errors['contactDetails'] = 'Must not be empty'
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
})
|
||||
|
||||
async function removeHeir(heir: AccountHeir) {
|
||||
let currentHeirs = structuredClone($account!.heirs)
|
||||
let updatedHeirs = currentHeirs
|
||||
.filter((v) => v.value !== heir.value);
|
||||
|
||||
await updateHeirs($credentials!, updatedHeirs);
|
||||
await refreshAccount();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mt-3.5 justify-center flex">
|
||||
<div class="w-[60%] flex flex-col">
|
||||
<h1 class="text-2xl pb-3.5">Welcome back, <span class="font-bold">{$account?.name}</span>.</h1>
|
||||
<p>
|
||||
The account panel is still work in progress, feel free to contact support with any inquires.
|
||||
</p>
|
||||
<div>
|
||||
<div class="flex justify-between mb-2">
|
||||
<h2 class="text-xl pb-2.5">Heirs</h2>
|
||||
{#if $account?.heirs.length > 0}
|
||||
<button on:click={() => heirWizard = !heirWizard} class="rounded-lg bg-violet-700 text-white px-2.5 py-1 text-center hover:bg-violet-800 focus:ring-4 focus:ring-violet-300">
|
||||
+ Add a heir
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if !heirWizard && $account?.heirs.length === 0}
|
||||
<div class="flex flex-col">
|
||||
<button on:click={() => heirWizard = true} class="flex h-60 flex-col items-center justify-center gap-3 rounded border border-gray-300 p-2 text-black">
|
||||
<span class="text-4xl">+</span>
|
||||
<h2 class="text-xl font-semibold">Add a heir</h2>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if heirWizard}
|
||||
<div class="border border-gray-200 rounded-lg shadow w-full flex flex-col p-3.5 mb-4">
|
||||
<form use:form>
|
||||
<div class="mb-5">
|
||||
<label for="heir__contact-method" class="block mb-2 text-sm font-medium text-gray-900">Contact method</label>
|
||||
<select id="heir__contact-method" name="contactMethod" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-violet-500 focus:border-violet-500 block w-full p-2.5">
|
||||
<option value="" selected>Choose a contact method</option>
|
||||
<option value="email">Email</option>
|
||||
</select>
|
||||
{#if $errors.contactMethod != null}
|
||||
<p class="mt-2 text-sm text-red-600"><span class="font-medium">{$errors.contactMethod[0]}</span></p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<label for="heir__name" class="block mb-2 text-sm font-medium text-gray-900">Heir name</label>
|
||||
<input id="heir__name" type="text" name="name" placeholder="Jane Doe" class="bg-gray-50 border border-gray-300 text-greay-900 text-sm rounded-lg focus:ring-violet-500 focus:border-violet-500 block w-full p-2.5">
|
||||
{#if $errors.name != null}
|
||||
<p class="mt-2 text-sm text-red-600"><span class="font-medium">{$errors.name[0]}</span></p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<label for="heir__contactDetails" class="block mb-2 text-sm font-medium text-gray-900">Contact details</label>
|
||||
<input id="heir__contactDetails" type="text" name="contactDetails" placeholder="jane@identity.net" class="bg-gray-50 border border-gray-300 text-greay-900 text-sm rounded-lg focus:ring-violet-500 focus:border-violet-500 block w-full p-2.5">
|
||||
{#if $errors.contactDetails != null}
|
||||
<p class="mt-2 text-sm text-red-600"><span class="font-medium">{$errors.contactDetails[0]}</span></p>
|
||||
{/if}
|
||||
</div>
|
||||
<button class="mt-2 text-white bg-violet-700 hover:bg-violet-800 focus:ring-4 focust:outline-none focus:ring-violet-300 font-medium rounded-lg px-5 py-2.5 text-center" type="submit">Add new heir</button>
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
{#each $account?.heirs || [] as heir (heir.value)}
|
||||
<div class="border border-gray-200 rounded-lg shadow w-full flex flex-col p-3.5 mb-2.5">
|
||||
<div class="flex justify-between">
|
||||
<span class="block text-sm font-medium text-gray-900">Contact method: <span class="capitalize">{heir.contactMethod}</span></span>
|
||||
<button on:click={() => removeHeir(heir)} class="rounded-lg bg-red-600 text-white px-2.5 py-1 text-center hover:bg-red-700 focus:ring-4 focus:ring-violet-300">Delete heir</button>
|
||||
</div>
|
||||
<div>
|
||||
<span>{heir.name}</span> · <span>{heir.value}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -68,7 +68,10 @@
|
|||
if (filters.searchQuery != null) {
|
||||
let fuse = new Fuse(entries, {
|
||||
keys: [
|
||||
"title",
|
||||
{
|
||||
name: "title",
|
||||
weight: 2,
|
||||
},
|
||||
"description",
|
||||
],
|
||||
});
|
||||
|
@ -87,6 +90,13 @@
|
|||
$: applyFilters(filters)
|
||||
</script>
|
||||
|
||||
{#if entries.length != filteredEntries.length && filteredEntries.length === 0}
|
||||
<div class="justify-center flex py-6">
|
||||
<div role="status" class="flex justify-center items-center gap-5">
|
||||
<span class="text-xl">No results found</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#each filteredEntries as entry (entry.id)}
|
||||
<Entry
|
||||
on:extended={(e) => extended = [e.detail.id, ...extended]}
|
||||
|
|
Loading…
Reference in a new issue