Compare commits

..

19 Commits

Author SHA1 Message Date
a3a2d97b9a update version to v 1.4.2 build 68 2026-05-24 21:50:39 -06:00
802050259c update android build, fix ios build for new version of MLKit BarcodeScanner (both build) 2026-05-24 21:24:18 -06:00
efd7d50a84 fix build error 2026-05-24 19:12:40 -06:00
39c389cda8 make do-not-pair group verbiage more clear 2026-05-24 18:38:56 -06:00
93fdcaf7ff fix timing error for a click (that only showed in firefox) 2026-05-24 18:29:11 -06:00
ad419efa0d utilize 'userMessage' if sent by server 2026-05-24 16:23:47 -06:00
ce45ddb2bd update after 'audit fix' 2026-05-24 16:23:09 -06:00
7d306bd204 add first cut for 16kb page sizes, all by Claude 2026-05-10 10:15:10 -06:00
9713313a40 fix HTML syntax warning 2026-05-10 09:43:46 -06:00
Jose Olarte III
ffa7bac319 fix(ios): ensure capacitor-assets output dirs exist on fresh clones
Gitignored AppIcon.appiconset and Splash.imageset are absent after clone,
which made `capacitor-assets generate --ios` fail (missing paths and
Contents.json). Add ensure_ios_capacitor_asset_directories in common.sh
to mkdir and seed minimal Contents.json when needed; call it from
build-ios.sh before asset generation and from the build:native npm script.
Document the behavior in ios/.gitignore.
2026-04-13 16:20:51 +08:00
e0e0a0a183 bump version and add -beta 2026-04-05 20:08:24 -06:00
ea662f4430 bump to v 1.3.13 (for a web release) 2026-04-05 19:58:36 -06:00
81647e1f3c make terms & conditions into a separate page 2026-04-05 19:21:43 -06:00
bf1ee78025 allow a custom error message to stay on the screen indefinitely 2026-03-29 19:11:49 -06:00
Jose Olarte III
66b7d0f46e docs(readme): expand Setup & Building quick start for all platforms
Restructure the quick start with Web, Android, and iOS subheadings; put
each npm command in its own code block; fold the test-page step into the
Web section. Document Android (build:android:test:run + ADB, link to
BUILDING.md) and iOS (build:ios:studio + Xcode prerequisites).
2026-03-26 19:41:03 +08:00
Jose Olarte III
63dcf44125 fix(ios): make build-ios.sh work on current simulators and trim xcodebuild noise
Use generic/platform=iOS Simulator instead of a fixed device name so CLI builds
do not fail when that simulator is not installed (e.g. newer Xcode runtimes).

Pass -quiet to xcodebuild and enable SWIFT_SUPPRESS_WARNINGS plus
GCC_WARN_INHIBIT_ALL_WARNINGS for scripted builds and IPA archive/export so
terminal output stays smaller; full diagnostics remain available in Xcode.
2026-03-26 19:40:07 +08:00
cf1ecdfb4c add registration for new contacts that are unregistered 2026-03-22 20:20:33 -06:00
e9ad61b780 don't delete a gift image on an edit unless they hit 'save' 2026-03-22 20:07:59 -06:00
ad8df3eb93 fix problem where canceling an edit deletes an image 2026-03-22 20:06:58 -06:00
31 changed files with 4485 additions and 3835 deletions

View File

@@ -1 +1 @@
18.19.0
20.18.1

2
.nvmrc
View File

@@ -1 +1 @@
18.19.0
20.18.1

View File

@@ -333,11 +333,11 @@ The `serve` functionality provides a local HTTP server for testing production bu
- If there are DB changes: before updating the test server, open browser(s) with
current version to test DB migrations.
- Update the ClickUp tasks & CHANGELOG.md & the version in package.json, run
- Update the ClickUp tasks & CHANGELOG.md & the version in package.json, run:
`npm install`.
- Run a build to make sure package-lock version is updated, linting works, etc:
`npm install && npm run build:web`
- Run a build to make sure linting works, etc:
`npm run build:web`
- Commit everything (since the commit hash is used the app).
@@ -346,7 +346,7 @@ current version to test DB migrations.
- Tag with the new version,
[online](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases) or
`git tag 1.0.2 && git push origin 1.0.2`.
`git tag 1.3.13 && git push origin 1.3.13`.
- For test, build the app:
@@ -1140,7 +1140,7 @@ export GEM_PATH=$shortened_path
##### 1. Bump the version in package.json & CHANGELOG.md for `MARKETING_VERSION`, then `grep CURRENT_PROJECT_VERSION ios/App/App.xcodeproj/project.pbxproj` and add 1 for the numbered version here:
```bash
cd ios/App && xcrun agvtool new-version 67 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.3.12;/g" App.xcodeproj/project.pbxproj && cd -
cd ios/App && xcrun agvtool new-version 68 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.4.2;/g" App.xcodeproj/project.pbxproj && cd -
# Unfortunately this edits Info.plist directly.
#xcrun agvtool new-marketing-version 0.4.5
```
@@ -1419,8 +1419,8 @@ The recommended way to build for Android is using the automated build script:
##### 1. Bump the version in package.json, then update these versions & run:
```bash
perl -p -i -e 's/versionCode .*/versionCode 67/g' android/app/build.gradle
perl -p -i -e 's/versionName .*/versionName "1.3.12"/g' android/app/build.gradle
perl -p -i -e 's/versionCode .*/versionCode 68/g' android/app/build.gradle
perl -p -i -e 's/versionName .*/versionName "1.4.2"/g' android/app/build.gradle
```
##### 2. Build

View File

@@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.4.2] - 2026.05.24
### Changed
- Support 16 KB page sizes
## [1.3.13] - 2026.04.05
### Added
- Ability to select project that the current one fulfills
- Separate Terms & Conditions page (required for SMS campaigns)
### Fixed
- Edits to a 'give' would delete the image
## [1.3.12] - 2026.03.21
### Added
- Device wake-up for notifications

View File

@@ -15,10 +15,31 @@ Quick start:
```bash
npm install
```
### Web
```bash
npm run build:web:dev
```
To be able to take action on the platform: go to [the test page](http://localhost:8080/test) and click "Become User 0".
Then go to [the test page](http://localhost:8080/test) and click "Become User 0" to take action on the platform.
### Android
```bash
npm run build:android:test:run
```
Assumes ADB is installed; see [Android Build](BUILDING.md#android-build) for SDK, emulator, and `PATH` setup.
### iOS
```bash
npm run build:ios:studio
```
Assumes Xcode and Xcode Command Line Tools are installed.
See [BUILDING.md](BUILDING.md) for comprehensive build instructions for all platforms (Web, Electron, iOS, Android, Docker).

View File

@@ -29,16 +29,16 @@ android {
compileSdk rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
defaultConfig {
applicationId "app.timesafari.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 67
versionName "1.3.12"
versionCode 68
versionName "1.4.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -72,13 +72,14 @@ android {
}
packagingOptions {
jniLibs {
// Required for 16 KB page-size support: keep native libs uncompressed and
// page-aligned inside the APK (default on AGP 8.x with minSdk 23+, set
// explicitly so it does not regress).
useLegacyPackaging = false
pickFirsts += ['**/lib/x86_64/libbarhopper_v3.so', '**/lib/x86_64/libimage_processing_util_jni.so', '**/lib/x86_64/libsqlcipher.so']
}
}
// Configure for 16 KB page size compatibility
// Enable bundle builds (without which it doesn't work right for bundleDebug vs bundleRelease)
bundle {
language {

View File

@@ -2,8 +2,8 @@
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
}

View File

@@ -1,6 +1,6 @@
{
"appId": "app.timesafari",
"appName": "TimeSafari",
"appName": "Giftopia",
"webDir": "dist",
"server": {
"cleartext": true
@@ -34,12 +34,12 @@
"iosIsEncryption": false,
"iosBiometric": {
"biometricAuth": false,
"biometricTitle": "Biometric login for TimeSafari"
"biometricTitle": "Biometric login for Giftopia"
},
"androidIsEncryption": false,
"androidBiometric": {
"biometricAuth": false,
"biometricTitle": "Biometric login for TimeSafari"
"biometricTitle": "Biometric login for Giftopia"
},
"electronIsEncryption": false
},
@@ -100,7 +100,7 @@
},
"buildOptions": {
"appId": "app.timesafari",
"productName": "TimeSafari",
"productName": "Giftopia",
"directories": {
"output": "dist-electron-packages"
},

View File

@@ -1,5 +1,5 @@
ext {
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.6.1'
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
cordovaAndroidVersion = project.hasProperty('cordovaAndroidVersion') ? rootProject.ext.cordovaAndroidVersion : '10.1.1'
}
@@ -9,7 +9,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.2.1'
classpath 'com.android.tools.build:gradle:8.7.2'
}
}
@@ -17,10 +17,10 @@ apply plugin: 'com.android.library'
android {
namespace "capacitor.cordova.android.plugins"
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
defaultConfig {
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
versionCode 1
versionName "1.0"
}
@@ -28,8 +28,8 @@ android {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
}

View File

@@ -1,6 +1,6 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
ext {
cdvMinSdkVersion = project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
cdvMinSdkVersion = project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
// Plugin gradle extensions can append to this to have code run at the end.
cdvPluginPostBuildExtras = []
cordovaConfig = [:]

View File

@@ -13,4 +13,11 @@ ext {
androidxJunitVersion = '1.1.5'
androidxEspressoCoreVersion = '3.5.1'
cordovaAndroidVersion = '10.1.1'
// Pin CameraX to 1.4.2: first stable line shipping a 16 KB page-size-aligned
// libimage_processing_util_jni.so. The barcode-scanning plugin still defaults to 1.1.0.
androidxCameraCamera2Version = '1.4.2'
androidxCameraCoreVersion = '1.4.2'
androidxCameraLifecycleVersion = '1.4.2'
androidxCameraViewVersion = '1.4.2'
}

3
ios/.gitignore vendored
View File

@@ -17,6 +17,7 @@ App/App/config.xml
App/App.xcodeproj/xcuserdata/*.xcuserdatad/
App/App.xcodeproj/*.xcuserstate
# Generated Icons from capacitor-assets (also Contents.json which is confusing; see BUILDING.md)
# Generated by capacitor-assets at build time (not in repo). Fresh clones lack these
# folders; scripts/common.sh ensure_ios_capacitor_asset_directories creates them before generate.
App/App/Assets.xcassets/AppIcon.appiconset
App/App/Assets.xcassets/Splash.imageset

View File

@@ -452,7 +452,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -508,7 +508,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -524,18 +524,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 67;
CURRENT_PROJECT_VERSION = 68;
DEVELOPMENT_TEAM = GM3FS5JQPH;
ENABLE_APP_SANDBOX = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Giftopia;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3.12;
MARKETING_VERSION = 1.4.2;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -553,18 +553,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 67;
CURRENT_PROJECT_VERSION = 68;
DEVELOPMENT_TEAM = GM3FS5JQPH;
ENABLE_APP_SANDBOX = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Giftopia;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3.12;
MARKETING_VERSION = 1.4.2;
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -582,7 +582,7 @@
CLANG_ENABLE_OBJC_WEAK = YES;
CODE_SIGN_ENTITLEMENTS = TimeSafariShareExtension/TimeSafariShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 67;
CURRENT_PROJECT_VERSION = 68;
DEVELOPMENT_TEAM = GM3FS5JQPH;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
@@ -596,7 +596,7 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.3.12;
MARKETING_VERSION = 1.4.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.TimeSafariShareExtension;
@@ -620,7 +620,7 @@
CLANG_ENABLE_OBJC_WEAK = YES;
CODE_SIGN_ENTITLEMENTS = TimeSafariShareExtension/TimeSafariShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 67;
CURRENT_PROJECT_VERSION = 68;
DEVELOPMENT_TEAM = GM3FS5JQPH;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
@@ -634,7 +634,7 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.3.12;
MARKETING_VERSION = 1.4.2;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.TimeSafariShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -1,6 +1,6 @@
require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'
platform :ios, '13.0'
platform :ios, '15.5'
use_frameworks!
# workaround to avoid Xcode caching of Pods that requires
@@ -30,9 +30,4 @@ end
post_install do |installer|
assertDeploymentTarget(installer)
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
end
end
end

View File

@@ -1,94 +1,84 @@
PODS:
- Capacitor (6.2.1):
- Capacitor (7.6.4):
- CapacitorCordova
- CapacitorApp (6.0.2):
- CapacitorApp (7.1.2):
- Capacitor
- CapacitorCamera (6.1.2):
- CapacitorCamera (7.0.5):
- Capacitor
- CapacitorClipboard (6.0.2):
- CapacitorClipboard (7.0.4):
- Capacitor
- CapacitorCommunitySqlite (6.0.2):
- CapacitorCommunitySqlite (7.0.3):
- Capacitor
- SQLCipher
- ZIPFoundation
- CapacitorCordova (6.2.1)
- CapacitorFilesystem (6.0.3):
- CapacitorCordova (7.6.4)
- CapacitorFilesystem (7.1.8):
- Capacitor
- CapacitorMlkitBarcodeScanning (6.2.0):
- IONFilesystemLib (~> 1.1.1)
- CapacitorMlkitBarcodeScanning (7.5.0):
- Capacitor
- GoogleMLKit/BarcodeScanning (= 5.0.0)
- CapacitorShare (6.0.3):
- GoogleMLKit/BarcodeScanning (= 7.0.0)
- CapacitorShare (7.0.4):
- Capacitor
- CapacitorStatusBar (6.0.2):
- CapacitorStatusBar (7.0.6):
- Capacitor
- CapawesomeCapacitorFilePicker (6.2.0):
- CapawesomeCapacitorFilePicker (7.2.0):
- Capacitor
- GoogleDataTransport (9.4.1):
- GoogleUtilities/Environment (~> 7.7)
- nanopb (< 2.30911.0, >= 2.30908.0)
- PromisesObjC (< 3.0, >= 1.2)
- GoogleMLKit/BarcodeScanning (5.0.0):
- GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- GoogleMLKit/BarcodeScanning (7.0.0):
- GoogleMLKit/MLKitCore
- MLKitBarcodeScanning (~> 4.0.0)
- GoogleMLKit/MLKitCore (5.0.0):
- MLKitCommon (~> 10.0.0)
- GoogleToolboxForMac/DebugUtils (2.3.2):
- GoogleToolboxForMac/Defines (= 2.3.2)
- GoogleToolboxForMac/Defines (2.3.2)
- GoogleToolboxForMac/Logger (2.3.2):
- GoogleToolboxForMac/Defines (= 2.3.2)
- "GoogleToolboxForMac/NSData+zlib (2.3.2)":
- GoogleToolboxForMac/Defines (= 2.3.2)
- "GoogleToolboxForMac/NSDictionary+URLArguments (2.3.2)":
- GoogleToolboxForMac/DebugUtils (= 2.3.2)
- GoogleToolboxForMac/Defines (= 2.3.2)
- "GoogleToolboxForMac/NSString+URLArguments (= 2.3.2)"
- "GoogleToolboxForMac/NSString+URLArguments (2.3.2)"
- GoogleUtilities/Environment (7.13.3):
- MLKitBarcodeScanning (~> 6.0.0)
- GoogleMLKit/MLKitCore (7.0.0):
- MLKitCommon (~> 12.0.0)
- GoogleToolboxForMac/Defines (4.2.1)
- GoogleToolboxForMac/Logger (4.2.1):
- GoogleToolboxForMac/Defines (= 4.2.1)
- "GoogleToolboxForMac/NSData+zlib (4.2.1)":
- GoogleToolboxForMac/Defines (= 4.2.1)
- GoogleUtilities/Environment (8.1.0):
- GoogleUtilities/Privacy
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/Logger (7.13.3):
- GoogleUtilities/Logger (8.1.0):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/Privacy (7.13.3)
- GoogleUtilities/UserDefaults (7.13.3):
- GoogleUtilities/Privacy (8.1.0)
- GoogleUtilities/UserDefaults (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilitiesComponents (1.1.0):
- GoogleUtilities/Logger
- GTMSessionFetcher/Core (3.5.0)
- MLImage (1.0.0-beta5)
- MLKitBarcodeScanning (4.0.0):
- MLKitCommon (~> 10.0)
- MLKitVision (~> 6.0)
- MLKitCommon (10.0.0):
- GoogleDataTransport (~> 9.0)
- GoogleToolboxForMac/Logger (~> 2.1)
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
- "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)"
- GoogleUtilities/UserDefaults (~> 7.0)
- GoogleUtilitiesComponents (~> 1.0)
- GTMSessionFetcher/Core (< 4.0, >= 1.1)
- MLKitVision (6.0.0):
- GoogleToolboxForMac/Logger (~> 2.1)
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
- GTMSessionFetcher/Core (< 4.0, >= 1.1)
- MLImage (= 1.0.0-beta5)
- MLKitCommon (~> 10.0)
- nanopb (2.30910.0):
- nanopb/decode (= 2.30910.0)
- nanopb/encode (= 2.30910.0)
- nanopb/decode (2.30910.0)
- nanopb/encode (2.30910.0)
- IONFilesystemLib (1.1.2)
- MLImage (1.0.0-beta6)
- MLKitBarcodeScanning (6.0.0):
- MLKitCommon (~> 12.0)
- MLKitVision (~> 8.0)
- MLKitCommon (12.0.0):
- GoogleDataTransport (~> 10.0)
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
- GoogleUtilities/Logger (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
- MLKitVision (8.0.0):
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
- MLImage (= 1.0.0-beta6)
- MLKitCommon (~> 12.0)
- nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0)
- PromisesObjC (2.4.0)
- SQLCipher (4.9.0):
- SQLCipher/standard (= 4.9.0)
- SQLCipher/common (4.9.0)
- SQLCipher/standard (4.9.0):
- SQLCipher (4.10.0):
- SQLCipher/standard (= 4.10.0)
- SQLCipher/common (4.10.0)
- SQLCipher/standard (4.10.0):
- SQLCipher/common
- TimesafariDailyNotificationPlugin (2.0.0):
- Capacitor
- ZIPFoundation (0.9.19)
- ZIPFoundation (0.9.20)
DEPENDENCIES:
- "Capacitor (from `../../node_modules/@capacitor/ios`)"
@@ -110,8 +100,8 @@ SPEC REPOS:
- GoogleMLKit
- GoogleToolboxForMac
- GoogleUtilities
- GoogleUtilitiesComponents
- GTMSessionFetcher
- IONFilesystemLib
- MLImage
- MLKitBarcodeScanning
- MLKitCommon
@@ -148,33 +138,33 @@ EXTERNAL SOURCES:
:path: "../../node_modules/@timesafari/daily-notification-plugin"
SPEC CHECKSUMS:
Capacitor: c95400d761e376be9da6be5a05f226c0e865cebf
CapacitorApp: e1e6b7d05e444d593ca16fd6d76f2b7c48b5aea7
CapacitorCamera: 9bc7b005d0e6f1d5f525b8137045b60cffffce79
CapacitorClipboard: 4443c3cdb7c77b1533dfe3ff0f9f7756aa8579df
CapacitorCommunitySqlite: 0299d20f4b00c2e6aa485a1d8932656753937b9b
CapacitorCordova: 8d93e14982f440181be7304aa9559ca631d77fff
CapacitorFilesystem: 59270a63c60836248812671aa3b15df673fbaf74
CapacitorMlkitBarcodeScanning: 7652be9c7922f39203a361de735d340ae37e134e
CapacitorShare: d2a742baec21c8f3b92b361a2fbd2401cdd8288e
CapacitorStatusBar: b16799a26320ffa52f6c8b01737d5a95bbb8f3eb
CapawesomeCapacitorFilePicker: c40822f0a39f86855321943c7829d52bca7f01bd
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
GoogleMLKit: 90ba06e028795a50261f29500d238d6061538711
GoogleToolboxForMac: 8bef7c7c5cf7291c687cf5354f39f9db6399ad34
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe
Capacitor: 69dc07ebc6bd064747c5e76922f97e4862d9cc23
CapacitorApp: f01a913211780e0718dae9750442c3e23f96e106
CapacitorCamera: 9e952270be355797f769aa835bb7643a96c871fe
CapacitorClipboard: d1f123674cf413125db816a45e8f70e8770972fc
CapacitorCommunitySqlite: 4813d82ad33001e612a39d313cb5d28066cbafda
CapacitorCordova: e343e95a672ff73e21a77a80257b52fb609b47d5
CapacitorFilesystem: c63fc54df41e5a6761785a7f3c49dc696c22e296
CapacitorMlkitBarcodeScanning: afd6fc431b550026a2c052e11ab2b71c7ae30011
CapacitorShare: 25f7fc5dd0e4edbde5d6801c6de5d14a8b450a41
CapacitorStatusBar: 416e9e53fd6397e668d4a181cd2131617d949bd6
CapawesomeCapacitorFilePicker: 0f4a913a00e39dd77213449f0d917e92f35a5ca9
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318
GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
MLImage: 1824212150da33ef225fbd3dc49f184cf611046c
MLKitBarcodeScanning: 9cb0ec5ec65bbb5db31de4eba0a3289626beab4e
MLKitCommon: afcd11b6c0735066a0dde8b4bf2331f6197cbca2
MLKitVision: 90922bca854014a856f8b649d1f1f04f63fd9c79
nanopb: 438bc412db1928dac798aa6fd75726007be04262
IONFilesystemLib: 21a63377696b2d8fab5632ecfb7d2ac67bddb68a
MLImage: 0ad1c5f50edd027672d8b26b0fee78a8b4a0fc56
MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2
MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d
MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
SQLCipher: 31878d8ebd27e5c96db0b7cb695c96e9f8ad77da
SQLCipher: eb79c64049cb002b4e9fcb30edb7979bf4706dfc
TimesafariDailyNotificationPlugin: 3c12e8c39fc27f689f56cf4e57230a8c28611fcc
ZIPFoundation: b8c29ea7ae353b309bc810586181fd073cb3312c
ZIPFoundation: dfd3d681c4053ff7e2f7350bc4e53b5dba3f5351
PODFILE CHECKSUM: 6d92bfa46c6c2d31d19b8c0c38f56a8ae9fd222f
PODFILE CHECKSUM: 87c07d03f36ef38ab0c873802aee1ce9b5d34448
COCOAPODS: 1.16.2

7513
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
{
"name": "giftopia",
"version": "1.3.13-beta",
"version": "1.4.2",
"description": "Giftopia App",
"author": {
"name": "Gift Economies Team"
@@ -28,7 +29,7 @@
"auto-run:electron": "./scripts/auto-run.sh --platform=electron",
"build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts",
"build:capacitor:sync": "npm run build:capacitor && npx cap sync && node scripts/restore-local-plugins.js",
"build:native": "vite build && npx cap sync && node scripts/restore-local-plugins.js && npx capacitor-assets generate",
"build:native": "vite build && npx cap sync && node scripts/restore-local-plugins.js && bash -c 'source scripts/common.sh && ensure_ios_capacitor_asset_directories' && npx capacitor-assets generate",
"assets:config": "npx tsx scripts/assets-config.ts",
"assets:validate": "npx tsx scripts/assets-validator.ts",
"assets:validate:android": "./scripts/build-android.sh --assets-only",
@@ -138,19 +139,19 @@
},
"dependencies": {
"@capacitor-community/electron": "^5.0.1",
"@capacitor-community/sqlite": "6.0.2",
"@capacitor-mlkit/barcode-scanning": "^6.0.0",
"@capacitor/android": "^6.2.0",
"@capacitor/app": "^6.0.0",
"@capacitor/camera": "^6.0.0",
"@capacitor/cli": "^6.2.0",
"@capacitor/clipboard": "^6.0.2",
"@capacitor/core": "^6.2.0",
"@capacitor/filesystem": "^6.0.0",
"@capacitor/ios": "^6.2.0",
"@capacitor/share": "^6.0.3",
"@capacitor/status-bar": "^6.0.2",
"@capawesome/capacitor-file-picker": "^6.2.0",
"@capacitor-community/sqlite": "^7.0.3",
"@capacitor-mlkit/barcode-scanning": "^7.5.0",
"@capacitor/android": "^7.6.4",
"@capacitor/app": "^7.1.0",
"@capacitor/camera": "^7.0.5",
"@capacitor/cli": "^7.6.4",
"@capacitor/clipboard": "^7.0.4",
"@capacitor/core": "^7.6.4",
"@capacitor/filesystem": "^7.1.8",
"@capacitor/ios": "^7.6.4",
"@capacitor/share": "^7.0.4",
"@capacitor/status-bar": "^7.0.6",
"@capawesome/capacitor-file-picker": "^7.2.0",
"@dicebear/collection": "^5.4.1",
"@dicebear/core": "^5.4.1",
"@ethersproject/hdnode": "^5.7.0",

View File

@@ -222,7 +222,9 @@ build_ios_app() {
if [ "$BUILD_TYPE" = "debug" ]; then
build_config="Debug"
destination="platform=iOS Simulator,name=iPhone 15 Pro"
# Use device SDK — prebuilt MLKit frameworks (MLImage, MLKitBarcodeScanning) ship
# iOS device slices only and cannot link against the iOS Simulator SDK.
destination="generic/platform=iOS"
else
build_config="Release"
destination="platform=iOS,id=auto"
@@ -232,18 +234,34 @@ build_ios_app() {
cd ios/App
# Build the app
xcodebuild -workspace App.xcworkspace \
# Prevent pkgx-managed libs (e.g. zlib) from leaking into the iOS SDK linker.
unset LIBRARY_PATH
unset DYLD_LIBRARY_PATH
unset DYLD_FALLBACK_LIBRARY_PATH
# Build the app:
# -quiet: skip the huge export VAR dump (compiler warnings still show unless suppressed below).
# SWIFT_SUPPRESS_WARNINGS / GCC_WARN_INHIBIT_ALL_WARNINGS: quiet CLI output from Pods + plugins;
# build in Xcode for full diagnostics. Real errors still fail the build.
local build_exit=0
xcodebuild -quiet \
-workspace App.xcworkspace \
-scheme "$scheme" \
-configuration "$build_config" \
-destination "$destination" \
build \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO
CODE_SIGNING_ALLOWED=NO \
SWIFT_SUPPRESS_WARNINGS=YES \
GCC_WARN_INHIBIT_ALL_WARNINGS=YES || build_exit=$?
cd ../..
if [ $build_exit -ne 0 ]; then
return $build_exit
fi
log_success "iOS app built successfully"
}
@@ -406,6 +424,7 @@ fi
# Handle assets-only mode
if [ "$ASSETS_ONLY" = true ]; then
log_info "Assets-only mode: generating assets"
ensure_ios_capacitor_asset_directories
safe_execute "Generating assets" "npx capacitor-assets generate --ios" || exit 7
log_success "Assets generation completed successfully!"
exit 0
@@ -555,6 +574,7 @@ safe_execute "Installing CocoaPods dependencies" "run_pod_install_with_workaroun
safe_execute "Syncing with Capacitor" "run_cap_sync_with_workaround" || exit 6
# Step 7: Generate assets
ensure_ios_capacitor_asset_directories
safe_execute "Generating assets" "npx capacitor-assets generate --ios" || exit 7
# Step 8: Build iOS app
@@ -564,16 +584,19 @@ safe_execute "Building iOS app" "build_ios_app" || exit 5
if [ "$BUILD_IPA" = true ]; then
log_info "Building IPA package..."
cd ios/App
xcodebuild -workspace App.xcworkspace \
xcodebuild -quiet \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-archivePath build/App.xcarchive \
archive \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO
CODE_SIGNING_ALLOWED=NO \
SWIFT_SUPPRESS_WARNINGS=YES \
GCC_WARN_INHIBIT_ALL_WARNINGS=YES
xcodebuild -exportArchive \
xcodebuild -quiet -exportArchive \
-archivePath build/App.xcarchive \
-exportPath build/ \
-exportOptionsPlist exportOptions.plist

View File

@@ -337,6 +337,27 @@ parse_args() {
fi
}
# iOS: capacitor-assets writes into AppIcon.appiconset and Splash.imageset under
# Assets.xcassets. Those paths are gitignored (generated). On a fresh clone the
# folders and Contents.json are missing; the tool opens Contents.json before writing
# PNGs, so we create minimal asset-catalog stubs when absent.
ensure_ios_capacitor_asset_directories() {
local base="ios/App/App/Assets.xcassets"
if [ ! -d "$base" ]; then
log_warn "Missing $base — cannot prepare iOS asset directories"
return 0
fi
mkdir -p "$base/AppIcon.appiconset" "$base/Splash.imageset"
local minimal_contents='{"images":[],"info":{"author":"xcode","version":1}}'
if [ ! -f "$base/AppIcon.appiconset/Contents.json" ]; then
printf '%s\n' "$minimal_contents" > "$base/AppIcon.appiconset/Contents.json"
fi
if [ ! -f "$base/Splash.imageset/Contents.json" ]; then
printf '%s\n' "$minimal_contents" > "$base/Splash.imageset/Contents.json"
fi
log_debug "Ensured iOS capacitor-assets output directories exist"
}
# Export functions for use in child scripts
export -f log_info log_success log_warn log_error log_debug log_step
export -f measure_time print_header print_footer
@@ -344,4 +365,5 @@ export -f check_command check_directory check_file
export -f safe_execute check_venv get_git_hash
export -f clean_build_artifacts validate_env_vars
export -f setup_build_env setup_app_directories load_env_file print_env_vars
export -f print_usage parse_args
export -f print_usage parse_args
export -f ensure_ios_capacitor_asset_directories

View File

@@ -129,7 +129,7 @@
@click="disabled ? notifyLocked() : addGroup()"
>
<font-awesome icon="plus" class="text-sm" />
New Group
New Do-Not-Pair Group
</button>
</div>
</template>

View File

@@ -1175,11 +1175,6 @@ export const NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_CONFIRM = {
message: "",
};
export const NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_ERROR = {
title: "Error",
message: "There was a problem deleting the image.",
};
export const NOTIFY_GIFTED_DETAILS_NO_IDENTIFIER = {
title: "Missing Identifier",
message: "You must select an identifier before you can record a give.",

View File

@@ -136,6 +136,11 @@ const routes: Array<RouteRecordRaw> = [
name: "help-onboarding",
component: () => import("../views/HelpOnboardingView.vue"),
},
{
path: "/help-terms",
name: "help-terms",
component: () => import("../views/HelpTermsView.vue"),
},
{
path: "/",
name: "home",

View File

@@ -123,10 +123,10 @@ export class CapacitorQRScanner implements QRScannerService {
// Add listener for barcode scans
const handle = await BarcodeScanner.addListener(
"barcodeScanned",
"barcodesScanned",
(result) => {
if (this.scanListener && result.barcode?.rawValue) {
this.scanListener.onScan(result.barcode.rawValue);
if (this.scanListener && result.barcodes?.[0]?.rawValue) {
this.scanListener.onScan(result.barcodes[0].rawValue);
}
},
);

View File

@@ -2116,6 +2116,10 @@ export default class AccountViewView extends Vue {
hasLocation: result.includeLocation,
});
if (response.data.userMessage) {
this.notify.info(response.data.userMessage);
}
return result;
} else {
logger.debug("[AccountViewView] No profile data found in response:", {
@@ -2123,6 +2127,10 @@ export default class AccountViewView extends Vue {
hasData: !!response.data,
hasDataData: !!(response.data && response.data.data),
});
if (response.data?.userMessage) {
this.notify.info(response.data.userMessage);
}
}
return null;
@@ -2229,6 +2237,10 @@ export default class AccountViewView extends Vue {
status: response.status,
});
if (response.data?.userMessage) {
this.notify.info(response.data.userMessage);
}
return true;
} catch (error: unknown) {
// Handle specific HTTP status codes cleanly to suppress console spam
@@ -2345,6 +2357,10 @@ export default class AccountViewView extends Vue {
status: response.status,
});
if (response.data?.userMessage) {
this.notify.info(response.data.userMessage);
}
return true;
} catch (error: unknown) {
// Handle specific HTTP status codes cleanly to suppress console spam

View File

@@ -118,11 +118,13 @@ import {
CONTACT_CSV_HEADER,
CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI,
generateEndorserJwtUrlForAccount,
register,
setVisibilityUtil,
} from "../libs/endorserServer";
import UserNameDialog from "../components/UserNameDialog.vue";
import { retrieveAccountMetadata } from "../libs/util";
import { AxiosError } from "axios";
import { Account } from "@/db/tables/accounts";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import {
@@ -139,7 +141,10 @@ import {
NOTIFY_QR_URL_COPIED,
NOTIFY_QR_CODE_HELP,
NOTIFY_QR_DID_COPIED,
NOTIFY_QR_REGISTRATION_SUBMITTED,
NOTIFY_QR_REGISTRATION_ERROR,
createQRContactAddedMessage,
createQRRegistrationSuccessMessage,
QR_TIMEOUT_MEDIUM,
QR_TIMEOUT_STANDARD,
QR_TIMEOUT_LONG,
@@ -204,6 +209,7 @@ export default class ContactQRScanFull extends Vue {
activeDid = "";
apiServer = "";
givenName = "";
hideRegisterPromptOnNewContact = false;
isRegistered = false;
profileImageUrl = "";
qrValue = "";
@@ -278,6 +284,8 @@ export default class ContactQRScanFull extends Vue {
this.apiServer = settings.apiServer || "";
this.givenName = settings.firstName || "";
this.hideRegisterPromptOnNewContact =
!!settings.hideRegisterPromptOnNewContact;
this.isRegistered = !!settings.isRegistered;
this.profileImageUrl = settings.profileImageUrl || "";
@@ -575,6 +583,34 @@ export default class ContactQRScanFull extends Vue {
createQRContactAddedMessage(!!this.activeDid),
QR_TIMEOUT_STANDARD,
);
if (
this.isRegistered &&
!this.hideRegisterPromptOnNewContact &&
!contact.registered
) {
setTimeout(() => {
this.$notify(
{
group: "modal",
type: "confirm",
title: "Register",
text: "Do you want to register them?",
onCancel: async (stopAsking?: boolean) => {
await this.handleRegistrationPromptResponse(stopAsking);
},
onNo: async (stopAsking?: boolean) => {
await this.handleRegistrationPromptResponse(stopAsking);
},
onYes: async () => {
await this.register(contact);
},
promptToStopAsking: true,
},
-1,
);
}, 500);
}
} catch (error) {
logger.error("Error saving contact to database:", {
did: contact.did,
@@ -585,6 +621,74 @@ export default class ContactQRScanFull extends Vue {
}
}
async register(contact: Contact) {
logger.debug("Submitting contact registration", {
did: contact.did,
name: contact.name,
});
try {
const regResult = await register(
this.activeDid,
this.apiServer,
this.axios,
contact,
);
this.notify.toast("Submitted", NOTIFY_QR_REGISTRATION_SUBMITTED.message);
if (regResult.success) {
contact.registered = true;
await this.$updateContact(contact.did, { registered: true });
logger.debug("Contact registration successful", { did: contact.did });
this.notify.success(
createQRRegistrationSuccessMessage(contact.name || ""),
QR_TIMEOUT_LONG,
);
} else {
this.notify.error(
(regResult.error as string) || NOTIFY_QR_REGISTRATION_ERROR.message,
QR_TIMEOUT_LONG,
);
}
} catch (error) {
logger.error("Error registering contact:", {
did: contact.did,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
let userMessage = "There was an error.";
const serverError = error as AxiosError;
if (serverError) {
if (
serverError.response?.data &&
typeof serverError.response.data === "object" &&
"message" in serverError.response.data
) {
userMessage = (serverError.response.data as { message: string })
.message;
} else if (serverError.message) {
userMessage = serverError.message;
} else {
userMessage = JSON.stringify(serverError.toJSON());
}
} else {
userMessage = error as string;
}
this.notify.error(userMessage, QR_TIMEOUT_LONG);
}
}
private async handleRegistrationPromptResponse(
stopAsking?: boolean,
): Promise<void> {
if (stopAsking) {
await this.$saveSettings({
hideRegisterPromptOnNewContact: stopAsking,
});
this.hideRegisterPromptOnNewContact = stopAsking;
}
}
/**
* Vue lifecycle hook - component mounted
* Sets up event listeners and starts scanning automatically

View File

@@ -263,7 +263,6 @@ import { showSeedPhraseReminder } from "@/utils/seedPhraseReminder";
import {
NOTIFY_GIFTED_DETAILS_RETRIEVAL_ERROR,
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_CONFIRM,
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_ERROR,
NOTIFY_GIFTED_DETAILS_NO_IDENTIFIER,
NOTIFY_GIFT_ERROR_NEGATIVE_AMOUNT,
NOTIFY_GIFTED_DETAILS_RECORDING_GIVE,
@@ -302,6 +301,7 @@ export default class GiftedDetails extends Vue {
giverName = "";
hideBackButton = false;
imageUrl = "";
imageUrlToDelete = "";
message = "";
offerId = "";
prevCredToEdit?: GenericCredWrapper<GiveActionClaim>;
@@ -517,7 +517,10 @@ export default class GiftedDetails extends Vue {
}
cancel() {
this.deleteImage(); // not awaiting, so they'll go back immediately
// Only delete freshly uploaded images, not ones from an existing claim
if (this.imageUrl && this.imageUrl !== this.prevCredToEdit?.claim?.image) {
this.deleteImage(this.imageUrl); // not awaiting, so they'll go back immediately
}
if (this.destinationPathAfter) {
(this.$router as Router).push({ path: this.destinationPathAfter });
} else {
@@ -526,7 +529,10 @@ export default class GiftedDetails extends Vue {
}
cancelBack() {
this.deleteImage(); // not awaiting, so they'll go back immediately
// Only delete freshly uploaded images, not ones from an existing claim
if (this.imageUrl && this.imageUrl !== this.prevCredToEdit?.claim?.image) {
this.deleteImage(this.imageUrl); // not awaiting, so they'll go back immediately
}
(this.$router as Router).back();
}
@@ -539,13 +545,18 @@ export default class GiftedDetails extends Vue {
confirmDeleteImage() {
this.notify.confirm(
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_CONFIRM.message,
this.deleteImage,
() => {
// Stage the image for deletion on submit rather than deleting immediately,
// so that canceling the edit doesn't destroy the referenced image.
this.imageUrlToDelete = this.imageUrl;
this.imageUrl = "";
},
TIMEOUTS.LONG,
);
}
async deleteImage() {
if (!this.imageUrl) {
async deleteImage(imageUrl: string) {
if (!imageUrl) {
return;
}
try {
@@ -559,38 +570,21 @@ export default class GiftedDetails extends Vue {
);
}
const response = await this.axios.delete(
DEFAULT_IMAGE_API_SERVER +
"/image/" +
encodeURIComponent(this.imageUrl),
DEFAULT_IMAGE_API_SERVER + "/image/" + encodeURIComponent(imageUrl),
{ headers },
);
if (response.status === 204) {
// don't bother with a notification
// (either they'll simply continue or they're canceling and going back)
} else {
logger.error("Problem deleting image:", response);
this.notify.error(
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_ERROR.message,
TIMEOUTS.LONG,
);
return;
}
this.imageUrl = "";
} catch (error) {
logger.error("Error deleting image:", error);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((error as any)?.response?.status === 404) {
logger.log("Weird: the image was already deleted.", error);
this.imageUrl = "";
// it already doesn't exist so we won't say anything to the user
logger.log("Image was already deleted:", error);
} else {
this.notify.error(
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_ERROR.message,
TIMEOUTS.LONG,
);
logger.error("Failed to delete image from server:", error);
}
}
}
@@ -733,6 +727,12 @@ export default class GiftedDetails extends Vue {
TIMEOUTS.LONG,
);
} else {
// Delete the old image from storage now that the edit is saved
if (this.imageUrlToDelete) {
this.deleteImage(this.imageUrlToDelete); // not awaiting
this.imageUrlToDelete = "";
}
this.notify.success(
NOTIFY_GIFTED_DETAILS_GIFT_RECORDED.message,
TIMEOUTS.SHORT,

144
src/views/HelpTermsView.vue Normal file
View File

@@ -0,0 +1,144 @@
<template>
<QuickNav />
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Sub View Heading -->
<div id="SubViewHeading" class="flex gap-4 items-start mb-8">
<h1 class="grow text-xl text-center font-semibold leading-tight">
Terms & Conditions and Privacy Policies
</h1>
<!-- Back -->
<a
class="order-first text-lg text-center leading-none p-1"
@click="$router.go(-1)"
>
<font-awesome icon="chevron-left" class="block text-center w-[1em]" />
</a>
<!-- Help button -->
<router-link
:to="{ name: 'help' }"
class="block ms-auto text-sm text-center text-white bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-1.5 rounded-full"
>
<font-awesome icon="question" class="block text-center w-[1em]" />
</router-link>
</div>
<!-- eslint-disable prettier/prettier -->
<div>
<p style="display:inline; align-items: center">
This work is public domain. (If you like rules, reference
<a
href="http://creativecommons.org/publicdomain/zero/1.0?ref=chooser-v1"
target="_blank"
rel="license noopener noreferrer"
>
<span class="text-blue-500 mr-1">CC0 1.0</span>
<img
src="../assets/help/creative-commons-circle.svg"
alt="CC circle"
width="20"
class="display: inline"
/>
<img
src="../assets/help/creative-commons-zero.svg"
alt="CC zero"
width="20"
style="display: inline"
/>
</a>
)
</p>
<p class="mt-4">
This is offered freely, with the hope that it helps but without any
warranty or guarantee. When you share data or even look at information here,
you accept the risk that goes with those activities. In other words,
if you expect some functionality or you expect some protection, and you
feel it is appropriate to force those expectations on the system or its
operators or creators, then you are not allowed to use it.
</p>
<p class="mt-4">
Here is how your data is used:
</p>
<ul class="list-disc list-outside ml-4">
<li>
If sending images, a server stores them. They can be removed by editing
each claim and deleting the image.
</li>
<li>
If sending other partner system data (eg. to Trustroots) a public key
and message data are stored on a server. Those can be removed via
direct personal request (email
<a :href="`mailto:${SUPPORT_EMAIL}`" class="text-blue-500">
{{ SUPPORT_EMAIL }}
</a>).
</li>
<li>
For all other claim data,
<a
href="https://endorser.ch/privacy-policy"
target="_blank"
class="text-blue-500"
>
the Endorser Service has this Privacy Policy.
</a>
</li>
</ul>
<div class="mt-4">
<!--
This section is for Twilio's A2P Campaign requirements.
They say: Ensure it includes the program name, description, message/data rates, message frequency, support contact info, and opt-out instructions (HELP and STOP in bold).
They link here for a sample: https://help.twilio.com/articles/223134847-Industry-standards-for-US-Short-Code-Terms-of-Service
-->
Here are the details for SMS notifications:
<ul class="list-disc list-outside ml-4">
<li>You may opt to receive SMS messages for two purposes:
<ul class="list-disc list-outside ml-4">
<li>A daily reminder message</li>
<li>A notification of new activity for items that you are watching</li>
</ul>
</li>
<li>
Before enabling these notifications, you must register your phone number and give permission to use it for searches.
</li>
<li>
Once your phone number is registered and linked to your DID, you can enable or disable either kind of SMS message.
You can disable these any time with the same toggle.
</li>
<li>
If you lose your credentials, you can register your phone with a different DID.
Then you can enable and disable notifications for your phone.
</li>
<li>
Carriers are not liable for delayed or undelivered messages.
</li>
<li>
As always, message and data rates may apply for any messages sent to you from us and to us from you.
You will receive at most one of each kind of message per day.
If you have any questions about your text plan or data plan, it is best to contact your wireless provider.
</li>
<li>
Our servers will only store your phone number and the type of notifications you have enabled,
along with the explicit signed permission to use it for searches.
</li>
</ul>
</div>
</div>
<!-- eslint-enable -->
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import QuickNav from "../components/QuickNav.vue";
import { SUPPORT_EMAIL } from "../constants/app";
@Component({ components: { QuickNav } })
export default class HelpTermsView extends Vue {
SUPPORT_EMAIL = SUPPORT_EMAIL;
}
</script>

View File

@@ -480,46 +480,14 @@
</p>
<h2 class="text-xl font-semibold">What are the terms & conditions and the privacy policy?</h2>
<p style="display:inline; align-items: center">
This work is public domain. (If you like rules, reference
<a href="http://creativecommons.org/publicdomain/zero/1.0?ref=chooser-v1" target="_blank" rel="license noopener noreferrer">
<span class="text-blue-500 mr-1">CC0 1.0</span>
<img
src="../assets/help/creative-commons-circle.svg"
alt="CC circle"
width="20"
class="display: inline"
/>
<img
src="../assets/help/creative-commons-zero.svg"
alt="CC zero"
width="20"
style="display: inline"
/>
</a>
.) This is offered freely, with the hope that it helps but without any warranty or guarantee;
if it helps you then enjoy using it,
but if you may try to forcibly collect damages for things you think it should do (or not do)
then don't use it.
<br />
As for data & privacy:
<p>
<router-link
class="text-blue-500"
:to="{ name: 'help-terms' }"
>
Read them here.
</router-link>
</p>
<ul class="list-disc list-outside ml-4">
<li>
If sending images, a server stores them. They can be removed by editing each claim
and deleting the image.
</li>
<li>
If sending other partner system data (eg. to Trustroots) a public key and message
data are stored on a server. Those can be removed via direct personal request (via contact below).
</li>
<li>
For all other claim data,
<a href="https://endorser.ch/privacy-policy" target="_blank" class="text-blue-500">
the Endorser Service has this Privacy Policy.
</a>
</li>
</ul>
<h2 class="text-xl font-semibold">How can I contribute?</h2>
<p>

View File

@@ -366,7 +366,8 @@
Do Not Pair Together
</h4>
<p class="text-xs text-gray-500 mb-2">
People in the same group will not be matched with each other.
People in the same do-not-pair group will not be matched with each
other.
</p>
<p v-if="hasActiveMatches" class="text-xs text-amber-600 mb-2">
Erase matches to change restrictions.

View File

@@ -223,7 +223,7 @@ export default class QuickActionBvcBeginView extends Vue {
);
this.notify.error(
timeResult?.error || NOTIFY_BVC_TIME_ERROR.message,
TIMEOUTS.LONG,
timeResult?.error ? TIMEOUTS.MODAL : TIMEOUTS.LONG,
);
}
}
@@ -251,7 +251,7 @@ export default class QuickActionBvcBeginView extends Vue {
);
this.notify.error(
attendResult?.error || NOTIFY_BVC_ATTENDANCE_ERROR.message,
TIMEOUTS.LONG,
attendResult?.error ? TIMEOUTS.MODAL : TIMEOUTS.LONG,
);
}
}
@@ -276,7 +276,7 @@ export default class QuickActionBvcBeginView extends Vue {
logger.error("[QuickActionBvcBeginView] Error sending claims:", error);
this.notify.error(
error.userMessage || NOTIFY_BVC_SUBMISSION_ERROR.message,
TIMEOUTS.LONG,
error.userMessage ? TIMEOUTS.MODAL : TIMEOUTS.LONG,
);
}
}

View File

@@ -130,11 +130,13 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
// Refresh home view and check gift
await page.goto('./');
// Wait for async data (e.g. "new offers" banner) to finish loading so the layout
// is stable before clicking — otherwise a layout shift can redirect the click.
await page.waitForLoadState('networkidle');
// Firefox complains on load the initial feed here when we use the test server.
// It may be similar to the CORS problem below.
const item = await page.locator('li:first-child').filter({ hasText: finalTitle });
await item.locator('[data-testid="circle-info-link"]').click();
const item = page.locator('li').filter({ hasText: finalTitle }).locator('[data-testid="circle-info-link"]');
await expect(item).toBeVisible();
await item.click();
await expect(page.getByRole('heading', { name: 'Verifiable Claim Details' })).toBeVisible();
await expect(page.getByText(finalTitle, { exact: true })).toBeVisible();