forked from trent_larson/crowd-funder-for-time-pwa
Merge branch 'master' into nearby-filter
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
|
||||
# Only the variables that start with VITE_ are seen in the application import.meta.env in Vue.
|
||||
|
||||
# Logging Configuration - Development environment gets maximum visibility
|
||||
VITE_LOG_LEVEL=debug
|
||||
|
||||
# iOS doesn't like spaces in the app title.
|
||||
TIME_SAFARI_APP_TITLE="TimeSafari_Dev"
|
||||
VITE_APP_SERVER=http://localhost:8080
|
||||
# This is the claim ID for actions in the BVC project, with the JWT ID on this environment (not production).
|
||||
# This is the claim ID for actions in the BVC project, with the JWT ID on this environment (not
|
||||
|
||||
|
||||
VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F
|
||||
VITE_DEFAULT_ENDORSER_API_SERVER=http://localhost:3000
|
||||
# Using shared server by default to ease setup, which works for shared test users.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Only the variables that start with VITE_ are seen in the application import.meta.env in Vue.
|
||||
|
||||
|
||||
# Logging Configuration - Production environment gets minimal logging for performance
|
||||
VITE_LOG_LEVEL=warn
|
||||
|
||||
VITE_APP_SERVER=https://timesafari.app
|
||||
# This is the claim ID for actions in the BVC project.
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# Only the variables that start with VITE_ are seen in the application import.meta.env in Vue.
|
||||
|
||||
# Logging Configuration - Test environment gets balanced logging for debugging
|
||||
VITE_LOG_LEVEL=info
|
||||
|
||||
# iOS doesn't like spaces in the app title.
|
||||
TIME_SAFARI_APP_TITLE="TimeSafari_Test"
|
||||
VITE_APP_SERVER=https://test.timesafari.app
|
||||
# This is the claim ID for actions in the BVC project, with the JWT ID on this environment (not production).
|
||||
# This is the claim ID for actions in the BVC project, with the JWT ID on this environment (not
|
||||
production).
|
||||
|
||||
VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F
|
||||
VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@ This guide explains how to build TimeSafari for different platforms using the co
|
||||
|
||||
```bash
|
||||
# 🖥️ Web Development
|
||||
npm run build:web:dev # Start development server with hot reload
|
||||
npm run build:web:prod # Production build
|
||||
npm install # setup -- and pkgx.dev `dev` command before this will set environment with npm, etc
|
||||
npm run build:web:serve -- --test # Start with test endorser server
|
||||
npm run build:web:dev # Start development server with hot reload with local endorser server
|
||||
npm run build:web:prod # Production build
|
||||
|
||||
# 📱 Mobile Development
|
||||
npm run build:ios # iOS build (opens Xcode)
|
||||
@@ -2401,4 +2403,4 @@ All scripts use consistent error handling:
|
||||
|
||||
---
|
||||
|
||||
**Note**: This documentation is maintained alongside the build system. For the most up-to-date information, refer to the actual script files and Vite configuration files in the repository.
|
||||
**Note**: This documentation is maintained alongside the build system. For the most up-to-date information, refer to the actual script files and Vite configuration files in the repository.
|
||||
|
||||
65
README.md
65
README.md
@@ -3,36 +3,9 @@
|
||||
[Time Safari](https://timesafari.org/) allows people to ease into collaboration: start with expressions of gratitude
|
||||
and expand to crowd-fund with time & money, then record and see the impact of contributions.
|
||||
|
||||
## Database Migration Status
|
||||
|
||||
**Current Status**: The application is undergoing a migration from Dexie (IndexedDB) to SQLite using absurd-sql. This migration is in **Phase 2** with a well-defined migration fence in place.
|
||||
|
||||
### Migration Progress
|
||||
- ✅ **SQLite Database Service**: Fully implemented with absurd-sql
|
||||
- ✅ **Platform Service Layer**: Unified database interface across platforms
|
||||
- ✅ **Settings Migration**: Core user settings transferred
|
||||
- ✅ **Account Migration**: Identity and key management
|
||||
- 🔄 **Contact Migration**: User contact data (via import interface)
|
||||
- 📋 **Code Cleanup**: Remove unused Dexie imports
|
||||
|
||||
### Migration Fence
|
||||
The migration is controlled by a **migration fence** that separates legacy Dexie code from the new SQLite implementation. See [Migration Fence Definition](doc/migration-fence-definition.md) for complete details.
|
||||
|
||||
**Key Points**:
|
||||
- Legacy Dexie database is disabled by default
|
||||
- All database operations go through `PlatformServiceMixin`
|
||||
- Migration tools provide controlled access to both databases
|
||||
- Clear separation between legacy and new code
|
||||
|
||||
### Migration Documentation
|
||||
- [Migration Guide](doc/migration-to-wa-sqlite.md) - Complete migration process
|
||||
- [Migration Fence Definition](doc/migration-fence-definition.md) - Fence boundaries and rules
|
||||
- [Database Migration Guide](doc/database-migration-guide.md) - User-facing migration tools
|
||||
|
||||
## Roadmap
|
||||
|
||||
See [project.task.yaml](project.task.yaml) for current priorities.
|
||||
(Numbers at the beginning of lines are estimated hours. See [taskyaml.org](https://taskyaml.org/) for details.)
|
||||
See [ClickUp](https://sharing.clickup.com/9014278710/l/h/8cmnyhp-174/10573fec74e2ba0) for current priorities.
|
||||
|
||||
## Setup & Building
|
||||
|
||||
@@ -42,14 +15,45 @@ Quick start:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
npm run build:web:serve -- --test
|
||||
```
|
||||
|
||||
To be able to make submissions: go to "profile" (bottom left), go to the bottom and expand "Show Advanced Settings", go to the bottom and to the "Test Page", and finally "Become User 0" to see all the functionality.
|
||||
|
||||
See [BUILDING.md](BUILDING.md) for comprehensive build instructions for all platforms (Web, Electron, iOS, Android, Docker).
|
||||
|
||||
## Development Database Clearing
|
||||
|
||||
TimeSafari provides a simple script-based approach to clear the database for development purposes.
|
||||
TimeSafari provides a simple script-based approach to clear the local database (not the claim server) for development purposes.
|
||||
|
||||
## Logging Configuration
|
||||
|
||||
TimeSafari supports configurable logging levels via the `VITE_LOG_LEVEL` environment variable. This allows developers to control console output verbosity without modifying code.
|
||||
|
||||
### Quick Usage
|
||||
|
||||
```bash
|
||||
# Show only errors
|
||||
VITE_LOG_LEVEL=error npm run dev
|
||||
|
||||
# Show warnings and errors
|
||||
VITE_LOG_LEVEL=warn npm run dev
|
||||
|
||||
# Show info, warnings, and errors (default)
|
||||
VITE_LOG_LEVEL=info npm run dev
|
||||
|
||||
# Show all log levels including debug
|
||||
VITE_LOG_LEVEL=debug npm run dev
|
||||
```
|
||||
|
||||
### Available Levels
|
||||
|
||||
- **`error`**: Critical errors only
|
||||
- **`warn`**: Warnings and errors (default for production web)
|
||||
- **`info`**: Info, warnings, and errors (default for development/capacitor)
|
||||
- **`debug`**: All log levels including verbose debugging
|
||||
|
||||
See [Logging Configuration Guide](doc/logging-configuration.md) for complete details.
|
||||
|
||||
### Quick Usage
|
||||
```bash
|
||||
@@ -126,7 +130,6 @@ const apiUrl = `${APP_SERVER}/api/claim/123`;
|
||||
|
||||
### Documentation
|
||||
|
||||
- [Domain Configuration System](docs/domain-configuration.md) - Complete guide
|
||||
- [Constants and Configuration](src/constants/app.ts) - Core constants
|
||||
|
||||
## Tests
|
||||
|
||||
117
doc/logging-configuration.md
Normal file
117
doc/logging-configuration.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# Logging Configuration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
TimeSafari now supports configurable logging levels via the `VITE_LOG_LEVEL` environment variable. This allows developers to control the verbosity of console output without modifying code.
|
||||
|
||||
## Available Log Levels
|
||||
|
||||
| Level | Value | Description | Console Output |
|
||||
|-------|-------|-------------|----------------|
|
||||
| `error` | 0 | Errors only | Critical errors only |
|
||||
| `warn` | 1 | Warnings and errors | Warnings and errors |
|
||||
| `info` | 2 | Info, warnings, and errors | General information, warnings, and errors |
|
||||
| `debug` | 3 | All log levels | Verbose debugging information |
|
||||
|
||||
## Environment Variable
|
||||
|
||||
Set the `VITE_LOG_LEVEL` environment variable to control logging:
|
||||
|
||||
```bash
|
||||
# Show only errors
|
||||
VITE_LOG_LEVEL=error
|
||||
|
||||
# Show warnings and errors (default for production web)
|
||||
VITE_LOG_LEVEL=warn
|
||||
|
||||
# Show info, warnings, and errors (default for development/capacitor)
|
||||
VITE_LOG_LEVEL=info
|
||||
|
||||
# Show all log levels including debug
|
||||
VITE_LOG_LEVEL=debug
|
||||
```
|
||||
|
||||
## Default Behavior by Platform
|
||||
|
||||
The logger automatically selects appropriate default log levels based on your platform and environment:
|
||||
|
||||
- **Production Web**: `warn` (warnings and errors only)
|
||||
- **Electron**: `error` (errors only - very quiet)
|
||||
- **Development/Capacitor**: `info` (info and above)
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Setting Log Level in Development
|
||||
|
||||
```bash
|
||||
# In your terminal before running the app
|
||||
export VITE_LOG_LEVEL=debug
|
||||
npm run dev
|
||||
|
||||
# Or inline
|
||||
VITE_LOG_LEVEL=debug npm run dev
|
||||
```
|
||||
|
||||
### Setting Log Level in Production
|
||||
|
||||
```bash
|
||||
# For verbose production logging
|
||||
VITE_LOG_LEVEL=info npm run build:web
|
||||
|
||||
# For minimal production logging
|
||||
VITE_LOG_LEVEL=warn npm run build:web
|
||||
```
|
||||
|
||||
### Programmatic Access
|
||||
|
||||
The logger provides methods to check current configuration:
|
||||
|
||||
```typescript
|
||||
import { logger } from '@/utils/logger';
|
||||
|
||||
// Get current log level
|
||||
const currentLevel = logger.getCurrentLevel(); // 'info'
|
||||
|
||||
// Check if a level is enabled
|
||||
const debugEnabled = logger.isLevelEnabled('debug'); // false if level < debug
|
||||
|
||||
// Get available levels
|
||||
const levels = logger.getAvailableLevels(); // ['error', 'warn', 'info', 'debug']
|
||||
```
|
||||
|
||||
## Database Logging
|
||||
|
||||
Database logging continues to work regardless of console log level settings. All log messages are still stored in the database for debugging and audit purposes.
|
||||
|
||||
## Migration Notes
|
||||
|
||||
- **Existing code**: No changes required - logging behavior remains the same
|
||||
- **New feature**: Use `VITE_LOG_LEVEL` to override default behavior
|
||||
- **Backward compatible**: All existing logging calls work as before
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Development**: Use `VITE_LOG_LEVEL=debug` for maximum visibility
|
||||
2. **Testing**: Use `VITE_LOG_LEVEL=info` for balanced output
|
||||
3. **Production**: Use `VITE_LOG_LEVEL=warn` for minimal noise
|
||||
4. **Debugging**: Temporarily set `VITE_LOG_LEVEL=debug` to troubleshoot issues
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Logs Appearing
|
||||
|
||||
Check your `VITE_LOG_LEVEL` setting:
|
||||
```bash
|
||||
echo $VITE_LOG_LEVEL
|
||||
```
|
||||
|
||||
### Too Many Logs
|
||||
|
||||
Reduce verbosity by setting a lower log level:
|
||||
```bash
|
||||
VITE_LOG_LEVEL=warn
|
||||
```
|
||||
|
||||
### Platform-Specific Issues
|
||||
|
||||
Remember that Electron is very quiet by default. Use `VITE_LOG_LEVEL=info` to see more output in Electron builds.
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "timesafari",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7-beta",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "timesafari",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7-beta",
|
||||
"dependencies": {
|
||||
"@capacitor-community/electron": "^5.0.1",
|
||||
"@capacitor-community/sqlite": "6.0.2",
|
||||
@@ -96,7 +96,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/assets": "^3.0.5",
|
||||
"@playwright/test": "^1.45.2",
|
||||
"@playwright/test": "^1.54.2",
|
||||
"@types/dom-webcodecs": "^0.1.7",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "timesafari",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7-beta",
|
||||
"description": "Time Safari Application",
|
||||
"author": {
|
||||
"name": "Time Safari Team"
|
||||
@@ -53,6 +53,8 @@
|
||||
"build:web:docker:test": "./scripts/build-web.sh --docker:test",
|
||||
"build:web:docker:prod": "./scripts/build-web.sh --docker:prod",
|
||||
"build:web:serve": "./scripts/build-web.sh --serve",
|
||||
"build:web:serve:test": "./scripts/build-web.sh --serve --test",
|
||||
"build:web:serve:prod": "./scripts/build-web.sh --serve --prod",
|
||||
"docker:up": "docker-compose up",
|
||||
"docker:up:test": "npm run build:web:build -- --mode test && docker-compose up test",
|
||||
"docker:up:prod": "npm run build:web:build -- --mode production && docker-compose up production",
|
||||
@@ -204,7 +206,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/assets": "^3.0.5",
|
||||
"@playwright/test": "^1.45.2",
|
||||
"@playwright/test": "^1.54.2",
|
||||
"@types/dom-webcodecs": "^0.1.7",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
|
||||
@@ -184,7 +184,7 @@ log_info "Build mode: $BUILD_MODE"
|
||||
log_info "Build type: $BUILD_TYPE"
|
||||
|
||||
# Setup environment for Capacitor build
|
||||
setup_build_env "capacitor"
|
||||
setup_build_env "capacitor" "$BUILD_MODE"
|
||||
|
||||
# Override API servers for Android development
|
||||
if [ "$BUILD_MODE" = "development" ]; then
|
||||
|
||||
@@ -339,7 +339,7 @@ main_electron_build() {
|
||||
fi
|
||||
|
||||
# Setup environment
|
||||
setup_build_env "electron"
|
||||
setup_build_env "electron" "$BUILD_MODE"
|
||||
setup_app_directories
|
||||
load_env_file ".env"
|
||||
|
||||
|
||||
@@ -311,7 +311,7 @@ log_info "Build mode: $BUILD_MODE"
|
||||
log_info "Build type: $BUILD_TYPE"
|
||||
|
||||
# Setup environment for Capacitor build
|
||||
setup_build_env "capacitor"
|
||||
setup_build_env "capacitor" "$BUILD_MODE"
|
||||
|
||||
# Override API servers for iOS development when custom IP is specified
|
||||
if [ "$BUILD_MODE" = "development" ] && [ -n "$CUSTOM_API_IP" ]; then
|
||||
|
||||
@@ -300,18 +300,20 @@ serve_build() {
|
||||
exit 5
|
||||
fi
|
||||
|
||||
# Use a simple HTTP server to serve the build
|
||||
if command -v python3 &> /dev/null; then
|
||||
# Use a server that supports SPA routing (serves index.html for all routes)
|
||||
if command -v npx &> /dev/null; then
|
||||
log_info "Starting npx serve with SPA support on port 8080..."
|
||||
npx serve -s dist -l 8080
|
||||
elif command -v python3 &> /dev/null; then
|
||||
log_warn "Python HTTP server doesn't support SPA routing. Routes like /discover, /account will return 404."
|
||||
log_info "Starting Python HTTP server on port 8080..."
|
||||
cd dist && python3 -m http.server 8080
|
||||
elif command -v python &> /dev/null; then
|
||||
log_warn "Python HTTP server doesn't support SPA routing. Routes like /discover, /account will return 404."
|
||||
log_info "Starting Python HTTP server on port 8080..."
|
||||
cd dist && python -m SimpleHTTPServer 8080
|
||||
elif command -v npx &> /dev/null; then
|
||||
log_info "Starting npx serve on port 8080..."
|
||||
npx serve -s dist -l 8080
|
||||
else
|
||||
log_error "No suitable HTTP server found. Install Python or npx serve."
|
||||
log_error "No suitable HTTP server found. Install npx serve or Python."
|
||||
exit 5
|
||||
fi
|
||||
}
|
||||
@@ -330,7 +332,7 @@ log_info "Serve build: $SERVE_BUILD"
|
||||
validate_web_environment
|
||||
|
||||
# Setup environment for web build
|
||||
setup_build_env "web"
|
||||
setup_build_env "web" "$BUILD_MODE"
|
||||
|
||||
# Setup application directories
|
||||
setup_app_directories
|
||||
|
||||
@@ -174,9 +174,9 @@ validate_env_vars() {
|
||||
# Function to set environment variables for different build types
|
||||
setup_build_env() {
|
||||
local build_type="$1"
|
||||
local production="${2:-false}"
|
||||
local build_mode="${2:-development}"
|
||||
|
||||
log_info "Setting up environment for $build_type build"
|
||||
log_info "Setting up environment for $build_type build (mode: $build_mode)"
|
||||
|
||||
# Get git hash for versioning
|
||||
local git_hash=$(get_git_hash)
|
||||
@@ -204,19 +204,19 @@ setup_build_env() {
|
||||
esac
|
||||
|
||||
# Set API server environment variables based on build mode
|
||||
if [ "$BUILD_MODE" = "development" ]; then
|
||||
if [ "$build_mode" = "development" ]; then
|
||||
# For Capacitor development, use localhost by default
|
||||
# Android builds will override this in build-android.sh
|
||||
export VITE_DEFAULT_ENDORSER_API_SERVER="http://localhost:3000"
|
||||
export VITE_DEFAULT_PARTNER_API_SERVER="http://localhost:3000"
|
||||
log_debug "Development mode: Using localhost for Endorser and Partner APIs"
|
||||
export VITE_DEFAULT_IMAGE_API_SERVER="https://image-api.timesafari.app"
|
||||
elif [ "$BUILD_MODE" = "test" ]; then
|
||||
elif [ "$build_mode" = "test" ]; then
|
||||
export VITE_DEFAULT_ENDORSER_API_SERVER="https://test-api.endorser.ch"
|
||||
export VITE_DEFAULT_PARTNER_API_SERVER="https://test-partner-api.endorser.ch"
|
||||
log_debug "Test mode: Using test Endorser and Partner APIs"
|
||||
export VITE_DEFAULT_IMAGE_API_SERVER="https://image-api.timesafari.app"
|
||||
elif [ "$BUILD_MODE" = "production" ]; then
|
||||
elif [ "$build_mode" = "production" ]; then
|
||||
export VITE_DEFAULT_ENDORSER_API_SERVER="https://api.endorser.ch"
|
||||
export VITE_DEFAULT_PARTNER_API_SERVER="https://partner-api.endorser.ch"
|
||||
log_debug "Production mode: Using production API servers"
|
||||
|
||||
@@ -17,34 +17,40 @@ parse_args "$@"
|
||||
print_header "Environment Variable Test"
|
||||
log_info "Testing environment variable handling at $(date)"
|
||||
|
||||
# Test 1: Capacitor environment
|
||||
log_info "Test 1: Setting up Capacitor environment..."
|
||||
setup_build_env "capacitor"
|
||||
# Test 1: Capacitor environment (development)
|
||||
log_info "Test 1: Setting up Capacitor environment (development mode)..."
|
||||
setup_build_env "capacitor" "development"
|
||||
print_env_vars "VITE_"
|
||||
echo ""
|
||||
|
||||
# Test 2: Web environment
|
||||
log_info "Test 2: Setting up Web environment..."
|
||||
setup_build_env "web"
|
||||
# Test 2: Web environment (development)
|
||||
log_info "Test 2: Setting up Web environment (development mode)..."
|
||||
setup_build_env "web" "development"
|
||||
print_env_vars "VITE_"
|
||||
echo ""
|
||||
|
||||
# Test 3: Production Capacitor environment
|
||||
log_info "Test 3: Setting up Production Capacitor environment..."
|
||||
setup_build_env "capacitor" "true"
|
||||
# Test 3: Capacitor test environment
|
||||
log_info "Test 3: Setting up Capacitor environment (test mode)..."
|
||||
setup_build_env "capacitor" "test"
|
||||
print_env_vars "VITE_"
|
||||
echo ""
|
||||
|
||||
# Test 4: Application directories
|
||||
log_info "Test 4: Setting up application directories..."
|
||||
# Test 4: Capacitor production environment
|
||||
log_info "Test 4: Setting up Capacitor environment (production mode)..."
|
||||
setup_build_env "capacitor" "production"
|
||||
print_env_vars "VITE_"
|
||||
echo ""
|
||||
|
||||
# Test 5: Application directories
|
||||
log_info "Test 5: Setting up application directories..."
|
||||
setup_app_directories
|
||||
|
||||
# Test 5: Load .env file (if it exists)
|
||||
log_info "Test 5: Loading .env file..."
|
||||
# Test 6: Load .env file (if it exists)
|
||||
log_info "Test 6: Loading .env file..."
|
||||
load_env_file ".env"
|
||||
|
||||
# Test 6: Git hash
|
||||
log_info "Test 6: Getting git hash..."
|
||||
# Test 7: Git hash
|
||||
log_info "Test 7: Getting git hash..."
|
||||
GIT_HASH=$(get_git_hash)
|
||||
log_info "Git hash: $GIT_HASH"
|
||||
|
||||
|
||||
65
src/App.vue
65
src/App.vue
@@ -27,9 +27,13 @@
|
||||
v-if="notification.type === 'toast'"
|
||||
class="w-full max-w-sm mx-auto mb-3 overflow-hidden bg-slate-900/90 text-white rounded-lg shadow-md"
|
||||
>
|
||||
<div class="w-full px-4 py-3">
|
||||
<span class="font-semibold">{{ notification.title }}</span>
|
||||
<p class="text-sm">{{ notification.text }}</p>
|
||||
<div class="w-full px-4 py-3 overflow-hidden">
|
||||
<h4 class="font-semibold text-ellipsis overflow-hidden">
|
||||
{{ notification.title }}
|
||||
</h4>
|
||||
<p class="text-sm text-ellipsis overflow-hidden">
|
||||
{{ notification.text }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,9 +50,15 @@
|
||||
></font-awesome>
|
||||
</div>
|
||||
|
||||
<div class="relative w-full pl-4 pr-8 py-2 text-slate-900">
|
||||
<span class="font-semibold">{{ notification.title }}</span>
|
||||
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
||||
<div
|
||||
class="relative w-full pl-4 pr-8 py-2 text-slate-900 overflow-hidden"
|
||||
>
|
||||
<h4 class="font-semibold text-ellipsis overflow-hidden">
|
||||
{{ notification.title }}
|
||||
</h4>
|
||||
<p class="text-sm text-ellipsis overflow-hidden">
|
||||
{{ notification.text }}
|
||||
</p>
|
||||
|
||||
<button
|
||||
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-slate-200 text-slate-600"
|
||||
@@ -72,9 +82,15 @@
|
||||
></font-awesome>
|
||||
</div>
|
||||
|
||||
<div class="relative w-full pl-4 pr-8 py-2 text-emerald-900">
|
||||
<span class="font-semibold">{{ notification.title }}</span>
|
||||
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
||||
<div
|
||||
class="relative w-full pl-4 pr-8 py-2 text-emerald-900 overflow-hidden"
|
||||
>
|
||||
<h4 class="font-semibold text-ellipsis overflow-hidden">
|
||||
{{ notification.title }}
|
||||
</h4>
|
||||
<p class="text-sm text-ellipsis overflow-hidden">
|
||||
{{ notification.text }}
|
||||
</p>
|
||||
|
||||
<button
|
||||
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-emerald-200 text-emerald-600"
|
||||
@@ -98,9 +114,15 @@
|
||||
></font-awesome>
|
||||
</div>
|
||||
|
||||
<div class="relative w-full pl-4 pr-8 py-2 text-amber-900">
|
||||
<span class="font-semibold">{{ notification.title }}</span>
|
||||
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
||||
<div
|
||||
class="relative w-full pl-4 pr-8 py-2 text-amber-900 overflow-hidden"
|
||||
>
|
||||
<h4 class="font-semibold text-ellipsis overflow-hidden">
|
||||
{{ notification.title }}
|
||||
</h4>
|
||||
<p class="text-sm text-ellipsis overflow-hidden">
|
||||
{{ notification.text }}
|
||||
</p>
|
||||
|
||||
<button
|
||||
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-amber-200 text-amber-600"
|
||||
@@ -124,9 +146,15 @@
|
||||
></font-awesome>
|
||||
</div>
|
||||
|
||||
<div class="relative w-full pl-4 pr-8 py-2 text-rose-900">
|
||||
<span class="font-semibold">{{ notification.title }}</span>
|
||||
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
||||
<div
|
||||
class="relative w-full pl-4 pr-8 py-2 text-rose-900 overflow-hidden"
|
||||
>
|
||||
<h4 class="font-semibold text-ellipsis overflow-hidden">
|
||||
{{ notification.title }}
|
||||
</h4>
|
||||
<p class="text-sm text-ellipsis overflow-hidden">
|
||||
{{ notification.text }}
|
||||
</p>
|
||||
|
||||
<button
|
||||
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-rose-200 text-rose-600"
|
||||
@@ -349,13 +377,6 @@ export default class App extends Vue {
|
||||
|
||||
stopAsking = false;
|
||||
|
||||
truncateLongWords(sentence: string) {
|
||||
return sentence
|
||||
.split(" ")
|
||||
.map((word) => (word.length > 30 ? word.slice(0, 30) + "..." : word))
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
async turnOffNotifications(
|
||||
notification: NotificationIface,
|
||||
): Promise<boolean> {
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
class="w-full h-auto max-w-lg max-h-96 object-contain mx-auto drop-shadow-md"
|
||||
:src="record.image"
|
||||
alt="Activity image"
|
||||
@load="handleImageLoad(record.image)"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
@@ -253,7 +252,7 @@ import { GiveRecordWithContactInfo } from "@/interfaces/give";
|
||||
import EntityIcon from "./EntityIcon.vue";
|
||||
import { isHiddenDid } from "../libs/endorserServer";
|
||||
import ProjectIcon from "./ProjectIcon.vue";
|
||||
import { createNotifyHelpers } from "@/utils/notify";
|
||||
import { createNotifyHelpers, NotifyFunction } from "@/utils/notify";
|
||||
import {
|
||||
NOTIFY_PERSON_HIDDEN,
|
||||
NOTIFY_UNKNOWN_PERSON,
|
||||
@@ -272,16 +271,9 @@ export default class ActivityListItem extends Vue {
|
||||
@Prop() isRegistered!: boolean;
|
||||
@Prop() activeDid!: string;
|
||||
|
||||
/**
|
||||
* Function prop for handling image caching
|
||||
* Called when an image loads successfully, allowing parent to control caching behavior
|
||||
*/
|
||||
@Prop({ type: Function, default: () => {} })
|
||||
onImageCache!: (imageUrl: string) => void | Promise<void>;
|
||||
|
||||
isHiddenDid = isHiddenDid;
|
||||
notify!: ReturnType<typeof createNotifyHelpers>;
|
||||
$notify!: (notification: any, timeout?: number) => void;
|
||||
$notify!: NotifyFunction;
|
||||
|
||||
created() {
|
||||
this.notify = createNotifyHelpers(this.$notify);
|
||||
@@ -295,14 +287,6 @@ export default class ActivityListItem extends Vue {
|
||||
this.notify.warning(NOTIFY_UNKNOWN_PERSON.message, TIMEOUTS.STANDARD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle image load event - call function prop for caching
|
||||
* Allows parent to control caching behavior and validation
|
||||
*/
|
||||
handleImageLoad(imageUrl: string): void {
|
||||
this.onImageCache(imageUrl);
|
||||
}
|
||||
|
||||
get fetchAmount(): string {
|
||||
const claim =
|
||||
(this.record.fullClaim as any)?.claim || this.record.fullClaim;
|
||||
|
||||
@@ -167,7 +167,7 @@ export default class ContactInputForm extends Vue {
|
||||
*/
|
||||
@Emit("qr-scan")
|
||||
private handleQRScan(): void {
|
||||
console.log("[ContactInputForm] QR scan button clicked");
|
||||
// QR scan button clicked - event emitted for parent handling
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -80,7 +80,7 @@ import EntitySelectionStep from "../components/EntitySelectionStep.vue";
|
||||
import GiftDetailsStep from "../components/GiftDetailsStep.vue";
|
||||
import { PlanData } from "../interfaces/records";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||
import { createNotifyHelpers, TIMEOUTS, NotifyFunction } from "@/utils/notify";
|
||||
import {
|
||||
NOTIFY_GIFT_ERROR_NEGATIVE_AMOUNT,
|
||||
NOTIFY_GIFT_ERROR_NO_DESCRIPTION,
|
||||
@@ -98,7 +98,7 @@ import {
|
||||
mixins: [PlatformServiceMixin],
|
||||
})
|
||||
export default class GiftedDialog extends Vue {
|
||||
$notify!: (notification: any, timeout?: number) => void;
|
||||
$notify!: NotifyFunction;
|
||||
notify!: ReturnType<typeof createNotifyHelpers>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -282,7 +282,7 @@ import {
|
||||
NOTIFY_IMAGE_DIALOG_UNSUPPORTED_FORMAT,
|
||||
createImageDialogCameraErrorMessage,
|
||||
} from "../constants/notifications";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "../utils/notify";
|
||||
import { createNotifyHelpers, TIMEOUTS, NotifyFunction } from "../utils/notify";
|
||||
|
||||
const inputImageFileNameRef = ref<Blob>();
|
||||
|
||||
@@ -291,7 +291,7 @@ const inputImageFileNameRef = ref<Blob>();
|
||||
mixins: [PlatformServiceMixin],
|
||||
})
|
||||
export default class ImageMethodDialog extends Vue {
|
||||
$notify!: (notification: any, timeout?: number) => void;
|
||||
$notify!: NotifyFunction;
|
||||
$router!: Router;
|
||||
notify = createNotifyHelpers(this.$notify);
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ import { logger } from "../utils/logger";
|
||||
@Component({ emits: ["update:isOpen"] })
|
||||
export default class ImageViewer extends Vue {
|
||||
@Prop() imageUrl!: string;
|
||||
@Prop() imageData!: Blob | null;
|
||||
@Prop() isOpen!: boolean;
|
||||
|
||||
userAgent = new UAParser();
|
||||
|
||||
@@ -1348,12 +1348,12 @@ export async function createEndorserJwtVcFromClaim(
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JWT for a RegisterAction claim.
|
||||
* Create a JWT for a RegisterAction claim, used for registrations & invites.
|
||||
*
|
||||
* @param activeDid - The DID of the user creating the invite
|
||||
* @param contact - The contact to register, with a 'did' field (all optional for invites)
|
||||
* @param identifier - The identifier for the invite, usually random
|
||||
* @param expiresIn - The number of seconds until the invite expires
|
||||
* @param contact - Optional - The contact to register, with a 'did' field (all optional for invites)
|
||||
* @param identifier - Optional - The identifier for the invite, usually random
|
||||
* @param expiresIn - Optional - The number of seconds until the invite expires
|
||||
* @returns The JWT for the RegisterAction claim
|
||||
*/
|
||||
export async function createInviteJwt(
|
||||
@@ -1367,7 +1367,7 @@ export async function createInviteJwt(
|
||||
"@type": "RegisterAction",
|
||||
agent: { identifier: activeDid },
|
||||
object: SERVICE_ID,
|
||||
identifier: identifier,
|
||||
identifier: identifier, // not sent if undefined
|
||||
};
|
||||
if (contact?.did) {
|
||||
vcClaim.participant = { identifier: contact.did };
|
||||
|
||||
@@ -82,6 +82,15 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: "database-migration",
|
||||
component: () => import("../views/DatabaseMigration.vue"),
|
||||
},
|
||||
{
|
||||
path: "/deep-link-error",
|
||||
name: "deep-link-error",
|
||||
component: () => import("../views/DeepLinkErrorView.vue"),
|
||||
meta: {
|
||||
title: "Invalid Deep Link",
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/did/:did?",
|
||||
name: "did",
|
||||
@@ -276,15 +285,6 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: "user-profile",
|
||||
component: () => import("../views/UserProfileView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/deep-link-error",
|
||||
name: "deep-link-error",
|
||||
component: () => import("../views/DeepLinkErrorView.vue"),
|
||||
meta: {
|
||||
title: "Invalid Deep Link",
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const isElectron = window.location.protocol === "file:";
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// **WORKER-COMPATIBLE CRYPTO POLYFILL**: Must be at the very top
|
||||
// This prevents "crypto is not defined" errors when running in worker context
|
||||
if (typeof window === "undefined" && typeof crypto === "undefined") {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(globalThis as any).crypto = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getRandomValues: (array: any) => {
|
||||
// Simple fallback for worker context
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
DeepLinkRoute,
|
||||
} from "../interfaces/deepLinks";
|
||||
import type { DeepLinkError } from "../interfaces/deepLinks";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
// Helper function to extract the first key from a Zod object schema
|
||||
function getFirstKeyFromZodObject(
|
||||
@@ -178,7 +179,7 @@ export class DeepLinkHandler {
|
||||
const validRoute = routeSchema.parse(path) as DeepLinkRoute;
|
||||
routeName = ROUTE_MAP[validRoute].name;
|
||||
} catch (error) {
|
||||
console.error(`[DeepLink] Invalid route path: ${path}`);
|
||||
logger.error(`[DeepLink] Invalid route path: ${path}`);
|
||||
|
||||
// Redirect to error page with information about the invalid link
|
||||
await this.router.replace({
|
||||
@@ -204,9 +205,8 @@ export class DeepLinkHandler {
|
||||
validatedParams = await schema.parseAsync(params);
|
||||
} catch (error) {
|
||||
// For parameter validation errors, provide specific error feedback
|
||||
logConsoleAndDb(
|
||||
logger.error(
|
||||
`[DeepLink] Invalid parameters for route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with params: ${JSON.stringify(params)} ... and query: ${JSON.stringify(query)}`,
|
||||
true,
|
||||
);
|
||||
await this.router.replace({
|
||||
name: "deep-link-error",
|
||||
@@ -229,9 +229,8 @@ export class DeepLinkHandler {
|
||||
params: validatedParams,
|
||||
});
|
||||
} catch (error) {
|
||||
logConsoleAndDb(
|
||||
logger.error(
|
||||
`[DeepLink] Error routing to route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with validated params: ${JSON.stringify(validatedParams)}`,
|
||||
true,
|
||||
);
|
||||
// For parameter validation errors, provide specific error feedback
|
||||
await this.router.replace({
|
||||
@@ -263,9 +262,8 @@ export class DeepLinkHandler {
|
||||
await this.validateAndRoute(path, sanitizedParams, query);
|
||||
} catch (error) {
|
||||
const deepLinkError = error as DeepLinkError;
|
||||
logConsoleAndDb(
|
||||
logger.error(
|
||||
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.details}`,
|
||||
true,
|
||||
);
|
||||
|
||||
throw {
|
||||
|
||||
@@ -693,7 +693,8 @@ export class WebPlatformService implements PlatformService {
|
||||
const setClause = keys.map((key) => `${key} = ?`).join(", ");
|
||||
const sql = `UPDATE settings SET ${setClause} WHERE accountDid = ?`;
|
||||
const params = [...keys.map((key) => settings[key]), did];
|
||||
console.log(
|
||||
// Log update operation for debugging
|
||||
logger.debug(
|
||||
"[WebPlatformService] updateDidSpecificSettings",
|
||||
sql,
|
||||
JSON.stringify(params, null, 2),
|
||||
|
||||
@@ -92,6 +92,7 @@ import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
})
|
||||
export default class PlatformServiceMixinTest extends Vue {
|
||||
result: string = "";
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
userZeroTestResult: any = null;
|
||||
activeTest: string = ""; // Track which test is currently active
|
||||
|
||||
@@ -267,6 +268,7 @@ This tests the complete save → retrieve cycle with actual database interaction
|
||||
this.result = `User #0 settings test completed. isRegistered: ${accountSettings.isRegistered}`;
|
||||
} catch (error) {
|
||||
this.result = `Error testing User #0 settings: ${error}`;
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error testing User #0 settings:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,34 @@
|
||||
import axios from "axios";
|
||||
import * as didJwt from "did-jwt";
|
||||
import { SERVICE_ID } from "../libs/endorserServer";
|
||||
import { deriveAddress, newIdentifier } from "../libs/crypto";
|
||||
import {
|
||||
DEFAULT_ROOT_DERIVATION_PATH,
|
||||
deriveAddress,
|
||||
newIdentifier,
|
||||
} from "../libs/crypto";
|
||||
import { logger } from "../utils/logger";
|
||||
import { AppString } from "../constants/app";
|
||||
import { saveNewIdentity } from "@/libs/util";
|
||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
|
||||
const TEST_USER_0_MNEMONIC =
|
||||
"rigid shrug mobile smart veteran half all pond toilet brave review universe ship congress found yard skate elite apology jar uniform subway slender luggage";
|
||||
|
||||
export async function testBecomeUser0() {
|
||||
const [addr, privateHex, publicHex, deriPath] =
|
||||
deriveAddress(TEST_USER_0_MNEMONIC);
|
||||
|
||||
const identity0 = newIdentifier(addr, publicHex, privateHex, deriPath);
|
||||
await saveNewIdentity(
|
||||
identity0,
|
||||
TEST_USER_0_MNEMONIC,
|
||||
DEFAULT_ROOT_DERIVATION_PATH,
|
||||
);
|
||||
const platformService = await PlatformServiceFactory.getInstance();
|
||||
await platformService.updateDidSpecificSettings(identity0.did, {
|
||||
isRegistered: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get User #0 to sign & submit a RegisterAction for the user's activeDid.
|
||||
@@ -15,10 +40,8 @@ import { AppString } from "../constants/app";
|
||||
* @throws Error if registration fails or database access fails
|
||||
*/
|
||||
export async function testServerRegisterUser() {
|
||||
const testUser0Mnem =
|
||||
"seminar accuse mystery assist delay law thing deal image undo guard initial shallow wrestle list fragile borrow velvet tomorrow awake explain test offer control";
|
||||
|
||||
const [addr, privateHex, publicHex, deriPath] = deriveAddress(testUser0Mnem);
|
||||
const [addr, privateHex, publicHex, deriPath] =
|
||||
deriveAddress(TEST_USER_0_MNEMONIC);
|
||||
|
||||
const identity0 = newIdentifier(addr, publicHex, privateHex, deriPath);
|
||||
|
||||
@@ -32,9 +55,9 @@ export async function testServerRegisterUser() {
|
||||
const vcClaim = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "RegisterAction",
|
||||
agent: { did: identity0.did },
|
||||
agent: { identifier: identity0.did },
|
||||
object: SERVICE_ID,
|
||||
participant: { did: settings.activeDid },
|
||||
participant: { identifier: settings.activeDid },
|
||||
};
|
||||
|
||||
// Make a payload for the claim
|
||||
@@ -71,4 +94,5 @@ export async function testServerRegisterUser() {
|
||||
|
||||
const resp = await axios.post(url, payload, { headers });
|
||||
logger.log("User registration result:", resp);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@@ -133,6 +133,7 @@ export const PlatformServiceMixin = {
|
||||
* Used for change detection and component updates
|
||||
*/
|
||||
currentActiveDid(): string | null {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (this as any)._currentActiveDid;
|
||||
},
|
||||
|
||||
@@ -200,7 +201,9 @@ export const PlatformServiceMixin = {
|
||||
* This method should be called when the user switches identities
|
||||
*/
|
||||
async $updateActiveDid(newDid: string | null): Promise<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const oldDid = (this as any)._currentActiveDid;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(this as any)._currentActiveDid = newDid;
|
||||
|
||||
if (newDid !== oldDid) {
|
||||
@@ -291,6 +294,7 @@ export const PlatformServiceMixin = {
|
||||
|
||||
// Convert searchBoxes array to JSON string if present
|
||||
if (settings.searchBoxes !== undefined) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(converted as any).searchBoxes = Array.isArray(settings.searchBoxes)
|
||||
? JSON.stringify(settings.searchBoxes)
|
||||
: String(settings.searchBoxes);
|
||||
@@ -692,6 +696,7 @@ export const PlatformServiceMixin = {
|
||||
typeof method.value === "string";
|
||||
|
||||
if (!isValid && method !== undefined) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
"[ContactNormalization] Invalid contact method:",
|
||||
method,
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
* Enhanced logger with self-contained database logging
|
||||
*
|
||||
* Provides comprehensive logging with console and database output.
|
||||
* Supports configurable log levels via VITE_LOG_LEVEL environment variable.
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @version 2.0.0
|
||||
* @version 2.1.0
|
||||
* @since 2025-01-25
|
||||
*/
|
||||
|
||||
@@ -46,6 +47,42 @@ export function safeStringify(obj: unknown) {
|
||||
const isElectron = process.env.VITE_PLATFORM === "electron";
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
|
||||
// Log level configuration via environment variable
|
||||
const LOG_LEVELS = {
|
||||
error: 0,
|
||||
warn: 1,
|
||||
info: 2,
|
||||
debug: 3,
|
||||
} as const;
|
||||
|
||||
type LogLevel = keyof typeof LOG_LEVELS;
|
||||
|
||||
// Parse VITE_LOG_LEVEL environment variable
|
||||
const getLogLevel = (): LogLevel => {
|
||||
const envLogLevel = process.env.VITE_LOG_LEVEL?.toLowerCase();
|
||||
|
||||
if (envLogLevel && envLogLevel in LOG_LEVELS) {
|
||||
return envLogLevel as LogLevel;
|
||||
}
|
||||
|
||||
// Default log levels based on environment
|
||||
if (isProduction && !isElectron) {
|
||||
return "warn"; // Production web: warnings and errors only
|
||||
} else if (isElectron) {
|
||||
return "error"; // Electron: errors only
|
||||
} else {
|
||||
return "info"; // Development/Capacitor: info and above
|
||||
}
|
||||
};
|
||||
|
||||
const currentLogLevel = getLogLevel();
|
||||
const currentLevelValue = LOG_LEVELS[currentLogLevel];
|
||||
|
||||
// Helper function to check if a log level should be displayed
|
||||
const shouldLog = (level: LogLevel): boolean => {
|
||||
return LOG_LEVELS[level] <= currentLevelValue;
|
||||
};
|
||||
|
||||
// Track initialization state to prevent circular dependencies
|
||||
let isInitializing = true;
|
||||
|
||||
@@ -105,11 +142,11 @@ async function logToDatabase(
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced logger with self-contained database methods
|
||||
// Enhanced logger with self-contained database methods and log level control
|
||||
export const logger = {
|
||||
debug: (message: string, ...args: unknown[]) => {
|
||||
// Debug logs are very verbose - only show in development mode for web
|
||||
if (!isProduction && !isElectron) {
|
||||
// Debug logs only show if VITE_LOG_LEVEL allows it
|
||||
if (shouldLog("debug")) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(message, ...args);
|
||||
}
|
||||
@@ -117,11 +154,8 @@ export const logger = {
|
||||
},
|
||||
|
||||
log: (message: string, ...args: unknown[]) => {
|
||||
// Regular logs - show in development or for capacitor, but quiet for Electron
|
||||
if (
|
||||
(!isProduction && !isElectron) ||
|
||||
process.env.VITE_PLATFORM === "capacitor"
|
||||
) {
|
||||
// Regular logs - show if VITE_LOG_LEVEL allows info level
|
||||
if (shouldLog("info")) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(message, ...args);
|
||||
}
|
||||
@@ -132,11 +166,7 @@ export const logger = {
|
||||
},
|
||||
|
||||
info: (message: string, ...args: unknown[]) => {
|
||||
if (
|
||||
process.env.NODE_ENV !== "production" ||
|
||||
process.env.VITE_PLATFORM === "capacitor" ||
|
||||
process.env.VITE_PLATFORM === "electron"
|
||||
) {
|
||||
if (shouldLog("info")) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(message, ...args);
|
||||
}
|
||||
@@ -147,8 +177,7 @@ export const logger = {
|
||||
},
|
||||
|
||||
warn: (message: string, ...args: unknown[]) => {
|
||||
// Always show warnings, but for Electron, suppress routine database warnings
|
||||
if (!isElectron || !message.includes("[CapacitorPlatformService]")) {
|
||||
if (shouldLog("warn")) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(message, ...args);
|
||||
}
|
||||
@@ -159,9 +188,10 @@ export const logger = {
|
||||
},
|
||||
|
||||
error: (message: string, ...args: unknown[]) => {
|
||||
// Errors will always be logged to console
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message, ...args);
|
||||
if (shouldLog("error")) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message, ...args);
|
||||
}
|
||||
|
||||
// Database logging
|
||||
const messageString = safeStringify(message);
|
||||
@@ -175,11 +205,11 @@ export const logger = {
|
||||
},
|
||||
|
||||
toConsoleAndDb: async (message: string, isError = false): Promise<void> => {
|
||||
// Console output
|
||||
if (isError) {
|
||||
// Console output based on log level
|
||||
if (isError && shouldLog("error")) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message);
|
||||
} else {
|
||||
} else if (!isError && shouldLog("info")) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(message);
|
||||
}
|
||||
@@ -194,6 +224,12 @@ export const logger = {
|
||||
error: (message: string) =>
|
||||
logToDatabase(`[${componentName}] ${message}`, "error"),
|
||||
}),
|
||||
|
||||
// Log level information methods
|
||||
getCurrentLevel: (): LogLevel => currentLogLevel,
|
||||
getCurrentLevelValue: (): number => currentLevelValue,
|
||||
isLevelEnabled: (level: LogLevel): boolean => shouldLog(level),
|
||||
getAvailableLevels: (): LogLevel[] => Object.keys(LOG_LEVELS) as LogLevel[],
|
||||
};
|
||||
|
||||
// Function to manually mark initialization as complete
|
||||
|
||||
@@ -61,7 +61,8 @@
|
||||
/>
|
||||
|
||||
<!-- Notifications -->
|
||||
<!-- Currently disabled because it doesn't work, even on Chrome. If restored, make sure it works or doesn't show on mobile/electron. -->
|
||||
<!-- Currently disabled because it doesn't work, even on Chrome.
|
||||
If restored, make sure it works or doesn't show on mobile/electron. -->
|
||||
<section
|
||||
v-if="false"
|
||||
id="sectionNotifications"
|
||||
|
||||
@@ -724,6 +724,8 @@ export default class ClaimView extends Vue {
|
||||
}
|
||||
|
||||
async created() {
|
||||
this.notify = createNotifyHelpers(this.$notify);
|
||||
|
||||
const settings = await this.$accountSettings();
|
||||
|
||||
this.activeDid = settings.activeDid || "";
|
||||
@@ -754,8 +756,6 @@ export default class ClaimView extends Vue {
|
||||
this.windowDeepLink = `${APP_SERVER}/deep-link/claim/${claimId}`;
|
||||
|
||||
this.canShare = !!navigator.share;
|
||||
|
||||
this.notify = createNotifyHelpers(this.$notify);
|
||||
}
|
||||
|
||||
// insert a space before any capital letters except the initial letter
|
||||
|
||||
@@ -143,7 +143,7 @@ import {
|
||||
QR_TIMEOUT_STANDARD,
|
||||
QR_TIMEOUT_LONG,
|
||||
} from "@/constants/notifications";
|
||||
import { createNotifyHelpers } from "../utils/notify";
|
||||
import { createNotifyHelpers, NotifyFunction } from "../utils/notify";
|
||||
|
||||
interface QRScanResult {
|
||||
rawValue?: string;
|
||||
@@ -191,7 +191,7 @@ interface IUserNameDialog {
|
||||
* @since 2024
|
||||
*/
|
||||
export default class ContactQRScanFull extends Vue {
|
||||
$notify!: (notification: any, timeout?: number) => void;
|
||||
$notify!: NotifyFunction;
|
||||
$router!: Router;
|
||||
|
||||
// Notification helper system
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
<!-- Breadcrumb -->
|
||||
<div id="ViewBreadcrumb" class="mb-8">
|
||||
<h1 id="ViewHeading" class="text-lg text-center font-light relative px-7">
|
||||
<!-- Go to 'contacts' instead of just 'back' because they could get here from an edit page (and going back there is annoying). -->
|
||||
<!-- Go to 'contacts' instead of just 'back' because they could get here from an edit page
|
||||
(and going back there is annoying). -->
|
||||
<router-link
|
||||
:to="{ name: 'contacts' }"
|
||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||
|
||||
@@ -234,7 +234,6 @@ Raymer * @version 1.0.0 */
|
||||
:last-viewed-claim-id="feedLastViewedClaimId"
|
||||
:is-registered="isRegistered"
|
||||
:active-did="activeDid"
|
||||
:on-image-cache="cacheImageData"
|
||||
@load-claim="onClickLoadClaim"
|
||||
@view-image="openImageViewer"
|
||||
/>
|
||||
@@ -255,11 +254,7 @@ Raymer * @version 1.0.0 */
|
||||
|
||||
<ChoiceButtonDialog ref="choiceButtonDialog" />
|
||||
|
||||
<ImageViewer
|
||||
v-model:is-open="isImageViewerOpen"
|
||||
:image-url="selectedImage"
|
||||
:image-data="selectedImageData"
|
||||
/>
|
||||
<ImageViewer v-model:is-open="isImageViewerOpen" :image-url="selectedImage" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -434,9 +429,7 @@ export default class HomeView extends Vue {
|
||||
showShortcutBvc = false;
|
||||
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
|
||||
selectedImage = "";
|
||||
selectedImageData: Blob | null = null;
|
||||
isImageViewerOpen = false;
|
||||
imageCache: Map<string, Blob | null> = new Map();
|
||||
showProjectsDialog = false;
|
||||
|
||||
/**
|
||||
@@ -1712,23 +1705,6 @@ export default class HomeView extends Vue {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches image data for sharing
|
||||
*
|
||||
* @public
|
||||
* Called by ActivityListItem component function prop
|
||||
* @param imageUrl URL of image to cache
|
||||
*/
|
||||
async cacheImageData(imageUrl: string) {
|
||||
try {
|
||||
// For images that might fail CORS, just store the URL
|
||||
// The Web Share API will handle sharing the URL appropriately
|
||||
this.imageCache.set(imageUrl, null);
|
||||
} catch (error) {
|
||||
logger.warn("Failed to cache image:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens image viewer dialog
|
||||
*
|
||||
@@ -1737,7 +1713,6 @@ export default class HomeView extends Vue {
|
||||
* @param imageUrl URL of image to display
|
||||
*/
|
||||
async openImageViewer(imageUrl: string) {
|
||||
this.selectedImageData = this.imageCache.get(imageUrl) ?? null;
|
||||
this.selectedImage = imageUrl;
|
||||
this.isImageViewerOpen = true;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ import {
|
||||
import { logger } from "../utils/logger";
|
||||
import { Account, AccountEncrypted } from "../db/tables/accounts";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||
import { createNotifyHelpers, TIMEOUTS, NotifyFunction } from "@/utils/notify";
|
||||
import {
|
||||
NOTIFY_ACCOUNT_DERIVATION_SUCCESS,
|
||||
NOTIFY_ACCOUNT_DERIVATION_ERROR,
|
||||
@@ -100,7 +100,7 @@ import {
|
||||
export default class ImportAccountView extends Vue {
|
||||
$route!: RouteLocationNormalizedLoaded;
|
||||
$router!: Router;
|
||||
$notify!: (notification: any, timeout?: number) => void;
|
||||
$notify!: NotifyFunction;
|
||||
|
||||
notify!: ReturnType<typeof createNotifyHelpers>;
|
||||
|
||||
|
||||
@@ -21,7 +21,17 @@
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div v-if="isNotProdServer">
|
||||
<h2 class="text-xl font-bold mb-4">User Registration</h2>
|
||||
<button :class="primaryButtonClasses" @click="registerMe()">
|
||||
Register Yourself
|
||||
</button>
|
||||
<button :class="primaryButtonClasses" @click="becomeUser0()">
|
||||
Become User 0 (who can register others)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<h2 class="text-xl font-bold mb-4">Notiwind Alerts</h2>
|
||||
|
||||
<!-- Notification test buttons using computed configuration -->
|
||||
@@ -99,7 +109,7 @@
|
||||
|
||||
<div>
|
||||
Register Passkey
|
||||
<button :class="primaryButtonClasses" @click="register()">
|
||||
<button :class="primaryButtonClasses" @click="registerPasskey()">
|
||||
Simplewebauthn
|
||||
</button>
|
||||
</div>
|
||||
@@ -235,6 +245,7 @@ import {
|
||||
registerAndSavePasskey,
|
||||
SHARED_PHOTO_BASE64_KEY,
|
||||
} from "../libs/util";
|
||||
import { testBecomeUser0, testServerRegisterUser } from "@/test";
|
||||
import { logger } from "../utils/logger";
|
||||
import { Account } from "../db/tables/accounts";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
@@ -300,6 +311,7 @@ export default class Help extends Vue {
|
||||
// for passkeys
|
||||
credIdHex?: string;
|
||||
activeDid?: string;
|
||||
apiServer?: string;
|
||||
jwt?: string;
|
||||
peerSetup?: PeerSetup;
|
||||
userName?: string;
|
||||
@@ -521,17 +533,6 @@ export default class Help extends Vue {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to trigger notification test
|
||||
* Centralizes notification testing logic
|
||||
*/
|
||||
triggerTestNotification(config: {
|
||||
notification: NotificationIface;
|
||||
timeout?: number;
|
||||
}) {
|
||||
this.$notify(config.notification, config.timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component initialization
|
||||
*
|
||||
@@ -541,6 +542,7 @@ export default class Help extends Vue {
|
||||
async mounted() {
|
||||
const settings = await this.$accountSettings();
|
||||
this.activeDid = settings.activeDid || "";
|
||||
this.apiServer = settings.apiServer || "";
|
||||
this.userName = settings.firstName;
|
||||
|
||||
const account = await retrieveAccountMetadata(this.activeDid);
|
||||
@@ -553,6 +555,43 @@ export default class Help extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if running on production server
|
||||
*
|
||||
* @returns True if not on production server (enables test utilities)
|
||||
*/
|
||||
public isNotProdServer() {
|
||||
return this.apiServer !== AppString.PROD_ENDORSER_API_SERVER;
|
||||
}
|
||||
|
||||
async registerMe() {
|
||||
const response = await testServerRegisterUser();
|
||||
if (response.status === 201) {
|
||||
alert("Registration successful.");
|
||||
this.$router.push({ name: "home" }); // because this page checks for registered status and sets things if it detects a change
|
||||
} else {
|
||||
logger.error("Registration failure response:", response);
|
||||
alert("Registration failed: " + (response.data.error || response.data));
|
||||
}
|
||||
}
|
||||
|
||||
async becomeUser0() {
|
||||
await testBecomeUser0();
|
||||
alert("You are now User 0.");
|
||||
this.$router.push({ name: "home" }); // because this page checks for registered status and sets things if it detects a change
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to trigger notification test
|
||||
* Centralizes notification testing logic
|
||||
*/
|
||||
triggerTestNotification(config: {
|
||||
notification: NotificationIface;
|
||||
timeout?: number;
|
||||
}) {
|
||||
this.$notify(config.notification, config.timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles file upload for image sharing tests
|
||||
*
|
||||
@@ -609,7 +648,7 @@ export default class Help extends Vue {
|
||||
* Includes validation and user confirmation workflow
|
||||
* Uses notification helpers for consistent messaging
|
||||
*/
|
||||
public async register() {
|
||||
public async registerPasskey() {
|
||||
const DEFAULT_USERNAME = AppString.APP_NAME + " Tester";
|
||||
if (!this.userName) {
|
||||
const modalConfig = createPasskeyNameModal(
|
||||
|
||||
@@ -23,7 +23,7 @@ test('Record an offer', async ({ page }) => {
|
||||
await page.locator('button', { hasText: 'Edit' }).isVisible(); // since the 'edit' takes longer to show, wait for that (lest the click miss)
|
||||
await page.getByTestId('offerButton').click();
|
||||
await page.getByTestId('inputDescription').fill(description);
|
||||
await page.getByTestId('inputOfferAmount').fill(randomNonZeroNumber.toString());
|
||||
await page.getByTestId('inputOfferAmount').locator('input').fill(randomNonZeroNumber.toString());
|
||||
expect(page.getByRole('button', { name: 'Sign & Send' }));
|
||||
await page.getByRole('button', { name: 'Sign & Send' }).click();
|
||||
await expect(page.getByText('That offer was recorded.')).toBeVisible();
|
||||
|
||||
@@ -36,7 +36,7 @@ test('New offers for another user', async ({ page }) => {
|
||||
const randomString1 = Math.random().toString(36).substring(2, 5);
|
||||
await page.getByTestId('offerButton').click();
|
||||
await page.getByTestId('inputDescription').fill(`help of ${randomString1} from #000`);
|
||||
await page.getByTestId('inputOfferAmount').fill('1');
|
||||
await page.getByTestId('inputOfferAmount').locator('input').fill('1');
|
||||
await page.getByRole('button', { name: 'Sign & Send' }).click();
|
||||
await expect(page.getByText('That offer was recorded.')).toBeVisible();
|
||||
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
|
||||
@@ -46,7 +46,7 @@ test('New offers for another user', async ({ page }) => {
|
||||
const randomString2 = Math.random().toString(36).substring(2, 5);
|
||||
await page.getByTestId('offerButton').click();
|
||||
await page.getByTestId('inputDescription').fill(`help of ${randomString2} from #000`);
|
||||
await page.getByTestId('inputOfferAmount').fill('3');
|
||||
await page.getByTestId('inputOfferAmount').locator('input').fill('3');
|
||||
await page.getByRole('button', { name: 'Sign & Send' }).click();
|
||||
await expect(page.getByText('That offer was recorded.')).toBeVisible();
|
||||
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
|
||||
|
||||
Reference in New Issue
Block a user