app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit b3d4f579459ff15f1ddb94a47dea7d57f83b3a17
parent 04914f9b28456acb00fe4cdc0dba6f9443ae45a3
Author: triesap <triesap@radroots.dev>
Date:   Fri, 21 Nov 2025 02:33:56 +0000

Update `/profile` with new view component integrated from `@radroots/apps-lib-pwa`, upgrade configuration bootstrap to use persisted active key, standardize error guards and farms view typing.

Diffstat:
Mapp/src/lib/utils/app/handlers.ts | 6+++---
Mapp/src/lib/utils/app/routes.ts | 4++++
Mapp/src/routes/(app)/farms/+page.svelte | 2+-
Mapp/src/routes/(app)/farms/add/+page.svelte | 2+-
Aapp/src/routes/(app)/profile/+page.svelte | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapp/src/routes/(app)/profile/edit/+page.svelte | 8++++++++
Mapp/src/routes/(cfg)/+layout.ts | 11++++++-----
Mapp/src/routes/(cfg)/setup/+page.svelte | 4++--
8 files changed, 181 insertions(+), 12 deletions(-)

diff --git a/app/src/lib/utils/app/handlers.ts b/app/src/lib/utils/app/handlers.ts @@ -18,13 +18,13 @@ export const lc_gui_confirm: LocalCallbackGuiConfirm = async (opts) => { export const lc_geocode: LocalCallbackGeocode = async (geoc_p) => { await geoc.connect(); const geoc_res = await geoc.reverse(geoc_p); - if (`err` in geoc_res) throw_err(geoc_res); + if ("err" in geoc_res) throw_err(geoc_res); return geoc_res.results[0] || undefined; }; export const lc_geop_current: LocalCallbackGeocodeCurrent = async () => { const resolve = await geol.current(); - if (`err` in resolve) throw_err(resolve); + if ("err" in resolve) throw_err(resolve); return resolve; }; @@ -55,7 +55,7 @@ export const lc_photos_upload: LocalCallbackPhotosUpload = async ({ url, path }) }, data_bin, }); - if (`err` in res) throw_err(res); + if ("err" in res) throw_err(res); else if (res.data && res.data.res_base && res.data.res_path && res.data.file_ext) { return { base_url: res.data.res_base, diff --git a/app/src/lib/utils/app/routes.ts b/app/src/lib/utils/app/routes.ts @@ -4,6 +4,8 @@ export type NavigationRoute = | "/farms/add" | "/farms/info" | "/import" + | "/profile" + | "/profile/edit" | "/setup" export function parse_route(route: string): NavigationRoute { @@ -13,6 +15,8 @@ export function parse_route(route: string): NavigationRoute { case "/farms/add": case "/farms/info": case "/import": + case "/profile": + case "/profile/edit": case "/setup": return route; default: diff --git a/app/src/routes/(app)/farms/+page.svelte b/app/src/routes/(app)/farms/+page.svelte @@ -5,7 +5,7 @@ import type { FarmExtended, IViewFarmsData, - } from "@radroots/apps-lib-pwa/types/views/farm"; + } from "@radroots/apps-lib-pwa/types/views/farms"; import { onMount } from "svelte"; type LoadData = IViewFarmsData | undefined; diff --git a/app/src/routes/(app)/farms/add/+page.svelte b/app/src/routes/(app)/farms/add/+page.svelte @@ -50,7 +50,7 @@ JSON.stringify(farm_location_set, null, 4), `farm_location_set`, ); - if (`err` in farm_location_set) throw_err(farm_location_set); + if ("err" in farm_location_set) throw_err(farm_location_set); await route(`/farms`); } catch (e) { handle_err(e, `on_submit`); diff --git a/app/src/routes/(app)/profile/+page.svelte b/app/src/routes/(app)/profile/+page.svelte @@ -0,0 +1,156 @@ +<script lang="ts"> + import { db, fs, nostr_keys, notif, radroots, route } from "$lib/utils/app"; + import { ls } from "$lib/utils/i18n"; + import { ndk_user, parse_file_path } from "@radroots/apps-lib"; + import { Profile } from "@radroots/apps-lib-pwa"; + import type { IViewProfileData } from "@radroots/apps-lib-pwa/types/views/profile"; + import { handle_err, throw_err, type FilePath } from "@radroots/utils"; + import { onMount } from "svelte"; + + let data: IViewProfileData | undefined = $state(undefined); + let loading_photo_upload = $state(false); + let loading_photo_upload_open = $state(false); + let photo_path = $state(``); + + onMount(async () => { + try { + data = await load_data(); + } catch (e) { + handle_err(e, `on_mount`); + await route(`/`); + } + }); + + const load_data = async (): Promise<IViewProfileData | undefined> => { + const nostr_profile = await db.nostr_profile_find_one({ + on: { + public_key: $ndk_user?.pubkey, + }, + }); + if ("err" in nostr_profile) throw_err(nostr_profile); + + //await nostr_sync.metadata({ metadata: tb_nostr_profile.result }); // no await + return { profile: nostr_profile.result }; + }; +</script> + +<Profile + bind:photo_path + basis={{ + data, + loading_photo_upload, + loading_photo_upload_open, + on_destroy: async () => { + try { + const tb_nostrprofile = await db.nostr_profile_find_one({ + on: { + public_key: $ndk_user?.pubkey, + }, + }); + if ("err" in tb_nostrprofile) throw_err(tb_nostrprofile); //@todo + /*await nostr_sync.metadata({ + metadata: tb_nostrprofile.result, + }); // no await */ + } catch (e) { + handle_err(e, `on_destroy`); + } + }, + on_handle_back: async ({ is_photo_existing }) => { + try { + if (!photo_path || !$ndk_user?.pubkey) + return void (await route(`/`)); + const nostr_key = await nostr_keys.read($ndk_user.pubkey); + if ("err" in nostr_key) throw_err(nostr_key); + if (photo_path) { + const confirm = await notif.confirm({ + message: is_photo_existing + ? `${$ls( + `notification.profile.handle_back_with_selected_photo`, + )}` + : `${$ls( + `notification.profile.handle_back_with_selected_photo_no_existing`, + )}`, + ok: `${$ls(`common.upload_photo`)}`, + cancel: is_photo_existing + ? `${$ls(`common.keep_previous`)}` + : `${$ls(`common.continue`)}`, + }); + if (!confirm) return void (await route(`/`)); + } + loading_photo_upload = true; + let upload_file_path: FilePath | undefined = undefined; + parse_file_path(photo_path); + const profile_photo_curr = await db.media_image_find_one({ + on: { + file_path: photo_path, + }, + }); + if ("result" in profile_photo_curr) + upload_file_path = parse_file_path( + profile_photo_curr.result.file_path, + ); + else upload_file_path = parse_file_path(photo_path); + if (!upload_file_path) throw_err(`error.util.parse_file_path`); + const file_data = await fs.read_bin(upload_file_path.file_path); + if ("err" in file_data) throw_err(file_data); + const res_fetch_media_image_upload = + await radroots.media_image_upload({ + file_path: upload_file_path, + file_data, + secret_key: nostr_key.secret_key, + }); + if ("err" in res_fetch_media_image_upload) + throw_err(res_fetch_media_image_upload); + const { res_base: upload_res_base, res_path: upload_res_path } = + res_fetch_media_image_upload; + const tb_media_image_create = await db.media_image_create({ + file_path: upload_file_path.file_path, + res_base: upload_res_base, + res_path: upload_res_path, + mime_type: upload_file_path.mime_type, + }); + if ("err" in tb_media_image_create) + throw_err(tb_media_image_create); + const tb_nostr_profile_update = await db.nostr_profile_update({ + on: { public_key: $ndk_user.pubkey }, + fields: { + picture: `${upload_res_base}/${upload_res_path}.${upload_file_path.mime_type}`, + }, + }); + if ("err" in tb_nostr_profile_update) + throw_err(tb_nostr_profile_update); + await route(`/`); + } catch (e) { + handle_err(e, `on_handle_back`); + } finally { + loading_photo_upload = false; + } + }, + on_handle_edit_profile_field: async ({ field }) => { + try { + if (field === `name`) { + const confirm = await notif.confirm({ + message: `${$ls( + `notification.profile.update_name_confirmation`, + )}. ${$ls(`common.do_you_want_to_continue_q`)}`, + ok: `${$ls(`common.continue`)}`, + cancel: `${$ls(`common.cancel`)}`, + }); + if (!confirm) return; + } + await route(`/profile/edit`, [ + [`key_nostr`, $ndk_user?.pubkey], + [`field`, field], + ]); + } catch (e) { + handle_err(e, `on_handle_edit_profile_field`); + } + }, + on_handle_photo_options: async () => { + try { + } catch (e) { + handle_err(e, `on_handle_photo_options`); + } + }, + }} +/> diff --git a/app/src/routes/(app)/profile/edit/+page.svelte b/app/src/routes/(app)/profile/edit/+page.svelte @@ -0,0 +1,8 @@ +<script lang="ts"> +</script> + +<div class={`flex flex-col w-full justify-start items-start`}> + <p class={`font-sans font-[400] text-base text-ly0-gl`}> + {`profile edit`} + </p> +</div> diff --git a/app/src/routes/(cfg)/+layout.ts b/app/src/routes/(cfg)/+layout.ts @@ -1,16 +1,17 @@ import { datastore, nostr_keys, route } from '$lib/utils/app'; +import type { AppData } from '$lib/utils/config'; import { handle_err } from '@radroots/apps-lib'; import type { LayoutLoad, LayoutLoadEvent } from './$types'; export const load: LayoutLoad = async (_: LayoutLoadEvent) => { try { await datastore.init(); - const ds_key_nostr = await datastore.get("nostr_key"); - if (`result` in ds_key_nostr) { - const nostrkey = await nostr_keys.read(ds_key_nostr.result); - if (`result` in nostrkey) return void await route(`/`); - await datastore.del("nostr_key"); + const app_data = await datastore.get_obj<AppData>("app_data"); + if ("result" in app_data) { + const nostr_key = await nostr_keys.read(app_data.result.active_key); + if ("result" in nostr_key) return void await route(`/`); + // @todo } } catch (e) { handle_err(e, `(cfg)load`) diff --git a/app/src/routes/(cfg)/setup/+page.svelte b/app/src/routes/(cfg)/setup/+page.svelte @@ -197,7 +197,7 @@ const create_nostr_key = async (): Promise<void> => { const keys_nostr_gen = await nostr_keys.generate(); - if (`err` in keys_nostr_gen) return handle_config_err(); + if ("err" in keys_nostr_gen) return handle_config_err(); await datastore.update_obj<ConfigData>("cfg_data", { nostr_public_key: keys_nostr_gen.public_key, }); @@ -286,7 +286,7 @@ tok: accounts_req.result, secret_key: ks_nostr_key.secret_key, }); - if (`err` in accounts_create) + if ("err" in accounts_create) return void (await notif.alert( `${$ls(accounts_create.err, { default: `${$ls(