Compare commits
9 Commits
notify-api
...
edit-proj-
| Author | SHA1 | Date | |
|---|---|---|---|
| 05d346edce | |||
| e259e60fa7 | |||
| 821de3f006 | |||
| 43f83031d4 | |||
| 688a48a332 | |||
| 8938c242ee | |||
| 358af42afd | |||
| 59c00241b8 | |||
| 33ec90e571 |
@@ -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:
|
##### 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
|
```bash
|
||||||
cd ios/App && xcrun agvtool new-version 65 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.3.8;/g" App.xcodeproj/project.pbxproj && cd -
|
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 -
|
||||||
# Unfortunately this edits Info.plist directly.
|
# Unfortunately this edits Info.plist directly.
|
||||||
#xcrun agvtool new-marketing-version 0.4.5
|
#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:
|
##### 1. Bump the version in package.json, then update these versions & run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
perl -p -i -e 's/versionCode .*/versionCode 65/g' android/app/build.gradle
|
perl -p -i -e 's/versionCode .*/versionCode 67/g' android/app/build.gradle
|
||||||
perl -p -i -e 's/versionName .*/versionName "1.3.8"/g' android/app/build.gradle
|
perl -p -i -e 's/versionName .*/versionName "1.3.12"/g' android/app/build.gradle
|
||||||
```
|
```
|
||||||
|
|
||||||
##### 2. Build
|
##### 2. Build
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ 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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
|
||||||
## [1.3.8] - 2026
|
## [1.3.12] - 2026.03.21
|
||||||
### Added
|
### Added
|
||||||
- Device wake-up for notifications
|
- Device wake-up for notifications
|
||||||
|
### Changed
|
||||||
|
- Rename to "Gifties"
|
||||||
|
|
||||||
|
|
||||||
## [1.3.7]
|
## [1.3.7]
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ android {
|
|||||||
applicationId "app.timesafari.app"
|
applicationId "app.timesafari.app"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 65
|
versionCode 67
|
||||||
versionName "1.3.8"
|
versionName "1.3.12"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">TimeSafari</string>
|
<string name="app_name">Giftopia</string>
|
||||||
<string name="title_activity_main">TimeSafari</string>
|
<string name="title_activity_main">Giftopia</string>
|
||||||
<string name="package_name">timesafari.app</string>
|
<string name="package_name">timesafari.app</string>
|
||||||
<string name="custom_url_scheme">timesafari.app</string>
|
<string name="custom_url_scheme">timesafari.app</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"appId": "app.timesafari",
|
"appId": "app.timesafari",
|
||||||
"appName": "TimeSafari",
|
"appName": "Giftopia",
|
||||||
"webDir": "dist",
|
"webDir": "dist",
|
||||||
"server": {
|
"server": {
|
||||||
"cleartext": true
|
"cleartext": true
|
||||||
@@ -34,12 +34,12 @@
|
|||||||
"iosIsEncryption": false,
|
"iosIsEncryption": false,
|
||||||
"iosBiometric": {
|
"iosBiometric": {
|
||||||
"biometricAuth": false,
|
"biometricAuth": false,
|
||||||
"biometricTitle": "Biometric login for TimeSafari"
|
"biometricTitle": "Biometric login for Giftopia"
|
||||||
},
|
},
|
||||||
"androidIsEncryption": false,
|
"androidIsEncryption": false,
|
||||||
"androidBiometric": {
|
"androidBiometric": {
|
||||||
"biometricAuth": false,
|
"biometricAuth": false,
|
||||||
"biometricTitle": "Biometric login for TimeSafari"
|
"biometricTitle": "Biometric login for Giftopia"
|
||||||
},
|
},
|
||||||
"electronIsEncryption": false
|
"electronIsEncryption": false
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
},
|
},
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
"appId": "app.timesafari",
|
"appId": "app.timesafari",
|
||||||
"productName": "TimeSafari",
|
"productName": "Giftopia",
|
||||||
"directories": {
|
"directories": {
|
||||||
"output": "dist-electron-packages"
|
"output": "dist-electron-packages"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { CapacitorConfig } from '@capacitor/cli';
|
|||||||
|
|
||||||
const config: CapacitorConfig = {
|
const config: CapacitorConfig = {
|
||||||
appId: 'app.timesafari',
|
appId: 'app.timesafari',
|
||||||
appName: 'TimeSafari',
|
appName: 'Giftopia',
|
||||||
webDir: 'dist',
|
webDir: 'dist',
|
||||||
server: {
|
server: {
|
||||||
cleartext: true
|
cleartext: true
|
||||||
@@ -36,12 +36,12 @@ const config: CapacitorConfig = {
|
|||||||
iosIsEncryption: false,
|
iosIsEncryption: false,
|
||||||
iosBiometric: {
|
iosBiometric: {
|
||||||
biometricAuth: false,
|
biometricAuth: false,
|
||||||
biometricTitle: 'Biometric login for TimeSafari'
|
biometricTitle: 'Biometric login for Giftopia'
|
||||||
},
|
},
|
||||||
androidIsEncryption: false,
|
androidIsEncryption: false,
|
||||||
androidBiometric: {
|
androidBiometric: {
|
||||||
biometricAuth: false,
|
biometricAuth: false,
|
||||||
biometricTitle: 'Biometric login for TimeSafari'
|
biometricTitle: 'Biometric login for Giftopia'
|
||||||
},
|
},
|
||||||
electronIsEncryption: false
|
electronIsEncryption: false
|
||||||
},
|
},
|
||||||
@@ -100,7 +100,7 @@ const config: CapacitorConfig = {
|
|||||||
},
|
},
|
||||||
buildOptions: {
|
buildOptions: {
|
||||||
appId: 'app.timesafari',
|
appId: 'app.timesafari',
|
||||||
productName: 'TimeSafari',
|
productName: 'Giftopia',
|
||||||
directories: {
|
directories: {
|
||||||
output: 'dist-electron-packages'
|
output: 'dist-electron-packages'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"appId": "app.timesafari",
|
"appId": "app.timesafari",
|
||||||
"appName": "TimeSafari",
|
"appName": "Giftopia",
|
||||||
"webDir": "dist",
|
"webDir": "dist",
|
||||||
"server": {
|
"server": {
|
||||||
"cleartext": true
|
"cleartext": true
|
||||||
@@ -34,12 +34,12 @@
|
|||||||
"iosIsEncryption": false,
|
"iosIsEncryption": false,
|
||||||
"iosBiometric": {
|
"iosBiometric": {
|
||||||
"biometricAuth": false,
|
"biometricAuth": false,
|
||||||
"biometricTitle": "Biometric login for TimeSafari"
|
"biometricTitle": "Biometric login for Giftopia"
|
||||||
},
|
},
|
||||||
"androidIsEncryption": false,
|
"androidIsEncryption": false,
|
||||||
"androidBiometric": {
|
"androidBiometric": {
|
||||||
"biometricAuth": false,
|
"biometricAuth": false,
|
||||||
"biometricTitle": "Biometric login for TimeSafari"
|
"biometricTitle": "Biometric login for Giftopia"
|
||||||
},
|
},
|
||||||
"electronIsEncryption": false
|
"electronIsEncryption": false
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
},
|
},
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
"appId": "app.timesafari",
|
"appId": "app.timesafari",
|
||||||
"productName": "TimeSafari",
|
"productName": "Giftopia",
|
||||||
"directories": {
|
"directories": {
|
||||||
"output": "dist-electron-packages"
|
"output": "dist-electron-packages"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -524,17 +524,18 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 65;
|
CURRENT_PROJECT_VERSION = 67;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Giftopia;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.3.8;
|
MARKETING_VERSION = 1.3.12;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -552,17 +553,18 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 65;
|
CURRENT_PROJECT_VERSION = 67;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Giftopia;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.3.8;
|
MARKETING_VERSION = 1.3.12;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||||
@@ -580,12 +582,12 @@
|
|||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = TimeSafariShareExtension/TimeSafariShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = TimeSafariShareExtension/TimeSafariShareExtension.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 65;
|
CURRENT_PROJECT_VERSION = 67;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = TimeSafariShareExtension/Info.plist;
|
INFOPLIST_FILE = TimeSafariShareExtension/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = TimeSafari;
|
INFOPLIST_KEY_CFBundleDisplayName = Giftopia;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -594,7 +596,7 @@
|
|||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 1.3.8;
|
MARKETING_VERSION = 1.3.12;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.TimeSafariShareExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.TimeSafariShareExtension;
|
||||||
@@ -618,12 +620,12 @@
|
|||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = TimeSafariShareExtension/TimeSafariShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = TimeSafariShareExtension/TimeSafariShareExtension.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 65;
|
CURRENT_PROJECT_VERSION = 67;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = TimeSafariShareExtension/Info.plist;
|
INFOPLIST_FILE = TimeSafariShareExtension/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = TimeSafari;
|
INFOPLIST_KEY_CFBundleDisplayName = Giftopia;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -632,7 +634,7 @@
|
|||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 1.3.8;
|
MARKETING_VERSION = 1.3.12;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.TimeSafariShareExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari.TimeSafariShareExtension;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>TimeSafari</string>
|
<string>Giftopia</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
|
|||||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "timesafari",
|
"name": "giftopia",
|
||||||
"version": "1.3.8-beta",
|
"version": "1.3.13-beta",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "timesafari",
|
"name": "giftopia",
|
||||||
"version": "1.3.8-beta",
|
"version": "1.3.13-beta",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor-community/electron": "^5.0.1",
|
"@capacitor-community/electron": "^5.0.1",
|
||||||
"@capacitor-community/sqlite": "6.0.2",
|
"@capacitor-community/sqlite": "6.0.2",
|
||||||
@@ -15597,6 +15597,16 @@
|
|||||||
"expo": "*"
|
"expo": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expo-server": {
|
||||||
|
"version": "55.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/expo-server/-/expo-server-55.0.6.tgz",
|
||||||
|
"integrity": "sha512-xI72FTm469FfuuBL2R5aNtthgH+GR7ygOpsx/KcPS0K8AZaZd7VjtEExbzn9/qyyYkWW3T+3dAmCDKOMX8gdmQ==",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/expo/node_modules/@jest/schemas": {
|
"node_modules/expo/node_modules/@jest/schemas": {
|
||||||
"version": "29.6.3",
|
"version": "29.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "timesafari",
|
"name": "giftopia",
|
||||||
"version": "1.3.8-beta",
|
"version": "1.3.13-beta",
|
||||||
"description": "Gift Economies Application",
|
"description": "Giftopia App",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Gift Economies Team"
|
"name": "Gift Economies Team"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import { PlanData } from "../interfaces/records";
|
|||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MeetingProjectDialog - Dialog for selecting a project link for a meeting
|
* ProjectSelectionDialog - Dialog for selecting a project
|
||||||
*
|
*
|
||||||
* Features:
|
* Features:
|
||||||
* - EntityGrid integration for project selection
|
* - EntityGrid integration for project selection
|
||||||
@@ -52,7 +52,7 @@ import { NotificationIface } from "../constants/app";
|
|||||||
EntityGrid,
|
EntityGrid,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class MeetingProjectDialog extends Vue {
|
export default class ProjectSelectionDialog extends Vue {
|
||||||
/** Whether the dialog is visible */
|
/** Whether the dialog is visible */
|
||||||
visible = false;
|
visible = false;
|
||||||
|
|
||||||
@@ -6,8 +6,8 @@
|
|||||||
export enum AppString {
|
export enum AppString {
|
||||||
// This is used in titles and verbiage inside the app.
|
// This is used in titles and verbiage inside the app.
|
||||||
// There is also an app name without spaces, for packaging in the package.json file used in the manifest.
|
// There is also an app name without spaces, for packaging in the package.json file used in the manifest.
|
||||||
APP_NAME = "Gift Economies",
|
APP_NAME = "Giftopia",
|
||||||
APP_NAME_NO_SPACES = "GiftEconomies",
|
APP_NAME_NO_SPACES = APP_NAME,
|
||||||
|
|
||||||
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
|
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
|
||||||
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
|
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export interface PlanActionClaim extends ClaimObject {
|
|||||||
agent?: { identifier: string };
|
agent?: { identifier: string };
|
||||||
description?: string;
|
description?: string;
|
||||||
endTime?: string;
|
endTime?: string;
|
||||||
|
fulfills?: { "@type": string; identifier?: string; lastClaimId?: string };
|
||||||
identifier?: string;
|
identifier?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
lastClaimId?: string;
|
lastClaimId?: string;
|
||||||
|
|||||||
@@ -293,16 +293,12 @@
|
|||||||
<h3
|
<h3
|
||||||
data-testid="advancedSettings"
|
data-testid="advancedSettings"
|
||||||
class="text-blue-500 text-sm font-semibold mb-3 cursor-pointer"
|
class="text-blue-500 text-sm font-semibold mb-3 cursor-pointer"
|
||||||
@click="toggleShowGeneralAdvanced"
|
@click="showAdvanced = !showAdvanced"
|
||||||
>
|
>
|
||||||
{{
|
{{ showAdvanced ? "Hide Advanced Settings" : "Show Advanced Settings" }}
|
||||||
showGeneralAdvanced
|
|
||||||
? "Hide Advanced Settings"
|
|
||||||
: "Show Advanced Settings"
|
|
||||||
}}
|
|
||||||
</h3>
|
</h3>
|
||||||
<section
|
<section
|
||||||
v-if="showGeneralAdvanced"
|
v-if="showAdvanced"
|
||||||
id="sectionAdvanced"
|
id="sectionAdvanced"
|
||||||
aria-labelledby="advancedHeading"
|
aria-labelledby="advancedHeading"
|
||||||
>
|
>
|
||||||
@@ -1095,6 +1091,7 @@ export default class AccountViewView extends Vue {
|
|||||||
this.previousPasskeyExpirationMinutes = this.passkeyExpirationMinutes;
|
this.previousPasskeyExpirationMinutes = this.passkeyExpirationMinutes;
|
||||||
this.searchBox = settings.searchBoxes?.[0] || null;
|
this.searchBox = settings.searchBoxes?.[0] || null;
|
||||||
this.showGeneralAdvanced = !!settings.showGeneralAdvanced;
|
this.showGeneralAdvanced = !!settings.showGeneralAdvanced;
|
||||||
|
this.showAdvanced = this.showGeneralAdvanced;
|
||||||
this.showShortcutBvc = !!settings.showShortcutBvc;
|
this.showShortcutBvc = !!settings.showShortcutBvc;
|
||||||
this.warnIfProdServer = !!settings.warnIfProdServer;
|
this.warnIfProdServer = !!settings.warnIfProdServer;
|
||||||
this.warnIfTestServer = !!settings.warnIfTestServer;
|
this.warnIfTestServer = !!settings.warnIfTestServer;
|
||||||
|
|||||||
@@ -398,7 +398,165 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
Network Connections section: shows nearest neighbors in the registration
|
||||||
|
graph for all DIDs in this claim. The same conventions and styling are used
|
||||||
|
in UserProfileView.vue for user-profile nearest neighbors. Keep changes in sync.
|
||||||
|
-->
|
||||||
|
<div v-if="activeDid && hasVisibleNeighbors" class="mt-8">
|
||||||
|
<h2 class="text-lg font-semibold mb-3">
|
||||||
|
Network Connections
|
||||||
|
<button
|
||||||
|
title="What is this?"
|
||||||
|
class="ml-1 align-middle"
|
||||||
|
@click="showNeighborsInfo = true"
|
||||||
|
>
|
||||||
|
<font-awesome
|
||||||
|
icon="circle-info"
|
||||||
|
class="text-base text-blue-500 cursor-pointer"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<!-- Info modal for network connections explanation -->
|
||||||
|
<div
|
||||||
|
v-if="showNeighborsInfo"
|
||||||
|
class="fixed inset-0 bg-black bg-opacity-50 flex items-start justify-center pt-16 z-50"
|
||||||
|
@click.self="showNeighborsInfo = false"
|
||||||
|
>
|
||||||
|
<div class="bg-white rounded-lg p-6 mx-4 max-w-md">
|
||||||
|
<h3 class="text-lg font-semibold mb-2">Network Connections</h3>
|
||||||
|
<p class="text-sm text-slate-700">
|
||||||
|
This section shows
|
||||||
|
{{
|
||||||
|
Object.values(claimNeighbors).flat().length === 1
|
||||||
|
? "a contact that is"
|
||||||
|
: "contacts that are"
|
||||||
|
}}
|
||||||
|
nearer to the people involved in this activity. If you want more
|
||||||
|
information, reach out to one of them and ask for an introduction.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
class="mt-4 w-full bg-blue-600 text-white py-2 rounded-md"
|
||||||
|
@click="showNeighborsInfo = false"
|
||||||
|
>
|
||||||
|
Got it
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="loadingNeighbors">
|
||||||
|
<div class="flex justify-center items-center py-8">
|
||||||
|
<font-awesome
|
||||||
|
icon="spinner"
|
||||||
|
class="fa-spin-pulse text-2xl text-slate-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="neighborsError"
|
||||||
|
class="bg-red-50 border border-red-300 rounded-md p-4"
|
||||||
|
>
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
|
<font-awesome
|
||||||
|
icon="exclamation-triangle"
|
||||||
|
class="text-red-500 mt-0.5"
|
||||||
|
/>
|
||||||
|
<p class="text-red-700">{{ neighborsError }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="Object.keys(claimNeighbors).length > 0">
|
||||||
|
<div v-for="(neighbors, did) in claimNeighbors" :key="did" class="mb-4">
|
||||||
|
<h3
|
||||||
|
v-if="Object.keys(claimNeighbors).length > 1"
|
||||||
|
class="text-sm font-medium text-slate-600 mb-1"
|
||||||
|
>
|
||||||
|
Near {{ didInfo(did as string) }}:
|
||||||
|
</h3>
|
||||||
|
<!-- DID has no linked neighbors on this server -->
|
||||||
|
<p
|
||||||
|
v-if="neighbors.length === 0"
|
||||||
|
class="text-sm text-slate-500 italic"
|
||||||
|
>
|
||||||
|
Nobody on this server is linked to {{ didInfo(did as string) }}. The
|
||||||
|
data may be a mistake, or a test, or a reference to someone on a
|
||||||
|
different system. Anyway, we have no way to contact them.
|
||||||
|
</p>
|
||||||
|
<div v-else class="space-y-2">
|
||||||
|
<div
|
||||||
|
v-for="neighbor in neighbors"
|
||||||
|
:key="neighbor.did"
|
||||||
|
class="bg-slate-50 border border-slate-300 rounded-md"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between gap-3 p-3">
|
||||||
|
<div class="flex items-center gap-2 flex-1 min-w-0">
|
||||||
|
<button
|
||||||
|
title="Copy claim link and expand"
|
||||||
|
class="text-blue-600 flex-shrink-0"
|
||||||
|
@click="onNeighborExpandClick(neighbor.did)"
|
||||||
|
>
|
||||||
|
<font-awesome
|
||||||
|
:icon="
|
||||||
|
expandedNeighborDid === neighbor.did
|
||||||
|
? 'chevron-down'
|
||||||
|
: 'chevron-right'
|
||||||
|
"
|
||||||
|
class="text-sm"
|
||||||
|
/>
|
||||||
|
{{ getNeighborDisplayName(neighbor.did) }}
|
||||||
|
</button>
|
||||||
|
<span :class="getRelationBadgeClass(neighbor.relation)">
|
||||||
|
{{ getRelationLabel(neighbor.relation) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expandedNeighborDid === neighbor.did"
|
||||||
|
class="border-t border-slate-300 p-3 bg-white"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
:to="{ path: '/did/' + encodeURIComponent(neighbor.did) }"
|
||||||
|
class="text-blue-600 hover:text-blue-800 font-medium underline"
|
||||||
|
>
|
||||||
|
Go to contact info
|
||||||
|
</router-link>
|
||||||
|
and send them the claim link from your clipboard. Ask them for
|
||||||
|
an introduction.
|
||||||
|
<div
|
||||||
|
v-if="neighborIsNotInContacts(neighbor.did)"
|
||||||
|
class="flex flex-col gap-1 mt-2"
|
||||||
|
>
|
||||||
|
<p class="text-xs text-slate-600">
|
||||||
|
This person is connected to you, but they are not in this
|
||||||
|
device's contacts. Copy this DID link and check on another
|
||||||
|
device or check with different people.
|
||||||
|
</p>
|
||||||
|
<span class="flex items-center gap-1 min-w-0">
|
||||||
|
<span class="text-xs truncate text-slate-600 min-w-0">
|
||||||
|
{{ neighbor.did }}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
title="Copy DID Link"
|
||||||
|
class="text-blue-600 hover:text-blue-800 underline cursor-pointer flex-shrink-0"
|
||||||
|
@click.prevent="
|
||||||
|
copyTextToClipboard(
|
||||||
|
'DID link',
|
||||||
|
`${APP_SERVER}/deep-link/did/${encodeURIComponent(neighbor.did)}`,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<font-awesome icon="copy" class="text-sm" />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
Note that a similar section is found in ConfirmGiftView.vue, and kinda in HiddenDidDialog.vue
|
Note that a similar section is found in ConfirmGiftView.vue, and kinda in HiddenDidDialog.vue
|
||||||
-->
|
-->
|
||||||
<h2
|
<h2
|
||||||
@@ -631,12 +789,19 @@ export default class ClaimView extends Vue {
|
|||||||
numConfsNotVisible = 0; // number of hidden DIDs in the confirmerIdList, minus the issuer if they aren't visible
|
numConfsNotVisible = 0; // number of hidden DIDs in the confirmerIdList, minus the issuer if they aren't visible
|
||||||
providersForGive: ProviderInfo[] = [];
|
providersForGive: ProviderInfo[] = [];
|
||||||
showIdCopy = false;
|
showIdCopy = false;
|
||||||
|
showNeighborsInfo = false;
|
||||||
showVeriClaimDump = false;
|
showVeriClaimDump = false;
|
||||||
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
|
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
|
||||||
veriClaimDump = "";
|
veriClaimDump = "";
|
||||||
veriClaimDidsVisible: { [key: string]: string[] } = {};
|
veriClaimDidsVisible: { [key: string]: string[] } = {};
|
||||||
windowDeepLink = window.location.href; // changed in the setup for deep linking
|
windowDeepLink = window.location.href; // changed in the setup for deep linking
|
||||||
|
|
||||||
|
// Network Connections state (same pattern as UserProfileView.vue)
|
||||||
|
claimNeighbors: Record<string, Array<{ did: string; relation: string }>> = {};
|
||||||
|
expandedNeighborDid: string | null = null;
|
||||||
|
loadingNeighbors = false;
|
||||||
|
neighborsError = "";
|
||||||
|
|
||||||
APP_SERVER = APP_SERVER;
|
APP_SERVER = APP_SERVER;
|
||||||
R = R;
|
R = R;
|
||||||
yaml = yaml;
|
yaml = yaml;
|
||||||
@@ -745,6 +910,16 @@ export default class ClaimView extends Vue {
|
|||||||
return (claim as { image?: string })?.image;
|
return (claim as { image?: string })?.image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the Network Connections section should be shown.
|
||||||
|
* Hidden if the only DIDs in claimNeighbors are the active user,
|
||||||
|
* or if there are no entries at all (after filtering).
|
||||||
|
*/
|
||||||
|
get hasVisibleNeighbors(): boolean {
|
||||||
|
const keys = Object.keys(this.claimNeighbors);
|
||||||
|
return keys.length > 0 || this.loadingNeighbors;
|
||||||
|
}
|
||||||
|
|
||||||
resetThisValues() {
|
resetThisValues() {
|
||||||
this.confirmerIdList = [];
|
this.confirmerIdList = [];
|
||||||
this.confsVisibleErrorMessage = "";
|
this.confsVisibleErrorMessage = "";
|
||||||
@@ -759,6 +934,10 @@ export default class ClaimView extends Vue {
|
|||||||
this.isEditedGlobalId = false;
|
this.isEditedGlobalId = false;
|
||||||
this.numConfsNotVisible = 0;
|
this.numConfsNotVisible = 0;
|
||||||
this.providersForGive = [];
|
this.providersForGive = [];
|
||||||
|
this.claimNeighbors = {};
|
||||||
|
this.expandedNeighborDid = null;
|
||||||
|
this.loadingNeighbors = false;
|
||||||
|
this.neighborsError = "";
|
||||||
this.veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
|
this.veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
|
||||||
this.veriClaimDump = "";
|
this.veriClaimDump = "";
|
||||||
this.veriClaimDidsVisible = {};
|
this.veriClaimDidsVisible = {};
|
||||||
@@ -825,6 +1004,7 @@ export default class ClaimView extends Vue {
|
|||||||
const claimId = this.$route.params.id as string;
|
const claimId = this.$route.params.id as string;
|
||||||
if (claimId) {
|
if (claimId) {
|
||||||
await this.loadClaim(claimId, this.activeDid);
|
await this.loadClaim(claimId, this.activeDid);
|
||||||
|
await this.loadClaimNeighbors();
|
||||||
} else {
|
} else {
|
||||||
this.notify.error("No claim ID was provided.");
|
this.notify.error("No claim ID was provided.");
|
||||||
}
|
}
|
||||||
@@ -1000,6 +1180,125 @@ export default class ClaimView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads nearest neighbors for all DIDs in this claim via the
|
||||||
|
* endorser-ch claimNearestNeighbors endpoint.
|
||||||
|
* Same display conventions as UserProfileView.vue's loadNeighbors.
|
||||||
|
*/
|
||||||
|
async loadClaimNeighbors() {
|
||||||
|
if (!this.veriClaim.id) return;
|
||||||
|
|
||||||
|
this.loadingNeighbors = true;
|
||||||
|
this.neighborsError = "";
|
||||||
|
try {
|
||||||
|
const url =
|
||||||
|
this.apiServer +
|
||||||
|
"/api/claim/claimNearestNeighbors/" +
|
||||||
|
encodeURIComponent(this.veriClaim.id as string);
|
||||||
|
const headers = await serverUtil.getHeaders(this.activeDid);
|
||||||
|
const resp = await this.axios.get(url, { headers });
|
||||||
|
if (resp.status === 200) {
|
||||||
|
const raw = resp.data.data || {};
|
||||||
|
// Filter out the current user's own DID entry — their neighbors
|
||||||
|
// aren't useful here since "You" is already known.
|
||||||
|
const filtered: Record<
|
||||||
|
string,
|
||||||
|
Array<{ did: string; relation: string }>
|
||||||
|
> = {};
|
||||||
|
for (const [did, neighbors] of Object.entries(raw)) {
|
||||||
|
if (did === this.activeDid) continue;
|
||||||
|
filtered[did] = neighbors as Array<{
|
||||||
|
did: string;
|
||||||
|
relation: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
this.claimNeighbors = filtered;
|
||||||
|
} else {
|
||||||
|
this.claimNeighbors = {};
|
||||||
|
this.neighborsError = "Failed to load network connections.";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await this.$logError(
|
||||||
|
"Error loading claim neighbors: " + JSON.stringify(error),
|
||||||
|
);
|
||||||
|
this.claimNeighbors = {};
|
||||||
|
this.neighborsError =
|
||||||
|
"An error occurred while loading network connections.";
|
||||||
|
} finally {
|
||||||
|
this.loadingNeighbors = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets display name for a neighbor DID (same as UserProfileView.vue)
|
||||||
|
*/
|
||||||
|
getNeighborDisplayName(did: string): string {
|
||||||
|
return serverUtil.didInfo(
|
||||||
|
did,
|
||||||
|
this.activeDid,
|
||||||
|
this.allMyDids,
|
||||||
|
this.allContacts,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
neighborIsNotInContacts(did: string): boolean {
|
||||||
|
return !this.allContacts.some((contact) => contact.did === did);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets human-readable label for relation type (same as UserProfileView.vue)
|
||||||
|
*/
|
||||||
|
getRelationLabel(relation: string): string {
|
||||||
|
switch (relation) {
|
||||||
|
case "REGISTERED_BY_YOU":
|
||||||
|
return "Registered by You";
|
||||||
|
case "REGISTERED_YOU":
|
||||||
|
return "Registered You";
|
||||||
|
case "TARGET":
|
||||||
|
return "Yourself";
|
||||||
|
default:
|
||||||
|
return relation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets CSS classes for relation badge styling (same as UserProfileView.vue)
|
||||||
|
*/
|
||||||
|
getRelationBadgeClass(relation: string): string {
|
||||||
|
const baseClasses =
|
||||||
|
"text-xs font-semibold px-2 py-1 rounded whitespace-nowrap";
|
||||||
|
switch (relation) {
|
||||||
|
case "REGISTERED_BY_YOU":
|
||||||
|
return `${baseClasses} bg-blue-100 text-blue-700`;
|
||||||
|
case "REGISTERED_YOU":
|
||||||
|
return `${baseClasses} bg-green-100 text-green-700`;
|
||||||
|
case "TARGET":
|
||||||
|
return `${baseClasses} bg-purple-100 text-purple-700`;
|
||||||
|
default:
|
||||||
|
return `${baseClasses} bg-slate-100 text-slate-700`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles clicking expand on a neighbor - copies claim link and toggles
|
||||||
|
*/
|
||||||
|
async onNeighborExpandClick(did: string) {
|
||||||
|
if (this.expandedNeighborDid === did) {
|
||||||
|
this.expandedNeighborDid = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await copyToClipboard(this.windowDeepLink);
|
||||||
|
this.notify.copied("Claim link");
|
||||||
|
} catch (error) {
|
||||||
|
this.$logAndConsole(`Error copying claim link: ${error}`, true);
|
||||||
|
this.notify.error("Failed to copy claim link.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.expandedNeighborDid = did;
|
||||||
|
}
|
||||||
|
|
||||||
async showFullClaim(claimId: string) {
|
async showFullClaim(claimId: string) {
|
||||||
const url =
|
const url =
|
||||||
this.apiServer + "/api/claim/full/" + encodeURIComponent(claimId);
|
this.apiServer + "/api/claim/full/" + encodeURIComponent(claimId);
|
||||||
@@ -1110,6 +1409,7 @@ export default class ClaimView extends Vue {
|
|||||||
(this.$router as Router).push(route).then(async () => {
|
(this.$router as Router).push(route).then(async () => {
|
||||||
this.resetThisValues();
|
this.resetThisValues();
|
||||||
await this.loadClaim(claimId, this.activeDid);
|
await this.loadClaim(claimId, this.activeDid);
|
||||||
|
await this.loadClaimNeighbors();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -897,7 +897,7 @@ export default class DiscoverView extends Vue {
|
|||||||
public computedStarredTabStyleClassNames() {
|
public computedStarredTabStyleClassNames() {
|
||||||
return {
|
return {
|
||||||
"inline-block": true,
|
"inline-block": true,
|
||||||
"py-3": true,
|
"py-2": true,
|
||||||
"rounded-t-lg": true,
|
"rounded-t-lg": true,
|
||||||
"border-b-2": true,
|
"border-b-2": true,
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,9 @@
|
|||||||
>
|
>
|
||||||
<h3>Troubleshooting Notifications</h3>
|
<h3>Troubleshooting Notifications</h3>
|
||||||
|
|
||||||
|
Note that the notifications will not arrive exactly at the time you set
|
||||||
|
(because phones don't let non-alarm-apps set exact alarms).
|
||||||
|
|
||||||
<h4>Check your in-app notification settings</h4>
|
<h4>Check your in-app notification settings</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Tap <strong>Profile</strong> in the bottom bar</li>
|
<li>Tap <strong>Profile</strong> in the bottom bar</li>
|
||||||
|
|||||||
@@ -114,6 +114,49 @@
|
|||||||
@assign="handleRepresentativeAssigned"
|
@assign="handleRepresentativeAssigned"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Parent Project Selection -->
|
||||||
|
<div class="w-full flex items-stretch my-4">
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-2 grow border border-slate-400 border-r-0 last:border-r px-3 py-2 rounded-l last:rounded overflow-hidden cursor-pointer hover:bg-slate-100"
|
||||||
|
@click="openParentProjectDialog"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<font-awesome icon="folder" class="text-slate-400" />
|
||||||
|
</div>
|
||||||
|
<div class="overflow-hidden">
|
||||||
|
<div
|
||||||
|
:class="{
|
||||||
|
'text-sm font-semibold': parentProjectHandleId,
|
||||||
|
'text-slate-400': !parentProjectHandleId,
|
||||||
|
}"
|
||||||
|
class="truncate"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
parentProjectHandleId
|
||||||
|
? parentProjectName || "Parent Project"
|
||||||
|
: "Select Parent Project\u2026"
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="parentProjectHandleId"
|
||||||
|
class="text-rose-600 px-3 py-2 border border-slate-400 border-l-0 rounded-r hover:bg-rose-600 hover:text-white hover:border-rose-600"
|
||||||
|
@click="unsetParentProject"
|
||||||
|
>
|
||||||
|
<font-awesome icon="trash-can" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ProjectSelectionDialog
|
||||||
|
ref="parentProjectDialog"
|
||||||
|
:active-did="activeDid"
|
||||||
|
:all-my-dids="allMyDids"
|
||||||
|
:all-contacts="allContacts"
|
||||||
|
:notify="$notify"
|
||||||
|
@assign="handleParentProjectSelected"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<p v-if="shouldShowOwnershipWarning">
|
<p v-if="shouldShowOwnershipWarning">
|
||||||
<span class="text-red-500">Beware!</span>
|
<span class="text-red-500">Beware!</span>
|
||||||
@@ -283,6 +326,7 @@ import { LeafletMouseEvent } from "leaflet";
|
|||||||
import EntityIcon from "../components/EntityIcon.vue";
|
import EntityIcon from "../components/EntityIcon.vue";
|
||||||
import ImageMethodDialog from "../components/ImageMethodDialog.vue";
|
import ImageMethodDialog from "../components/ImageMethodDialog.vue";
|
||||||
import ProjectRepresentativeDialog from "../components/ProjectRepresentativeDialog.vue";
|
import ProjectRepresentativeDialog from "../components/ProjectRepresentativeDialog.vue";
|
||||||
|
import ProjectSelectionDialog from "../components/ProjectSelectionDialog.vue";
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import {
|
import {
|
||||||
AppString,
|
AppString,
|
||||||
@@ -311,6 +355,7 @@ import {
|
|||||||
PROJECT_TIMEOUT_VERY_LONG,
|
PROJECT_TIMEOUT_VERY_LONG,
|
||||||
} from "../constants/notifications";
|
} from "../constants/notifications";
|
||||||
import { PlanActionClaim } from "../interfaces/claims";
|
import { PlanActionClaim } from "../interfaces/claims";
|
||||||
|
import { PlanData } from "../interfaces/records";
|
||||||
import {
|
import {
|
||||||
createEndorserJwtVcFromClaim,
|
createEndorserJwtVcFromClaim,
|
||||||
getHeaders,
|
getHeaders,
|
||||||
@@ -378,6 +423,7 @@ import { logger } from "../utils/logger";
|
|||||||
components: {
|
components: {
|
||||||
EntityIcon,
|
EntityIcon,
|
||||||
ImageMethodDialog,
|
ImageMethodDialog,
|
||||||
|
ProjectSelectionDialog,
|
||||||
ProjectRepresentativeDialog,
|
ProjectRepresentativeDialog,
|
||||||
LMap,
|
LMap,
|
||||||
LMarker,
|
LMarker,
|
||||||
@@ -429,6 +475,8 @@ export default class NewEditProjectView extends Vue {
|
|||||||
latitude = 0;
|
latitude = 0;
|
||||||
longitude = 0;
|
longitude = 0;
|
||||||
numAccounts = 0;
|
numAccounts = 0;
|
||||||
|
parentProjectHandleId = "";
|
||||||
|
parentProjectName = "";
|
||||||
projectId = "";
|
projectId = "";
|
||||||
projectIssuerDid = "";
|
projectIssuerDid = "";
|
||||||
sendToTrustroots = false;
|
sendToTrustroots = false;
|
||||||
@@ -510,6 +558,10 @@ export default class NewEditProjectView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.fullClaim?.fulfills?.identifier) {
|
||||||
|
this.parentProjectHandleId = this.fullClaim.fulfills.identifier;
|
||||||
|
this.loadParentProjectName(this.parentProjectHandleId);
|
||||||
|
}
|
||||||
if (this.fullClaim.startTime) {
|
if (this.fullClaim.startTime) {
|
||||||
const localDateTime = DateTime.fromISO(
|
const localDateTime = DateTime.fromISO(
|
||||||
this.fullClaim.startTime as string,
|
this.fullClaim.startTime as string,
|
||||||
@@ -623,6 +675,14 @@ export default class NewEditProjectView extends Vue {
|
|||||||
} else {
|
} else {
|
||||||
delete vcClaim.agent;
|
delete vcClaim.agent;
|
||||||
}
|
}
|
||||||
|
if (this.parentProjectHandleId) {
|
||||||
|
vcClaim.fulfills = {
|
||||||
|
"@type": "PlanAction",
|
||||||
|
identifier: this.parentProjectHandleId,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
delete vcClaim.fulfills;
|
||||||
|
}
|
||||||
if (this.imageUrl) {
|
if (this.imageUrl) {
|
||||||
vcClaim.image = this.imageUrl;
|
vcClaim.image = this.imageUrl;
|
||||||
} else {
|
} else {
|
||||||
@@ -1075,5 +1135,33 @@ export default class NewEditProjectView extends Vue {
|
|||||||
unsetRepresentative(): void {
|
unsetRepresentative(): void {
|
||||||
this.agentDid = "";
|
this.agentDid = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openParentProjectDialog(): void {
|
||||||
|
(this.$refs.parentProjectDialog as ProjectSelectionDialog).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleParentProjectSelected(project: PlanData): void {
|
||||||
|
this.parentProjectHandleId = project.handleId;
|
||||||
|
this.parentProjectName = project.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsetParentProject(): void {
|
||||||
|
this.parentProjectHandleId = "";
|
||||||
|
this.parentProjectName = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadParentProjectName(handleId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const url =
|
||||||
|
this.apiServer + "/api/claim/byHandle/" + encodeURIComponent(handleId);
|
||||||
|
const headers = await getHeaders(this.activeDid);
|
||||||
|
const resp = await this.axios.get(url, { headers });
|
||||||
|
if (resp.status === 200 && resp.data?.claim?.name) {
|
||||||
|
this.parentProjectName = resp.data.claim.name;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Parent project name will remain empty
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -267,7 +267,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MeetingProjectDialog
|
<ProjectSelectionDialog
|
||||||
ref="meetingProjectDialog"
|
ref="meetingProjectDialog"
|
||||||
:active-did="activeDid"
|
:active-did="activeDid"
|
||||||
:all-my-dids="allMyDids"
|
:all-my-dids="allMyDids"
|
||||||
@@ -585,7 +585,7 @@ import TopMessage from "../components/TopMessage.vue";
|
|||||||
import MeetingMembersList from "../components/MeetingMembersList.vue";
|
import MeetingMembersList from "../components/MeetingMembersList.vue";
|
||||||
import MeetingMemberMatch from "../components/MeetingMemberMatch.vue";
|
import MeetingMemberMatch from "../components/MeetingMemberMatch.vue";
|
||||||
import MeetingExclusionGroups from "../components/MeetingExclusionGroups.vue";
|
import MeetingExclusionGroups from "../components/MeetingExclusionGroups.vue";
|
||||||
import MeetingProjectDialog from "../components/MeetingProjectDialog.vue";
|
import ProjectSelectionDialog from "../components/ProjectSelectionDialog.vue";
|
||||||
import ProjectIcon from "../components/ProjectIcon.vue";
|
import ProjectIcon from "../components/ProjectIcon.vue";
|
||||||
import {
|
import {
|
||||||
errorStringForLog,
|
errorStringForLog,
|
||||||
@@ -637,7 +637,7 @@ interface MeetingSetupInputs {
|
|||||||
MeetingMembersList,
|
MeetingMembersList,
|
||||||
MeetingMemberMatch,
|
MeetingMemberMatch,
|
||||||
MeetingExclusionGroups,
|
MeetingExclusionGroups,
|
||||||
MeetingProjectDialog,
|
ProjectSelectionDialog,
|
||||||
ProjectIcon,
|
ProjectIcon,
|
||||||
},
|
},
|
||||||
mixins: [PlatformServiceMixin],
|
mixins: [PlatformServiceMixin],
|
||||||
@@ -1468,7 +1468,7 @@ export default class OnboardMeetingView extends Vue {
|
|||||||
* Open the project link selection dialog
|
* Open the project link selection dialog
|
||||||
*/
|
*/
|
||||||
openProjectLinkDialog(): void {
|
openProjectLinkDialog(): void {
|
||||||
(this.$refs.meetingProjectDialog as MeetingProjectDialog).open();
|
(this.$refs.meetingProjectDialog as ProjectSelectionDialog).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -193,7 +193,7 @@
|
|||||||
class="bg-slate-100 px-4 py-3 rounded-md"
|
class="bg-slate-100 px-4 py-3 rounded-md"
|
||||||
>
|
>
|
||||||
<h3 class="text-sm uppercase font-semibold mt-3">
|
<h3 class="text-sm uppercase font-semibold mt-3">
|
||||||
Projects That Contribute To This
|
These Projects Are Part Of This
|
||||||
</h3>
|
</h3>
|
||||||
<!--
|
<!--
|
||||||
centering because long, wrapped project names didn't left align with blank
|
centering because long, wrapped project names didn't left align with blank
|
||||||
@@ -218,7 +218,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md">
|
<div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md">
|
||||||
<h3 class="text-sm uppercase font-semibold mb-3">
|
<h3 class="text-sm uppercase font-semibold mb-3">
|
||||||
Projects Getting Contributions From This
|
This Project Is Part Of These
|
||||||
</h3>
|
</h3>
|
||||||
<!--
|
<!--
|
||||||
centering because long, wrapped project names didn't left align with blank
|
centering because long, wrapped project names didn't left align with blank
|
||||||
|
|||||||
@@ -54,7 +54,11 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Nearest Neighbors Section -->
|
<!--
|
||||||
|
Network Connections section: shows nearest neighbors in the registration
|
||||||
|
graph for this user profile. The same conventions and styling are used in
|
||||||
|
ClaimView.vue for claim-level nearest neighbors. Keep changes in sync.
|
||||||
|
-->
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
profile.issuerDid !== activeDid && // only show neighbors if they're not current user
|
profile.issuerDid !== activeDid && // only show neighbors if they're not current user
|
||||||
@@ -63,7 +67,46 @@
|
|||||||
"
|
"
|
||||||
class="mt-6"
|
class="mt-6"
|
||||||
>
|
>
|
||||||
<h2 class="text-lg font-semibold mb-3">Network Connections</h2>
|
<h2 class="text-lg font-semibold mb-3">
|
||||||
|
Network Connections
|
||||||
|
<button
|
||||||
|
title="What is this?"
|
||||||
|
class="ml-1 align-middle"
|
||||||
|
@click="showNeighborsInfo = true"
|
||||||
|
>
|
||||||
|
<font-awesome
|
||||||
|
icon="circle-info"
|
||||||
|
class="text-base text-blue-500 cursor-pointer"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<!-- Info modal for network connections explanation -->
|
||||||
|
<div
|
||||||
|
v-if="showNeighborsInfo"
|
||||||
|
class="fixed inset-0 bg-black bg-opacity-50 flex items-start justify-center pt-16 z-50"
|
||||||
|
@click.self="showNeighborsInfo = false"
|
||||||
|
>
|
||||||
|
<div class="bg-white rounded-lg p-6 mx-4 max-w-md">
|
||||||
|
<h3 class="text-lg font-semibold mb-2">Network Connections</h3>
|
||||||
|
<p class="text-sm text-slate-700">
|
||||||
|
This section shows
|
||||||
|
{{
|
||||||
|
neighbors.length === 1
|
||||||
|
? "a contact that is"
|
||||||
|
: "contacts that are"
|
||||||
|
}}
|
||||||
|
nearer to this person. If you want more information, reach out to
|
||||||
|
one of them and ask for an introduction.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
class="mt-4 w-full bg-blue-600 text-white py-2 rounded-md"
|
||||||
|
@click="showNeighborsInfo = false"
|
||||||
|
>
|
||||||
|
Got it
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="loadingNeighbors">
|
<div v-if="loadingNeighbors">
|
||||||
<div class="flex justify-center items-center py-8">
|
<div class="flex justify-center items-center py-8">
|
||||||
@@ -124,8 +167,8 @@
|
|||||||
>
|
>
|
||||||
Go to contact info
|
Go to contact info
|
||||||
</router-link>
|
</router-link>
|
||||||
and send them the link in your clipboard and ask for an
|
and send them the profile link from your clipboard. Ask them to
|
||||||
introduction to this person.
|
introduce you to this person.
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
getNeighborDisplayName(neighbor.did) === '' ||
|
getNeighborDisplayName(neighbor.did) === '' ||
|
||||||
@@ -269,6 +312,7 @@ export default class UserProfileView extends Vue {
|
|||||||
neighborsError = "";
|
neighborsError = "";
|
||||||
partnerApiServer = DEFAULT_PARTNER_API_SERVER;
|
partnerApiServer = DEFAULT_PARTNER_API_SERVER;
|
||||||
profile: UserProfile | null = null;
|
profile: UserProfile | null = null;
|
||||||
|
showNeighborsInfo = false;
|
||||||
|
|
||||||
// make this function available to the Vue template
|
// make this function available to the Vue template
|
||||||
didInfo = didInfo;
|
didInfo = didInfo;
|
||||||
|
|||||||
Reference in New Issue
Block a user