commit 53a7515db0d03d5bdfcea73203a576ac3da0c24d parent 8df7cbb480582dae558304105845c5b383e6f4a9 Author: triesap <137732411+triesap@users.noreply.github.com> Date: Fri, 6 Dec 2024 01:50:15 +0000 Edit crate `core` add `log_error` model, edit `media_uploads` add file path query bind value, edit `trade_product` add category field. Edit `tauri` crate add `log_error` model handlers, add/edit SQL up migrations. Edit `/cfg/init` add button styles, move fetch methods to utils. Edit `/models/trade-product/add` add kv init, edit styles. Edit root layout add init app handler. Edit app layout add nostr ndk init to app nostr key subscriber. Add models media uploads, error utils. Add/edit fetch utils. Edit lib components. Edit routes. Add/edit conf, types, styles. Diffstat:
44 files changed, 1065 insertions(+), 873 deletions(-)
diff --git a/crates/core/src/models/log_error.rs b/crates/core/src/models/log_error.rs @@ -0,0 +1,225 @@ +use crate::{ + error::ModelError, + types::{IModelsId, IModelsQueryBindValue, IModelsQueryBindValueTuple, IModelsResults}, + utils::{time_created_on, uuidv4}, +}; +use futures::TryStreamExt; + +#[derive(Debug, serde::Serialize, serde::Deserialize, sqlx::FromRow)] +pub struct LogError { + id: String, + created_at: String, + updated_at: String, + error: String, + message: String, + stack_trace: Option<String>, + cause: Option<String>, + app_system: String, + app_version: String, + nostr_pubkey: String, + data: Option<String>, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct ILogErrorFields { + pub error: String, + pub message: String, + pub stack_trace: Option<String>, + pub cause: Option<String>, + pub app_system: String, + pub app_version: String, + pub nostr_pubkey: String, + pub data: Option<String>, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct ILogErrorFieldsUpdate { + pub error: Option<String>, + pub message: Option<String>, + pub stack_trace: Option<String>, + pub cause: Option<String>, + pub app_system: Option<String>, + pub app_version: Option<String>, + pub nostr_pubkey: Option<String>, + pub data: Option<String>, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum LogErrorSort { + Newest, + Oldest, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum LogErrorQueryBindValues { + Id(IModelsQueryBindValue), +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum LogErrorQueryListOf { + All(IModelsQueryBindValue), +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct ILogErrorQueryGetList { + pub of: LogErrorQueryListOf, + pub sort: Option<LogErrorSort>, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct ILogErrorQueryGet { + pub on: Option<LogErrorQueryBindValues>, + pub list: Option<ILogErrorQueryGetList>, +} + +pub type ILogErrorAdd = ILogErrorFields; +pub type ILogErrorAddResolve = IModelsId; +pub type ILogErrorGet = ILogErrorQueryGet; +pub type ILogErrorGetResolve = IModelsResults<LogError>; +pub type ILogErrorDelete = LogErrorQueryBindValues; +pub type ILogErrorDeleteResolve = (); + +pub fn log_error_query_bind_values(opts: LogErrorQueryBindValues) -> IModelsQueryBindValueTuple { + match opts { + LogErrorQueryBindValues::Id(id) => ("id".to_string(), id), + } +} + +pub fn log_error_query_get_list(opts: ILogErrorQueryGetList) -> IModelsQueryBindValueTuple { + let query_sort = match opts.sort { + Some(LogErrorSort::Newest) => " ORDER BY le.created_at DESC", + Some(LogErrorSort::Oldest) => " ORDER BY le.created_at ASC", + None => "", + }; + match opts.of { + LogErrorQueryListOf::All(_) => (format!("SELECT le.* FROM log_error le{}", query_sort), "".to_string()), + } +} + +fn log_error_fields_bind_values( + opts: ILogErrorFields, +) -> Result<Vec<IModelsQueryBindValueTuple>, ModelError> { + let bind_values = serde_json::to_value(&opts) + .map_err(|err| ModelError::SerializationError(err.to_string()))? + .as_object() + .ok_or_else(|| ModelError::InvalidArgument("model.error.object_invalid".to_string()))? + .iter() + .filter_map(|(key, value)| value.as_str().map(|v| (key.clone(), v.to_string()))) + .collect::<Vec<_>>(); + Ok(bind_values) +} + +fn log_error_fields_update_bind_values( + opts: ILogErrorFieldsUpdate, +) -> Result<Vec<IModelsQueryBindValueTuple>, ModelError> { + let bind_values = serde_json::to_value(&opts) + .map_err(|err| ModelError::SerializationError(err.to_string()))? + .as_object() + .ok_or_else(|| ModelError::InvalidArgument("model.error.object_invalid".to_string()))? + .iter() + .filter_map(|(key, value)| value.as_str().map(|v| (key.clone(), v.to_string()))) + .collect::<Vec<_>>(); + Ok(bind_values) +} + +pub async fn lib_model_log_error_add( + db: &sqlx::Pool<sqlx::Sqlite>, + opts: ILogErrorAdd, +) -> Result<ILogErrorAddResolve, ModelError> { + let id = uuidv4(); + let created_at = time_created_on(); + let updated_at = created_at.clone(); + let bind_values = log_error_fields_bind_values(opts) + .map_err(|e| ModelError::InvalidArgument(e.to_string()))?; + let mut query_col = vec![ + "id".to_string(), + "created_at".to_string(), + "updated_at".to_string(), + ]; + let mut query_pl = vec!["?1".to_string(), "?2".to_string(), "?3".to_string()]; + let mut query_vals: Vec<String> = vec![id.to_string(), created_at.clone(), updated_at.clone()]; + for (k, v) in bind_values.iter() { + query_col.push(k.clone()); + query_pl.push(format!("?{}", query_col.len())); + query_vals.push(v.clone()); + } + let query = format!( + "INSERT INTO log_error ({}) VALUES ({});", + query_col.join(", "), + query_pl.join(", ") + ); + let mut query_builder = sqlx::query(&query); + for value in query_vals.iter() { + query_builder = query_builder.bind(value); + } + query_builder + .execute(db) + .await + .map_err(|e| ModelError::InvalidQuery(e.to_string()))?; + Ok(IModelsId { id }) +} + +fn log_error_query_get( + opts: ILogErrorGet, +) -> Result<(String, Vec<IModelsQueryBindValue>), ModelError> { + match opts { + ILogErrorQueryGet { + list: Some(opts_list), + .. + } => { + let (query, bv) = log_error_query_get_list(opts_list); + Ok((query, vec![bv])) + } + ILogErrorQueryGet { + on: Some(opts_on), .. + } => { + let (bv_k, bv) = log_error_query_bind_values(opts_on); + let query = format!("SELECT * FROM log_error WHERE {} = ?1;", bv_k); + Ok((query, vec![bv])) + } + _ => Err(ModelError::InvalidQuery( + "model.log_error.error.query_invalid".to_string(), + )), + } +} + +pub async fn lib_model_log_error_get( + db: &sqlx::Pool<sqlx::Sqlite>, + opts: ILogErrorQueryGet, +) -> Result<ILogErrorGetResolve, ModelError> { + let (query, bind_values) = log_error_query_get(opts)?; + let mut query_builder = sqlx::query_as::<_, LogError>(&query); + for value in bind_values.iter() { + query_builder = query_builder.bind(value); + } + let results = query_builder + .fetch(db) + .try_collect() + .await + .map_err(|e: sqlx::Error| ModelError::InvalidQuery(e.to_string()))?; + Ok(IModelsResults { results }) +} + +pub async fn lib_model_log_error_delete( + db: &sqlx::Pool<sqlx::Sqlite>, + opts: ILogErrorDelete, +) -> Result<ILogErrorDeleteResolve, ModelError> { + let (bv_k, bv) = log_error_query_bind_values(opts); + let query = format!("DELETE FROM log_error WHERE {} = ?1;", bv_k); + let result = sqlx::query(&query) + .bind(bv) + .execute(db) + .await + .map_err(|e: sqlx::Error| ModelError::InvalidQuery(e.to_string()))?; + println!("{:?}", result); + if result.rows_affected() > 0 { + Ok(()) + } else { + Err(ModelError::InvalidQuery( + "models.error.model_not_found".to_string(), + )) + } +} diff --git a/crates/core/src/models/media_upload.rs b/crates/core/src/models/media_upload.rs @@ -49,6 +49,7 @@ pub enum MediaUploadSort { #[serde(rename_all = "snake_case")] pub enum MediaUploadQueryBindValues { Id(IModelsQueryBindValue), + FilePath(IModelsQueryBindValue), } #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] @@ -89,6 +90,7 @@ pub type IMediaUploadUpdateResolve = (); pub fn media_upload_query_bind_values(opts: MediaUploadQueryBindValues) -> IModelsQueryBindValueTuple { match opts { MediaUploadQueryBindValues::Id(id) => ("id".to_string(), id), + MediaUploadQueryBindValues::FilePath(file_path) => ("file_path".to_string(), file_path), } } diff --git a/crates/core/src/models/mod.rs b/crates/core/src/models/mod.rs @@ -1,4 +1,5 @@ pub mod location_gcs; +pub mod log_error; pub mod media_upload; pub mod nostr_profile; pub mod nostr_profile_relay; diff --git a/crates/core/src/models/trade_product.rs b/crates/core/src/models/trade_product.rs @@ -11,6 +11,7 @@ pub struct TradeProduct { created_at: String, updated_at: String, key: String, + category: String, title: String, summary: String, process: String, @@ -31,6 +32,7 @@ pub struct TradeProduct { #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] pub struct ITradeProductFields { pub key: String, + pub category: String, pub title: String, pub summary: String, pub process: String, @@ -51,6 +53,7 @@ pub struct ITradeProductFields { #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] pub struct ITradeProductFieldsUpdate { pub key: Option<String>, + pub category: Option<String>, pub title: Option<String>, pub summary: Option<String>, pub process: Option<String>, diff --git a/crates/tauri/migrations/0001_location_gcs.sql b/crates/tauri/migrations/0001_location_gcs.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS location_gcs ( id CHAR(36) PRIMARY KEY NOT NULL UNIQUE CHECK(length(id) = 36), created_at DATETIME NOT NULL CHECK(length(created_at) = 24), - updated_at DATETIME NOT NULL CHECK(length(created_at) = 24), + updated_at DATETIME NOT NULL CHECK(length(updated_at) = 24), lat REAL NOT NULL, lng REAL NOT NULL, geohash CHAR(12) UNIQUE CHECK(length(geohash) = 9), diff --git a/crates/tauri/migrations/0002_trade_product.sql b/crates/tauri/migrations/0002_trade_product.sql @@ -1,8 +1,9 @@ CREATE TABLE IF NOT EXISTS trade_product ( id CHAR(36) PRIMARY KEY NOT NULL UNIQUE CHECK(length(id) = 36), created_at DATETIME NOT NULL CHECK(length(created_at) = 24), - updated_at DATETIME NOT NULL CHECK(length(created_at) = 24), + updated_at DATETIME NOT NULL CHECK(length(updated_at) = 24), key TEXT NOT NULL, + category TEXT NOT NULL, title TEXT NOT NULL, summary TEXT NOT NULL, process TEXT NOT NULL, diff --git a/crates/tauri/migrations/0003_nostr_profile.sql b/crates/tauri/migrations/0003_nostr_profile.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS nostr_profile ( id CHAR(36) PRIMARY KEY NOT NULL UNIQUE CHECK(length(id) = 36), created_at DATETIME NOT NULL CHECK(length(created_at) = 24), - updated_at DATETIME NOT NULL CHECK(length(created_at) = 24), + updated_at DATETIME NOT NULL CHECK(length(updated_at) = 24), public_key CHAR(64) NOT NULL CHECK(length(public_key) = 64), name TEXT, display_name TEXT, diff --git a/crates/tauri/migrations/0004_nostr_relay.sql b/crates/tauri/migrations/0004_nostr_relay.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS nostr_relay ( id CHAR(36) PRIMARY KEY NOT NULL UNIQUE CHECK(length(id) = 36), created_at DATETIME NOT NULL CHECK(length(created_at) = 24), - updated_at DATETIME NOT NULL CHECK(length(created_at) = 24), + updated_at DATETIME NOT NULL CHECK(length(updated_at) = 24), url TEXT NOT NULL UNIQUE, relay_id TEXT, name TEXT, diff --git a/crates/tauri/migrations/0005_media_upload.sql b/crates/tauri/migrations/0005_media_upload.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS media_upload ( id CHAR(36) PRIMARY KEY NOT NULL UNIQUE CHECK(length(id) = 36), created_at DATETIME NOT NULL CHECK(length(created_at) = 24), - updated_at DATETIME NOT NULL CHECK(length(created_at) = 24), + updated_at DATETIME NOT NULL CHECK(length(updated_at) = 24), file_path TEXT NOT NULL, mime_type TEXT NOT NULL, res_base TEXT NOT NULL, diff --git a/crates/tauri/migrations/0006_log_error.sql b/crates/tauri/migrations/0006_log_error.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS log_error ( + id CHAR(36) PRIMARY KEY NOT NULL UNIQUE CHECK(length(id) = 36), + created_at DATETIME NOT NULL CHECK(length(created_at) = 24), + error TEXT NOT NULL, + message TEXT NOT NULL, + stack_trace TEXT, + cause TEXT, + app_system TEXT NOT NULL, + app_version TEXT NOT NULL, + nostr_pubkey TEXT NOT NULL, + data TEXT +); +\ No newline at end of file diff --git a/crates/tauri/migrations/0006_nostr_profile_relay.sql b/crates/tauri/migrations/0007_nostr_profile_relay.sql diff --git a/crates/tauri/migrations/0007_trade_product_location.sql b/crates/tauri/migrations/0008_trade_product_location.sql diff --git a/crates/tauri/migrations/0008_trade_product_media.sql b/crates/tauri/migrations/0009_trade_product_media.sql diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs @@ -8,6 +8,7 @@ use models::{ model_location_gcs_add, model_location_gcs_delete, model_location_gcs_get, model_location_gcs_update, }, + log_error::{model_log_error_add, model_log_error_delete, model_log_error_get}, media_upload::{ model_media_upload_add, model_media_upload_delete, model_media_upload_get, model_media_upload_update, @@ -98,6 +99,9 @@ pub fn run() { model_trade_product_media_set, model_trade_product_media_unset, model_trade_product_media_get_all, + model_log_error_add, + model_log_error_get, + model_log_error_delete, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/crates/tauri/src/models/log_error.rs b/crates/tauri/src/models/log_error.rs @@ -0,0 +1,46 @@ +use crate::radroots::Radroots; +use radroots_core::{ + models::log_error::{lib_model_log_error_add, ILogErrorAdd, ILogErrorAddResolve, lib_model_log_error_get, ILogErrorGet, ILogErrorGetResolve, lib_model_log_error_delete, ILogErrorDelete, ILogErrorDeleteResolve}, +}; + +#[tauri::command] +pub async fn model_log_error_add( + state: tauri::State<'_, Radroots>, + opts: ILogErrorAdd, +) -> Result<ILogErrorAddResolve, String> { + match lib_model_log_error_add(&state.db, opts).await { + Ok(result) => Ok(result), + Err(e) => { + println!("ERROR {}", e); + Err(e.to_string()) + } + } +} + +#[tauri::command] +pub async fn model_log_error_get( + state: tauri::State<'_, Radroots>, + opts: ILogErrorGet, +) -> Result<ILogErrorGetResolve, String> { + match lib_model_log_error_get(&state.db, opts).await { + Ok(result) => Ok(result), + Err(e) => { + println!("ERROR {}", e); + Err(e.to_string()) + } + } +} + +#[tauri::command] +pub async fn model_log_error_delete( + state: tauri::State<'_, Radroots>, + opts: ILogErrorDelete, +) -> Result<ILogErrorDeleteResolve, String> { + match lib_model_log_error_delete(&state.db, opts).await { + Ok(result) => Ok(result), + Err(e) => { + println!("ERROR {}", e); + Err(e.to_string()) + } + } +} diff --git a/crates/tauri/src/models/mod.rs b/crates/tauri/src/models/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod location_gcs; +pub(crate) mod log_error; pub(crate) mod media_upload; pub(crate) mod nostr_profile; pub(crate) mod nostr_profile_relay; diff --git a/package.json b/package.json @@ -33,14 +33,15 @@ "svelte": "^4.2.19", "svelte-check": "^3.8.6", "tailwindcss": "^3.4.3", + "tailwindcss-hero-patterns": "^0.1.2", "tslib": "^2.4.1", "typescript": "^5.3.3", "vite": "^5.0.11" }, "type": "module", "dependencies": { - "@nostr-dev-kit/ndk": "^2.10.0", - "@nostr-dev-kit/ndk-svelte": "^2.2.18", + "@nostr-dev-kit/ndk": "^2.10.6", + "@nostr-dev-kit/ndk-svelte": "^2.3.1", "@radroots/client": "workspace:*", "@radroots/geocoder": "workspace:*", "@radroots/models": "workspace:*", diff --git a/src/app.css b/src/app.css @@ -4,6 +4,8 @@ @import "/static/stylesheets/tailwindcss-app.css"; @import "/static/stylesheets/tailwindcss-spinner.css"; +@import "/static/stylesheets/tailwindcss-app-layer-1.css"; + @import "/static/webfonts/apercu-mono-pro/styles.css"; @import "/static/webfonts/lust/styles.css"; @import "/static/webfonts/magda-text/styles.css"; @@ -21,53 +23,4 @@ @layer base {} -@layer components { - .label-sg { - @apply font-sg tracking-wide scale-y-[97%]; - } - .tap-scale { - @apply active:scale-[97%] group-active:scale-[97%] delay-75 duration-700 ease-in-out transition-all; - } - - .focus-layer-1 { - @apply focus:bg-layer-1-surface-focus group-focus:bg-layer-1-surface-focus transition-all; - } - - .focus-layer-1-raise, - .focus-layer-1-raise-less { - @apply focus:scale-[101%] group-focus:scale-[101%] ease-in-out transition-all; - } - - .focus-layer-1-raise { - @apply ring-layer-2-surface/60 focus:ring-[0.3rem] group-focus:ring-[0.3rem] delay-[75ms] duration-[350ms]; - } - - .focus-layer-1-raise-less { - @apply ring-layer-2-surface/60 focus:ring-[0.25rem] group-focus:ring-[0.25rem] delay-[50ms] duration-[350ms]; - } - - .active-layer-1 { - @apply active:bg-layer-1-surface-focus group-active:bg-layer-1-surface-focus transition-all; - } - - .active-layer-1-raise, - .active-layer-1-raise-less { - @apply active:scale-[101%] group-active:scale-[101%] ease-in-out transition-all; - } - - .active-layer-1-raise { - @apply ring-layer-2-surface/60 active:ring-[0.3rem] group-active:ring-[0.3rem] delay-[75ms] duration-[350ms]; - } - - .active-layer-1-raise-less { - @apply ring-layer-2-surface/60 active:ring-[0.25rem] group-active:ring-[0.25rem] delay-[50ms] duration-[350ms]; - } - - .layer-1-ring { - @apply ring-layer-2-surface/60 ring-[0.3rem] - } - - .layer-1-ring-less { - @apply ring-layer-2-surface/60 ring-[0.25rem] - } -} -\ No newline at end of file +@layer components {} +\ No newline at end of file diff --git a/src/lib/components/trade_field_display_kv.svelte b/src/lib/components/trade_field_display_kv.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { el_focus } from "$lib/utils/client"; + import { el_focus } from "$lib/util/client"; import { fmt_cl, fmt_id, diff --git a/src/lib/components/trade_product_list_card.svelte b/src/lib/components/trade_product_list_card.svelte @@ -89,7 +89,7 @@ class={`hidden absolute top-0 left-0 flex flex-row h-12 w-full justify-center items-start el-re`} > <button - class={`flex flex-row px-5 py-1 justify-center items-center bg-layer-1-surface active-layer-1 rounded-full`} + class={`flex flex-row px-5 py-1 justify-center items-center bg-layer-1-surface layer-1-active-surface rounded-full`} on:click|stopPropagation={async () => { await route(`/models/trade-product/view`, [ [`id`, trade_product.id], @@ -103,7 +103,7 @@ </div> <button id={`${id_pref}-display-${basis.index}-${trade_product.id}`} - class={`flex flex-col min-h-[22rem] w-${$app_layout} justify-start items-start bg-layer-1-surface focus-layer-1 focus-layer-1-raise-less round-44 focus:translate-y-12`} + class={`flex flex-col min-h-[22rem] w-${$app_layout} justify-start items-start bg-layer-1-surface layer-1-focus-surface layer-1-focus-raise-less round-44 focus:translate-y-12`} on:click|stopPropagation={async ({ currentTarget: el }) => { handle_display_focus(el); }} diff --git a/src/lib/conf.ts b/src/lib/conf.ts @@ -18,7 +18,6 @@ export const ks = { export const root_symbol = "»--`--,---"; - export const ascii = { bullet: '•', dash: `—` @@ -27,7 +26,8 @@ export const ascii = { export const cfg = { app: { title: `Radroots`, - description: `Creating networks between farmers, communities and small businesses that give customers greater access to natural foods and grow circular economies where profits are more fairly distributed. Radroots is built on the Nostr protocol and released under a copyleft open source license to provide transparency and give users the option to offer feedback and add or request new features.` + description: `Creating networks between farmers, communities and small businesses that give customers greater access to natural foods and grow circular economies where profits are more fairly distributed. Radroots is built on the Nostr protocol and released under a copyleft open source license to provide transparency and give users the option to offer feedback and add or request new features.`, + version: `0.0.1` }, nostr: { relay_url: `wss://radroots.org`, diff --git a/src/lib/types.ts b/src/lib/types.ts @@ -1,7 +1,15 @@ import type { NDKEvent } from "@nostr-dev-kit/ndk"; import type { ExtendedBaseType, NDKEventStore } from "@nostr-dev-kit/ndk-svelte"; +import type { IClientDialogAlertOpts, IClientDialogConfirmOpts } from "@radroots/client"; import type { LocationGcs, MediaUpload, TradeProduct } from "@radroots/models"; +export type IDialogConfirm = { + confirm: IClientDialogConfirmOpts; +} + +export type IDialogAlert = { + alert: IClientDialogAlertOpts +} export type TradeProductBundle = { trade_product: TradeProduct; location_gcs: LocationGcs; diff --git a/src/lib/utils/client.ts b/src/lib/util/client.ts diff --git a/src/lib/util/error.ts b/src/lib/util/error.ts @@ -0,0 +1,16 @@ +import type { ErrorMessage } from "@radroots/utils"; + +export const throw_err = (param: string | ErrorMessage<string>, cause: string = ``): void => { + if (typeof param === `string`) throw new Error(param, { cause }); + else if (`err` in param) throw new Error(param.err, { cause }); +}; + +export const catch_err = async (e: unknown, fn_name: string): Promise<void> => { + if (e instanceof Error) { + console.error(`(catch_err) `, e.name, e.message, e.stack, e.cause) + } else { + console.log(`(catch_err) `, e) + } + //@todo +}; + diff --git a/src/lib/util/fetch-radroots-profile.ts b/src/lib/util/fetch-radroots-profile.ts @@ -0,0 +1,126 @@ +import { PUBLIC_RADROOTS_URL } from "$env/static/public"; +import { http, nostr } from "$lib/client"; +import { cfg } from "$lib/conf"; +import { get_store, t } from "@radroots/svelte-lib"; +import { err_msg, type ErrorMessage } from "@radroots/utils"; +import { catch_err, throw_err } from "./error"; + +const $t = get_store(t); + +export const fetch_radroots_profile_validate = async (opts: { + profile_name: string; +}): Promise<{ profile_name: string } | ErrorMessage<string>> => { + try { + const res = await http.fetch({ + url: `${PUBLIC_RADROOTS_URL}/public/accounts/list`, + method: `post`, + }); + console.log(JSON.stringify(res, null, 4), `res`); + if (`err` in res) throw_err(res) + else if (res.error) throw_err(res.error.message) + else if (res.data && Array.isArray(res.data.results)) { + const existing_profile = res.data.results.find( + (i: any) => + `nip_05` in i && + String(i.nip_05).toLowerCase() === + opts.profile_name.toLowerCase(), + ); + if (existing_profile) + return err_msg( + `${`${$t(`icu.the_*_is_registered`, { value: `${$t(`common.profile_name`)}`.toLowerCase() })} `}`, + ); + return { profile_name: opts.profile_name }; + } + return err_msg(`${$t(`error.client.request_failure`)}`); + } catch (e) { + await catch_err(e, "fetch_radroots_profile_validate"); + return err_msg(`${$t(`error.client.network_failure`)}`); + } +}; + +export const fetch_radroots_profile_init = async (opts: { + profile_name: string; + secret_key: string; + nostr_relays?: string[]; +}): Promise<{ tok: string } | ErrorMessage<string>> => { + try { + const res = await http.fetch({ + url: `${PUBLIC_RADROOTS_URL}/public/accounts/add/init`, + method: `post`, + data: { + nip_05: opts.profile_name, + public_key: nostr.lib.public_key(opts.secret_key), + nostr_relays: opts.nostr_relays?.length + ? Array.from( + new Set([ + ...opts.nostr_relays, + cfg.nostr.relay_url, + ]), + ).join(`,`) + : [cfg.nostr.relay_url].join(`,`), + }, + }); + if (`err` in res) return res; + else if (res.data && `tok` in res.data) { + return { tok: res.data.tok }; + } else if (res.data && `message` in res.data) + return err_msg( + `${$t(`radroots-org.error.${res.data.message}`)}`, + ); + return err_msg(`${$t(`error.client.request_failure`)}`); + } catch (e) { + await catch_err(e, "fetch_radroots_profile_init"); + return err_msg(`${$t(`error.client.network_failure`)}`); + } +}; + +export const fetch_radroots_profile_confirm = async ( + authorization: string, +): Promise<{ pass: true } | ErrorMessage<string>> => { + try { + const res = await http.fetch({ + url: `${PUBLIC_RADROOTS_URL}/public/accounts/add/conf`, + method: `post`, + authorization, + }); + if (`err` in res) return res; + return { pass: true }; + } catch (e) { + await catch_err(e, "fetch_radroots_profile_confirm"); + return err_msg(`${$t(`error.client.network_failure`)}`); + } +}; + +export const fetch_radroots_profile_status = async ( + authorization: string, +): Promise< + | { active: { public_key: string; nip_05?: string } } + | ErrorMessage<string> +> => { + try { + const res = await http.fetch({ + url: `${PUBLIC_RADROOTS_URL}/public/accounts/add/status`, + method: `post`, + authorization, + }); + if (`err` in res) return res; + else if ( + `public_key` in res.data && + typeof res.data.public_key === `string` + ) + return { + active: { + public_key: res.data.public_key, + nip_05: + `nip_05` in res.data && + typeof res.data.nip_05 === `string` + ? res.data.nip_05 + : undefined, + }, + }; + return err_msg(`${$t(`error.client.network_failure`)}`); + } catch (e) { + await catch_err(e, "fetch_radroots_profile_status"); + return err_msg(`${$t(`error.client.network_failure`)}`); + } +}; +\ No newline at end of file diff --git a/src/lib/util/fetch.ts b/src/lib/util/fetch.ts @@ -0,0 +1,132 @@ +import { db, fs, http, keystore } from "$lib/client"; +import { cfg, ks } from "$lib/conf"; +import type { IClientHttpResponseError } from "@radroots/client"; +import { parse_nostr_relay_form_keys, type NostrRelayFormFields } from "@radroots/models"; +import { app_nostr_key, get_store, nostr_relays_connected, nostr_relays_poll_documents, nostr_relays_poll_documents_count, t } from "@radroots/svelte-lib"; +import { err_msg, err_res, nostr_event_sign_attest, parse_nostr_relay_information_document_fields, type ErrorMessage, type ErrorResponse, type FilePath } from "@radroots/utils"; + +const $t = get_store(t); + +export const fetch_put_upload = async (opts: { + url: string; + file_path: FilePath; +}): Promise<{ + res_base: string; + res_path: string; +} | ErrorResponse<IClientHttpResponseError> | ErrorMessage<string>> => { + try { + const nostr_public_key = get_store(app_nostr_key); + const secret_key = await keystore.get( + ks.keys.nostr_secretkey(nostr_public_key), + ); + if (`err` in secret_key) return err_msg(`error.client.keystore_nostr_secretkey`); + const { url, file_path } = opts; + const file_data = await fs.read_bin(file_path.file_path); + if (!file_data) return err_msg(`error.client.file_path_read_bin_undefined`);; + const res = await http.fetch({ + url, + method: `put`, + headers: { + "Content-Type": file_path.mime_type, + "X-Nostr-Event": JSON.stringify(nostr_event_sign_attest(secret_key.result)), + }, + authorization: nostr_public_key, + data_bin: file_data, + }); + if (`err` in res) err_msg(`error.client.request_failure`); + else if (res.error) { + return err_res(res.error); + } + else if ( + res.status === 200 && + res.data && + `pass` in res.data && + `res_base` in res.data && + typeof res.data.res_base === `string` && + `res_path` in res.data && + typeof res.data.res_path === `string` + ) { + return { + res_base: res.data.res_base, + res_path: res.data.res_path, + }; + } + return err_msg(`error.client.request_unhandled`); + } catch (e) { + console.log(`(error) fetch_put_upload `, e); + return err_msg(`error.client.network_failure`); + } +}; + +export const fetch_relay_documents = async (): Promise<void> => { + try { + const $nostr_relays_poll_documents_count = get_store(nostr_relays_poll_documents_count); + const $app_nostr_key = get_store(app_nostr_key); + const $nostr_relays_connected = get_store(nostr_relays_connected); + if ( + $nostr_relays_poll_documents_count >= + cfg.nostr.relay_polling_count_max + ) { + nostr_relays_poll_documents.set(false); + return; + } + nostr_relays_poll_documents_count.set( + $nostr_relays_poll_documents_count + 1, + ); + const nostr_relays = await db.nostr_relay_get({ + list: [`on_profile`, { public_key: $app_nostr_key }], + }); + if (`err` in nostr_relays) throw new Error(nostr_relays.err); + + const unconnected_relays = nostr_relays.results.filter( + (i) => !$nostr_relays_connected.includes(i.id), + ); + if (unconnected_relays.length === 0) { + nostr_relays_poll_documents.set(false); + return; + } + + for (const nostr_relay of unconnected_relays) { + const res = await http.fetch({ + url: nostr_relay.url.replace(`ws://`, `http://`), + headers: { + Accept: "application/nostr+json", + }, + }); + if (`err` in res) continue; + else if (res.status === 200 && res.data) { + const doc = parse_nostr_relay_information_document_fields( + res.data, + ); + if (!doc) continue; + const fields: Partial<NostrRelayFormFields> = {}; + for (const [k, v] of Object.entries(doc)) { + const field_k = parse_nostr_relay_form_keys(k); + if (field_k) fields[field_k] = v; + } + if (Object.keys(fields).length < 1) continue; + await db.nostr_relay_update({ + on: { + url: nostr_relay.url, + }, + fields, + }); + nostr_relays_connected.set( + Array.from( + new Set([ + ...$nostr_relays_connected, + nostr_relay.id, + ]), + ), + ); + } + } + + setTimeout( + fetch_relay_documents, + cfg.delay.nostr_relay_poll_document, + ); + } catch (e) { + console.log(`(error) fetch_relay_documents `, e); + } +}; +\ No newline at end of file diff --git a/src/lib/utils/geocode.ts b/src/lib/util/geocode.ts diff --git a/src/lib/util/kv.ts b/src/lib/util/kv.ts @@ -0,0 +1,30 @@ +import { fmt_id, kv } from "@radroots/svelte-lib"; + +export const kv_init_app = async (): Promise<void> => { + try { + const range = Keyva.prefix(`*`); + const kv_list = await kv.each({ range }, `keys`); + await Promise.all(kv_list.map((i) => kv.delete(i))); + } catch (e) { + console.log(`(error) kv_init_page `, e); + } +}; + +export const kv_init_page = async (): Promise<void> => { + try { + const kv_pref = fmt_id(); + const range = Keyva.prefix(kv_pref); + const kv_list = await kv.each({ range }, `keys`); + await Promise.all(kv_list.map((i) => kv.delete(i))); + } catch (e) { + console.log(`(error) kv_init_page `, e); + } +}; + +export const kv_sync = async (list: [string, string][]): Promise<void> => { + try { + for (const [key, val] of list) await kv.set(key, val); + } catch (e) { + console.log(`(error) kv_sync `, e); + } +}; +\ No newline at end of file diff --git a/src/lib/util/models-media-upload.ts b/src/lib/util/models-media-upload.ts @@ -0,0 +1,116 @@ +import { PUBLIC_RADROOTS_URL } from "$env/static/public"; +import { db } from "$lib/client"; +import type { IDialogAlert, IDialogConfirm } from "$lib/types"; +import type { IClientHttpResponseError } from "@radroots/client"; +import { t } from "@radroots/svelte-lib"; +import { parse_file_path, type FilePath, type ResultsList } from "@radroots/utils"; +import { get } from "svelte/store"; +import { fetch_put_upload } from "./fetch"; + +const $t = get(t); + +export const model_media_upload_add_list = async (opts: { + photo_paths: string[]; +}): Promise<IDialogAlert | IDialogConfirm | ResultsList<string>> => { + try { + if (!opts.photo_paths.length) { + return { + alert: `No photos provided` + } + } + const photo_path_uploads: { + file_path: FilePath; + res_base: string; + res_path: string; + }[] = []; + const photo_path_uploads_err: { + file_path: FilePath; + err_msg: string; + }[] = []; + const photo_path_uploads_error: IClientHttpResponseError[] = []; + + for (const photo_path of opts.photo_paths) { + const file_path = parse_file_path(photo_path); + console.log(JSON.stringify(file_path, null, 4), `file_path`) + if (!file_path) continue; + const url = `${PUBLIC_RADROOTS_URL}/public/upload/image`; //@todo + const put_upload = await fetch_put_upload({ + url, + file_path, + }); + console.log(JSON.stringify(put_upload, null, 4), `put_upload`) + if (`err` in put_upload) { + photo_path_uploads_err.push({ + file_path, + err_msg: put_upload.err, + }); + continue; + } else if (`error` in put_upload) { + photo_path_uploads_error.push(put_upload.error); + continue; + } + photo_path_uploads.push({ + file_path, + res_base: put_upload.res_base, + res_path: put_upload.res_path, + }); + } + + if (photo_path_uploads_error.length) { + return { + confirm: { + message: `${$t(photo_path_uploads_error[0].message)}`, //@todo + ok_label: photo_path_uploads_error[0].label_ok + ? `${$t(photo_path_uploads_error[0].label_ok)}` || + undefined + : undefined, + cancel_label: photo_path_uploads_error[0].label_cancel + ? `${$t(photo_path_uploads_error[0].label_cancel)}` || + undefined + : undefined, + } + }; + + } + if (photo_path_uploads_err.length) { + return { + alert: `${$t(`icu.there_was_a_failure_while_*`, { + value: `${$t(`icu.uploading_*_photos`, { + value: + photo_path_uploads_err.length === + opts.photo_paths.length + ? `${$t(`common.all`)}` + : `${photo_path_uploads_err.length}`, + })}`.toLowerCase(), + })}` + }; + } + + const results: string[] = []; + if (photo_path_uploads.length) { + for (const photo_path_upload of photo_path_uploads) { + const media_upload_add = await db.media_upload_add({ + file_path: photo_path_upload.file_path.file_path, + mime_type: photo_path_upload.file_path.mime_type, + res_base: photo_path_upload.res_base, + res_path: photo_path_upload.res_path, + }); + if ( + `err` in media_upload_add || + `err_s` in media_upload_add + ) + continue; //@todo + results.push(media_upload_add.id); + } + } + + return { + results + }; + } catch (e) { + console.log(`(error) model_media_upload_add_list `, e); + return { + alert: `Failed to upload photos` //@todo + } + } +}; +\ No newline at end of file diff --git a/src/lib/utils/trade_product.ts b/src/lib/util/models-trade-product.ts diff --git a/src/lib/utils/models.ts b/src/lib/util/models.ts diff --git a/src/lib/util/nostr.ts b/src/lib/util/nostr.ts @@ -0,0 +1,116 @@ +import { db, dialog } from "$lib/client"; +import { nostr_client, root_symbol } from "$lib/conf"; +import { NDKKind } from "@nostr-dev-kit/ndk"; +import { app_nostr_key, ndk, ndk_user, nostr_sync_prevent, t } from "@radroots/svelte-lib"; +import { fmt_tags_basis_nip99, ndk_event, nevent_encode, num_str } from "@radroots/utils"; +import { get as get_store } from "svelte/store"; + +export const nostr_sync = async (): Promise<void> => { + try { + console.log(`run nostr sync!`) + const $t = get_store(t); + const $nostr_sync_prevent = get_store(nostr_sync_prevent); + const $app_nostr_key = get_store(app_nostr_key); + + if ($nostr_sync_prevent) { + const confirm = await dialog.confirm({ + message: `${$t(`error.client.nostr_sync_disabled`)}`, + cancel_label: `${$t(`common.cancel`)}`, + ok_label: `${$t(`common.ok`)}` + }); + if (confirm) { + nostr_sync_prevent.set(false); + await nostr_sync(); + } + return; + } + + const $ndk = get_store(ndk); + const $ndk_user = get_store(ndk_user); + + const nostr_relays_active = await db.nostr_relay_get({ + list: [`on_profile`, { public_key: $app_nostr_key }], + }); + if (`err` in nostr_relays_active) return; //@todo + if (!nostr_relays_active.results.length) return; //@todo + const trade_products_all = await db.trade_product_get({ + list: [`all`], + }); + if (`err` in trade_products_all) return; //@todo + for (const trade_product of trade_products_all.results) { + console.log(`SYNC trade_product.id `, trade_product.id) + const trade_product_location_res = await db.location_gcs_get({ + list: [`on_trade_product`, { id: trade_product.id }], + }); + if (`err` in trade_product_location_res) continue; //@todo + const trade_product_location = trade_product_location_res.results[0]; + + const media_upload_res = await db.media_upload_get({ + list: [`on_trade_product`, { id: trade_product.id }], + }); + if (`err` in media_upload_res) continue; //@todo + + const ev = await ndk_event({ + $ndk, + $ndk_user, + basis: { + kind: NDKKind.Classified, + content: ``, + tags: fmt_tags_basis_nip99({ + d_tag: trade_product.id, + client: nostr_client, + listing: { + key: trade_product.key, + category: trade_product.category, + title: trade_product.title, + summary: trade_product.summary, + process: trade_product.process, + lot: trade_product.lot, + profile: trade_product.profile, + year: num_str(trade_product.year), + }, + quantity: { + amt: num_str(trade_product.qty_amt), + unit: trade_product.qty_unit, + label: trade_product.qty_label + }, + price: { + amt: num_str(trade_product.price_amt), + currency: trade_product.price_currency, + qty_amt: num_str(trade_product.price_qty_amt), + qty_unit: trade_product.price_qty_unit, + }, + location: { + city: trade_product_location.gc_name, + region: trade_product_location.gc_admin1_name, + region_code: trade_product_location.gc_admin1_id, + country: trade_product_location.gc_country_name, + country_code: trade_product_location.gc_country_id, + lat: trade_product_location.lat, + lng: trade_product_location.lng, + geohash: trade_product_location.geohash, + }, + images: media_upload_res.results.length ? media_upload_res.results.map(i => ({ url: `${i.res_base}/${i.res_path}.${i.mime_type}` })) : undefined + }), + }, + }); + if (ev) { + ev.content = `radroots:[nostr:${nevent_encode({ + id: ev.id, + author: ev.pubkey, + relays: nostr_relays_active.results.map(i => i.url), + kind: NDKKind.Classified, + })}]` + await ev.publish(); + } + } + } catch (e) { + console.log(`(error) nostr_sync `, e); + } +}; + +export const nostr_tags_basis = (): string[][] => { + const tags: string[][] = []; + for (const tag of [`app/0.0.0`]) tags.push([root_symbol, tag]) + return tags; +}; diff --git a/src/lib/utils/fetch.ts b/src/lib/utils/fetch.ts @@ -1,131 +0,0 @@ -import { db, fs, http, keystore } from "$lib/client"; -import { cfg, ks } from "$lib/conf"; -import type { IClientHttpResponseError } from "@radroots/client"; -import { parse_nostr_relay_form_keys, type NostrRelayFormFields } from "@radroots/models"; -import { app_nostr_key, nostr_relays_connected, nostr_relays_poll_documents, nostr_relays_poll_documents_count } from "@radroots/svelte-lib"; -import { err_msg, err_res, nostr_event_sign_attest, parse_nostr_relay_information_document_fields, type ErrorMessage, type ErrorResponse, type FilePath } from "@radroots/utils"; -import { get as get_store } from "svelte/store"; - -export const fetch_put_upload = async (opts: { - url: string; - file_path: FilePath; -}): Promise<{ - res_base: string; - res_path: string; -} | ErrorResponse<IClientHttpResponseError> | ErrorMessage<string>> => { - try { - const nostr_public_key = get_store(app_nostr_key); - const secret_key = await keystore.get( - ks.keys.nostr_secretkey(nostr_public_key), - ); - if (`err` in secret_key) return err_msg(`error.client.keystore_nostr_secretkey`); - const { url, file_path } = opts; - const file_data = await fs.read_bin(file_path.file_path); - if (!file_data) return err_msg(`error.client.file_path_read_bin_undefined`);; - const res = await http.fetch({ - url, - method: `put`, - headers: { - "Content-Type": file_path.mime_type, - "X-Nostr-Event": JSON.stringify(nostr_event_sign_attest(secret_key.result)), - }, - authorization: nostr_public_key, - data_bin: file_data, - }); - if (`err` in res) err_msg(`error.client.request_failure`); - else if (res.error) { - return err_res(res.error); - } - else if ( - res.status === 200 && - res.data && - `pass` in res.data && - `res_base` in res.data && - typeof res.data.res_base === `string` && - `res_path` in res.data && - typeof res.data.res_path === `string` - ) { - return { - res_base: res.data.res_base, - res_path: res.data.res_path, - }; - } - return err_msg(`error.client.request_unhandled`); - } catch (e) { - console.log(`(error) fetch_put_upload `, e); - return err_msg(`error.client.network_failure`); - } -}; - -export const fetch_relay_documents = async (): Promise<void> => { - try { - const $nostr_relays_poll_documents_count = get_store(nostr_relays_poll_documents_count); - const $app_nostr_key = get_store(app_nostr_key); - const $nostr_relays_connected = get_store(nostr_relays_connected); - if ( - $nostr_relays_poll_documents_count >= - cfg.nostr.relay_polling_count_max - ) { - nostr_relays_poll_documents.set(false); - return; - } - nostr_relays_poll_documents_count.set( - $nostr_relays_poll_documents_count + 1, - ); - const nostr_relays = await db.nostr_relay_get({ - list: [`on_profile`, { public_key: $app_nostr_key }], - }); - if (`err` in nostr_relays) throw new Error(nostr_relays.err); - - const unconnected_relays = nostr_relays.results.filter( - (i) => !$nostr_relays_connected.includes(i.id), - ); - if (unconnected_relays.length === 0) { - nostr_relays_poll_documents.set(false); - return; - } - - for (const nostr_relay of unconnected_relays) { - const res = await http.fetch({ - url: nostr_relay.url.replace(`ws://`, `http://`), - headers: { - Accept: "application/nostr+json", - }, - }); - if (`err` in res) continue; - else if (res.status === 200 && res.data) { - const doc = parse_nostr_relay_information_document_fields( - res.data, - ); - if (!doc) continue; - const fields: Partial<NostrRelayFormFields> = {}; - for (const [k, v] of Object.entries(doc)) { - const field_k = parse_nostr_relay_form_keys(k); - if (field_k) fields[field_k] = v; - } - if (Object.keys(fields).length < 1) continue; - await db.nostr_relay_update({ - on: { - url: nostr_relay.url, - }, - fields, - }); - nostr_relays_connected.set( - Array.from( - new Set([ - ...$nostr_relays_connected, - nostr_relay.id, - ]), - ), - ); - } - } - - setTimeout( - fetch_relay_documents, - cfg.delay.nostr_relay_poll_document, - ); - } catch (e) { - console.log(`(error) fetch_relay_documents `, e); - } -}; -\ No newline at end of file diff --git a/src/lib/utils/kv.ts b/src/lib/utils/kv.ts @@ -1,20 +0,0 @@ -import { fmt_id, kv } from "@radroots/svelte-lib"; - -export const kv_init_page = async (): Promise<void> => { - try { - const kv_pref = fmt_id(); - const range = Keyva.prefix(kv_pref); - const kv_list = await kv.each({ range }, `keys`); - await Promise.all(kv_list.map((k) => kv.set(k, ``))); - } catch (e) { - console.log(`(error) kv_init_page `, e); - } -}; - -export const kv_sync = async (list: [string, string][]): Promise<void> => { - try { - for (const [key, val] of list) await kv.set(key, val); - } catch (e) { - console.log(`(error) kv_sync `, e); - } -}; -\ No newline at end of file diff --git a/src/lib/utils/nostr.ts b/src/lib/utils/nostr.ts @@ -1,112 +0,0 @@ -import { db, dialog } from "$lib/client"; -import { nostr_client, root_symbol } from "$lib/conf"; -import { NDKKind } from "@nostr-dev-kit/ndk"; -import { app_nostr_key, ndk, ndk_user, nostr_sync_prevent, t } from "@radroots/svelte-lib"; -import { fmt_tags_basis_nip99, ndk_event, nevent_encode, num_str } from "@radroots/utils"; -import { get as get_store } from "svelte/store"; - -export const nostr_sync = async (): Promise<void> => { - try { - const $t = get_store(t); - const $nostr_sync_prevent = get_store(nostr_sync_prevent); - const $app_nostr_key = get_store(app_nostr_key); - - if ($nostr_sync_prevent) { - const confirm = await dialog.confirm({ - message: `${$t(`error.client.nostr_sync_disabled`)}`, - cancel_label: `${$t(`common.cancel`)}`, - ok_label: `${$t(`common.ok`)}` - }); - if (confirm) { - nostr_sync_prevent.set(false); - await nostr_sync(); - } - return; - } - - const $ndk = get_store(ndk); - const $ndk_user = get_store(ndk_user); - - const nostr_relays_active = await db.nostr_relay_get({ - list: [`on_profile`, { public_key: $app_nostr_key }], - }); - if (`err` in nostr_relays_active) return; //@todo - if (!nostr_relays_active.results.length) return; //@todo - const trade_products_all = await db.trade_product_get({ - list: [`all`], - }); - if (`err` in trade_products_all) return; //@todo - for (const trade_product of trade_products_all.results) { - const trade_product_location_res = await db.location_gcs_get({ - list: [`on_trade_product`, { id: trade_product.id }], - }); - if (`err` in trade_product_location_res) continue; //@todo - const trade_product_location = trade_product_location_res.results[0]; - - const media_upload_res = await db.media_upload_get({ - list: [`on_trade_product`, { id: trade_product.id }], - }); - if (`err` in media_upload_res) continue; //@todo - - const ev = await ndk_event({ - $ndk, - $ndk_user, - basis: { - kind: NDKKind.Classified, - content: ``, - tags: await fmt_tags_basis_nip99({ - d_tag: trade_product.id, - client: nostr_client, - listing: { - title: trade_product.title, - summary: trade_product.summary, - process: trade_product.process, - lot: trade_product.lot, - profile: trade_product.profile, - year: num_str(trade_product.year), - }, - quantity: { - amt: num_str(trade_product.qty_amt), - unit: trade_product.qty_unit, - label: trade_product.qty_label - }, - price: { - amt: num_str(trade_product.price_amt), - currency: trade_product.price_currency, - qty_amt: num_str(trade_product.price_qty_amt), - qty_unit: trade_product.price_qty_unit, - }, - location: { - city: trade_product_location.gc_name, - region: trade_product_location.gc_admin1_name, - region_code: trade_product_location.gc_admin1_id, - country: trade_product_location.gc_country_name, - country_code: trade_product_location.gc_country_id, - lat: trade_product_location.lat, - lng: trade_product_location.lng, - geohash: trade_product_location.geohash, - }, - images: media_upload_res.results.length ? media_upload_res.results.map(i => ({ url: `${i.res_base}/${i.res_path}.${i.mime_type}` })) : undefined - }), - }, - }); - if (ev) { - ev.content = `radroots:[nostr:${nevent_encode({ - id: ev.id, - author: ev.pubkey, - relays: nostr_relays_active.results.map(i => i.url), - kind: NDKKind.Classified, - })}]` - await ev.publish(); - } - } - } catch (e) { - console.log(`(error) nostr_sync `, e); - } -}; - -export const nostr_tags_basis = (): string[][] => { - const tags: string[][] = []; - for (const tag of [`app/0.0.0`]) tags.push([root_symbol, tag]) - return tags; -}; diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte @@ -1,8 +1,8 @@ <script lang="ts"> import { db, geoc, keystore, notification } from "$lib/client"; import { ks } from "$lib/conf"; - import { fetch_relay_documents } from "$lib/utils/fetch"; - import { nostr_sync } from "$lib/utils/nostr"; + import { fetch_relay_documents } from "$lib/util/fetch"; + import { nostr_sync } from "$lib/util/nostr"; import { app_cfg_type, app_geoc, @@ -49,35 +49,36 @@ }); app_nostr_key.subscribe(async (_app_nostr_key) => { - console.log(`_app_nostr_key `, _app_nostr_key); - if (!_app_nostr_key) return; - - const ks_nostr_secretkey = await keystore.get( - ks.keys.nostr_secretkey($app_nostr_key), - ); - - if (`err` in ks_nostr_secretkey) { - //@todo; - return; - } - - const nostr_relays = await db.nostr_relay_get({ - list: [`on_profile`, { public_key: $app_nostr_key }], - }); - if (`err` in nostr_relays) throw new Error(nostr_relays.err); - for (const { url } of nostr_relays.results) $ndk.addExplicitRelay(url); - await $ndk.connect(); - const ndk_user = await ndk_init({ - $ndk, - secret_key: ks_nostr_secretkey.result, - }); - if (!ndk_user) { - nostr_ndk_configured.set(false); - return; + try { + console.log(`_app_nostr_key `, _app_nostr_key); + if (!_app_nostr_key) return; + const ks_nostr_secretkey = await keystore.get( + ks.keys.nostr_secretkey($app_nostr_key), + ); + if (`err` in ks_nostr_secretkey) { + return; //@todo; + } + const nostr_relays = await db.nostr_relay_get({ + list: [`on_profile`, { public_key: $app_nostr_key }], + }); + if (`err` in nostr_relays) throw new Error(nostr_relays.err); + for (const { url } of nostr_relays.results) + $ndk.addExplicitRelay(url); + await $ndk.connect(); + const ndk_user = await ndk_init({ + $ndk, + secret_key: ks_nostr_secretkey.result, + }); + if (!ndk_user) { + nostr_ndk_configured.set(false); + return; + } + $ndk_user = ndk_user; + $ndk_user.ndk = $ndk; + nostr_ndk_configured.set(true); + } catch (e) { + console.log(`(error) app_nostr_key`, e); } - $ndk_user = ndk_user; - $ndk_user.ndk = $ndk; - nostr_ndk_configured.set(true); }); nostr_ndk_configured.subscribe(async (_nostr_ndk_configured) => { diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte @@ -1,8 +1,6 @@ <script lang="ts"> - import { db, dialog, notification } from "$lib/client"; + import { db, device, dialog } from "$lib/client"; import { - app_cfg_type, - app_layout, app_nostr_key, envelope_visible, EnvelopeLower, @@ -10,68 +8,9 @@ LayoutView, nav_prev, route, - t, - Tabs, - TrellisTitle, - type AppConfigType, - type CallbackPromise, - type GlyphKey, - type GlyphWeight, - type NavigationRoute, } from "@radroots/svelte-lib"; import { onMount } from "svelte"; - type PageParamButtons = { - route?: NavigationRoute; - label: string; - key: GlyphKey; - weight?: GlyphWeight; - callback?: CallbackPromise; - }; - const page_param: { - buttons: Record<AppConfigType, PageParamButtons[]>; - } = { - buttons: { - personal: [ - { - route: `/`, - label: `Marketplace`, - key: `basket`, - }, - { - route: `/`, - label: `Forum`, - key: `basket`, - }, - ], - farmer: [ - { - route: `/test`, - label: `Post New`, - key: `note-blank`, - }, - { - label: `Farm Products`, - key: `basket`, - callback: async () => { - $nav_prev.push({ - route: `/`, - label: `Home`, - }); - await route(`/models/trade-product/add`); - }, - }, - { - route: `/settings/nostr`, - label: `Cooperatives`, - key: `users-three`, - }, - ], - }, - }; - - let tmp_show_no_profile = false; - onMount(async () => { try { nav_prev.set([]); @@ -83,156 +22,43 @@ await dialog.alert(`@todo Nostr profile configuration failure`); return; } - if (!nostr_profile.results[0].name) tmp_show_no_profile = true; } catch (e) { } finally { } }); </script> -<LayoutView basis={{ classes: `gap-4` }}> - <div class={`flex flex-row w-full justify-start items-center`}> +<LayoutView> + <div class={`flex flex-row h-12 w-full px-6 justify-between items-center`}> + <div class={`flex flex-row gap-2 justify-start items-center`}> + <p class={`font-mono font-[600] text-[1.3rem] text-layer-0-glyph`}> + {`radRoots`} + </p> + {#if device.metadata?.version} + <p + class={`font-mono font-[400] text-[1.3rem] text-layer-0-glyph`} + > + {`/${device.metadata.version}`} + </p> + {/if} + </div> <button - class={`flex flex-row pt-4 px-6 gap-2 justify-center items-center`} + class={`flex flex-row justify-center items-center`} on:click={async () => { - await notification.send(`hi`); + await route(`/settings`); }} > - <p - class={`font-mono font-[600] text-layer-2-glyph max-sm:text-lg text-xl tracking-wide`} - > - {`radroots (alpha-1.0.0)`} - </p> - </button> - </div> - <!--<div class={`flex flex-col pt-2 justify-center items-center`}> - {#if tmp_show_no_profile} - <button - class={`relative flex flex-row h-24 w-${$app_layout} p-4 gap-4 justify-center items-center bg-layer-2-surface/60 round-20 active-layer-1 active-layer-1-raise-less el-re`} - on:click={async () => { - await route(`/models/nostr-profile`); - }} - > - <div - class={`absolute left-6 flex flex-row h-full justify-end items-center`} - > - <Glyph - basis={{ - classes: `text-layer-2-glyph`, - key: `user-circle-plus`, - dim: `xl`, - weight: `bold`, - }} - /> - </div> - <div class={`flex flex-col justify-center items-center`}> - <p - class={`font-circ font-[500] text-lg text-layer-2-glyph/90`} - > - {`No farm profile added.`} - </p> - <p - class={`font-circ font-[500] text-sm text-layer-2-glyph/90`} - > - {`Click to add your details`} - </p> - </div> - <Glyph - basis={{ - classes: `absolute top-2 right-3 text-layer-2-glyph/40`, - key: `x`, - dim: `sm`, - weight: `bold`, - }} - /> - </button> - {/if} - </div>--> - <div class={`flex flex-col w-full gap-[2px] justify-start items-center`}> - <div class={`flex flex-row w-full px-6 justify-center items-center`}> - <TrellisTitle + <Glyph basis={{ - value: `${$t(`common.options`)}`, + classes: `text-layer-0-glyph`, + dim: `md`, + weight: `bold`, + key: `gear`, }} /> - </div> - <div class={`flex flex-col w-full gap-5 justify-start items-center`}> - {#each page_param.buttons[$app_cfg_type] as btn} - <button - class={`flex flex-row h-20 w-${$app_layout} py-2 px-6 justify-between items-center bg-layer-1-surface active-layer-1 active-layer-1-raise-less round-20 el-re`} - on:click={async () => { - if (btn.callback) await btn.callback(); - else if (btn.route) await route(btn.route); - }} - > - <div - class={`flex flex-row gap-4 justify-start items-center`} - > - <Glyph - basis={{ - classes: `text-layer-2-glyph`, - key: btn.key, - dim: `md`, - weight: btn.weight || `bold`, - }} - /> - <div class={`flex flex-row justify-start items-center`}> - <p - class={`font-mono font-[500] text-lg text-layer-2-glyph`} - > - {btn.label} - </p> - </div> - </div> - <div class={`flex flex-row justify-start items-center`}> - <Glyph - basis={{ - key: `caret-right`, - dim: `sm-`, - weight: `bold`, - classes: `text-layer-2-glyph`, - }} - /> - </div> - </button> - {/each} - </div> + </button> </div> </LayoutView> -<Tabs - basis={{ - list: [ - { - icon: `house-line`, - label: `${$t(`common.home`)}`, - callback: async () => { - await route(`/`); - }, - }, - { - icon: `arrows-down-up`, - label: `Products`, - callback: async () => { - await route(`/models/trade-product`); - }, - }, - { - icon: `cardholder`, - label: `Test`, - callback: async () => { - await route(`/test`); - }, - }, - { - icon: `squares-four`, - label: `Settings`, - callback: async () => { - await route(`/settings`); - }, - }, - ], - }} -/> <EnvelopeLower basis={{ close: async () => { diff --git a/src/routes/(app)/models/trade-product/add/+page.svelte b/src/routes/(app)/models/trade-product/add/+page.svelte @@ -1,5 +1,4 @@ <script lang="ts"> - import { PUBLIC_RADROOTS_URL } from "$env/static/public"; import { db, dialog, geol } from "$lib/client"; import ImageUploadControl from "$lib/components/image_upload_control.svelte"; import ImageUploadEditEnvelope from "$lib/components/image_upload_edit_envelope.svelte"; @@ -7,17 +6,16 @@ import TradeFieldDisplayEl from "$lib/components/trade_field_display_el.svelte"; import TradeFieldDisplayKv from "$lib/components/trade_field_display_kv.svelte"; import { ascii } from "$lib/conf"; - import { el_focus } from "$lib/utils/client"; - import { fetch_put_upload } from "$lib/utils/fetch"; - import { location_gcs_to_geoc } from "$lib/utils/geocode"; - import { kv_init_page, kv_sync } from "$lib/utils/kv"; - import { model_location_gcs_add_geocode } from "$lib/utils/models"; + import { el_focus } from "$lib/util/client"; + import { location_gcs_to_geoc } from "$lib/util/geocode"; + import { kv_init_page, kv_sync } from "$lib/util/kv"; + import { model_location_gcs_add_geocode } from "$lib/util/models"; + import { model_media_upload_add_list } from "$lib/util/models-media-upload"; import { trade_product_fields_validate, tradeproduct_init_kv, tradeproduct_validate_fields, - } from "$lib/utils/trade_product"; - import type { IClientHttpResponseError } from "@radroots/client"; + } from "$lib/util/models-trade-product"; import type { GeocoderReverseResult } from "@radroots/geocoder"; import { trade_product_form_fields, @@ -64,7 +62,6 @@ num_str, parse_currency_marker, parse_currency_price, - parse_file_path, parse_trade_key, parse_trade_quantity_tup, sum_currency_price, @@ -72,7 +69,6 @@ trade_keys, year_curr, type CurrencyPrice, - type FilePath, type GeolocationCoordinatesPoint, type TradeKey, type TradeQuantity, @@ -507,8 +503,9 @@ } return; } + //@todo edit here // photos - const photo_path_uploads: { + /*const photo_path_uploads: { file_path: FilePath; res_base: string; res_path: string; @@ -574,23 +571,18 @@ })}`, ); return; - } - const media_upload_added: string[] = []; - if (photo_path_uploads.length) { - for (const photo_path_upload of photo_path_uploads) { - const media_upload_add = await db.media_upload_add({ - file_path: photo_path_upload.file_path.file_path, - mime_type: photo_path_upload.file_path.mime_type, - res_base: photo_path_upload.res_base, - res_path: photo_path_upload.res_path, - }); - if ( - `err` in media_upload_add || - `err_s` in media_upload_add - ) - continue; //@todo - media_upload_added.push(media_upload_add.id); - } + }*/ + + const media_uploads = await model_media_upload_add_list({ + photo_paths: tradepr_photo_paths, + }); + + if (`alert` in media_uploads) { + await dialog.alert(media_uploads.alert); + return; + } else if (`confirm` in media_uploads) { + await dialog.confirm(media_uploads.confirm); + return; } // trade product @@ -632,7 +624,7 @@ trade_product_location_set_err = trade_product_location_set.err; } const trade_product_media_set_err: string[] = []; - for (const media_upload of media_upload_added) { + for (const media_upload of media_uploads.results) { const trade_product_media_set = await db.trade_product_media_set({ trade_product: { @@ -778,7 +770,7 @@ el_id( fmt_id(`key_wrap`), )?.classList.remove( - `entry-layer-1-highlight`, + `layer-1-ring-apply`, ); if (!opt.value) { await handle_tradepr_key_toggle( @@ -890,7 +882,7 @@ el_id( fmt_id(`process_wrap`), )?.classList.remove( - `entry-layer-1-highlight`, + `layer-1-ring-apply`, ); if (!value) { await handle_tradepr_process_toggle( @@ -993,7 +985,7 @@ el_id( fmt_id(`process_wrap`), )?.classList.remove( - `entry-layer-1-highlight`, + `layer-1-ring-apply`, ); if (value === `*map`) { await handle_tradepr_lgc_sel_map(); @@ -1191,13 +1183,13 @@ el_id( fmt_id(`price_wrap`), )?.classList.remove( - `entry-layer-1-highlight`, + `layer-1-ring-apply`, ); } else { el_id( fmt_id(`price_wrap`), )?.classList.add( - `entry-layer-1-highlight`, + `layer-1-ring-apply`, ); } }, @@ -1297,7 +1289,7 @@ el_id( fmt_id(`qty_wrap`), )?.classList.remove( - `entry-layer-1-highlight`, + `layer-1-ring-apply`, ); if (value === ``) { await handle_tradepr_qty_amt_toggle( diff --git a/src/routes/(app)/settings/+page.svelte b/src/routes/(app)/settings/+page.svelte @@ -1,7 +1,7 @@ <script lang="ts"> import { dialog, geol, haptics, keystore } from "$lib/client"; import { ascii, ks } from "$lib/conf"; - import { reset_device } from "$lib/utils/client"; + import { reset_device } from "$lib/util/client"; import { app_thc, LayoutTrellis, diff --git a/src/routes/(app)/test/+page.svelte b/src/routes/(app)/test/+page.svelte @@ -1,51 +1,4 @@ <script lang="ts"> - import { nostr_tags_basis } from "$lib/utils/nostr"; - import { NDKKind } from "@nostr-dev-kit/ndk"; - import { LayoutView, Nav, ndk, ndk_user, t } from "@radroots/svelte-lib"; - import { ndk_event } from "@radroots/utils"; - - const post = async (): Promise<void> => { - try { - const tags = nostr_tags_basis(); - - const ev = await ndk_event({ - $ndk, - $ndk_user, - basis: { - kind: NDKKind.Text, - content: `testing radroots at ${new Date().toISOString()}`, - tags, - }, - }); - if (ev) await ev.publish(); - console.log(JSON.stringify(ev, null, 4), `ev`); - } catch (e) { - console.log(`(error) post `, e); - } - }; </script> -<LayoutView> - <div class={`flex flex-col w-full px-4 gap-4 justify-center items-center`}> - <button - class={`flex flex-row justify-center items-center`} - on:click={async () => { - await post(); - }} - > - post - </button> - </div> -</LayoutView> - -<Nav - basis={{ - prev: { - label: `${$t("common.home")}`, - route: `/`, - }, - title: { - label: { value: `Image Upload` }, - }, - }} -/> +<div></div> diff --git a/src/routes/(cfg)/cfg/error/+page.svelte b/src/routes/(cfg)/cfg/error/+page.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { restart } from "$lib/utils/client"; + import { restart } from "$lib/util/client"; import { LayoutView } from "@radroots/svelte-lib"; </script> diff --git a/src/routes/(cfg)/cfg/init/+page.svelte b/src/routes/(cfg)/cfg/init/+page.svelte @@ -1,11 +1,15 @@ <script lang="ts"> - import { - PUBLIC_NOSTR_RELAY_DEFAULTS, - PUBLIC_RADROOTS_URL, - } from "$env/static/public"; - import { db, dialog, http, keystore, nostr } from "$lib/client"; + import { PUBLIC_NOSTR_RELAY_DEFAULTS } from "$env/static/public"; + import { db, dialog, keystore, nostr } from "$lib/client"; import { cfg, ks } from "$lib/conf"; - import { restart } from "$lib/utils/client"; + import { restart } from "$lib/util/client"; + import { + fetch_radroots_profile_confirm, + fetch_radroots_profile_init, + fetch_radroots_profile_status, + fetch_radroots_profile_validate, + } from "$lib/util/fetch-radroots-profile"; + import { kv_init_page } from "$lib/util/kv"; import type { IClientUnlisten } from "@radroots/client"; import { app_layout, @@ -24,41 +28,32 @@ InputElement, kv, LayoutView, + Loading, route, sleep, t, view_effect, } from "@radroots/svelte-lib"; - import { err_msg, regex, type ErrorMessage } from "@radroots/utils"; + import { regex } from "@radroots/utils"; import { onDestroy, onMount } from "svelte"; - const page_param: { - kv: { - nostr_secretkey: string; - nostr_profilename: string; - }; - carousel: Record<View, { max_index: number }>; - } = { - kv: { - nostr_secretkey: `kv_secretkey`, - nostr_profilename: `kv_profilename`, + const page_carousel: Record<View, { max_index: number }> = { + cfg_init: { + max_index: 2, }, - carousel: { - cfg_init: { - max_index: 2, - }, - cfg_main: { - max_index: 2, - }, - eula: { - max_index: 1, - }, + cfg_main: { + max_index: 2, + }, + eula: { + max_index: 1, }, }; let el_eula: HTMLDivElement; let el_eula_scrolled = false; + let loading_submit = false; + type CfgInitKeyOption = `key_gen` | `kv_nostr_secretkey`; let cgf_init_key_option: CfgInitKeyOption | undefined = undefined; @@ -84,8 +79,29 @@ onMount(async () => { try { - handle_view(initial_view); + await init_page(); + } catch (e) { + console.log(`e mount`, e); + } finally { + app_splash.set(false); + } + }); + onDestroy(async () => { + try { + await kv_init_page(); + if (unlisten_cfg_init_nostr_secretkey) + unlisten_cfg_init_nostr_secretkey(); + el_eula?.removeEventListener(`scroll`, () => null); + } catch (e) { + console.log(`e destroy`, e); + } finally { + } + }); + + const init_page = async (): Promise<void> => { + try { + handle_view(initial_view); const unlisten_1 = await keystore.on_key_change( ks.cfg_init.nostr_secretkey, async (_cfg_init_nostr_secretkey) => { @@ -106,35 +122,9 @@ if (scroll_top + client_h >= scroll_h) el_eula_scrolled = true; }); await lookup_ks(); + await kv_init_page(); } catch (e) { - console.log(`e mount`, e); - } finally { - app_splash.set(false); - } - }); - - onDestroy(async () => { - try { - await reset_kv(); - if (unlisten_cfg_init_nostr_secretkey) - unlisten_cfg_init_nostr_secretkey(); - el_eula?.removeEventListener(`scroll`, () => null); - } catch (e) { - console.log(`e destroy`, e); - } finally { - } - }); - - const reset_page = async (alert_message?: string): Promise<void> => { - try { - app_loading.set(true); - handle_view(`cfg_init`); - if (alert_message) await dialog.alert(alert_message); - await sleep(cfg.delay.load); - } catch (e) { - console.log(`(error) reset `, e); - } finally { - app_loading.set(false); + console.log(`(error) init_page `, e); } }; @@ -185,12 +175,12 @@ } else { if (`nip_05` in profile_status.active) { await kv.set( - page_param.kv.nostr_profilename, + fmt_id(`nostr_profilename`), profile_status.active.nip_05, ); } await kv.set( - page_param.kv.nostr_profilename, + fmt_id(`nostr_profilename`), profile_status.active.nip_05, ); handle_view(`eula`); @@ -207,6 +197,19 @@ } }; + const reset_page = async (alert_message?: string): Promise<void> => { + try { + app_loading.set(true); + handle_view(`cfg_init`); + if (alert_message) await dialog.alert(alert_message); + await sleep(cfg.delay.load); + } catch (e) { + console.log(`(error) reset `, e); + } finally { + app_loading.set(false); + } + }; + const reset_ks = async (): Promise<void> => { try { const ks_entries = await keystore.entries(); @@ -221,24 +224,13 @@ } }; - const reset_kv = async (): Promise<void> => { - try { - for (const kv_key of Object.values(page_param.kv)) - await kv.delete(kv_key); - } catch (e) { - console.log(`(error) reset_ks `, e); - } - }; - const handle_view = (new_view: View): void => { if (new_view === `cfg_init` && view === `cfg_main`) { const offset = cgf_init_key_option === `key_gen` ? 1 : 0; - carousel_index.set( - page_param.carousel[new_view].max_index - offset, - ); + carousel_index.set(page_carousel[new_view].max_index - offset); } else { carousel_index.set(0); - carousel_index_max.set(page_param.carousel[new_view].max_index); + carousel_index_max.set(page_carousel[new_view].max_index); } view = new_view; }; @@ -321,7 +313,7 @@ case 2: { const kv_nostr_secretkey = await kv.get( - fmt_id(page_param.kv.nostr_secretkey), + fmt_id(`nostr_secretkey`), ); if (!kv_nostr_secretkey) { await dialog.alert( @@ -373,7 +365,7 @@ return; } const kv_profile_name = await kv.get( - fmt_id(page_param.kv.nostr_profilename), + fmt_id(`nostr_profilename`), ); if (!kv_profile_name) { await dialog.alert( @@ -447,126 +439,10 @@ } }; - const fetch_radroots_profile_validate = async (opts: { - profile_name: string; - }): Promise<{ profile_name: string } | ErrorMessage<string>> => { - try { - const res = await http.fetch({ - url: `${PUBLIC_RADROOTS_URL}/public/accounts/list`, - method: `post`, - }); - if (`err` in res) - return err_msg(`${$t(`error.client.network_failure`)}`); - else if (Array.isArray(res.data.results)) { - const existing_profile = res.data.results.find( - (i: any) => - `nip_05` in i && - String(i.nip_05).toLowerCase() === - opts.profile_name.toLowerCase(), - ); - if (existing_profile) - return err_msg( - `${`${$t(`icu.the_*_is_registered`, { value: `${$t(`common.profile_name`)}`.toLowerCase() })} `}`, - ); - return { profile_name: opts.profile_name }; - } - - return err_msg(`${$t(`error.client.request_failure`)}`); - } catch (e) { - console.log(`(error) fetch_radroots_profile_validate `, e); - return err_msg(`${$t(`error.client.network_failure`)}`); - } - }; - - const fetch_radroots_profile_init = async (opts: { - profile_name: string; - secret_key: string; - nostr_relays?: string[]; - }): Promise<{ tok: string } | ErrorMessage<string>> => { - try { - const res = await http.fetch({ - url: `${PUBLIC_RADROOTS_URL}/public/accounts/add/init`, - method: `post`, - data: { - nip_05: opts.profile_name, - public_key: nostr.lib.public_key(opts.secret_key), - nostr_relays: opts.nostr_relays?.length - ? Array.from( - new Set([ - ...opts.nostr_relays, - cfg.nostr.relay_url, - ]), - ).join(`,`) - : [cfg.nostr.relay_url].join(`,`), - }, - }); - if (`err` in res) return res; - else if (res.data && `tok` in res.data) { - return { tok: res.data.tok }; - } else if (res.data && `message` in res.data) - return err_msg( - `${$t(`radroots-org.error.${res.data.message}`)}`, - ); - return err_msg(`${$t(`error.client.request_failure`)}`); - } catch (e) { - console.log(`(error) fetch_radroots_profile_init `, e); - return err_msg(`${$t(`error.client.network_failure`)}`); - } - }; - - const fetch_radroots_profile_confirm = async ( - authorization: string, - ): Promise<{ pass: true } | ErrorMessage<string>> => { - try { - const res = await http.fetch({ - url: `${PUBLIC_RADROOTS_URL}/public/accounts/add/conf`, - method: `post`, - authorization, - }); - if (`err` in res) return res; - return { pass: true }; - } catch (e) { - console.log(`(error) fetch_radroots_profile_confirm `, e); - return err_msg(`${$t(`error.client.network_failure`)}`); - } - }; - - const fetch_radroots_profile_status = async ( - authorization: string, - ): Promise< - | { active: { public_key: string; nip_05?: string } } - | ErrorMessage<string> - > => { - try { - const res = await http.fetch({ - url: `${PUBLIC_RADROOTS_URL}/public/accounts/add/status`, - method: `post`, - authorization, - }); - if (`err` in res) return res; - else if ( - `public_key` in res.data && - typeof res.data.public_key === `string` - ) - return { - active: { - public_key: res.data.public_key, - nip_05: - `nip_05` in res.data && - typeof res.data.nip_05 === `string` - ? res.data.nip_05 - : undefined, - }, - }; - return err_msg(`${$t(`error.client.network_failure`)}`); - } catch (e) { - console.log(`(error) fetch_radroots_profile_confirm `, e); - return err_msg(`${$t(`error.client.network_failure`)}`); - } - }; - const submit = async (): Promise<void> => { try { + loading_submit = true; + const ks_nostr_secretkey = await keystore.get( ks.cfg_init.nostr_secretkey, ); @@ -602,6 +478,7 @@ ); return; //@todo } + const nostr_profile_add = await db.nostr_profile_add({ public_key: nostr_publickey, name: @@ -622,10 +499,7 @@ ks_nostr_secretkey.result, ); for (const url of Array.from( - new Set([ - //cfg.nostr.relay_url, - ...PUBLIC_NOSTR_RELAY_DEFAULTS.split(","), - ]), + new Set([...PUBLIC_NOSTR_RELAY_DEFAULTS.split(",")]), )) { const nostr_relay_add = await db.nostr_relay_add({ url }); if (`err` in nostr_relay_add || `err_s` in nostr_relay_add) { @@ -634,19 +508,14 @@ ); return; // @todo } - const nostr_profile_relay_add = - await db.nostr_profile_relay_set({ - nostr_profile: { - id: nostr_profile_add.id, - }, - nostr_relay: { - id: nostr_relay_add.id, - }, - }); - console.log( - JSON.stringify(nostr_profile_relay_add, null, 4), - `nostr_profile_relay_add`, - ); + await db.nostr_profile_relay_set({ + nostr_profile: { + id: nostr_profile_add.id, + }, + nostr_relay: { + id: nostr_relay_add.id, + }, + }); } await reset_ks(); await restart({ @@ -655,6 +524,8 @@ }); } catch (e) { console.log(`(error) submit `, e); + } finally { + loading_submit = false; } }; </script> @@ -741,7 +612,7 @@ class={`flex flex-col w-full gap-5 justify-center items-center`} > <button - class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center bg-layer-1-surface active-layer-1-raise-less round-44 ${cgf_init_key_option === `key_gen` ? `bg-layer-1-surface_a/40` : ``}`} + class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center rounded-touch ${cgf_init_key_option === `key_gen` ? `layer-1-surface-apply-active layer-1-raise-apply layer-1-ring-apply` : `bg-layer-1-surface`} el-re`} on:click|stopPropagation={async () => { cgf_init_key_option = `key_gen`; }} @@ -753,7 +624,7 @@ </p> </button> <button - class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center bg-layer-1-surface round-44 active-layer-1-raise-less ${cgf_init_key_option === `kv_nostr_secretkey` ? `bg-layer-1-surface_a/40` : ``}`} + class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center rounded-touch ${cgf_init_key_option === `kv_nostr_secretkey` ? `layer-1-surface-apply-active layer-1-raise-apply layer-1-ring-apply` : `bg-layer-1-surface`} el-re`} on:click|stopPropagation={async () => { cgf_init_key_option = `kv_nostr_secretkey`; }} @@ -806,7 +677,7 @@ <InputElement basis={{ classes: `h-entry_guide w-${$app_layout} bg-layer-1-surface rounded-touch font-mono text-lg placeholder:opacity-60 items-end text-center`, - id: fmt_id(page_param.kv.nostr_secretkey), + id: fmt_id(`nostr_secretkey`), sync: true, placeholder: `${$t(`icu.enter_*`, { value: `nostr nsec/hex` })}`, field: { @@ -871,12 +742,13 @@ basis={{ loading: cfg_main_profilename_loading, wrap: { + layer: 1, classes: `w-${$app_layout}`, style: `guide`, }, el: { - classes: `font-mono text-lg text-center placeholder:opacity-60`, - id: fmt_id(page_param.kv.nostr_profilename), + classes: `font-sans text-[1.25rem] text-center placeholder:opacity-60`, + id: fmt_id(`nostr_profilename`), sync: true, placeholder: `${$t(`icu.enter_*`, { value: `${$t(`common.profile_name`)}`.toLowerCase() })}`, field: { @@ -922,7 +794,7 @@ class={`flex flex-col w-full gap-5 justify-center items-center`} > <button - class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center bg-layer-1-surface active-layer-1-raise-less round-44 ${cfg_main_opt_idx1 === `farmer` ? `bg-layer-1-surface_a/40` : ``}`} + class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center rounded-touch ${cfg_main_opt_idx1 === `farmer` ? `layer-1-surface-apply-active layer-1-raise-apply layer-1-ring-apply` : `bg-layer-1-surface`} el-re`} on:click|stopPropagation={async () => { cfg_main_opt_idx1 = `farmer`; }} @@ -934,7 +806,7 @@ </p> </button> <button - class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center bg-layer-1-surface active-layer-1-raise-less round-44 ${cfg_main_opt_idx1 === `personal` ? `bg-layer-1-surface_a/40` : ``}`} + class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center rounded-touch ${cfg_main_opt_idx1 === `personal` ? `layer-1-surface-apply-active layer-1-raise-apply layer-1-ring-apply` : `bg-layer-1-surface`} el-re`} on:click|stopPropagation={async () => { cfg_main_opt_idx1 = `personal`; }} @@ -999,9 +871,7 @@ ok_label: `${$t(`common.continue`)}`, }); if (confirm === false) { - el_id( - fmt_id(page_param.kv.nostr_profilename), - )?.focus(); + el_id(fmt_id(`nostr_profilename`))?.focus(); return; } carousel_inc(view); @@ -1240,7 +1110,7 @@ </p> </button> <button - class={`group flex flex-row basis-1/2 gap-4 justify-center items-center ${el_eula_scrolled ? `` : `opacity-40`}`} + class={`relative group flex flex-row basis-1/2 gap-4 justify-center items-center ${el_eula_scrolled ? `` : `opacity-40`}`} on:click={async () => { if (el_eula_scrolled) await submit(); }} @@ -1260,6 +1130,13 @@ > {`- `} </p> + {#if loading_submit} + <div + class={`absolute right-0 flex flex-row justify-start items-center`} + > + <Loading basis={{ dim: `xs` }} /> + </div> + {/if} </button> </div> </div> diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte @@ -1,6 +1,7 @@ <script lang="ts"> import { device, dialog, http, logger, os } from "$lib/client"; import { cfg } from "$lib/conf"; + import { kv_init_app } from "$lib/util/kv"; import type { IClientDeviceMetadata, IClientUnlisten, @@ -35,16 +36,7 @@ onMount(async () => { try { - if (`paintWorklet` in CSS) - (CSS as any).paintWorklet.addModule(`/squircle.min.js`); - const metadata: IClientDeviceMetadata = { - version: os.version(), - platform: os.platform(), - locale: $locale, - }; - await device.init(metadata); - await http.init(metadata); - log_unlisten = await logger.init(); + await init_app(); } catch (e) { } finally { } @@ -62,13 +54,11 @@ app_thc.subscribe((app_thc) => { const color_mode = parse_color_mode(app_thc); theme_set(parse_theme_key($app_th), color_mode); - //window.status_style(color_mode); }); app_th.subscribe((app_th) => { const color_mode = parse_color_mode($app_thc); theme_set(parse_theme_key(app_th), color_mode); - //window.status_style(color_mode); }); app_db.subscribe((_app_db) => { @@ -90,6 +80,24 @@ dialog.alert(_app_notify); app_notify.set(``); }); + + const init_app = async (): Promise<void> => { + try { + if (`paintWorklet` in CSS) + (CSS as any).paintWorklet.addModule(`/squircle.min.js`); + const metadata: IClientDeviceMetadata = { + version: cfg.app.version, + platform: os.platform(), + locale: $locale, + }; + await device.init(metadata); + await http.init(metadata); + log_unlisten = await logger.init(); + await kv_init_app(); + } catch (e) { + console.log(`(error) init_app `, e); + } + }; </script> <svelte:head> @@ -110,5 +118,5 @@ <CssStatic /> <CssStyles /> <div - class="hidden h-entry_guide h-entry_line h-[calc(100vh-12%)] h-trellis_centered_mobile_base h-trellis_centered_mobile_y" + class="hidden h-entry_guide h-entry_line h-[calc(100vh-12%)] h-trellis_centered_mobile_base h-trellis_centered_mobile_y bg-white/40 backdrop-blur-lg backdrop-blur-sm" /> diff --git a/tailwind.config.ts b/tailwind.config.ts @@ -3,6 +3,7 @@ import { wind } from "@radroots/utils"; import aspect_ratio from "@tailwindcss/aspect-ratio"; import daisyui from "daisyui"; import type { Config } from "tailwindcss"; +import heropatterns from "tailwindcss-hero-patterns"; import tailwind_default from "tailwindcss/defaultTheme"; const { fontFamily: tw_font } = tailwind_default; @@ -178,7 +179,8 @@ const config: Config = { }, plugins: [ daisyui, - aspect_ratio + aspect_ratio, + heropatterns ], daisyui: { themes: [