Compare commits
20 Commits
ui-fixes-2
...
side_step
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02bf0b3f1a | ||
|
|
89d970da1d | ||
|
|
cb03df9240 | ||
|
|
20620c3aae | ||
|
|
9d04db4a71 | ||
|
|
1a9c97fe88 | ||
|
|
3b4f4dc125 | ||
|
|
f6802cd160 | ||
|
|
a2e19d7e9a | ||
|
|
42055a2d66 | ||
|
|
dc16cb393e | ||
|
|
c708716675 | ||
|
|
fbb9fba347 | ||
|
|
3b7a872ae1 | ||
|
|
a8e15804a6 | ||
|
|
cee7a6ded3 | ||
|
|
d2157a7d8c | ||
|
|
fbdf72557c | ||
|
|
74a412745a | ||
|
|
eaf0b76e9e |
33
.eslintrc.js
33
.eslintrc.js
@@ -5,30 +5,27 @@ module.exports = {
|
|||||||
es2022: true,
|
es2022: true,
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
"plugin:vue/vue3-essential",
|
"plugin:vue/vue3-recommended",
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"@vue/typescript/recommended",
|
"@vue/typescript/recommended",
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended"
|
||||||
],
|
],
|
||||||
// parserOptions: {
|
// parserOptions: {
|
||||||
// ecmaVersion: 2020,
|
// ecmaVersion: 2020,
|
||||||
// },
|
// },
|
||||||
rules: {
|
rules: {
|
||||||
"max-len": [
|
"max-len": ["warn", {
|
||||||
"warn",
|
code: 100,
|
||||||
{
|
ignoreComments: true,
|
||||||
code: 120,
|
ignorePattern: '^\\s*class="[^"]*"$',
|
||||||
ignoreComments: true, // why does this not make it allow comment of any length?
|
ignoreStrings: true,
|
||||||
ignorePattern: '^\\s*class="[^"]*"$',
|
ignoreTemplateLiterals: true,
|
||||||
ignoreStrings: true,
|
ignoreUrls: true,
|
||||||
ignoreTemplateLiterals: true,
|
}],
|
||||||
ignoreTrailingComments: true,
|
"no-console": process.env.NODE_ENV === "production" ? "error" : "warn",
|
||||||
ignoreUrls: true,
|
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "warn",
|
||||||
},
|
"@typescript-eslint/no-explicit-any": "warn",
|
||||||
],
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
"@typescript-eslint/no-unnecessary-type-constraint": "off"
|
||||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
|
||||||
// "prettier/prettier": ["warn", { printWidth: 120 }], // removes errors but adds thousands of warnings
|
|
||||||
"@typescript-eslint/no-unnecessary-type-constraint": "off",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -36,4 +36,7 @@ pnpm-debug.log*
|
|||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
/dist-electron-build/
|
/dist-electron-build/
|
||||||
/dist-capacitor/
|
/dist-capacitor/
|
||||||
/test-playwright-results/
|
/test-playwright-results/
|
||||||
|
playwright-tests
|
||||||
|
test-playwright
|
||||||
|
dist-electron-packages
|
||||||
54
BUILDING.md
54
BUILDING.md
@@ -14,12 +14,14 @@ This guide explains how to build TimeSafari for different platforms.
|
|||||||
## Initial Setup
|
## Initial Setup
|
||||||
|
|
||||||
1. Clone the repository:
|
1. Clone the repository:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone [repository-url]
|
git clone [repository-url]
|
||||||
cd TimeSafari
|
cd TimeSafari
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install dependencies:
|
2. Install dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
@@ -29,6 +31,7 @@ This guide explains how to build TimeSafari for different platforms.
|
|||||||
To build for web deployment:
|
To build for web deployment:
|
||||||
|
|
||||||
1. Run the production build:
|
1. Run the production build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
@@ -36,6 +39,7 @@ To build for web deployment:
|
|||||||
2. The built files will be in the `dist` directory.
|
2. The built files will be in the `dist` directory.
|
||||||
|
|
||||||
3. To test the production build locally:
|
3. To test the production build locally:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run serve
|
npm run serve
|
||||||
```
|
```
|
||||||
@@ -45,11 +49,13 @@ To build for web deployment:
|
|||||||
### Building for Linux
|
### Building for Linux
|
||||||
|
|
||||||
1. Build the electron app in production mode:
|
1. Build the electron app in production mode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build:electron-prod
|
npm run build:electron-prod
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Package the Electron app for Linux:
|
2. Package the Electron app for Linux:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# For AppImage (recommended)
|
# For AppImage (recommended)
|
||||||
npm run electron:build-linux
|
npm run electron:build-linux
|
||||||
@@ -65,12 +71,14 @@ To build for web deployment:
|
|||||||
### Running the Packaged App
|
### Running the Packaged App
|
||||||
|
|
||||||
- AppImage: Make executable and run
|
- AppImage: Make executable and run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
chmod +x dist-electron-packages/TimeSafari-*.AppImage
|
chmod +x dist-electron-packages/TimeSafari-*.AppImage
|
||||||
./dist-electron-packages/TimeSafari-*.AppImage
|
./dist-electron-packages/TimeSafari-*.AppImage
|
||||||
```
|
```
|
||||||
|
|
||||||
- DEB: Install and run
|
- DEB: Install and run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo dpkg -i dist-electron-packages/timesafari_*_amd64.deb
|
sudo dpkg -i dist-electron-packages/timesafari_*_amd64.deb
|
||||||
timesafari
|
timesafari
|
||||||
@@ -95,21 +103,25 @@ npm run build:electron-prod && npm run electron:start
|
|||||||
Prerequisites: macOS with Xcode installed
|
Prerequisites: macOS with Xcode installed
|
||||||
|
|
||||||
1. Build the web assets:
|
1. Build the web assets:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build -- --mode capacitor
|
npm run build -- --mode capacitor
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Add iOS platform if not already added:
|
2. Add iOS platform if not already added:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx cap add ios
|
npx cap add ios
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Update iOS project with latest build:
|
3. Update iOS project with latest build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx cap sync ios
|
npx cap sync ios
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Open the project in Xcode:
|
4. Open the project in Xcode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx cap open ios
|
npx cap open ios
|
||||||
```
|
```
|
||||||
@@ -121,21 +133,25 @@ Prerequisites: macOS with Xcode installed
|
|||||||
Prerequisites: Android Studio with SDK installed
|
Prerequisites: Android Studio with SDK installed
|
||||||
|
|
||||||
1. Build the web assets:
|
1. Build the web assets:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build -- --mode capacitor
|
npm run build -- --mode capacitor
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Add Android platform if not already added:
|
2. Add Android platform if not already added:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx cap add android
|
npx cap add android
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Update Android project with latest build:
|
3. Update Android project with latest build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx cap sync android
|
npx cap sync android
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Open the project in Android Studio:
|
4. Open the project in Android Studio:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx cap open android
|
npx cap open android
|
||||||
```
|
```
|
||||||
@@ -147,25 +163,29 @@ Prerequisites: Android Studio with SDK installed
|
|||||||
To run the application in development mode:
|
To run the application in development mode:
|
||||||
|
|
||||||
1. Start the development server:
|
1. Start the development server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## PyWebView Desktop Build
|
## PyWebView Desktop Build
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites for PyWebView
|
||||||
|
|
||||||
- Python 3.8 or higher
|
- Python 3.8 or higher
|
||||||
- pip (Python package manager)
|
- pip (Python package manager)
|
||||||
- virtualenv (recommended)
|
- virtualenv (recommended)
|
||||||
- System dependencies:
|
- System dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# For Ubuntu/Debian
|
# For Ubuntu/Debian
|
||||||
sudo apt-get install python3-webview
|
sudo apt-get install python3-webview
|
||||||
# or
|
# or
|
||||||
sudo apt-get install python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-webkit2-4.0
|
sudo apt-get install python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-webkit2-4.0
|
||||||
|
|
||||||
# For Arch Linux
|
# For Arch Linux
|
||||||
sudo pacman -S webkit2gtk python-gobject python-cairo
|
sudo pacman -S webkit2gtk python-gobject python-cairo
|
||||||
|
|
||||||
# For Fedora
|
# For Fedora
|
||||||
sudo dnf install python3-webview
|
sudo dnf install python3-webview
|
||||||
# or
|
# or
|
||||||
@@ -173,7 +193,9 @@ To run the application in development mode:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
1. Create and activate a virtual environment (recommended):
|
1. Create and activate a virtual environment (recommended):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m venv .venv
|
python -m venv .venv
|
||||||
source .venv/bin/activate # On Linux/macOS
|
source .venv/bin/activate # On Linux/macOS
|
||||||
@@ -182,6 +204,7 @@ To run the application in development mode:
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. Install Python dependencies:
|
2. Install Python dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
@@ -189,13 +212,16 @@ To run the application in development mode:
|
|||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
If encountering PyInstaller version errors:
|
If encountering PyInstaller version errors:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Try installing the latest stable version
|
# Try installing the latest stable version
|
||||||
pip install --upgrade pyinstaller
|
pip install --upgrade pyinstaller
|
||||||
```
|
```
|
||||||
|
|
||||||
### Development
|
### Development of PyWebView
|
||||||
|
|
||||||
1. Start the PyWebView development build:
|
1. Start the PyWebView development build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run pywebview:dev
|
npm run pywebview:dev
|
||||||
```
|
```
|
||||||
@@ -203,31 +229,39 @@ pip install --upgrade pyinstaller
|
|||||||
### Building for Distribution
|
### Building for Distribution
|
||||||
|
|
||||||
#### Linux
|
#### Linux
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run pywebview:package-linux
|
npm run pywebview:package-linux
|
||||||
```
|
```
|
||||||
|
|
||||||
The packaged application will be in `dist/TimeSafari`
|
The packaged application will be in `dist/TimeSafari`
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run pywebview:package-win
|
npm run pywebview:package-win
|
||||||
```
|
```
|
||||||
|
|
||||||
The packaged application will be in `dist/TimeSafari`
|
The packaged application will be in `dist/TimeSafari`
|
||||||
|
|
||||||
#### macOS
|
#### macOS
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run pywebview:package-mac
|
npm run pywebview:package-mac
|
||||||
```
|
```
|
||||||
|
|
||||||
The packaged application will be in `dist/TimeSafari`
|
The packaged application will be in `dist/TimeSafari`
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
Run local tests:
|
Run local tests:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run test-local
|
npm run test-local
|
||||||
```
|
```
|
||||||
|
|
||||||
Run all tests (includes building):
|
Run all tests (includes building):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run test-all
|
npm run test-all
|
||||||
```
|
```
|
||||||
@@ -235,11 +269,13 @@ npm run test-all
|
|||||||
## Linting
|
## Linting
|
||||||
|
|
||||||
Check code style:
|
Check code style:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run lint
|
npm run lint
|
||||||
```
|
```
|
||||||
|
|
||||||
Fix code style issues:
|
Fix code style issues:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run lint-fix
|
npm run lint-fix
|
||||||
```
|
```
|
||||||
@@ -260,16 +296,20 @@ See `.env.*` files for configuration.
|
|||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
### Version Management
|
### Version Management
|
||||||
|
|
||||||
1. Update CHANGELOG.md with new changes
|
1. Update CHANGELOG.md with new changes
|
||||||
2. Update version in package.json
|
2. Update version in package.json
|
||||||
3. Commit changes and tag release:
|
3. Commit changes and tag release:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git tag <VERSION_TAG>
|
git tag <VERSION_TAG>
|
||||||
git push origin <VERSION_TAG>
|
git push origin <VERSION_TAG>
|
||||||
```
|
```
|
||||||
|
|
||||||
4. After deployment, update package.json with next version + "-beta"
|
4. After deployment, update package.json with next version + "-beta"
|
||||||
|
|
||||||
### Test Server
|
### Test Server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build using staging environment
|
# Build using staging environment
|
||||||
npm run build -- --mode staging
|
npm run build -- --mode staging
|
||||||
@@ -279,6 +319,7 @@ rsync -azvu -e "ssh -i ~/.ssh/<YOUR_KEY>" dist ubuntutest@test.timesafari.app:ti
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Production Server
|
### Production Server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# On the production server:
|
# On the production server:
|
||||||
pkgx +npm sh
|
pkgx +npm sh
|
||||||
@@ -293,7 +334,7 @@ cd -
|
|||||||
mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist time-safari/
|
mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist time-safari/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting Builds
|
||||||
|
|
||||||
### Common Build Issues
|
### Common Build Issues
|
||||||
|
|
||||||
@@ -310,4 +351,3 @@ mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist
|
|||||||
- For iOS: Xcode command line tools must be installed
|
- For iOS: Xcode command line tools must be installed
|
||||||
- For Android: Correct SDK version must be installed
|
- For Android: Correct SDK version must be installed
|
||||||
- Check Capacitor configuration in capacitor.config.ts
|
- Check Capacitor configuration in capacitor.config.ts
|
||||||
|
|
||||||
|
|||||||
596
CHANGELOG.md
596
CHANGELOG.md
@@ -5,279 +5,348 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
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).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [0.4.5] - 2025.02.23
|
|
||||||
### Added
|
|
||||||
- Total amounts of gives on project page
|
|
||||||
### Changed in DB or environment
|
|
||||||
- Requires Endorser.ch version 4.2.6+
|
|
||||||
|
|
||||||
|
|
||||||
## [0.4.4] - 2025.02.17
|
## [0.4.4] - 2025.02.17
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.4.4
|
||||||
|
|
||||||
- On production (due to data?) the search results would disappear after scrolling down. Now we don't show any results when going to the people map with a shortcut.
|
- On production (due to data?) the search results would disappear after scrolling down. Now we don't show any results when going to the people map with a shortcut.
|
||||||
|
|
||||||
|
|
||||||
## [0.4.3] - 2025.02.17
|
## [0.4.3] - 2025.02.17
|
||||||
### Added
|
|
||||||
|
### Added in 0.4.3
|
||||||
|
|
||||||
- Discover query parameter searchPeople to go directly to the people map
|
- Discover query parameter searchPeople to go directly to the people map
|
||||||
|
|
||||||
|
|
||||||
## [0.4.2] - 2025.02.17
|
## [0.4.2] - 2025.02.17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Capacitor build to Android
|
|
||||||
|
- Capacitor on iOS and Android
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Path issues
|
- Path issues
|
||||||
|
|
||||||
|
|
||||||
## [0.4.1] - 2025.02.16
|
## [0.4.1] - 2025.02.16
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.4.1
|
||||||
|
|
||||||
- nostr build issue
|
- nostr build issue
|
||||||
- Linting
|
- Linting
|
||||||
|
|
||||||
|
|
||||||
## [0.4.0] - 2025.02.14
|
## [0.4.0] - 2025.02.14
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Images in the home feed now take up the full width of the card.
|
- Images in the home feed now take up the full width of the card.
|
||||||
- Clicking the image previously, would open the image in a new tab. Now, clicking the image opens the image in a lightbox view.
|
- Clicking the image previously, would open the image in a new tab. Now, clicking the image opens the image in a lightbox view.
|
||||||
### Added
|
|
||||||
|
### Added in 0.4.0
|
||||||
|
|
||||||
- Clicking an image also now displays an in-app lightbox view of the image.
|
- Clicking an image also now displays an in-app lightbox view of the image.
|
||||||
- The lightbox view includes a download button for the image in mobile view.
|
- The lightbox view includes a download button for the image in mobile view.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.57] - 2025.02.11
|
## [0.3.57] - 2025.02.11
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.57
|
||||||
|
|
||||||
- Automatic user creation in onboarding meetings
|
- Automatic user creation in onboarding meetings
|
||||||
|
|
||||||
|
|
||||||
## [0.3.55] - 2025.02.07
|
## [0.3.55] - 2025.02.07
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.55
|
||||||
|
|
||||||
- End time for projects
|
- End time for projects
|
||||||
|
|
||||||
|
|
||||||
## [0.3.54] - 2025.02.06
|
## [0.3.54] - 2025.02.06
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.54
|
||||||
|
|
||||||
- Group onboarding meetings
|
- Group onboarding meetings
|
||||||
|
|
||||||
|
|
||||||
## [0.3.53] - 2025.01.30
|
## [0.3.53] - 2025.01.30
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.53
|
||||||
|
|
||||||
- Hints for contacting the creator of a project
|
- Hints for contacting the creator of a project
|
||||||
|
|
||||||
|
|
||||||
## [0.3.52] - 2025.01.22
|
## [0.3.52] - 2025.01.22
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.52
|
||||||
|
|
||||||
- User profile endpoint server for map was broken.
|
- User profile endpoint server for map was broken.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.51] - 2025.01.22
|
## [0.3.51] - 2025.01.22
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.51
|
||||||
|
|
||||||
- User profile map jumped on first zoom.
|
- User profile map jumped on first zoom.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.50] - 2025.01.20 - b9fedcd3fd3e34c3fb0fc79150d1a81a76eaeb40
|
## [0.3.50] - 2025.01.20 - b9fedcd3fd3e34c3fb0fc79150d1a81a76eaeb40
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.50
|
||||||
|
|
||||||
- User public profiles
|
- User public profiles
|
||||||
|
|
||||||
|
|
||||||
## [0.3.49] - 2025.01.09 - 36301ed238ff84df25bb11a8d44a295ee7eaf0f8
|
## [0.3.49] - 2025.01.09 - 36301ed238ff84df25bb11a8d44a295ee7eaf0f8
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.49
|
||||||
|
|
||||||
- Make all external contact links direct to the contact-import page.
|
- Make all external contact links direct to the contact-import page.
|
||||||
- Handle all new-single-contact JWTs in the contacts page, and multiple-contact JWTs in the contacts-import page.
|
- Handle all new-single-contact JWTs in the contacts page, and multiple-contact JWTs in the contacts-import page.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.48] - 2025.01.08 - 398f3e64a376789f7eb1c400cd886f5a2cacd588 (but app shows 07c4e58)
|
## [0.3.48] - 2025.01.08 - 398f3e64a376789f7eb1c400cd886f5a2cacd588 (but app shows 07c4e58)
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.48
|
||||||
|
|
||||||
- More sanity-checks on contact-import JWT
|
- More sanity-checks on contact-import JWT
|
||||||
|
|
||||||
|
|
||||||
## [0.3.47] - 2025.01.06 - 5bf6dd1ee32ca7cc46d39bd7afca58365b422f93
|
## [0.3.47] - 2025.01.06 - 5bf6dd1ee32ca7cc46d39bd7afca58365b422f93
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.47
|
||||||
|
|
||||||
- Notes on contacts page with new contact-edit page
|
- Notes on contacts page with new contact-edit page
|
||||||
- Contact methods (only on contact-edit page and under DID details)
|
- Contact methods (only on contact-edit page and under DID details)
|
||||||
- DID view with no DID shows user's info.
|
- DID view with no DID shows user's info.
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.47
|
||||||
|
|
||||||
- URL for user's contact info is now URL to this app (not endorser.ch).
|
- URL for user's contact info is now URL to this app (not endorser.ch).
|
||||||
- Extended details (eg. full claim) is beneath details link on claim page.
|
- Extended details (eg. full claim) is beneath details link on claim page.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.46] - 2025.01.03 - 9e7056616b5e5acc51e5a8cf7354d408029fefb3
|
## [0.3.46] - 2025.01.03 - 9e7056616b5e5acc51e5a8cf7354d408029fefb3
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.46
|
||||||
|
|
||||||
- More action-oriented questions for the gift prompts
|
- More action-oriented questions for the gift prompts
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.46
|
||||||
|
|
||||||
- Contact-list import set visibility for all, even if not chosen.
|
- Contact-list import set visibility for all, even if not chosen.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.45] - 2025.01.01 - 65402dc68ce69ccc6cb9aa8d2e7a9249bf4298e0
|
## [0.3.45] - 2025.01.01 - 65402dc68ce69ccc6cb9aa8d2e7a9249bf4298e0
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.45
|
||||||
|
|
||||||
- Previous project links stayed when following a link.
|
- Previous project links stayed when following a link.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.44] - 2024.12.31 - 694b22987b05482e4527c2478bbe15e6b6f3b532
|
## [0.3.44] - 2024.12.31 - 694b22987b05482e4527c2478bbe15e6b6f3b532
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.44
|
||||||
|
|
||||||
- Project counts on a map
|
- Project counts on a map
|
||||||
|
|
||||||
|
|
||||||
## [0.3.42] - 2024.12.27 - 9751934bc24a1040415a8cfeacbae59ed91f92a5
|
## [0.3.42] - 2024.12.27 - 9751934bc24a1040415a8cfeacbae59ed91f92a5
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.42
|
||||||
|
|
||||||
- Link from certificate page to the claim
|
- Link from certificate page to the claim
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.42
|
||||||
|
|
||||||
- Contact data sharing is now a verified JWT.
|
- Contact data sharing is now a verified JWT.
|
||||||
- Feed pictures are larger.
|
- Feed pictures are larger.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.41] - 2024.12.21 - ff6d14138f26daea6216b051562f0a04681f69fc
|
## [0.3.41] - 2024.12.21 - ff6d14138f26daea6216b051562f0a04681f69fc
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.41
|
||||||
|
|
||||||
- Link from certificate page to the claim
|
- Link from certificate page to the claim
|
||||||
|
|
||||||
|
|
||||||
## [0.3.40] - 2024.12.20 - 77290d9fed3c364243793dc3e9bfe2e994a016b8
|
## [0.3.40] - 2024.12.20 - 77290d9fed3c364243793dc3e9bfe2e994a016b8
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.40
|
||||||
|
|
||||||
- Only show issuer on certificate if it's not the agent.
|
- Only show issuer on certificate if it's not the agent.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.39] - 2024.12.20 - d8819155e2acd2b57fdab523168fa5d1d09e80cc
|
## [0.3.39] - 2024.12.20 - d8819155e2acd2b57fdab523168fa5d1d09e80cc
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.39
|
||||||
|
|
||||||
- Page for a framed claim certificate
|
- Page for a framed claim certificate
|
||||||
|
|
||||||
|
|
||||||
## [0.3.38] - 2024.12.14 - f8cae5ad4fee1f114320dcce052299eab12108b2
|
## [0.3.38] - 2024.12.14 - f8cae5ad4fee1f114320dcce052299eab12108b2
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.38
|
||||||
|
|
||||||
- Error on BVC confirmation screen (from IndexedDB refactor)
|
- Error on BVC confirmation screen (from IndexedDB refactor)
|
||||||
|
|
||||||
|
|
||||||
## [0.3.37] - 2024.12.13 - 4d805b43cd25eed73cdd6651f36ad1ec8c109555
|
## [0.3.37] - 2024.12.13 - 4d805b43cd25eed73cdd6651f36ad1ec8c109555
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.37
|
||||||
|
|
||||||
- Record a give from a project on the project page.
|
- Record a give from a project on the project page.
|
||||||
- New button on home page opens the gifted dialog.
|
- New button on home page opens the gifted dialog.
|
||||||
- On confirmation buttons on the project page gives, mark when unavailable and explain why.
|
- On confirmation buttons on the project page gives, mark when unavailable and explain why.
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.37
|
||||||
|
|
||||||
- Moved the secret into IndexedDB (and out of localStorage) for more reliability.
|
- Moved the secret into IndexedDB (and out of localStorage) for more reliability.
|
||||||
- New "invite" destination page helps troubleshoot when JWT link doesn't come through.
|
- New "invite" destination page helps troubleshoot when JWT link doesn't come through.
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.37
|
||||||
|
|
||||||
- Problem showing claim issuer name
|
- Problem showing claim issuer name
|
||||||
- Problem going "back" from a project page
|
- Problem going "back" from a project page
|
||||||
|
|
||||||
|
|
||||||
## [0.3.36] - 2024.11.24 - c8d23647d165016f8a8f575e13d32583242e53ac
|
## [0.3.36] - 2024.11.24 - c8d23647d165016f8a8f575e13d32583242e53ac
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.36
|
||||||
|
|
||||||
- More friendly default reminder message
|
- More friendly default reminder message
|
||||||
- Blue borders around people to indicate clickability
|
- Blue borders around people to indicate clickability
|
||||||
|
|
||||||
|
|
||||||
## [0.3.35] - 2024.11.24 - bff7d0a6320b70349185e26bfac72e3bb17f76df
|
## [0.3.35] - 2024.11.24 - bff7d0a6320b70349185e26bfac72e3bb17f76df
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.35
|
||||||
|
|
||||||
- Daily reliable, hard-coded notification message
|
- Daily reliable, hard-coded notification message
|
||||||
- Setting to change the partner API server
|
- Setting to change the partner API server
|
||||||
|
|
||||||
|
|
||||||
## [0.3.33] - 2024.11.07 - adb7b16ecf1343c39cba71a7d6bb0e7a973e1102
|
## [0.3.33] - 2024.11.07 - adb7b16ecf1343c39cba71a7d6bb0e7a973e1102
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.33
|
||||||
|
|
||||||
- Affirm Delivery button on offer claim page didn't work.
|
- Affirm Delivery button on offer claim page didn't work.
|
||||||
- Plans were not showing by default on project page.
|
- Plans were not showing by default on project page.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.32] - 2024.11.06 - 9a3fa38a3fd28f977e06f0265fc39e635c9c5ccd
|
## [0.3.32] - 2024.11.06 - 9a3fa38a3fd28f977e06f0265fc39e635c9c5ccd
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.32
|
||||||
|
|
||||||
- Highlight in green new offers to user & to user's projects on the front page.
|
- Highlight in green new offers to user & to user's projects on the front page.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.31] - 2024.10.25 - 07c02ab98a09d293dd90d9289a7872e7d681d296
|
## [0.3.31] - 2024.10.25 - 07c02ab98a09d293dd90d9289a7872e7d681d296
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.31
|
||||||
|
|
||||||
- Onboarding messages about offers
|
- Onboarding messages about offers
|
||||||
|
|
||||||
|
|
||||||
## [0.3.30]
|
## [0.3.30]
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.30
|
||||||
|
|
||||||
- Onboarding messages
|
- Onboarding messages
|
||||||
|
|
||||||
|
|
||||||
## [0.3.29] - 2024.10.09 - babd3832bdfe0c40eaa3869de1b41399a51713c1
|
## [0.3.29] - 2024.10.09 - babd3832bdfe0c40eaa3869de1b41399a51713c1
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.29
|
||||||
|
|
||||||
- Invite for a contact to join immediately
|
- Invite for a contact to join immediately
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.29
|
||||||
|
|
||||||
- Send signed data to nostr endpoints to verify public key ownership.
|
- Send signed data to nostr endpoints to verify public key ownership.
|
||||||
- Enhanced help & help onboarding.
|
- Enhanced help & help onboarding.
|
||||||
|
|
||||||
### Changed in DB or environment
|
### Changed in DB or environment
|
||||||
|
|
||||||
- Uses Endorser.ch version 4.1.1
|
- Uses Endorser.ch version 4.1.1
|
||||||
|
|
||||||
|
|
||||||
## [0.3.28] - 2024.09.30 - 84720b94049d29cc0ddd99c50cef2e7176130133
|
## [0.3.28] - 2024.09.30 - 84720b94049d29cc0ddd99c50cef2e7176130133
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.28
|
||||||
|
|
||||||
- Posting to nostr apps Trustroots & TripHopping
|
- Posting to nostr apps Trustroots & TripHopping
|
||||||
- Display of providers on claim view page
|
- Display of providers on claim view page
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.28
|
||||||
|
|
||||||
- Switched BVC-meeting-ending gift to be a gift from the group.
|
- Switched BVC-meeting-ending gift to be a gift from the group.
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.28
|
||||||
|
|
||||||
- Requires Endorser.ch version 4.1.0
|
- Requires Endorser.ch version 4.1.0
|
||||||
|
|
||||||
|
|
||||||
## [0.3.27] - 2024.09.22 - ee23e6f005e47f5bd6f04d804599f6395371b0e4
|
## [0.3.27] - 2024.09.22 - ee23e6f005e47f5bd6f04d804599f6395371b0e4
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.27
|
||||||
|
|
||||||
- Error loading BVC claims to confirm
|
- Error loading BVC claims to confirm
|
||||||
- Really allow visibility of bulk-imported contacts
|
- Really allow visibility of bulk-imported contacts
|
||||||
|
|
||||||
|
|
||||||
## [0.3.26] - 2024.09.16 - 8263ed2b29947b3ccc6f3133bbc9454c222bce28
|
## [0.3.26] - 2024.09.16 - 8263ed2b29947b3ccc6f3133bbc9454c222bce28
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.26
|
||||||
|
|
||||||
- Separate 'isRegistered' flag for each account
|
- Separate 'isRegistered' flag for each account
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.26
|
||||||
|
|
||||||
- Failure to assign offers to their project
|
- Failure to assign offers to their project
|
||||||
- Alert when looking at one's own activity if not in contacts.
|
- Alert when looking at one's own activity if not in contacts.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.25] - 2024.08.30 - dcbe02d877aecb4cdef2643d90e6595d246a9f82
|
## [0.3.25] - 2024.08.30 - dcbe02d877aecb4cdef2643d90e6595d246a9f82
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.25
|
||||||
|
|
||||||
- "Ideas" now jumps directly to giving prompt or contact list.
|
- "Ideas" now jumps directly to giving prompt or contact list.
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.25
|
||||||
|
|
||||||
- Empty giver name on gifted-details view
|
- Empty giver name on gifted-details view
|
||||||
- Previously visited project would show up on the giving-details page.
|
- Previously visited project would show up on the giving-details page.
|
||||||
### Removed
|
|
||||||
|
### Removed in 0.3.25
|
||||||
|
|
||||||
- All unnecessary localStorage for project IDs
|
- All unnecessary localStorage for project IDs
|
||||||
|
|
||||||
|
|
||||||
## [0.3.23] - 2024.08.30
|
## [0.3.23] - 2024.08.30
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.23
|
||||||
|
|
||||||
- Sections in Help for different kinds of users
|
- Sections in Help for different kinds of users
|
||||||
- Discovery page parameters so that links with search text work
|
- Discovery page parameters so that links with search text work
|
||||||
- Message when no projects are found
|
- Message when no projects are found
|
||||||
|
|
||||||
|
|
||||||
## [0.3.21] - 2024.08.24 - a7b89f4bb6da928d56daeffaae7741fa74cc80bf
|
## [0.3.21] - 2024.08.24 - a7b89f4bb6da928d56daeffaae7741fa74cc80bf
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.21
|
||||||
|
|
||||||
- Send list of contacts to someone, and move individual contact actions to detail page.
|
- Send list of contacts to someone, and move individual contact actions to detail page.
|
||||||
- Prompt for name in pop-up, and send to different contact-sharing screens.
|
- Prompt for name in pop-up, and send to different contact-sharing screens.
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.21
|
||||||
|
|
||||||
- Moved contact actions from list onto detail page
|
- Moved contact actions from list onto detail page
|
||||||
|
|
||||||
|
|
||||||
## [0.3.20] - 2024.08.18 - 4064eb75a9743ca268bf00016fa0a5fc5dec4e30
|
## [0.3.20] - 2024.08.18 - 4064eb75a9743ca268bf00016fa0a5fc5dec4e30
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.20
|
||||||
|
|
||||||
- Bad "give" verbiage on offer page
|
- Bad "give" verbiage on offer page
|
||||||
- Failing offer test
|
- Failing offer test
|
||||||
|
|
||||||
|
|
||||||
## [0.3.19] - 2024.08.18 - ee9c14942ceba993bf21a11249601f205158ec71
|
## [0.3.19] - 2024.08.18 - ee9c14942ceba993bf21a11249601f205158ec71
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.19
|
||||||
|
|
||||||
- Update of an offer
|
- Update of an offer
|
||||||
- Recipient description in offer list
|
- Recipient description in offer list
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.19
|
||||||
|
|
||||||
- List of offers wasn't showing.
|
- List of offers wasn't showing.
|
||||||
- Destination page after sharing photo was wrong.
|
- Destination page after sharing photo was wrong.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.17] - 2024.07.11 - cefa384ff1a2d922848c370640c096c529920fab
|
## [0.3.17] - 2024.07.11 - cefa384ff1a2d922848c370640c096c529920fab
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.17
|
||||||
|
|
||||||
- Photos on more screens
|
- Photos on more screens
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.17
|
||||||
|
|
||||||
- Share of a photo, including sharing a photo from webkit/Safari which never worked
|
- Share of a photo, including sharing a photo from webkit/Safari which never worked
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.17
|
||||||
|
|
||||||
- Nothing (though there's a new temp field in IndexedDB)
|
- Nothing (though there's a new temp field in IndexedDB)
|
||||||
|
|
||||||
|
|
||||||
## [0.3.15] - 2024.08.04 - c8f0f2c2b16b9f0b4b47d40f7bf29058c7baa68e
|
## [0.3.15] - 2024.08.04 - c8f0f2c2b16b9f0b4b47d40f7bf29058c7baa68e
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.15
|
||||||
|
|
||||||
- Edit gives
|
- Edit gives
|
||||||
- Page to edit claim JSON before submitting
|
- Page to edit claim JSON before submitting
|
||||||
- Update of imported contacts
|
- Update of imported contacts
|
||||||
@@ -288,263 +357,364 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Cache signatures for reports for passkey-signed requests
|
- Cache signatures for reports for passkey-signed requests
|
||||||
- Refactor: consolidate alternative signing, eg. for passkeys & did:peer
|
- Refactor: consolidate alternative signing, eg. for passkeys & did:peer
|
||||||
- Playwright tests
|
- Playwright tests
|
||||||
### Changed
|
|
||||||
- Linked projects display below description (instead of at bottom)
|
|
||||||
### Fixed
|
|
||||||
- Visibility toggle appearance
|
|
||||||
### Changed in DB or environment
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Changed in 0.3.15
|
||||||
|
|
||||||
|
- Linked projects display below description (instead of at bottom)
|
||||||
|
|
||||||
|
### Fixed in 0.3.15
|
||||||
|
|
||||||
|
- Visibility toggle appearance
|
||||||
|
|
||||||
|
### Changed in DB or environment in 0.3.15
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.3.14] - 2024.06.22 - 1611d22892f683f43856d2503eee7f391b6bbce8
|
## [0.3.14] - 2024.06.22 - 1611d22892f683f43856d2503eee7f391b6bbce8
|
||||||
### Added
|
|
||||||
- Clearer give-confirmation screen
|
|
||||||
- BX currency https://thebx.medium.com/
|
|
||||||
- Deselection of project on gifted details page
|
|
||||||
### Fixed
|
|
||||||
- Don't show registration pop-up for a new contact that is registered
|
|
||||||
### Changed in DB or environment
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Added in 0.3.14
|
||||||
|
|
||||||
|
- Clearer give-confirmation screen
|
||||||
|
- BX currency <https://thebx.medium.com/>
|
||||||
|
- Deselection of project on gifted details page
|
||||||
|
|
||||||
|
### Fixed in 0.3.14
|
||||||
|
|
||||||
|
- Don't show registration pop-up for a new contact that is registered
|
||||||
|
|
||||||
|
### Changed in DB or environment in 0.3.14
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.3.13] - 2024.05.24 - 08b67984e443c58d9178ad3776013b0bce7afddc
|
## [0.3.13] - 2024.05.24 - 08b67984e443c58d9178ad3776013b0bce7afddc
|
||||||
### Added
|
|
||||||
- Photos on projects
|
|
||||||
### Changed in DB or environment
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Added in 0.3.13
|
||||||
|
|
||||||
|
- Photos on projects
|
||||||
|
|
||||||
|
### Changed in DB or environment in 0.3.13
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.3.12] - 2024.05.19 - 141fb39ad19c44d82fe1a33bf85115beacf50870
|
## [0.3.12] - 2024.05.19 - 141fb39ad19c44d82fe1a33bf85115beacf50870
|
||||||
### Fixed
|
|
||||||
- Photo share (share_target) failed because requests were sent to server
|
|
||||||
### Changed in DB or environment
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Fixed in 0.3.12
|
||||||
|
|
||||||
|
- Photo share (share_target) failed because requests were sent to server
|
||||||
|
|
||||||
|
### Changed in DB or environment in 0.3.12
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.3.11] - 2024.05.19 - 567bcad88dfb7e9ac8fea72530d1163985e4a7cc
|
## [0.3.11] - 2024.05.19 - 567bcad88dfb7e9ac8fea72530d1163985e4a7cc
|
||||||
### Added
|
|
||||||
- Choose a file for gifts, and a URL for gifts & profiles
|
|
||||||
### Fixed
|
|
||||||
- Multiple button pushes were required to switch camera
|
|
||||||
### Changed in DB or environment
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Added in 0.3.11
|
||||||
|
|
||||||
|
- Choose a file for gifts, and a URL for gifts & profiles
|
||||||
|
|
||||||
|
### Fixed in 0.3.11
|
||||||
|
|
||||||
|
- Multiple button pushes were required to switch camera
|
||||||
|
|
||||||
|
### Changed in DB or environment in 0.3.11
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.3.10] - 2024.05.11 - 03ac31d98110f7828cf9acb366db8d01b185f64c
|
## [0.3.10] - 2024.05.11 - 03ac31d98110f7828cf9acb366db8d01b185f64c
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.10
|
||||||
|
|
||||||
- Share an image
|
- Share an image
|
||||||
- Choose a file on the device for a profile image
|
- Choose a file on the device for a profile image
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.10
|
||||||
|
|
||||||
- Nothing
|
- Nothing
|
||||||
|
|
||||||
|
|
||||||
## [0.3.9] - 2024.04.28 - 874e717e698b93a1ace9f588e675b8a3dccd7617
|
## [0.3.9] - 2024.04.28 - 874e717e698b93a1ace9f588e675b8a3dccd7617
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.9
|
||||||
|
|
||||||
- Offers on contacts page
|
- Offers on contacts page
|
||||||
- Checks on front page until they show as registered
|
- Checks on front page until they show as registered
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.9
|
||||||
|
|
||||||
- Scanned contacts now add immediately and prompt for registration.
|
- Scanned contacts now add immediately and prompt for registration.
|
||||||
- Better UI for gives on contact page
|
- Better UI for gives on contact page
|
||||||
- Better UI for all confirmation messages
|
- Better UI for all confirmation messages
|
||||||
### Fixed
|
|
||||||
- Repeated elements at top of main feed
|
|
||||||
### Changed in DB or environment
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Fixed in 0.3.9
|
||||||
|
|
||||||
|
- Repeated elements at top of main feed
|
||||||
|
|
||||||
|
### Changed in DB or environment in 0.3.9
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.3.8] - 2024.04.20 - 15c026c80ce03a26cae3ff80b0888934c101c7e2
|
## [0.3.8] - 2024.04.20 - 15c026c80ce03a26cae3ff80b0888934c101c7e2
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.8
|
||||||
|
|
||||||
- Profile image for user
|
- Profile image for user
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.8
|
||||||
|
|
||||||
- Slow loading of home page feed
|
- Slow loading of home page feed
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.8
|
||||||
|
|
||||||
- Nothing
|
- Nothing
|
||||||
|
|
||||||
|
|
||||||
## [0.3.7] - 2024.04.10 - cf18f1543a700d62a5f9e764905a4aafe1fb229b
|
## [0.3.7] - 2024.04.10 - cf18f1543a700d62a5f9e764905a4aafe1fb229b
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.7
|
||||||
|
|
||||||
- Filter on home page feed
|
- Filter on home page feed
|
||||||
- Ability to set time of daily notification
|
- Ability to set time of daily notification
|
||||||
- Jump to app on click of notification
|
- Jump to app on click of notification
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.3.7
|
||||||
|
|
||||||
- Built with vite
|
- Built with vite
|
||||||
- Descriptions on home page to include projects
|
- Descriptions on home page to include projects
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.7
|
||||||
|
|
||||||
- Nothing
|
- Nothing
|
||||||
|
|
||||||
|
|
||||||
## [0.3.6] - 2024.03.24 - 3a07e31d6313ab95711265562d9023c42916e141
|
## [0.3.6] - 2024.03.24 - 3a07e31d6313ab95711265562d9023c42916e141
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.6
|
||||||
|
|
||||||
- Button to mirror photo during video
|
- Button to mirror photo during video
|
||||||
- More detailed onboarding help screen
|
- More detailed onboarding help screen
|
||||||
- Public-data blurb
|
- Public-data blurb
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.6
|
||||||
|
|
||||||
- Nothing
|
- Nothing
|
||||||
|
|
||||||
|
|
||||||
## [0.3.5] - 2024.03.23 - 28754bdfb1e11aa221dd49a5dce4219b69cf6a9d
|
## [0.3.5] - 2024.03.23 - 28754bdfb1e11aa221dd49a5dce4219b69cf6a9d
|
||||||
### Added
|
|
||||||
|
### Added in 0.3.5
|
||||||
|
|
||||||
- Photo on gift records
|
- Photo on gift records
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.3.5
|
||||||
|
|
||||||
- Environment variable for BVC meetings project
|
- Environment variable for BVC meetings project
|
||||||
- Environment variables and build enhancements for test vs prod
|
- Environment variables and build enhancements for test vs prod
|
||||||
### Changed in DB or environment
|
|
||||||
|
### Changed in DB or environment in 0.3.5
|
||||||
|
|
||||||
- New environment variable for image API server
|
- New environment variable for image API server
|
||||||
- Test that a new browser session will get the right default APIs.
|
- Test that a new browser session will get the right default APIs.
|
||||||
- Test that a new browser session will send the right BVC meetings project.
|
- Test that a new browser session will send the right BVC meetings project.
|
||||||
|
|
||||||
|
|
||||||
## [0.2.17] - 2024.03.01 - 3612ea42240c5e1b7d7eff29a39ff18f1b869b36
|
## [0.2.17] - 2024.03.01 - 3612ea42240c5e1b7d7eff29a39ff18f1b869b36
|
||||||
### Added
|
|
||||||
- Shortcut page for Bountiful Voluntaryist Community
|
|
||||||
### Changed
|
|
||||||
- More readable, targeted summaries in home-page feed items
|
|
||||||
### Changed in DB
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Added in 0.2.17
|
||||||
|
|
||||||
|
- Shortcut page for Bountiful Voluntaryist Community
|
||||||
|
|
||||||
|
### Changed in 0.2.17
|
||||||
|
|
||||||
|
- More readable, targeted summaries in home-page feed items
|
||||||
|
|
||||||
|
### Changed in DB
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.2.14] - 2024.02.14 - 5f9edea1167dbfb64e16648764eed8c09b24eaeb
|
## [0.2.14] - 2024.02.14 - 5f9edea1167dbfb64e16648764eed8c09b24eaeb
|
||||||
### Changed
|
|
||||||
- Combine all service worker scripts into a single file.
|
|
||||||
### Changed in DB
|
|
||||||
- Nothing
|
|
||||||
|
|
||||||
|
### Changed in 0.2.14
|
||||||
|
|
||||||
|
- Combine all service worker scripts into a single file.
|
||||||
|
|
||||||
|
### Changed in DB in 0.2.14
|
||||||
|
|
||||||
|
- Nothing
|
||||||
|
|
||||||
## [0.2.13] - 2024.02.07
|
## [0.2.13] - 2024.02.07
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.13
|
||||||
|
|
||||||
- Display of user's offers
|
- Display of user's offers
|
||||||
- Check for valid DIDs
|
- Check for valid DIDs
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.2.13
|
||||||
|
|
||||||
- Name display on give prompt
|
- Name display on give prompt
|
||||||
- Non-numbers on number input & autocapitalize on URL input
|
- Non-numbers on number input & autocapitalize on URL input
|
||||||
### Changed in DB
|
|
||||||
|
### Changed in DB in 0.2.13
|
||||||
|
|
||||||
- Nothing
|
- Nothing
|
||||||
|
|
||||||
|
|
||||||
## [0.2.12] - 2024.02.01
|
## [0.2.12] - 2024.02.01
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.12
|
||||||
|
|
||||||
- Prompts for gratitude
|
- Prompts for gratitude
|
||||||
|
|
||||||
|
|
||||||
## [0.2.11] - 2024.01.28
|
## [0.2.11] - 2024.01.28
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.11
|
||||||
|
|
||||||
- Actions to share claim data with contacts
|
- Actions to share claim data with contacts
|
||||||
- Bulk CSV import from Endorser Mobile export
|
- Bulk CSV import from Endorser Mobile export
|
||||||
- Dates on give summaries
|
- Dates on give summaries
|
||||||
|
|
||||||
|
|
||||||
## [0.2.10] - 2024.01.18 - 667e1e8890b42de59cd939caca1a01c7a7a702be
|
## [0.2.10] - 2024.01.18 - 667e1e8890b42de59cd939caca1a01c7a7a702be
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.10
|
||||||
|
|
||||||
- Person identicons for contacts
|
- Person identicons for contacts
|
||||||
- Confirmation & delivery directly from project page
|
- Confirmation & delivery directly from project page
|
||||||
- Offer dialog now allows units
|
- Offer dialog now allows units
|
||||||
- Links from claim detail page to the fulfilled project or offer
|
- Links from claim detail page to the fulfilled project or offer
|
||||||
- Link to project from home feed
|
- Link to project from home feed
|
||||||
- Copy to clipboard in more places
|
- Copy to clipboard in more places
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.2.10
|
||||||
|
|
||||||
- "More Contacts" for give on project page now links correctly.
|
- "More Contacts" for give on project page now links correctly.
|
||||||
|
|
||||||
|
|
||||||
## [0.2.9] - 2024.01.15 - e5e702f8a5a53a6efbed48d35f0bc3cee63024a0
|
## [0.2.9] - 2024.01.15 - e5e702f8a5a53a6efbed48d35f0bc3cee63024a0
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.2.9
|
||||||
|
|
||||||
- Set visibility for new contact.
|
- Set visibility for new contact.
|
||||||
|
|
||||||
|
|
||||||
## [0.2.8] - 2024.01.14
|
## [0.2.8] - 2024.01.14
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.8
|
||||||
|
|
||||||
- Automatic ID creation from home page
|
- Automatic ID creation from home page
|
||||||
- Agent who can also edit a project
|
- Agent who can also edit a project
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.2.8
|
||||||
|
|
||||||
- Cannot declare anonymous gift
|
- Cannot declare anonymous gift
|
||||||
|
|
||||||
|
|
||||||
## [0.2.7] - 2024.01.12
|
## [0.2.7] - 2024.01.12
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.7
|
||||||
|
|
||||||
- Give to fulfill a particular offer
|
- Give to fulfill a particular offer
|
||||||
- Give as part of a trade as opposed to a donation
|
- Give as part of a trade as opposed to a donation
|
||||||
- Error notifications on import
|
- Error notifications on import
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.2.7
|
||||||
|
|
||||||
- Library security updates
|
- Library security updates
|
||||||
- Visibility of actions & confirmations on claim page
|
- Visibility of actions & confirmations on claim page
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.2.7
|
||||||
|
|
||||||
- Name of offerer
|
- Name of offerer
|
||||||
|
|
||||||
|
|
||||||
## [0.2.2] - 2024.01.05
|
## [0.2.2] - 2024.01.05
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.2
|
||||||
|
|
||||||
- Check for notification capability on front screen
|
- Check for notification capability on front screen
|
||||||
- Contact next-public-key-hash in manual textual input
|
- Contact next-public-key-hash in manual textual input
|
||||||
- Confirmation for contact visibility change
|
- Confirmation for contact visibility change
|
||||||
- YAML rendering of full claim details
|
- YAML rendering of full claim details
|
||||||
- Hints for onboarding on the contact screen
|
- Hints for onboarding on the contact screen
|
||||||
|
|
||||||
|
|
||||||
## [0.2.0] - 2024.01.04
|
## [0.2.0] - 2024.01.04
|
||||||
### Added
|
|
||||||
|
### Added in 0.2.0
|
||||||
|
|
||||||
- Contact next-public-key-hash
|
- Contact next-public-key-hash
|
||||||
- Icon for Android
|
- Icon for Android
|
||||||
- More thorough messaging and testing for notifications
|
- More thorough messaging and testing for notifications
|
||||||
|
|
||||||
|
|
||||||
## [0.1.9] - 2024.01.01
|
## [0.1.9] - 2024.01.01
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.9
|
||||||
|
|
||||||
- Import for contacts and settings
|
- Import for contacts and settings
|
||||||
- Second download button for DuckDuckGo
|
- Second download button for DuckDuckGo
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.1.9
|
||||||
|
|
||||||
- Removed some keys from Dexie's IndexedDB declarations
|
- Removed some keys from Dexie's IndexedDB declarations
|
||||||
|
|
||||||
|
|
||||||
## [0.1.8] - 2023.12.27- d26d1d360152a7d0e559b68486e85b72b88bd9ff
|
## [0.1.8] - 2023.12.27- d26d1d360152a7d0e559b68486e85b72b88bd9ff
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.8
|
||||||
|
|
||||||
- DB logging for service-worker events
|
- DB logging for service-worker events
|
||||||
- Help page for notifications
|
- Help page for notifications
|
||||||
- Test notification & web-push triggers inside app
|
- Test notification & web-push triggers inside app
|
||||||
- Check that the app is installed
|
- Check that the app is installed
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.1.8
|
||||||
|
|
||||||
- Project issuer display name
|
- Project issuer display name
|
||||||
|
|
||||||
|
|
||||||
## [0.1.7] - 2023.12.19 - 91c6c7c11c71f96006cc876fc946f1f98a274ba2
|
## [0.1.7] - 2023.12.19 - 91c6c7c11c71f96006cc876fc946f1f98a274ba2
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.1.7
|
||||||
|
|
||||||
- Icons
|
- Icons
|
||||||
### Fixed
|
|
||||||
|
### Fixed in 0.1.7
|
||||||
|
|
||||||
- Notification switch now shows message
|
- Notification switch now shows message
|
||||||
- Prod/test server warning message at top of page
|
- Prod/test server warning message at top of page
|
||||||
|
|
||||||
|
|
||||||
## [0.1.6] - 2023.12.17 - b445b1234fbfcf6b37d695373f259aab0eda1118
|
## [0.1.6] - 2023.12.17 - b445b1234fbfcf6b37d695373f259aab0eda1118
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.6
|
||||||
|
|
||||||
- Infinite scroll on home page
|
- Infinite scroll on home page
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.1.6
|
||||||
|
|
||||||
- UI improvements
|
- UI improvements
|
||||||
- Show web-push subscription info
|
- Show web-push subscription info
|
||||||
- Icon
|
- Icon
|
||||||
|
|
||||||
|
|
||||||
## [0.1.5] - 2023.12.09 - 9c36bb509a9bae9bb3306d3bd9eeb144b67aa8ad
|
## [0.1.5] - 2023.12.09 - 9c36bb509a9bae9bb3306d3bd9eeb144b67aa8ad
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.5
|
||||||
|
|
||||||
- Web push notifications (though not finalized)
|
- Web push notifications (though not finalized)
|
||||||
- Credentials details page
|
- Credentials details page
|
||||||
- See more data without an ID
|
- See more data without an ID
|
||||||
- Change units of a give
|
- Change units of a give
|
||||||
|
|
||||||
|
|
||||||
## [0.1.4] - 2023.11.20 - 7311d36726f3667ec4c68f241f91d404273ad4db
|
## [0.1.4] - 2023.11.20 - 7311d36726f3667ec4c68f241f91d404273ad4db
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.4
|
||||||
|
|
||||||
- Offer on a project
|
- Offer on a project
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.1.4
|
||||||
|
|
||||||
- Automatically set as visible when importing a contact
|
- Automatically set as visible when importing a contact
|
||||||
|
|
||||||
|
|
||||||
## [0.1.3] - 2023.11.08 - 910f57ec7d2e50803ae3d04f4b927e0f5219fbde
|
## [0.1.3] - 2023.11.08 - 910f57ec7d2e50803ae3d04f4b927e0f5219fbde
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.3
|
||||||
|
|
||||||
- Contact name editing
|
- Contact name editing
|
||||||
### Changed
|
|
||||||
|
### Changed in 0.1.3
|
||||||
|
|
||||||
- Don't show actions on front page if not registered.
|
- Don't show actions on front page if not registered.
|
||||||
### Removed
|
|
||||||
|
### Removed in 0.1.3
|
||||||
|
|
||||||
- Home page Notiwind test buttons
|
- Home page Notiwind test buttons
|
||||||
|
|
||||||
|
|
||||||
## [0.1.2] - 2023.11.01 - 7f6c93802911a030a89fe3706e18b5c17151e5bb
|
## [0.1.2] - 2023.11.01 - 7f6c93802911a030a89fe3706e18b5c17151e5bb
|
||||||
### Added
|
|
||||||
|
### Added in 0.1.2
|
||||||
|
|
||||||
- Basics: create ID, record a give, declare a project, search, and get notifications.
|
- Basics: create ID, record a give, declare a project, search, and get notifications.
|
||||||
|
|||||||
33
README.md
33
README.md
@@ -8,8 +8,6 @@ and expand to crowd-fund with time & money, then record and see the impact of co
|
|||||||
See [project.task.yaml](project.task.yaml) for current priorities.
|
See [project.task.yaml](project.task.yaml) for current priorities.
|
||||||
(Numbers at the beginning of lines are estimated hours. See [taskyaml.org](https://taskyaml.org/) for details.)
|
(Numbers at the beginning of lines are estimated hours. See [taskyaml.org](https://taskyaml.org/) for details.)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Setup & Building
|
## Setup & Building
|
||||||
|
|
||||||
Quick start:
|
Quick start:
|
||||||
@@ -19,15 +17,17 @@ npm install
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
See the test locations for "IMAGE_API_SERVER" or "PARTNER_API_SERVER" below, or use http://localhost:3000 for local endorser.ch
|
See the test locations for "IMAGE_API_SERVER" or "PARTNER_API_SERVER" below, or use <http://localhost:3000> for local endorser.ch
|
||||||
|
|
||||||
### Build the test & production app
|
### Build the test & production app
|
||||||
```
|
|
||||||
|
```bash
|
||||||
npm run serve
|
npm run serve
|
||||||
```
|
```
|
||||||
|
|
||||||
### Lint and fix files
|
### Lint and fix files
|
||||||
```
|
|
||||||
|
```bash
|
||||||
npm run lint
|
npm run lint
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -35,7 +35,6 @@ npm run lint
|
|||||||
|
|
||||||
Look below for the "test-all" instructions.
|
Look below for the "test-all" instructions.
|
||||||
|
|
||||||
|
|
||||||
### Compile and minify for test & production
|
### Compile and minify for test & production
|
||||||
|
|
||||||
* If there are DB changes: before updating the test server, open browser(s) with current version to test DB migrations.
|
* If there are DB changes: before updating the test server, open browser(s) with current version to test DB migrations.
|
||||||
@@ -52,15 +51,19 @@ Look below for the "test-all" instructions.
|
|||||||
|
|
||||||
* For test, build the app (because test server is not yet set up to build):
|
* For test, build the app (because test server is not yet set up to build):
|
||||||
|
|
||||||
```
|
```bash
|
||||||
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_PASSKEYS_ENABLED=true npm run build
|
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_PASSKEYS_ENABLED=true npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
... and transfer to the test server: `rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari`
|
... and transfer to the test server:
|
||||||
|
|
||||||
(Let's replace that with a .env.development or .env.staging file.)
|
```bash
|
||||||
|
rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari
|
||||||
|
```
|
||||||
|
|
||||||
(Note: The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.)
|
(Let's replace that with a .env.development or .env.staging file.)
|
||||||
|
|
||||||
|
(Note: The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.)
|
||||||
|
|
||||||
* For prod, get on the server and run the correct build:
|
* For prod, get on the server and run the correct build:
|
||||||
|
|
||||||
@@ -76,23 +79,14 @@ TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.
|
|||||||
|
|
||||||
* Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production.
|
* Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
See [TESTING.md](test-playwright/TESTING.md) for detailed test instructions.
|
See [TESTING.md](test-playwright/TESTING.md) for detailed test instructions.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Icons
|
## Icons
|
||||||
|
|
||||||
To add an icon, add to main.ts and reference with `fa` element and `icon` attribute with the hyphenated name.
|
To add an icon, add to main.ts and reference with `fa` element and `icon` attribute with the hyphenated name.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Other
|
## Other
|
||||||
|
|
||||||
### Reference Material
|
### Reference Material
|
||||||
@@ -104,7 +98,6 @@ To add an icon, add to main.ts and reference with `fa` element and `icon` attrib
|
|||||||
|
|
||||||
* If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
|
* If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
|
||||||
|
|
||||||
|
|
||||||
### Kudos
|
### Kudos
|
||||||
|
|
||||||
Gifts make the world go 'round!
|
Gifts make the world go 'round!
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ android {
|
|||||||
|
|
||||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation project(':capacitor-app')
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,13 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="timesafari" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||||
include ':capacitor-android'
|
include ':capacitor-android'
|
||||||
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||||
|
|
||||||
|
include ':capacitor-app'
|
||||||
|
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
|
||||||
|
|||||||
@@ -8,6 +8,18 @@ const config: CapacitorConfig = {
|
|||||||
server: {
|
server: {
|
||||||
cleartext: true,
|
cleartext: true,
|
||||||
},
|
},
|
||||||
|
plugins: {
|
||||||
|
App: {
|
||||||
|
appUrlOpen: {
|
||||||
|
handlers: [
|
||||||
|
{
|
||||||
|
url: "timesafari://*",
|
||||||
|
autoVerify: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
91
docs/DEEP_LINKS.md
Normal file
91
docs/DEEP_LINKS.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# TimeSafari Deep Linking Documentation
|
||||||
|
|
||||||
|
## Type System Overview
|
||||||
|
|
||||||
|
The deep linking system uses a multi-layered type safety approach:
|
||||||
|
|
||||||
|
1. **Runtime Validation (Zod Schemas)**
|
||||||
|
- Validates URL structure
|
||||||
|
- Enforces parameter requirements
|
||||||
|
- Sanitizes input data
|
||||||
|
- Provides detailed validation errors
|
||||||
|
|
||||||
|
2. **TypeScript Types**
|
||||||
|
- Generated from Zod schemas
|
||||||
|
- Ensures compile-time type safety
|
||||||
|
- Provides IDE autocompletion
|
||||||
|
- Catches type errors during development
|
||||||
|
|
||||||
|
3. **Router Integration**
|
||||||
|
- Type-safe parameter passing
|
||||||
|
- Route-specific parameter validation
|
||||||
|
- Query parameter type checking
|
||||||
|
|
||||||
|
## Implementation Files
|
||||||
|
|
||||||
|
- `src/types/deepLinks.ts`: Type definitions and validation schemas
|
||||||
|
- `src/services/deepLinks.ts`: Deep link processing service
|
||||||
|
- `src/main.capacitor.ts`: Capacitor integration
|
||||||
|
|
||||||
|
## Type Safety Examples
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Parameter type safety
|
||||||
|
type ClaimParams = DeepLinkParams["claim"];
|
||||||
|
// TypeScript knows this has:
|
||||||
|
// - id: string
|
||||||
|
// - view?: "details" | "certificate" | "raw"
|
||||||
|
// Runtime validation
|
||||||
|
const result = deepLinkSchemas.claim.safeParse({
|
||||||
|
id: "123",
|
||||||
|
view: "details"
|
||||||
|
});
|
||||||
|
// Validates at runtime with detailed error messages
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported URL Schemes
|
||||||
|
|
||||||
|
All deep links follow the format: `timesafari://<route>/<param>?<query>`
|
||||||
|
|
||||||
|
### Claim Routes
|
||||||
|
|
||||||
|
- `timesafari://claim/:id`
|
||||||
|
- Query params:
|
||||||
|
- `view`: "details" | "certificate" | "raw"
|
||||||
|
|
||||||
|
- `timesafari://claim-cert/:id`
|
||||||
|
- `timesafari://claim-add-raw/:id`
|
||||||
|
- Query params:
|
||||||
|
- `claim`: JSON string of claim data
|
||||||
|
- `claimJwtId`: JWT ID for claim
|
||||||
|
|
||||||
|
### Contact Routes
|
||||||
|
|
||||||
|
- `timesafari://contact-edit/:did`
|
||||||
|
- `timesafari://contact-import/:jwt`
|
||||||
|
- Query params:
|
||||||
|
- `contacts`: JSON array of contacts
|
||||||
|
|
||||||
|
### Project Routes
|
||||||
|
|
||||||
|
- `timesafari://project/:id`
|
||||||
|
- Query params:
|
||||||
|
- `view`: "details" | "edit"
|
||||||
|
|
||||||
|
### Invite Routes
|
||||||
|
|
||||||
|
- `timesafari://invite-one-accept/:jwt`
|
||||||
|
- Query params:
|
||||||
|
- `type`: "one" | "many"
|
||||||
|
|
||||||
|
### Gift Routes
|
||||||
|
|
||||||
|
- `timesafari://confirm-gift/:id`
|
||||||
|
- Query params:
|
||||||
|
- `action`: "confirm" | "details"
|
||||||
|
|
||||||
|
### Offer Routes
|
||||||
|
|
||||||
|
- `timesafari://offer-details/:id`
|
||||||
|
- Query params:
|
||||||
|
- `view`: "details"
|
||||||
17
index.html
17
index.html
@@ -12,6 +12,21 @@
|
|||||||
<strong>We're sorry but TimeSafari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
<strong>We're sorry but TimeSafari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module">
|
||||||
|
const platform = process.env.VITE_PLATFORM;
|
||||||
|
switch (platform) {
|
||||||
|
case 'capacitor':
|
||||||
|
import('./src/main.capacitor.ts');
|
||||||
|
break;
|
||||||
|
case 'electron':
|
||||||
|
import('./src/main.electron.ts');
|
||||||
|
break;
|
||||||
|
case 'pywebview':
|
||||||
|
import('./src/main.pywebview.ts');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
import('./src/main.web.ts');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -45,5 +45,14 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>timesafari</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ install! 'cocoapods', :disable_input_output_paths => true
|
|||||||
def capacitor_pods
|
def capacitor_pods
|
||||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||||
|
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
||||||
end
|
end
|
||||||
|
|
||||||
target 'App' do
|
target 'App' do
|
||||||
|
|||||||
29
main.js
Normal file
29
main.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const { app, BrowserWindow } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width: 1200,
|
||||||
|
height: 800,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
contextIsolation: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
win.loadFile(path.join(__dirname, 'dist-electron/www/index.html'));
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(createWindow);
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
4614
package-lock.json
generated
4614
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
43
package.json
43
package.json
@@ -6,7 +6,7 @@
|
|||||||
"name": "TimeSafari Team"
|
"name": "TimeSafari Team"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --config vite.config.dev.mts",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"build": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build",
|
"build": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build",
|
||||||
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
|
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
|
||||||
@@ -15,23 +15,25 @@
|
|||||||
"test-local": "npx playwright test -c playwright.config-local.ts --trace on",
|
"test-local": "npx playwright test -c playwright.config-local.ts --trace on",
|
||||||
"test-all": "npm run build && npx playwright test -c playwright.config-local.ts --trace on",
|
"test-all": "npm run build && npx playwright test -c playwright.config-local.ts --trace on",
|
||||||
"clean:electron": "rimraf dist-electron",
|
"clean:electron": "rimraf dist-electron",
|
||||||
"build:electron": "npm run clean:electron && vite build --mode electron && node scripts/build-electron.js",
|
"build:pywebview": "vite build --config vite.config.pywebview.mts",
|
||||||
"build:capacitor": "vite build --mode capacitor",
|
"build:electron": "npm run clean:electron && vite build --config vite.config.electron.mts && node scripts/build-electron.js",
|
||||||
"build:web": "vite build",
|
"build:capacitor": "vite build --config vite.config.capacitor.mts",
|
||||||
|
"build:web": "vite build --config vite.config.web.mts",
|
||||||
"electron:dev": "npm run build && electron dist-electron",
|
"electron:dev": "npm run build && electron dist-electron",
|
||||||
"electron:start": "electron dist-electron",
|
"electron:start": "electron dist-electron",
|
||||||
"electron:build-linux": "electron-builder --linux AppImage",
|
"electron:build-linux": "npm run build:electron && electron-builder --linux AppImage",
|
||||||
"electron:build-linux-deb": "electron-builder --linux deb",
|
"electron:build-linux-deb": "npm run build:electron && electron-builder --linux deb",
|
||||||
|
"electron:build-linux-prod": "NODE_ENV=production npm run build:electron && electron-builder --linux AppImage",
|
||||||
"build:electron-prod": "NODE_ENV=production npm run build:electron",
|
"build:electron-prod": "NODE_ENV=production npm run build:electron",
|
||||||
"electron:build-linux-prod": "npm run build:electron-prod && electron-builder --linux AppImage",
|
"pywebview:dev": "vite build --config vite.config.pywebview.mts && .venv/bin/python src/pywebview/main.py",
|
||||||
"pywebview:dev": "vite build --mode pywebview && .venv/bin/python src/pywebview/main.py",
|
"pywebview:build": "vite build --config vite.config.pywebview.mts && .venv/bin/python src/pywebview/main.py",
|
||||||
"pywebview:build": "vite build --mode pywebview && .venv/bin/python src/pywebview/main.py",
|
"pywebview:package-linux": "vite build --mode pywebview --config vite.config.pywebview.mts && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py",
|
||||||
"pywebview:package-linux": "vite build --mode pywebview && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py",
|
"pywebview:package-win": "vite build --mode pywebview --config vite.config.pywebview.mts && .venv/Scripts/python -m PyInstaller --name TimeSafari --add-data 'dist;www' src/pywebview/main.py",
|
||||||
"pywebview:package-win": "vite build --mode pywebview && .venv/Scripts/python -m PyInstaller --name TimeSafari --add-data 'dist;www' src/pywebview/main.py",
|
"pywebview:package-mac": "vite build --mode pywebview --config vite.config.pywebview.mts && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py"
|
||||||
"pywebview:package-mac": "vite build --mode pywebview && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^6.2.0",
|
"@capacitor/android": "^6.2.0",
|
||||||
|
"@capacitor/app": "^6.0.0",
|
||||||
"@capacitor/cli": "^6.2.0",
|
"@capacitor/cli": "^6.2.0",
|
||||||
"@capacitor/core": "^6.2.0",
|
"@capacitor/core": "^6.2.0",
|
||||||
"@capacitor/ios": "^6.2.0",
|
"@capacitor/ios": "^6.2.0",
|
||||||
@@ -64,7 +66,7 @@
|
|||||||
"cbor-x": "^1.5.9",
|
"cbor-x": "^1.5.9",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"dexie": "^3.2.7",
|
"dexie": "^3.2.7",
|
||||||
"dexie-export-import": "^4.1.1",
|
"dexie-export-import": "^4.1.4",
|
||||||
"did-jwt": "^7.4.7",
|
"did-jwt": "^7.4.7",
|
||||||
"did-resolver": "^4.1.0",
|
"did-resolver": "^4.1.0",
|
||||||
"ethereum-cryptography": "^2.1.3",
|
"ethereum-cryptography": "^2.1.3",
|
||||||
@@ -89,16 +91,17 @@
|
|||||||
"reflect-metadata": "^0.1.14",
|
"reflect-metadata": "^0.1.14",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"simple-vue-camera": "^1.1.3",
|
"simple-vue-camera": "^1.1.3",
|
||||||
|
"stream-browserify": "^3.0.0",
|
||||||
"three": "^0.156.1",
|
"three": "^0.156.1",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"util": "^0.12.5",
|
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-axios": "^3.5.2",
|
"vue-axios": "^3.5.2",
|
||||||
"vue-facing-decorator": "^3.0.4",
|
"vue-facing-decorator": "^3.0.4",
|
||||||
"vue-picture-cropper": "^0.7.0",
|
"vue-picture-cropper": "^0.7.0",
|
||||||
"vue-qrcode-reader": "^5.5.3",
|
"vue-qrcode-reader": "^5.5.3",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0",
|
||||||
"web-did-resolver": "^2.0.27"
|
"web-did-resolver": "^2.0.27",
|
||||||
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.45.2",
|
"@playwright/test": "^1.45.2",
|
||||||
@@ -116,11 +119,14 @@
|
|||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
"electron": "^33.2.1",
|
"electron": "^33.2.1",
|
||||||
|
"electron-builder": "^25.1.8",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"eslint-plugin-vue": "^9.32.0",
|
"eslint-plugin-vue": "^9.32.0",
|
||||||
"fs-extra": "^11.3.0",
|
"fs-extra": "^11.3.0",
|
||||||
|
"markdownlint": "^0.37.4",
|
||||||
|
"markdownlint-cli": "^0.44.0",
|
||||||
"npm-check-updates": "^17.1.13",
|
"npm-check-updates": "^17.1.13",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
@@ -139,12 +145,13 @@
|
|||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist-electron/**/*",
|
"dist-electron/**/*",
|
||||||
"src/electron/**/*"
|
"src/electron/**/*",
|
||||||
|
"main.js"
|
||||||
],
|
],
|
||||||
"extraResources": [
|
"extraResources": [
|
||||||
{
|
{
|
||||||
"from": "dist-electron/www",
|
"from": "dist-electron",
|
||||||
"to": "www"
|
"to": "."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"linux": {
|
"linux": {
|
||||||
|
|||||||
54
src/App.vue
54
src/App.vue
@@ -40,7 +40,10 @@
|
|||||||
<div
|
<div
|
||||||
class="flex items-center justify-center w-12 bg-slate-600 text-slate-100"
|
class="flex items-center justify-center w-12 bg-slate-600 text-slate-100"
|
||||||
>
|
>
|
||||||
<fa icon="circle-info" class="fa-fw fa-xl"></fa>
|
<font-awesome
|
||||||
|
icon="circle-info"
|
||||||
|
class="fa-fw fa-xl"
|
||||||
|
></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative w-full pl-4 pr-8 py-2 text-slate-900">
|
<div class="relative w-full pl-4 pr-8 py-2 text-slate-900">
|
||||||
@@ -48,10 +51,10 @@
|
|||||||
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
|
||||||
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-slate-200 text-slate-600"
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-slate-200 text-slate-600"
|
||||||
|
@click="close(notification.id)"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="fa-fw"></fa>
|
<font-awesome icon="xmark" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,7 +66,10 @@
|
|||||||
<div
|
<div
|
||||||
class="flex items-center justify-center w-12 bg-emerald-600 text-emerald-100"
|
class="flex items-center justify-center w-12 bg-emerald-600 text-emerald-100"
|
||||||
>
|
>
|
||||||
<fa icon="circle-info" class="fa-fw fa-xl"></fa>
|
<font-awesome
|
||||||
|
icon="circle-info"
|
||||||
|
class="fa-fw fa-xl"
|
||||||
|
></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative w-full pl-4 pr-8 py-2 text-emerald-900">
|
<div class="relative w-full pl-4 pr-8 py-2 text-emerald-900">
|
||||||
@@ -71,10 +77,10 @@
|
|||||||
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
|
||||||
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-emerald-200 text-emerald-600"
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-emerald-200 text-emerald-600"
|
||||||
|
@click="close(notification.id)"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="fa-fw"></fa>
|
<font-awesome icon="xmark" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,7 +92,10 @@
|
|||||||
<div
|
<div
|
||||||
class="flex items-center justify-center w-12 bg-amber-600 text-amber-100"
|
class="flex items-center justify-center w-12 bg-amber-600 text-amber-100"
|
||||||
>
|
>
|
||||||
<fa icon="triangle-exclamation" class="fa-fw fa-xl"></fa>
|
<font-awesome
|
||||||
|
icon="triangle-exclamation"
|
||||||
|
class="fa-fw fa-xl"
|
||||||
|
></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative w-full pl-4 pr-8 py-2 text-amber-900">
|
<div class="relative w-full pl-4 pr-8 py-2 text-amber-900">
|
||||||
@@ -94,10 +103,10 @@
|
|||||||
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
|
||||||
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-amber-200 text-amber-600"
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-amber-200 text-amber-600"
|
||||||
|
@click="close(notification.id)"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="fa-fw"></fa>
|
<font-awesome icon="xmark" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,7 +118,10 @@
|
|||||||
<div
|
<div
|
||||||
class="flex items-center justify-center w-12 bg-rose-600 text-rose-100"
|
class="flex items-center justify-center w-12 bg-rose-600 text-rose-100"
|
||||||
>
|
>
|
||||||
<fa icon="triangle-exclamation" class="fa-fw fa-xl"></fa>
|
<font-awesome
|
||||||
|
icon="triangle-exclamation"
|
||||||
|
class="fa-fw fa-xl"
|
||||||
|
></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative w-full pl-4 pr-8 py-2 text-rose-900">
|
<div class="relative w-full pl-4 pr-8 py-2 text-rose-900">
|
||||||
@@ -117,10 +129,10 @@
|
|||||||
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
|
||||||
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-rose-200 text-rose-600"
|
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-rose-200 text-rose-600"
|
||||||
|
@click="close(notification.id)"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="fa-fw"></fa>
|
<font-awesome icon="xmark" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -174,11 +186,11 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="notification.onYes"
|
v-if="notification.onYes"
|
||||||
|
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
||||||
@click="
|
@click="
|
||||||
notification.onYes();
|
notification.onYes();
|
||||||
close(notification.id);
|
close(notification.id);
|
||||||
"
|
"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
|
||||||
>
|
>
|
||||||
Yes{{
|
Yes{{
|
||||||
notification.yesText ? ", " + notification.yesText : ""
|
notification.yesText ? ", " + notification.yesText : ""
|
||||||
@@ -187,12 +199,12 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="notification.onNo"
|
v-if="notification.onNo"
|
||||||
|
class="block w-full text-center text-md font-bold uppercase bg-yellow-600 text-white px-2 py-2 rounded-md mb-2"
|
||||||
@click="
|
@click="
|
||||||
notification.onNo(stopAsking);
|
notification.onNo(stopAsking);
|
||||||
close(notification.id);
|
close(notification.id);
|
||||||
stopAsking = false; // reset value
|
stopAsking = false; // reset value
|
||||||
"
|
"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-yellow-600 text-white px-2 py-2 rounded-md mb-2"
|
|
||||||
>
|
>
|
||||||
No{{ notification.noText ? ", " + notification.noText : "" }}
|
No{{ notification.noText ? ", " + notification.noText : "" }}
|
||||||
</button>
|
</button>
|
||||||
@@ -209,8 +221,8 @@
|
|||||||
<div class="relative ml-2">
|
<div class="relative ml-2">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="stopAsking"
|
v-model="stopAsking"
|
||||||
|
type="checkbox"
|
||||||
name="stopAsking"
|
name="stopAsking"
|
||||||
class="sr-only"
|
class="sr-only"
|
||||||
/>
|
/>
|
||||||
@@ -224,6 +236,7 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
||||||
@click="
|
@click="
|
||||||
notification.onCancel
|
notification.onCancel
|
||||||
? notification.onCancel(stopAsking)
|
? notification.onCancel(stopAsking)
|
||||||
@@ -231,7 +244,6 @@
|
|||||||
close(notification.id);
|
close(notification.id);
|
||||||
stopAsking = false; // reset value for next time they open this modal
|
stopAsking = false; // reset value for next time they open this modal
|
||||||
"
|
"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
|
||||||
>
|
>
|
||||||
{{ notification.onYes ? "Cancel" : "Close" }}
|
{{ notification.onYes ? "Cancel" : "Close" }}
|
||||||
</button>
|
</button>
|
||||||
@@ -270,8 +282,8 @@
|
|||||||
Until I turn it back on
|
Until I turn it back on
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
||||||
|
@click="close(notification.id)"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -292,17 +304,17 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
|
||||||
@click="
|
@click="
|
||||||
close(notification.id);
|
close(notification.id);
|
||||||
turnOffNotifications(notification);
|
turnOffNotifications(notification);
|
||||||
"
|
"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
|
|
||||||
>
|
>
|
||||||
Turn Off Notification
|
Turn Off Notification
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
||||||
|
@click="close(notification.id)"
|
||||||
>
|
>
|
||||||
Leave it On
|
Leave it On
|
||||||
</button>
|
</button>
|
||||||
@@ -315,8 +327,6 @@
|
|||||||
</NotificationGroup>
|
</NotificationGroup>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style></style>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component } from "vue-facing-decorator";
|
import { Vue, Component } from "vue-facing-decorator";
|
||||||
import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "./db/index";
|
import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "./db/index";
|
||||||
@@ -526,3 +536,5 @@ export default class App extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
|
|||||||
@@ -29,29 +29,29 @@
|
|||||||
<p class="text-sm mb-2">{{ text }}</p>
|
<p class="text-sm mb-2">{{ text }}</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="handleOption1(close)"
|
|
||||||
class="block w-full text-center text-md font-bold capitalize bg-blue-800 text-white px-2 py-2 rounded-md mb-2"
|
class="block w-full text-center text-md font-bold capitalize bg-blue-800 text-white px-2 py-2 rounded-md mb-2"
|
||||||
|
@click="handleOption1(close)"
|
||||||
>
|
>
|
||||||
{{ option1Text }}
|
{{ option1Text }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="handleOption2(close)"
|
|
||||||
class="block w-full text-center text-md font-bold capitalize bg-blue-700 text-white px-2 py-2 rounded-md mb-2"
|
class="block w-full text-center text-md font-bold capitalize bg-blue-700 text-white px-2 py-2 rounded-md mb-2"
|
||||||
|
@click="handleOption2(close)"
|
||||||
>
|
>
|
||||||
{{ option2Text }}
|
{{ option2Text }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="handleOption3(close)"
|
|
||||||
class="block w-full text-center text-md font-bold capitalize bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
class="block w-full text-center text-md font-bold capitalize bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
|
||||||
|
@click="handleOption3(close)"
|
||||||
>
|
>
|
||||||
{{ option3Text }}
|
{{ option3Text }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="handleCancel(close)"
|
|
||||||
class="block w-full text-center text-md font-bold capitalize bg-slate-600 text-white px-2 py-2 rounded-md"
|
class="block w-full text-center text-md font-bold capitalize bg-slate-600 text-white px-2 py-2 rounded-md"
|
||||||
|
@click="handleCancel(close)"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
{{ message }}
|
{{ message }}
|
||||||
Note that their name is only stored on this device.
|
Note that their name is only stored on this device.
|
||||||
<input
|
<input
|
||||||
|
v-model="newText"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
v-model="newText"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-html="generateIcon()" class="w-fit"></div>
|
<div class="w-fit" v-html="generateIcon()"></div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createAvatar, StyleOptions } from "@dicebear/core";
|
import { createAvatar, StyleOptions } from "@dicebear/core";
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
<div class="relative ml-2">
|
<div class="relative ml-2">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="hasVisibleDid"
|
v-model="hasVisibleDid"
|
||||||
|
type="checkbox"
|
||||||
name="toggleFilterFromMyContacts"
|
name="toggleFilterFromMyContacts"
|
||||||
class="sr-only"
|
class="sr-only"
|
||||||
/>
|
/>
|
||||||
@@ -46,8 +46,8 @@
|
|||||||
<div v-if="hasSearchBox" class="relative ml-2">
|
<div v-if="hasSearchBox" class="relative ml-2">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="isNearby"
|
v-model="isNearby"
|
||||||
|
type="checkbox"
|
||||||
name="toggleFilterNearby"
|
name="toggleFilterNearby"
|
||||||
class="sr-only"
|
class="sr-only"
|
||||||
/>
|
/>
|
||||||
@@ -98,7 +98,7 @@ import {
|
|||||||
LRectangle,
|
LRectangle,
|
||||||
LTileLayer,
|
LTileLayer,
|
||||||
} from "@vue-leaflet/vue-leaflet";
|
} from "@vue-leaflet/vue-leaflet";
|
||||||
|
import { Router } from "vue-router";
|
||||||
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
||||||
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
|
|
||||||
@@ -111,6 +111,7 @@ import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class FeedFilters extends Vue {
|
export default class FeedFilters extends Vue {
|
||||||
|
$router!: Router;
|
||||||
onCloseIfChanged = () => {};
|
onCloseIfChanged = () => {};
|
||||||
hasSearchBox = false;
|
hasSearchBox = false;
|
||||||
hasVisibleDid = false;
|
hasVisibleDid = false;
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
{{ customTitle }}
|
{{ customTitle }}
|
||||||
</h1>
|
</h1>
|
||||||
<input
|
<input
|
||||||
|
v-model="description"
|
||||||
type="text"
|
type="text"
|
||||||
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||||
:placeholder="prompt || 'What was given?'"
|
:placeholder="prompt || 'What was given?'"
|
||||||
v-model="description"
|
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-row justify-center">
|
<div class="flex flex-row justify-center">
|
||||||
<span
|
<span
|
||||||
@@ -21,19 +21,19 @@
|
|||||||
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
@click="amountInput === '0' ? null : decrement()"
|
@click="amountInput === '0' ? null : decrement()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" />
|
<font-awesome icon="chevron-left" />
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
id="inputGivenAmount"
|
id="inputGivenAmount"
|
||||||
|
v-model="amountInput"
|
||||||
type="number"
|
type="number"
|
||||||
class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20"
|
class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20"
|
||||||
v-model="amountInput"
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
@click="increment()"
|
@click="increment()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-right" />
|
<font-awesome icon="chevron-right" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex justify-center">
|
<div class="mt-4 flex justify-center">
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="text-center mb-2 mt-6 italic">
|
<p class="text-center mb-2 mt-6 italic">
|
||||||
Sign & Send to publish to the world
|
Sign & Send to publish to the world
|
||||||
<fa
|
<font-awesome
|
||||||
icon="circle-info"
|
icon="circle-info"
|
||||||
class="pl-2 text-blue-500 cursor-pointer"
|
class="pl-2 text-blue-500 cursor-pointer"
|
||||||
@click="explainData()"
|
@click="explainData()"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
class="text-lg text-center p-2 leading-none absolute right-0 -top-1"
|
class="text-lg text-center p-2 leading-none absolute right-0 -top-1"
|
||||||
@click="cancel"
|
@click="cancel"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="w-[1em]"></fa>
|
<font-awesome icon="xmark" class="w-[1em]"></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
<span class="mt-2 flex justify-between">
|
<span class="mt-2 flex justify-between">
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
class="rounded-l border border-slate-400 bg-slate-200 px-4 py-2 flex"
|
class="rounded-l border border-slate-400 bg-slate-200 px-4 py-2 flex"
|
||||||
@click="prevIdea()"
|
@click="prevIdea()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="m-auto" />
|
<font-awesome icon="chevron-left" class="m-auto" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="m-2">
|
<div class="m-2">
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
class="text-center bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4"
|
class="text-center bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4"
|
||||||
@click="nextIdeaPastContacts()"
|
@click="nextIdeaPastContacts()"
|
||||||
>
|
>
|
||||||
Skip Contacts <fa icon="forward" />
|
Skip Contacts <font-awesome icon="forward" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2 flex"
|
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2 flex"
|
||||||
@click="nextIdea()"
|
@click="nextIdea()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-right" class="m-auto" />
|
<font-awesome icon="chevron-right" class="m-auto" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
@@ -82,6 +82,7 @@ import { GiverReceiverInputInfo } from "../libs/util";
|
|||||||
@Component
|
@Component
|
||||||
export default class GivenPrompts extends Vue {
|
export default class GivenPrompts extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
CATEGORY_CONTACTS = 1;
|
CATEGORY_CONTACTS = 1;
|
||||||
CATEGORY_IDEAS = 0;
|
CATEGORY_IDEAS = 0;
|
||||||
@@ -145,7 +146,7 @@ export default class GivenPrompts extends Vue {
|
|||||||
// proceed with logic but don't change values (just in case some actions are added later)
|
// proceed with logic but don't change values (just in case some actions are added later)
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
if (this.currentCategory === this.CATEGORY_IDEAS) {
|
if (this.currentCategory === this.CATEGORY_IDEAS) {
|
||||||
(this.$router as Router).push({
|
this.$router.push({
|
||||||
name: "contact-gift",
|
name: "contact-gift",
|
||||||
query: {
|
query: {
|
||||||
prompt: this.IDEAS[this.currentIdeaIndex],
|
prompt: this.IDEAS[this.currentIdeaIndex],
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<h2 class="text-xl font-bold capitalize">{{ roleName }} Details</h2>
|
<h2 class="text-xl font-bold capitalize">{{ roleName }} Details</h2>
|
||||||
<button @click="close" class="text-gray-500 hover:text-gray-700">
|
<button class="text-gray-500 hover:text-gray-700" @click="close">
|
||||||
<fa icon="times" />
|
<font-awesome icon="times" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -53,7 +53,10 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
>
|
>
|
||||||
<fa icon="arrow-up-right-from-square" class="fa-fw" />
|
<font-awesome
|
||||||
|
icon="arrow-up-right-from-square"
|
||||||
|
class="fa-fw"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -66,7 +69,7 @@
|
|||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<span v-if="canShare">
|
<span v-if="canShare">
|
||||||
If you'd like an introduction,
|
If you'd like an introduction,
|
||||||
<a @click="onClickShareClaim()" class="text-blue-500"
|
<a class="text-blue-500" @click="onClickShareClaim()"
|
||||||
>click here to share the information with them and ask if they'll
|
>click here to share the information with them and ask if they'll
|
||||||
tell you more about the {{ roleName }}.</a
|
tell you more about the {{ roleName }}.</a
|
||||||
>
|
>
|
||||||
@@ -74,8 +77,8 @@
|
|||||||
<span v-else>
|
<span v-else>
|
||||||
If you'd like an introduction,
|
If you'd like an introduction,
|
||||||
<a
|
<a
|
||||||
@click="copyToClipboard('A link to this page', windowLocation)"
|
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
|
@click="copyToClipboard('A link to this page', windowLocation)"
|
||||||
>click here to copy this page, paste it into a message, and ask if
|
>click here to copy this page, paste it into a message, and ask if
|
||||||
they'll tell you more about the {{ roleName }}.</a
|
they'll tell you more about the {{ roleName }}.</a
|
||||||
>
|
>
|
||||||
@@ -86,8 +89,8 @@
|
|||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<button
|
<button
|
||||||
@click="close"
|
|
||||||
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
|
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
|
||||||
|
@click="close"
|
||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -12,14 +12,14 @@
|
|||||||
class="text-lg text-center px-2 py-0.5 leading-none absolute right-0 top-0 text-white"
|
class="text-lg text-center px-2 py-0.5 leading-none absolute right-0 top-0 text-white"
|
||||||
@click="close()"
|
@click="close()"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="w-[1em]"></fa>
|
<font-awesome icon="xmark" class="w-[1em]"></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="text-center mt-8">
|
<div class="text-center mt-8">
|
||||||
<div>
|
<div>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="camera"
|
icon="camera"
|
||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md"
|
||||||
@click="openPhotoDialog()"
|
@click="openPhotoDialog()"
|
||||||
@@ -31,17 +31,21 @@
|
|||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<span class="mt-2">
|
<span class="mt-2">
|
||||||
... or paste a URL:
|
... or paste a URL:
|
||||||
<input type="text" v-model="imageUrl" class="border-2" />
|
<input v-model="imageUrl" type="text" class="border-2" />
|
||||||
</span>
|
</span>
|
||||||
<span class="ml-2">
|
<span class="ml-2">
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="imageUrl"
|
v-if="imageUrl"
|
||||||
icon="check"
|
icon="check"
|
||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md cursor-pointer"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md cursor-pointer"
|
||||||
@click="acceptUrl"
|
@click="acceptUrl"
|
||||||
/>
|
/>
|
||||||
<!-- so that there's no shifting when it becomes visible -->
|
<!-- so that there's no shifting when it becomes visible -->
|
||||||
<fa v-else icon="check" class="text-white bg-white px-2 py-2" />
|
<font-awesome
|
||||||
|
v-else
|
||||||
|
icon="check"
|
||||||
|
class="text-white bg-white px-2 py-2"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
class="text-white text-2xl p-2 rounded-full hover:bg-white/10"
|
class="text-white text-2xl p-2 rounded-full hover:bg-white/10"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" />
|
<font-awesome icon="xmark" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Mobile share button -->
|
<!-- Mobile share button -->
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
class="text-white text-xl p-2 rounded-full hover:bg-white/10"
|
class="text-white text-xl p-2 rounded-full hover:bg-white/10"
|
||||||
@click="handleShare"
|
@click="handleShare"
|
||||||
>
|
>
|
||||||
<fa icon="ellipsis" />
|
<font-awesome icon="ellipsis" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
<img
|
<img
|
||||||
:src="imageUrl"
|
:src="imageUrl"
|
||||||
class="max-h-[calc(100vh-5rem)] w-full h-full object-contain"
|
class="max-h-[calc(100vh-5rem)] w-full h-full object-contain"
|
||||||
@click.stop
|
|
||||||
alt="expanded shared content"
|
alt="expanded shared content"
|
||||||
|
@click.stop
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,18 +8,18 @@
|
|||||||
If you want to store your own way, the invitation ID is:
|
If you want to store your own way, the invitation ID is:
|
||||||
{{ inviteIdentifier }}
|
{{ inviteIdentifier }}
|
||||||
<input
|
<input
|
||||||
|
v-model="text"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Notes"
|
placeholder="Notes"
|
||||||
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
v-model="text"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Add date selection element -->
|
<!-- Add date selection element -->
|
||||||
Expiration
|
Expiration
|
||||||
<input
|
<input
|
||||||
|
v-model="expiresAt"
|
||||||
type="date"
|
type="date"
|
||||||
class="block rounded border border-slate-400 mb-4 px-3 py-2"
|
class="block rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
v-model="expiresAt"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
class="mt-16 text-center text-4xl bg-slate-400 text-white w-14 py-2.5 rounded-full mx-auto"
|
class="mt-16 text-center text-4xl bg-slate-400 text-white w-14 py-2.5 rounded-full mx-auto"
|
||||||
>
|
>
|
||||||
<fa icon="spinner" class="fa-spin-pulse" />
|
<font-awesome icon="spinner" class="fa-spin-pulse" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Members List -->
|
<!-- Members List -->
|
||||||
@@ -33,13 +33,13 @@
|
|||||||
<span
|
<span
|
||||||
class="mx-2 min-w-[24px] min-h-[24px] w-6 h-6 flex items-center justify-center rounded-full bg-blue-100 text-blue-600"
|
class="mx-2 min-w-[24px] min-h-[24px] w-6 h-6 flex items-center justify-center rounded-full bg-blue-100 text-blue-600"
|
||||||
>
|
>
|
||||||
<fa icon="plus" class="text-sm" />
|
<font-awesome icon="plus" class="text-sm" />
|
||||||
</span>
|
</span>
|
||||||
/
|
/
|
||||||
<span
|
<span
|
||||||
class="mx-2 min-w-[24px] min-h-[24px] w-6 h-6 flex items-center justify-center rounded-full bg-blue-100 text-blue-600"
|
class="mx-2 min-w-[24px] min-h-[24px] w-6 h-6 flex items-center justify-center rounded-full bg-blue-100 text-blue-600"
|
||||||
>
|
>
|
||||||
<fa icon="minus" class="text-sm" />
|
<font-awesome icon="minus" class="text-sm" />
|
||||||
</span>
|
</span>
|
||||||
to add/remove them to/from the meeting.
|
to add/remove them to/from the meeting.
|
||||||
</span>
|
</span>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
<span
|
<span
|
||||||
class="mx-2 w-8 h-8 flex items-center justify-center rounded-full bg-green-100 text-green-600"
|
class="mx-2 w-8 h-8 flex items-center justify-center rounded-full bg-green-100 text-green-600"
|
||||||
>
|
>
|
||||||
<fa icon="circle-user" class="text-xl" />
|
<font-awesome icon="circle-user" class="text-xl" />
|
||||||
</span>
|
</span>
|
||||||
to add them to your contacts.
|
to add them to your contacts.
|
||||||
</span>
|
</span>
|
||||||
@@ -63,11 +63,11 @@
|
|||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<!-- always have at least one refresh button even without members in case the organizer changes the password -->
|
<!-- always have at least one refresh button even without members in case the organizer changes the password -->
|
||||||
<button
|
<button
|
||||||
@click="fetchMembers"
|
|
||||||
class="w-8 h-8 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors"
|
class="w-8 h-8 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors"
|
||||||
title="Refresh members list"
|
title="Refresh members list"
|
||||||
|
@click="fetchMembers"
|
||||||
>
|
>
|
||||||
<fa icon="rotate" :class="{ 'fa-spin': isLoading }" />
|
<font-awesome icon="rotate" :class="{ 'fa-spin': isLoading }" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -83,24 +83,24 @@
|
|||||||
class="flex justify-end"
|
class="flex justify-end"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@click="addAsContact(member)"
|
|
||||||
class="ml-2 w-8 h-8 flex items-center justify-center rounded-full bg-green-100 text-green-600 hover:bg-green-200 hover:text-green-800 transition-colors"
|
class="ml-2 w-8 h-8 flex items-center justify-center rounded-full bg-green-100 text-green-600 hover:bg-green-200 hover:text-green-800 transition-colors"
|
||||||
title="Add as contact"
|
title="Add as contact"
|
||||||
|
@click="addAsContact(member)"
|
||||||
>
|
>
|
||||||
<fa icon="circle-user" class="text-xl" />
|
<font-awesome icon="circle-user" class="text-xl" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-if="member.did !== activeDid"
|
v-if="member.did !== activeDid"
|
||||||
|
class="ml-2 mb-2 w-6 h-6 flex items-center justify-center rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 transition-colors"
|
||||||
|
title="Contact info"
|
||||||
@click="
|
@click="
|
||||||
informAboutAddingContact(
|
informAboutAddingContact(
|
||||||
getContactFor(member.did) !== undefined,
|
getContactFor(member.did) !== undefined,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
class="ml-2 mb-2 w-6 h-6 flex items-center justify-center rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 transition-colors"
|
|
||||||
title="Contact info"
|
|
||||||
>
|
>
|
||||||
<fa icon="circle-info" class="text-base" />
|
<font-awesome icon="circle-info" class="text-base" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
@@ -111,23 +111,23 @@
|
|||||||
class="flex items-center"
|
class="flex items-center"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@click="checkWhetherContactBeforeAdmitting(member)"
|
|
||||||
class="mr-2 w-6 h-6 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors"
|
class="mr-2 w-6 h-6 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors"
|
||||||
:title="
|
:title="
|
||||||
member.member.admitted ? 'Remove member' : 'Admit member'
|
member.member.admitted ? 'Remove member' : 'Admit member'
|
||||||
"
|
"
|
||||||
|
@click="checkWhetherContactBeforeAdmitting(member)"
|
||||||
>
|
>
|
||||||
<fa
|
<font-awesome
|
||||||
:icon="member.member.admitted ? 'minus' : 'plus'"
|
:icon="member.member.admitted ? 'minus' : 'plus'"
|
||||||
class="text-sm"
|
class="text-sm"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="informAboutAdmission()"
|
|
||||||
class="mr-2 mb-2 w-6 h-6 flex items-center justify-center rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 transition-colors"
|
class="mr-2 mb-2 w-6 h-6 flex items-center justify-center rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 transition-colors"
|
||||||
title="Admission info"
|
title="Admission info"
|
||||||
|
@click="informAboutAdmission()"
|
||||||
>
|
>
|
||||||
<fa icon="circle-info" class="text-base" />
|
<font-awesome icon="circle-info" class="text-base" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,11 +138,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="membersToShow().length > 0" class="flex justify-center mt-4">
|
<div v-if="membersToShow().length > 0" class="flex justify-center mt-4">
|
||||||
<button
|
<button
|
||||||
@click="fetchMembers"
|
|
||||||
class="w-8 h-8 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors"
|
class="w-8 h-8 flex items-center justify-center rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 hover:text-blue-800 transition-colors"
|
||||||
title="Refresh members list"
|
title="Refresh members list"
|
||||||
|
@click="fetchMembers"
|
||||||
>
|
>
|
||||||
<fa icon="rotate" :class="{ 'fa-spin': isLoading }" />
|
<font-awesome icon="rotate" :class="{ 'fa-spin': isLoading }" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
<div class="dialog">
|
<div class="dialog">
|
||||||
<h1 class="text-xl font-bold text-center mb-4">Offer Help</h1>
|
<h1 class="text-xl font-bold text-center mb-4">Offer Help</h1>
|
||||||
<input
|
<input
|
||||||
|
v-model="description"
|
||||||
type="text"
|
type="text"
|
||||||
data-testId="inputDescription"
|
data-testId="inputDescription"
|
||||||
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||||
placeholder="Description of what is offered"
|
placeholder="Description of what is offered"
|
||||||
v-model="description"
|
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-row mt-2">
|
<div class="flex flex-row mt-2">
|
||||||
<span
|
<span
|
||||||
@@ -17,23 +17,23 @@
|
|||||||
{{ libsUtil.UNIT_SHORT[amountUnitCode] }}
|
{{ libsUtil.UNIT_SHORT[amountUnitCode] }}
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
|
v-if="amountInput !== '0'"
|
||||||
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
@click="decrement()"
|
@click="decrement()"
|
||||||
v-if="amountInput !== '0'"
|
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" />
|
<font-awesome icon="chevron-left" />
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
v-model="amountInput"
|
||||||
data-testId="inputOfferAmount"
|
data-testId="inputOfferAmount"
|
||||||
type="number"
|
type="number"
|
||||||
class="w-full border border-r-0 border-slate-400 px-2 py-2 text-center"
|
class="w-full border border-r-0 border-slate-400 px-2 py-2 text-center"
|
||||||
v-model="amountInput"
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
@click="increment()"
|
@click="increment()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-right" />
|
<font-awesome icon="chevron-right" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex justify-center">
|
<div class="mt-4 flex justify-center">
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
class="text-lg text-center leading-none absolute right-0 -top-1"
|
class="text-lg text-center leading-none absolute right-0 -top-1"
|
||||||
@click="onClickClose(true)"
|
@click="onClickClose(true)"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="w-[1em]" />
|
<font-awesome icon="xmark" class="w-[1em]" />
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
</span>
|
</span>
|
||||||
click on the
|
click on the
|
||||||
<span class="bg-green-600 text-white rounded-full">
|
<span class="bg-green-600 text-white rounded-full">
|
||||||
<fa icon="plus" class="fa-fw" />
|
<font-awesome icon="plus" class="fa-fw" />
|
||||||
</span>
|
</span>
|
||||||
button to express your appreciation for... whatever -- maybe thanks for
|
button to express your appreciation for... whatever -- maybe thanks for
|
||||||
showing you all these fascinating stories of
|
showing you all these fascinating stories of
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
<p class="mt-4 flex items-center">
|
<p class="mt-4 flex items-center">
|
||||||
The
|
The
|
||||||
<fa
|
<font-awesome
|
||||||
icon="house-chimney"
|
icon="house-chimney"
|
||||||
class="ml-1 mr-1 text-lg text-white bg-slate-400 px-2 py-2 rounded"
|
class="ml-1 mr-1 text-lg text-white bg-slate-400 px-2 py-2 rounded"
|
||||||
/>
|
/>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
class="text-lg text-center leading-none absolute right-0 -top-1"
|
class="text-lg text-center leading-none absolute right-0 -top-1"
|
||||||
@click="onClickClose(true)"
|
@click="onClickClose(true)"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="w-[1em]" />
|
<font-awesome icon="xmark" class="w-[1em]" />
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
|
|
||||||
<p class="mt-4 flex items-center">
|
<p class="mt-4 flex items-center">
|
||||||
The
|
The
|
||||||
<fa
|
<font-awesome
|
||||||
icon="magnifying-glass"
|
icon="magnifying-glass"
|
||||||
class="ml-1 mr-1 text-lg text-white bg-slate-400 px-2 py-2 rounded"
|
class="ml-1 mr-1 text-lg text-white bg-slate-400 px-2 py-2 rounded"
|
||||||
/>
|
/>
|
||||||
@@ -141,14 +141,14 @@
|
|||||||
class="text-lg text-center leading-none absolute right-0 -top-1"
|
class="text-lg text-center leading-none absolute right-0 -top-1"
|
||||||
@click="onClickClose(true)"
|
@click="onClickClose(true)"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="w-[1em]" />
|
<font-awesome icon="xmark" class="w-[1em]" />
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p class="relative">
|
<p class="relative">
|
||||||
Now you can take a turn: click on the
|
Now you can take a turn: click on the
|
||||||
<span class="bg-green-600 text-white rounded-full">
|
<span class="bg-green-600 text-white rounded-full">
|
||||||
<fa icon="plus" class="fa-fw" />
|
<font-awesome icon="plus" class="fa-fw" />
|
||||||
</span>
|
</span>
|
||||||
button to throw out projects of your own... anything you'd like to see
|
button to throw out projects of your own... anything you'd like to see
|
||||||
happen. If your first idea doesn't catch anyone, try, try again... and
|
happen. If your first idea doesn't catch anyone, try, try again... and
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
|
|
||||||
<p class="mt-4 flex items-center">
|
<p class="mt-4 flex items-center">
|
||||||
The
|
The
|
||||||
<fa
|
<font-awesome
|
||||||
icon="hand"
|
icon="hand"
|
||||||
class="ml-1 mr-1 text-lg text-white bg-slate-400 px-2 py-2 rounded"
|
class="ml-1 mr-1 text-lg text-white bg-slate-400 px-2 py-2 rounded"
|
||||||
/>
|
/>
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
By the way, one good way to get to know your neighbors and their
|
By the way, one good way to get to know your neighbors and their
|
||||||
interests is to offer time directly to them. You can do this on the
|
interests is to offer time directly to them. You can do this on the
|
||||||
contacts screen
|
contacts screen
|
||||||
<fa icon="users" class="text-slate-500" />
|
<font-awesome icon="users" class="text-slate-500" />
|
||||||
which is a great way to get to know a neighbor's interests.
|
which is a great way to get to know a neighbor's interests.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -219,6 +219,7 @@ import { OnboardPage } from "../libs/util";
|
|||||||
})
|
})
|
||||||
export default class OnboardingDialog extends Vue {
|
export default class OnboardingDialog extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
firstContactName = null;
|
firstContactName = null;
|
||||||
@@ -254,7 +255,7 @@ export default class OnboardingDialog extends Vue {
|
|||||||
finishedOnboarding: true,
|
finishedOnboarding: true,
|
||||||
});
|
});
|
||||||
if (goHome) {
|
if (goHome) {
|
||||||
(this.$router as Router).push({ name: "home" });
|
this.$router.push({ name: "home" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,12 +15,12 @@
|
|||||||
class="text-lg text-center px-2 py-0.5 leading-none absolute right-0 top-0 text-white"
|
class="text-lg text-center px-2 py-0.5 leading-none absolute right-0 top-0 text-white"
|
||||||
@click="close()"
|
@click="close()"
|
||||||
>
|
>
|
||||||
<fa icon="xmark" class="w-[1em]"></fa>
|
<font-awesome icon="xmark" class="w-[1em]"></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="uploading" class="flex justify-center">
|
<div v-if="uploading" class="flex justify-center">
|
||||||
<fa
|
<font-awesome
|
||||||
icon="spinner"
|
icon="spinner"
|
||||||
class="fa-spin fa-3x text-center block px-12 py-12"
|
class="fa-spin fa-3x text-center block px-12 py-12"
|
||||||
/>
|
/>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<div v-else-if="blob">
|
<div v-else-if="blob">
|
||||||
<div v-if="crop">
|
<div v-if="crop">
|
||||||
<VuePictureCropper
|
<VuePictureCropper
|
||||||
:boxStyle="{
|
:box-style="{
|
||||||
backgroundColor: '#f8f8f8',
|
backgroundColor: '#f8f8f8',
|
||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
}"
|
}"
|
||||||
@@ -56,8 +56,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="absolute bottom-[1rem] left-[1rem] px-2 py-1">
|
<div class="absolute bottom-[1rem] left-[1rem] px-2 py-1">
|
||||||
<button
|
<button
|
||||||
@click="uploadImage"
|
|
||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-1 px-2 rounded-md"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-1 px-2 rounded-md"
|
||||||
|
@click="uploadImage"
|
||||||
>
|
>
|
||||||
<span>Upload</span>
|
<span>Upload</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -67,8 +67,8 @@
|
|||||||
class="absolute bottom-[1rem] right-[1rem] px-2 py-1"
|
class="absolute bottom-[1rem] right-[1rem] px-2 py-1"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@click="retryImage"
|
|
||||||
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-1 px-2 rounded-md"
|
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-1 px-2 rounded-md"
|
||||||
|
@click="retryImage"
|
||||||
>
|
>
|
||||||
<span>Retry</span>
|
<span>Retry</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -81,37 +81,37 @@
|
|||||||
:resolution="{ width: 375, height: 812 }"
|
:resolution="{ width: 375, height: 812 }"
|
||||||
-->
|
-->
|
||||||
<camera
|
<camera
|
||||||
facingMode="environment"
|
|
||||||
autoplay
|
|
||||||
ref="camera"
|
ref="camera"
|
||||||
|
facing-mode="environment"
|
||||||
|
autoplay
|
||||||
@started="cameraStarted()"
|
@started="cameraStarted()"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="absolute portrait:bottom-0 portrait:left-0 portrait:right-0 portrait:pb-2 landscape:right-0 landscape:top-0 landscape:bottom-0 landscape:pr-4 flex landscape:flex-row justify-center items-center"
|
class="absolute portrait:bottom-0 portrait:left-0 portrait:right-0 portrait:pb-2 landscape:right-0 landscape:top-0 landscape:bottom-0 landscape:pr-4 flex landscape:flex-row justify-center items-center"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@click="takeImage()"
|
|
||||||
class="bg-blue-500 hover:bg-blue-700 text-white font-bold p-3 rounded-full text-2xl leading-none"
|
class="bg-blue-500 hover:bg-blue-700 text-white font-bold p-3 rounded-full text-2xl leading-none"
|
||||||
|
@click="takeImage()"
|
||||||
>
|
>
|
||||||
<fa icon="camera" class="w-[1em]"></fa>
|
<font-awesome icon="camera" class="w-[1em]"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="absolute portrait:bottom-2 portrait:right-16 landscape:right-0 landscape:bottom-16 landscape:pr-4 flex justify-center items-center"
|
class="absolute portrait:bottom-2 portrait:right-16 landscape:right-0 landscape:bottom-16 landscape:pr-4 flex justify-center items-center"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@click="swapMirrorClass()"
|
|
||||||
class="bg-blue-500 hover:bg-blue-700 text-white font-bold p-3 rounded-full text-2xl leading-none"
|
class="bg-blue-500 hover:bg-blue-700 text-white font-bold p-3 rounded-full text-2xl leading-none"
|
||||||
|
@click="swapMirrorClass()"
|
||||||
>
|
>
|
||||||
<fa icon="left-right" class="w-[1em]"></fa>
|
<font-awesome icon="left-right" class="w-[1em]"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="numDevices > 1" class="absolute bottom-2 right-4">
|
<div v-if="numDevices > 1" class="absolute bottom-2 right-4">
|
||||||
<button
|
<button
|
||||||
@click="switchCamera()"
|
|
||||||
class="bg-blue-500 hover:bg-blue-700 text-white font-bold p-3 rounded-full text-2xl leading-none"
|
class="bg-blue-500 hover:bg-blue-700 text-white font-bold p-3 rounded-full text-2xl leading-none"
|
||||||
|
@click="switchCamera()"
|
||||||
>
|
>
|
||||||
<fa icon="rotate" class="w-[1em]"></fa>
|
<font-awesome icon="rotate" class="w-[1em]"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</camera>
|
</camera>
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
class="h-full w-full object-contain"
|
class="h-full w-full object-contain"
|
||||||
>
|
>
|
||||||
<div v-html="generateIdenticon()" class="h-full w-full object-contain" />
|
<div class="h-full w-full object-contain" v-html="generateIdenticon()" />
|
||||||
</a>
|
</a>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
v-html="generateIdenticon()"
|
|
||||||
class="h-full w-full object-contain"
|
class="h-full w-full object-contain"
|
||||||
|
v-html="generateIdenticon()"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p v-else class="text-lg mb-4">
|
<p v-else class="text-lg mb-4">
|
||||||
Waiting for system initialization, which may take up to 5 seconds...
|
Waiting for system initialization, which may take up to 5 seconds...
|
||||||
<fa icon="spinner" spin />
|
<font-awesome icon="spinner" spin />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div v-if="serviceWorkerReady && vapidKey">
|
<div v-if="serviceWorkerReady && vapidKey">
|
||||||
@@ -54,23 +54,25 @@
|
|||||||
<span class="flex flex-row justify-center">
|
<span class="flex flex-row justify-center">
|
||||||
<span class="mt-2">... at: </span>
|
<span class="mt-2">... at: </span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
|
||||||
@change="checkHourInput"
|
|
||||||
class="rounded-l border border-r-0 border-slate-400 ml-2 mt-2 px-2 py-2 text-center w-20"
|
|
||||||
v-model="hourInput"
|
v-model="hourInput"
|
||||||
|
type="number"
|
||||||
|
class="rounded-l border border-r-0 border-slate-400 ml-2 mt-2 px-2 py-2 text-center w-20"
|
||||||
|
@change="checkHourInput"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="number"
|
|
||||||
@change="checkMinuteInput"
|
|
||||||
class="border border-slate-400 mt-2 px-2 py-2 text-center w-20"
|
|
||||||
v-model="minuteInput"
|
v-model="minuteInput"
|
||||||
|
type="number"
|
||||||
|
class="border border-slate-400 mt-2 px-2 py-2 text-center w-20"
|
||||||
|
@change="checkMinuteInput"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class="rounded-r border border-slate-400 bg-slate-200 text-center text-blue-500 mt-2 px-2 py-2 w-20"
|
class="rounded-r border border-slate-400 bg-slate-200 text-center text-blue-500 mt-2 px-2 py-2 w-20"
|
||||||
@click="hourAm = !hourAm"
|
@click="hourAm = !hourAm"
|
||||||
>
|
>
|
||||||
<span v-if="hourAm"> AM <fa icon="chevron-down" /> </span>
|
<span v-if="hourAm">
|
||||||
<span v-else> PM <fa icon="chevron-up" /> </span>
|
AM <font-awesome icon="chevron-down" />
|
||||||
|
</span>
|
||||||
|
<span v-else> PM <font-awesome icon="chevron-up" /> </span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,8 +88,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="close()"
|
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white mt-4 px-2 py-2 rounded-md"
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white mt-4 px-2 py-2 rounded-md"
|
||||||
|
@click="close()"
|
||||||
>
|
>
|
||||||
No, Not Now
|
No, Not Now
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- QUICK NAV -->
|
<!-- QUICK NAV -->
|
||||||
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200 z-50">
|
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200 z-50">
|
||||||
<ul class="flex text-2xl px-6 py-2 gap-1 max-w-3xl mx-auto">
|
<ul class="flex text-2xl p-2 gap-2 max-w-3xl mx-auto">
|
||||||
<!-- Home Feed -->
|
<!-- Home Feed -->
|
||||||
<li
|
<li
|
||||||
:class="{
|
:class="{
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
>
|
>
|
||||||
<router-link :to="{ name: 'home' }" class="block text-center py-2 px-1">
|
<router-link :to="{ name: 'home' }" class="block text-center py-2 px-1">
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<fa icon="house-chimney" class="fa-fw" />
|
<font-awesome icon="house-chimney" class="fa-fw" />
|
||||||
<span class="text-xs mt-1">feed</span>
|
<span class="text-xs mt-1">feed</span>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
class="block text-center py-2 px-1"
|
class="block text-center py-2 px-1"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<fa icon="magnifying-glass" class="fa-fw" />
|
<font-awesome icon="magnifying-glass" class="fa-fw" />
|
||||||
<span class="text-xs mt-1">search</span>
|
<span class="text-xs mt-1">search</span>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
class="block text-center py-2 px-1"
|
class="block text-center py-2 px-1"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<fa icon="hand" class="fa-fw" />
|
<font-awesome icon="hand" class="fa-fw" />
|
||||||
<span class="text-xs mt-1">your work</span>
|
<span class="text-xs mt-1">your work</span>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
class="block text-center py-2 px-1"
|
class="block text-center py-2 px-1"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<fa icon="users" class="fa-fw" />
|
<font-awesome icon="users" class="fa-fw" />
|
||||||
<span class="text-xs mt-1">contacts</span>
|
<span class="text-xs mt-1">contacts</span>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
class="block text-center py-2 px-1"
|
class="block text-center py-2 px-1"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<fa icon="circle-user" class="fa-fw" />
|
<font-awesome icon="circle-user" class="fa-fw" />
|
||||||
<!--
|
<!--
|
||||||
We used to say "account", so we'll keep that in the code,
|
We used to say "account", so we'll keep that in the code,
|
||||||
but it isn't accurate because we don't hold anything for them.
|
but it isn't accurate because we don't hold anything for them.
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
{{ sharingExplanation }}
|
{{ sharingExplanation }}
|
||||||
<input
|
<input
|
||||||
|
v-model="givenName"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
v-model="givenName"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
|
|||||||
58
src/interfaces/claims-result.ts
Normal file
58
src/interfaces/claims-result.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { GiverReceiverInputInfo } from "../libs/util";
|
||||||
|
import { ErrorResult, ResultWithType } from "./common";
|
||||||
|
|
||||||
|
export interface GiverOutputInfo {
|
||||||
|
action: string;
|
||||||
|
giver?: GiverReceiverInputInfo;
|
||||||
|
description?: string;
|
||||||
|
amount?: number;
|
||||||
|
unitCode?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClaimResult {
|
||||||
|
success: { claimId: string; handleId: string };
|
||||||
|
error: { code: string; message: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VerifiableCredential {
|
||||||
|
exp?: number;
|
||||||
|
iat: number;
|
||||||
|
iss: string;
|
||||||
|
vc: {
|
||||||
|
"@context": string[];
|
||||||
|
type: string[];
|
||||||
|
credentialSubject: VerifiableCredentialSubject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VerifiableCredentialSubject {
|
||||||
|
"@context": string;
|
||||||
|
"@type": string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorldProperties {
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProviderInfo {
|
||||||
|
/**
|
||||||
|
* Could be a DID or a handleId that identifies the provider
|
||||||
|
*/
|
||||||
|
identifier: string;
|
||||||
|
/**
|
||||||
|
* Indicates if the provider link has been confirmed
|
||||||
|
*/
|
||||||
|
linkConfirmed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type for createAndSubmitClaim result
|
||||||
|
export type CreateAndSubmitClaimResult = SuccessResult | ErrorResult;
|
||||||
|
|
||||||
|
// Update SuccessResult to use ClaimResult
|
||||||
|
export interface SuccessResult extends ResultWithType {
|
||||||
|
type: "success";
|
||||||
|
response: AxiosResponse<ClaimResult>;
|
||||||
|
}
|
||||||
68
src/interfaces/claims.ts
Normal file
68
src/interfaces/claims.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { GenericVerifiableCredential } from "./common";
|
||||||
|
|
||||||
|
export interface AgreeVerifiableCredential {
|
||||||
|
"@context": string;
|
||||||
|
"@type": string;
|
||||||
|
object: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that previous VCs may have additional fields.
|
||||||
|
// https://endorser.ch/doc/html/transactions.html#id4
|
||||||
|
export interface GiveVerifiableCredential extends GenericVerifiableCredential {
|
||||||
|
"@context"?: string;
|
||||||
|
"@type": "GiveAction";
|
||||||
|
agent?: { identifier: string };
|
||||||
|
description?: string;
|
||||||
|
fulfills?: { "@type": string; identifier?: string; lastClaimId?: string }[];
|
||||||
|
identifier?: string;
|
||||||
|
image?: string;
|
||||||
|
object?: { amountOfThisGood: number; unitCode: string };
|
||||||
|
provider?: GenericVerifiableCredential;
|
||||||
|
recipient?: { identifier: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that previous VCs may have additional fields.
|
||||||
|
// https://endorser.ch/doc/html/transactions.html#id8
|
||||||
|
export interface OfferVerifiableCredential extends GenericVerifiableCredential {
|
||||||
|
"@context"?: string;
|
||||||
|
"@type": "Offer";
|
||||||
|
description?: string;
|
||||||
|
includesObject?: { amountOfThisGood: number; unitCode: string };
|
||||||
|
itemOffered?: {
|
||||||
|
description?: string;
|
||||||
|
isPartOf?: {
|
||||||
|
identifier?: string;
|
||||||
|
lastClaimId?: string;
|
||||||
|
"@type"?: string;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
offeredBy?: { identifier: string };
|
||||||
|
recipient?: { identifier: string };
|
||||||
|
validThrough?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that previous VCs may have additional fields.
|
||||||
|
// https://endorser.ch/doc/html/transactions.html#id7
|
||||||
|
export interface PlanVerifiableCredential extends GenericVerifiableCredential {
|
||||||
|
"@context": "https://schema.org";
|
||||||
|
"@type": "PlanAction";
|
||||||
|
name: string;
|
||||||
|
agent?: { identifier: string };
|
||||||
|
description?: string;
|
||||||
|
identifier?: string;
|
||||||
|
lastClaimId?: string;
|
||||||
|
location?: {
|
||||||
|
geo: { "@type": "GeoCoordinates"; latitude: number; longitude: number };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// AKA Registration & RegisterAction
|
||||||
|
export interface RegisterVerifiableCredential {
|
||||||
|
"@context": string;
|
||||||
|
"@type": "RegisterAction";
|
||||||
|
agent: { identifier: string };
|
||||||
|
identifier?: string;
|
||||||
|
object: string;
|
||||||
|
participant?: { identifier: string };
|
||||||
|
}
|
||||||
36
src/interfaces/common.ts
Normal file
36
src/interfaces/common.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// similar to VerifiableCredentialSubject... maybe rename this
|
||||||
|
export interface GenericVerifiableCredential {
|
||||||
|
"@context"?: string;
|
||||||
|
"@type": string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenericCredWrapper<T extends GenericVerifiableCredential> {
|
||||||
|
claim: T;
|
||||||
|
claimType?: string;
|
||||||
|
handleId: string;
|
||||||
|
id: string;
|
||||||
|
issuedAt: string;
|
||||||
|
issuer: string;
|
||||||
|
publicUrls?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResultWithType {
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorResponse {
|
||||||
|
error?: {
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InternalError {
|
||||||
|
error: string;
|
||||||
|
userMessage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorResult extends ResultWithType {
|
||||||
|
type: "error";
|
||||||
|
error: InternalError;
|
||||||
|
}
|
||||||
13
src/interfaces/deepLinks.ts
Normal file
13
src/interfaces/deepLinks.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* @file Deep Link Interface Definitions
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*
|
||||||
|
* Defines the core interfaces for the deep linking system.
|
||||||
|
* These interfaces are used across the deep linking implementation
|
||||||
|
* to ensure type safety and consistent error handling.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface DeepLinkError extends Error {
|
||||||
|
code: string;
|
||||||
|
details?: unknown;
|
||||||
|
}
|
||||||
7
src/interfaces/index.ts
Normal file
7
src/interfaces/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export * from "./claims";
|
||||||
|
export * from "./claims-result";
|
||||||
|
export * from "./common";
|
||||||
|
export * from "./limits";
|
||||||
|
export * from "./records";
|
||||||
|
export * from "./user";
|
||||||
|
export * from "./deepLinks";
|
||||||
14
src/interfaces/limits.ts
Normal file
14
src/interfaces/limits.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export interface EndorserRateLimits {
|
||||||
|
doneClaimsThisWeek: string;
|
||||||
|
doneRegistrationsThisMonth: string;
|
||||||
|
maxClaimsPerWeek: string;
|
||||||
|
maxRegistrationsPerMonth: string;
|
||||||
|
nextMonthBeginDateTime: string;
|
||||||
|
nextWeekBeginDateTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImageRateLimits {
|
||||||
|
doneImagesThisWeek: string;
|
||||||
|
maxImagesPerWeek: string;
|
||||||
|
nextWeekBeginDateTime: string;
|
||||||
|
}
|
||||||
92
src/interfaces/records.ts
Normal file
92
src/interfaces/records.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { GiveVerifiableCredential, OfferVerifiableCredential } from "./claims";
|
||||||
|
|
||||||
|
// a summary record; the VC is found the fullClaim field
|
||||||
|
export interface GiveSummaryRecord {
|
||||||
|
[x: string]: PropertyKey | undefined | GiveVerifiableCredential;
|
||||||
|
type?: string;
|
||||||
|
agentDid: string;
|
||||||
|
amount: number;
|
||||||
|
amountConfirmed: number;
|
||||||
|
description: string;
|
||||||
|
fullClaim: GiveVerifiableCredential;
|
||||||
|
fulfillsHandleId: string;
|
||||||
|
fulfillsPlanHandleId?: string;
|
||||||
|
fulfillsType?: string;
|
||||||
|
handleId: string;
|
||||||
|
issuedAt: string;
|
||||||
|
issuerDid: string;
|
||||||
|
jwtId: string;
|
||||||
|
providerPlanHandleId?: string;
|
||||||
|
recipientDid: string;
|
||||||
|
unit: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a summary record; the VC is found the fullClaim field
|
||||||
|
export interface OfferSummaryRecord {
|
||||||
|
amount: number;
|
||||||
|
amountGiven: number;
|
||||||
|
amountGivenConfirmed: number;
|
||||||
|
fullClaim: OfferVerifiableCredential;
|
||||||
|
fulfillsPlanHandleId: string;
|
||||||
|
handleId: string;
|
||||||
|
issuerDid: string;
|
||||||
|
jwtId: string;
|
||||||
|
nonAmountGivenConfirmed: number;
|
||||||
|
objectDescription: string;
|
||||||
|
offeredByDid: string;
|
||||||
|
recipientDid: string;
|
||||||
|
requirementsMet: boolean;
|
||||||
|
unit: string;
|
||||||
|
validThrough: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OfferToPlanSummaryRecord extends OfferSummaryRecord {
|
||||||
|
planName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a summary record; the VC is not currently part of this record
|
||||||
|
export interface PlanSummaryRecord {
|
||||||
|
agentDid?: string;
|
||||||
|
description: string;
|
||||||
|
endTime?: string;
|
||||||
|
fulfillsPlanHandleId: string;
|
||||||
|
handleId: string;
|
||||||
|
image?: string;
|
||||||
|
issuerDid: string;
|
||||||
|
locLat?: number;
|
||||||
|
locLon?: number;
|
||||||
|
name?: string;
|
||||||
|
startTime?: string;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents data about a project
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
* (Maybe we should use PlanSummaryRecord instead, either by adding rowId or by iterating with jwtId.)
|
||||||
|
**/
|
||||||
|
export interface PlanData {
|
||||||
|
/**
|
||||||
|
* Description of the project
|
||||||
|
**/
|
||||||
|
description: string;
|
||||||
|
/**
|
||||||
|
* URL referencing information about the project
|
||||||
|
**/
|
||||||
|
handleId: string;
|
||||||
|
image?: string;
|
||||||
|
/**
|
||||||
|
* The DID of the issuer
|
||||||
|
*/
|
||||||
|
issuerDid: string;
|
||||||
|
/**
|
||||||
|
* Name of the project
|
||||||
|
**/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* The identifier of the project record -- different from jwtId
|
||||||
|
* (Maybe we should use the jwtId to iterate through the records instead.)
|
||||||
|
**/
|
||||||
|
rowId?: string;
|
||||||
|
}
|
||||||
8
src/interfaces/user.ts
Normal file
8
src/interfaces/user.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface UserInfo {
|
||||||
|
did: string;
|
||||||
|
name: string;
|
||||||
|
publicEncKey: string;
|
||||||
|
registered: boolean;
|
||||||
|
profileImageUrl?: string;
|
||||||
|
nextPublicEncKeyHash?: string;
|
||||||
|
}
|
||||||
59
src/lib/capacitor/app.ts
Normal file
59
src/lib/capacitor/app.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// Import from node_modules using relative path
|
||||||
|
|
||||||
|
import {
|
||||||
|
App as CapacitorApp,
|
||||||
|
AppLaunchUrl,
|
||||||
|
BackButtonListener,
|
||||||
|
} from '../../../node_modules/@capacitor/app';
|
||||||
|
import type { PluginListenerHandle } from "@capacitor/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface defining the app event listener functionality
|
||||||
|
* Supports 'backButton' and 'appUrlOpen' events from Capacitor
|
||||||
|
*/
|
||||||
|
interface AppInterface {
|
||||||
|
/**
|
||||||
|
* Add listener for back button events
|
||||||
|
* @param eventName - Must be 'backButton'
|
||||||
|
* @param listenerFunc - Callback function for back button events
|
||||||
|
* @returns Promise that resolves with a removable listener handle
|
||||||
|
*/
|
||||||
|
addListener(
|
||||||
|
eventName: "backButton",
|
||||||
|
listenerFunc: BackButtonListener,
|
||||||
|
): Promise<PluginListenerHandle> & PluginListenerHandle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add listener for app URL open events
|
||||||
|
* @param eventName - Must be 'appUrlOpen'
|
||||||
|
* @param listenerFunc - Callback function for URL open events
|
||||||
|
* @returns Promise that resolves with a removable listener handle
|
||||||
|
*/
|
||||||
|
addListener(
|
||||||
|
eventName: "appUrlOpen",
|
||||||
|
listenerFunc: (data: AppLaunchUrl) => void,
|
||||||
|
): Promise<PluginListenerHandle> & PluginListenerHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App wrapper for Capacitor functionality
|
||||||
|
* Provides type-safe event listeners for back button and URL open events
|
||||||
|
*/
|
||||||
|
export const App: AppInterface = {
|
||||||
|
addListener(
|
||||||
|
eventName: "backButton" | "appUrlOpen",
|
||||||
|
listenerFunc: BackButtonListener | ((data: AppLaunchUrl) => void),
|
||||||
|
): Promise<PluginListenerHandle> & PluginListenerHandle {
|
||||||
|
if (eventName === "backButton") {
|
||||||
|
return CapacitorApp.addListener(
|
||||||
|
eventName,
|
||||||
|
listenerFunc as BackButtonListener,
|
||||||
|
) as Promise<PluginListenerHandle> & PluginListenerHandle;
|
||||||
|
} else {
|
||||||
|
return CapacitorApp.addListener(
|
||||||
|
eventName,
|
||||||
|
listenerFunc as (data: AppLaunchUrl) => void,
|
||||||
|
) as Promise<PluginListenerHandle> & PluginListenerHandle;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
168
src/lib/fontawesome.ts
Normal file
168
src/lib/fontawesome.ts
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
/**
|
||||||
|
* @file Font Awesome Icon Library Configuration
|
||||||
|
* @description Centralizes Font Awesome icon imports and library configuration
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||||
|
import {
|
||||||
|
faArrowDown,
|
||||||
|
faArrowLeft,
|
||||||
|
faArrowRight,
|
||||||
|
faArrowRotateBackward,
|
||||||
|
faArrowUpRightFromSquare,
|
||||||
|
faArrowUp,
|
||||||
|
faBan,
|
||||||
|
faBitcoinSign,
|
||||||
|
faBurst,
|
||||||
|
faCalendar,
|
||||||
|
faCamera,
|
||||||
|
faCaretDown,
|
||||||
|
faChair,
|
||||||
|
faCheck,
|
||||||
|
faChevronDown,
|
||||||
|
faChevronLeft,
|
||||||
|
faChevronRight,
|
||||||
|
faChevronUp,
|
||||||
|
faCircle,
|
||||||
|
faCircleCheck,
|
||||||
|
faCircleInfo,
|
||||||
|
faCircleQuestion,
|
||||||
|
faCircleUser,
|
||||||
|
faClock,
|
||||||
|
faCoins,
|
||||||
|
faComment,
|
||||||
|
faCopy,
|
||||||
|
faDollar,
|
||||||
|
faEllipsis,
|
||||||
|
faEllipsisVertical,
|
||||||
|
faEnvelopeOpenText,
|
||||||
|
faEraser,
|
||||||
|
faEye,
|
||||||
|
faEyeSlash,
|
||||||
|
faFileContract,
|
||||||
|
faFileLines,
|
||||||
|
faFilter,
|
||||||
|
faFloppyDisk,
|
||||||
|
faFolderOpen,
|
||||||
|
faForward,
|
||||||
|
faGift,
|
||||||
|
faGlobe,
|
||||||
|
faHammer,
|
||||||
|
faHand,
|
||||||
|
faHandHoldingDollar,
|
||||||
|
faHandHoldingHeart,
|
||||||
|
faHouseChimney,
|
||||||
|
faImagePortrait,
|
||||||
|
faLeftRight,
|
||||||
|
faLightbulb,
|
||||||
|
faLink,
|
||||||
|
faLocationDot,
|
||||||
|
faLongArrowAltLeft,
|
||||||
|
faLongArrowAltRight,
|
||||||
|
faMagnifyingGlass,
|
||||||
|
faMessage,
|
||||||
|
faMinus,
|
||||||
|
faPen,
|
||||||
|
faPersonCircleCheck,
|
||||||
|
faPersonCircleQuestion,
|
||||||
|
faPlus,
|
||||||
|
faQuestion,
|
||||||
|
faQrcode,
|
||||||
|
faRightFromBracket,
|
||||||
|
faRotate,
|
||||||
|
faShareNodes,
|
||||||
|
faSpinner,
|
||||||
|
faSquare,
|
||||||
|
faSquareCaretDown,
|
||||||
|
faSquareCaretUp,
|
||||||
|
faSquarePlus,
|
||||||
|
faTrashCan,
|
||||||
|
faTriangleExclamation,
|
||||||
|
faUser,
|
||||||
|
faUsers,
|
||||||
|
faXmark,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
// Initialize Font Awesome library with all required icons
|
||||||
|
library.add(
|
||||||
|
faArrowDown,
|
||||||
|
faArrowLeft,
|
||||||
|
faArrowRight,
|
||||||
|
faArrowRotateBackward,
|
||||||
|
faArrowUpRightFromSquare,
|
||||||
|
faArrowUp,
|
||||||
|
faBan,
|
||||||
|
faBitcoinSign,
|
||||||
|
faBurst,
|
||||||
|
faCalendar,
|
||||||
|
faCamera,
|
||||||
|
faCaretDown,
|
||||||
|
faChair,
|
||||||
|
faCheck,
|
||||||
|
faChevronDown,
|
||||||
|
faChevronLeft,
|
||||||
|
faChevronRight,
|
||||||
|
faChevronUp,
|
||||||
|
faCircle,
|
||||||
|
faCircleCheck,
|
||||||
|
faCircleInfo,
|
||||||
|
faCircleQuestion,
|
||||||
|
faCircleUser,
|
||||||
|
faClock,
|
||||||
|
faCoins,
|
||||||
|
faComment,
|
||||||
|
faCopy,
|
||||||
|
faDollar,
|
||||||
|
faEllipsis,
|
||||||
|
faEllipsisVertical,
|
||||||
|
faEnvelopeOpenText,
|
||||||
|
faEraser,
|
||||||
|
faEye,
|
||||||
|
faEyeSlash,
|
||||||
|
faFileContract,
|
||||||
|
faFileLines,
|
||||||
|
faFilter,
|
||||||
|
faFloppyDisk,
|
||||||
|
faFolderOpen,
|
||||||
|
faForward,
|
||||||
|
faGift,
|
||||||
|
faGlobe,
|
||||||
|
faHammer,
|
||||||
|
faHand,
|
||||||
|
faHandHoldingDollar,
|
||||||
|
faHandHoldingHeart,
|
||||||
|
faHouseChimney,
|
||||||
|
faImagePortrait,
|
||||||
|
faLeftRight,
|
||||||
|
faLightbulb,
|
||||||
|
faLink,
|
||||||
|
faLocationDot,
|
||||||
|
faLongArrowAltLeft,
|
||||||
|
faLongArrowAltRight,
|
||||||
|
faMagnifyingGlass,
|
||||||
|
faMessage,
|
||||||
|
faMinus,
|
||||||
|
faPen,
|
||||||
|
faPersonCircleCheck,
|
||||||
|
faPersonCircleQuestion,
|
||||||
|
faPlus,
|
||||||
|
faQrcode,
|
||||||
|
faQuestion,
|
||||||
|
faRotate,
|
||||||
|
faRightFromBracket,
|
||||||
|
faShareNodes,
|
||||||
|
faSpinner,
|
||||||
|
faSquare,
|
||||||
|
faSquareCaretDown,
|
||||||
|
faSquareCaretUp,
|
||||||
|
faSquarePlus,
|
||||||
|
faTrashCan,
|
||||||
|
faTriangleExclamation,
|
||||||
|
faUser,
|
||||||
|
faUsers,
|
||||||
|
faXmark,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Export the FontAwesomeIcon component for use in other files
|
||||||
|
export { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||||
@@ -1,4 +1,22 @@
|
|||||||
import { Axios, AxiosRequestConfig, AxiosResponse } from "axios";
|
/**
|
||||||
|
* @fileoverview Endorser Server Interface and Utilities
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*
|
||||||
|
* This module provides the interface and utilities for interacting with the Endorser server.
|
||||||
|
* It handles authentication, data validation, and server communication for claims, contacts,
|
||||||
|
* and other core functionality.
|
||||||
|
*
|
||||||
|
* Key Features:
|
||||||
|
* - Deep link URL path constants
|
||||||
|
* - DID validation and handling
|
||||||
|
* - Contact management utilities
|
||||||
|
* - Server authentication
|
||||||
|
* - Plan caching
|
||||||
|
*
|
||||||
|
* @module endorserServer
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Axios, AxiosRequestConfig } from "axios";
|
||||||
import { Buffer } from "buffer";
|
import { Buffer } from "buffer";
|
||||||
import { sha256 } from "ethereum-cryptography/sha256";
|
import { sha256 } from "ethereum-cryptography/sha256";
|
||||||
import { LRUCache } from "lru-cache";
|
import { LRUCache } from "lru-cache";
|
||||||
@@ -17,63 +35,62 @@ import {
|
|||||||
retrieveAccountMetadata,
|
retrieveAccountMetadata,
|
||||||
retrieveFullyDecryptedAccount,
|
retrieveFullyDecryptedAccount,
|
||||||
getPasskeyExpirationSeconds,
|
getPasskeyExpirationSeconds,
|
||||||
GiverReceiverInputInfo,
|
|
||||||
} from "../libs/util";
|
} from "../libs/util";
|
||||||
import { createEndorserJwtForKey, KeyMeta } from "../libs/crypto/vc";
|
import { createEndorserJwtForKey, KeyMeta } from "../libs/crypto/vc";
|
||||||
|
|
||||||
|
import {
|
||||||
|
GiveVerifiableCredential,
|
||||||
|
OfferVerifiableCredential,
|
||||||
|
RegisterVerifiableCredential,
|
||||||
|
GenericVerifiableCredential,
|
||||||
|
GenericCredWrapper,
|
||||||
|
PlanSummaryRecord,
|
||||||
|
UserInfo,
|
||||||
|
CreateAndSubmitClaimResult,
|
||||||
|
} from "../interfaces";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard context for schema.org data
|
||||||
|
* @constant {string}
|
||||||
|
*/
|
||||||
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
|
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
|
||||||
// the object in RegisterAction claims
|
|
||||||
|
/**
|
||||||
|
* Service identifier for RegisterAction claims
|
||||||
|
* @constant {string}
|
||||||
|
*/
|
||||||
export const SERVICE_ID = "endorser.ch";
|
export const SERVICE_ID = "endorser.ch";
|
||||||
// the header line for contacts exported via Endorser Mobile
|
|
||||||
|
/**
|
||||||
|
* Header line format for contacts exported via Endorser Mobile
|
||||||
|
* @constant {string}
|
||||||
|
*/
|
||||||
export const CONTACT_CSV_HEADER = "name,did,pubKeyBase64,seesMe,registered";
|
export const CONTACT_CSV_HEADER = "name,did,pubKeyBase64,seesMe,registered";
|
||||||
// the suffix for the contact URL in this app where they are confirmed before import
|
|
||||||
|
/**
|
||||||
|
* URL path suffix for contact confirmation before import
|
||||||
|
* @constant {string}
|
||||||
|
*/
|
||||||
export const CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI = "/contact-import/";
|
export const CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI = "/contact-import/";
|
||||||
// the suffix for the contact URL in this app where a single one gets imported automatically
|
|
||||||
|
/**
|
||||||
|
* URL path suffix for the contact URL in this app where a single one gets imported automatically
|
||||||
|
* @constant {string}
|
||||||
|
*/
|
||||||
export const CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI = "/contacts?contactJwt=";
|
export const CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI = "/contacts?contactJwt=";
|
||||||
// the suffix for the old contact URL -- deprecated Jan 2025, though "endorser.ch/contact?jwt=" shows data on endorser.ch server
|
|
||||||
|
/**
|
||||||
|
* URL path suffix for the old contact URL -- deprecated Jan 2025, though "endorser.ch/contact?jwt=" shows data on endorser.ch server
|
||||||
|
* @constant {string}
|
||||||
|
*/
|
||||||
export const CONTACT_URL_PATH_ENDORSER_CH_OLD = "/contact?jwt=";
|
export const CONTACT_URL_PATH_ENDORSER_CH_OLD = "/contact?jwt=";
|
||||||
// unused now that we match on the URL path; just note that it was used for a while to create URLs that showed at endorser.ch
|
|
||||||
//export const CONTACT_URL_PREFIX_ENDORSER_CH_OLD = "https://endorser.ch";
|
/**
|
||||||
// the prefix for handle IDs, the permanent ID for claims on Endorser
|
* The prefix for handle IDs, the permanent ID for claims on Endorser
|
||||||
|
* @constant {string}
|
||||||
|
*/
|
||||||
export const ENDORSER_CH_HANDLE_PREFIX = "https://endorser.ch/entity/";
|
export const ENDORSER_CH_HANDLE_PREFIX = "https://endorser.ch/entity/";
|
||||||
|
|
||||||
export interface AgreeVerifiableCredential {
|
|
||||||
"@context": string;
|
|
||||||
"@type": string;
|
|
||||||
// "any" because arbitrary objects can be subject of agreement
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
object: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GiverOutputInfo {
|
|
||||||
action: string;
|
|
||||||
giver?: GiverReceiverInputInfo;
|
|
||||||
description?: string;
|
|
||||||
amount?: number;
|
|
||||||
unitCode?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ClaimResult {
|
|
||||||
success: { claimId: string; handleId: string };
|
|
||||||
error: { code: string; message: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
// similar to VerifiableCredentialSubject... maybe rename this
|
|
||||||
export interface GenericVerifiableCredential {
|
|
||||||
"@context"?: string; // optional when embedded, eg. in an Agree
|
|
||||||
"@type": string;
|
|
||||||
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GenericCredWrapper<T extends GenericVerifiableCredential> {
|
|
||||||
claim: T;
|
|
||||||
claimType?: string;
|
|
||||||
handleId: string;
|
|
||||||
id: string;
|
|
||||||
issuedAt: string;
|
|
||||||
issuer: string;
|
|
||||||
publicUrls?: Record<string, string>; // only for IDs that want to be public
|
|
||||||
}
|
|
||||||
export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper<GenericVerifiableCredential> =
|
export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper<GenericVerifiableCredential> =
|
||||||
{
|
{
|
||||||
claim: { "@type": "" },
|
claim: { "@type": "" },
|
||||||
@@ -83,266 +100,98 @@ export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper<GenericVerifiableCr
|
|||||||
issuer: "",
|
issuer: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
// a summary record; the VC is found the fullClaim field
|
|
||||||
export interface GiveSummaryRecord {
|
|
||||||
agentDid: string;
|
|
||||||
amount: number;
|
|
||||||
amountConfirmed: number;
|
|
||||||
description: string;
|
|
||||||
fullClaim: GiveVerifiableCredential;
|
|
||||||
fulfillsHandleId: string;
|
|
||||||
fulfillsPlanHandleId?: string;
|
|
||||||
fulfillsType?: string;
|
|
||||||
handleId: string;
|
|
||||||
issuedAt: string;
|
|
||||||
issuerDid: string;
|
|
||||||
jwtId: string;
|
|
||||||
providerPlanHandleId?: string;
|
|
||||||
recipientDid: string;
|
|
||||||
unit: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// a summary record; the VC is found the fullClaim field
|
|
||||||
export interface OfferSummaryRecord {
|
|
||||||
amount: number;
|
|
||||||
amountGiven: number;
|
|
||||||
amountGivenConfirmed: number;
|
|
||||||
fullClaim: OfferVerifiableCredential;
|
|
||||||
fulfillsPlanHandleId: string;
|
|
||||||
handleId: string;
|
|
||||||
issuerDid: string;
|
|
||||||
jwtId: string;
|
|
||||||
nonAmountGivenConfirmed: number;
|
|
||||||
objectDescription: string;
|
|
||||||
offeredByDid: string;
|
|
||||||
recipientDid: string;
|
|
||||||
requirementsMet: boolean;
|
|
||||||
unit: string;
|
|
||||||
validThrough: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OfferToPlanSummaryRecord extends OfferSummaryRecord {
|
|
||||||
planName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// a summary record; the VC is not currently part of this record
|
|
||||||
export interface PlanSummaryRecord {
|
|
||||||
agentDid?: string; // optional, if the issuer wants someone else to manage as well
|
|
||||||
description: string;
|
|
||||||
endTime?: string;
|
|
||||||
fulfillsPlanHandleId: string;
|
|
||||||
handleId: string;
|
|
||||||
image?: string;
|
|
||||||
issuerDid: string;
|
|
||||||
locLat?: number;
|
|
||||||
locLon?: number;
|
|
||||||
name?: string;
|
|
||||||
startTime?: string;
|
|
||||||
url?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that previous VCs may have additional fields.
|
|
||||||
// https://endorser.ch/doc/html/transactions.html#id4
|
|
||||||
export interface GiveVerifiableCredential extends GenericVerifiableCredential {
|
|
||||||
"@context"?: string; // optional when embedded, eg. in an Agree
|
|
||||||
"@type": "GiveAction";
|
|
||||||
agent?: { identifier: string };
|
|
||||||
description?: string;
|
|
||||||
fulfills?: { "@type": string; identifier?: string; lastClaimId?: string }[];
|
|
||||||
identifier?: string;
|
|
||||||
image?: string;
|
|
||||||
object?: { amountOfThisGood: number; unitCode: string };
|
|
||||||
provider?: GenericVerifiableCredential; // typically @type & identifier
|
|
||||||
recipient?: { identifier: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that previous VCs may have additional fields.
|
|
||||||
// https://endorser.ch/doc/html/transactions.html#id8
|
|
||||||
export interface OfferVerifiableCredential extends GenericVerifiableCredential {
|
|
||||||
"@context"?: string; // optional when embedded... though it doesn't make sense to agree to an offer
|
|
||||||
"@type": "Offer";
|
|
||||||
description?: string; // conditions for the offer
|
|
||||||
includesObject?: { amountOfThisGood: number; unitCode: string };
|
|
||||||
itemOffered?: {
|
|
||||||
description?: string; // description of the item
|
|
||||||
isPartOf?: { identifier?: string; lastClaimId?: string; "@type"?: string };
|
|
||||||
};
|
|
||||||
offeredBy?: { identifier: string };
|
|
||||||
recipient?: { identifier: string };
|
|
||||||
validThrough?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that previous VCs may have additional fields.
|
|
||||||
// https://endorser.ch/doc/html/transactions.html#id7
|
|
||||||
export interface PlanVerifiableCredential extends GenericVerifiableCredential {
|
|
||||||
"@context": "https://schema.org";
|
|
||||||
"@type": "PlanAction";
|
|
||||||
name: string;
|
|
||||||
agent?: { identifier: string };
|
|
||||||
description?: string;
|
|
||||||
identifier?: string;
|
|
||||||
lastClaimId?: string;
|
|
||||||
location?: {
|
|
||||||
geo: { "@type": "GeoCoordinates"; latitude: number; longitude: number };
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents data about a project
|
|
||||||
*
|
|
||||||
* @deprecated
|
|
||||||
* (Maybe we should use PlanSummaryRecord instead, either by adding rowId or by iterating with jwtId.)
|
|
||||||
**/
|
|
||||||
export interface PlanData {
|
|
||||||
/**
|
|
||||||
* Description of the project
|
|
||||||
**/
|
|
||||||
description: string;
|
|
||||||
/**
|
|
||||||
* URL referencing information about the project
|
|
||||||
**/
|
|
||||||
handleId: string;
|
|
||||||
image?: string;
|
|
||||||
/**
|
|
||||||
* The DID of the issuer
|
|
||||||
*/
|
|
||||||
issuerDid: string;
|
|
||||||
/**
|
|
||||||
* Name of the project
|
|
||||||
**/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* The identifier of the project record -- different from jwtId
|
|
||||||
* (Maybe we should use the jwtId to iterate through the records instead.)
|
|
||||||
**/
|
|
||||||
rowId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EndorserRateLimits {
|
|
||||||
doneClaimsThisWeek: string;
|
|
||||||
doneRegistrationsThisMonth: string;
|
|
||||||
maxClaimsPerWeek: string;
|
|
||||||
maxRegistrationsPerMonth: string;
|
|
||||||
nextMonthBeginDateTime: string;
|
|
||||||
nextWeekBeginDateTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImageRateLimits {
|
|
||||||
doneImagesThisWeek: string;
|
|
||||||
maxImagesPerWeek: string;
|
|
||||||
nextWeekBeginDateTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VerifiableCredential {
|
|
||||||
exp?: number;
|
|
||||||
iat: number;
|
|
||||||
iss: string;
|
|
||||||
vc: {
|
|
||||||
"@context": string[];
|
|
||||||
type: string[];
|
|
||||||
credentialSubject: VerifiableCredentialSubject;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// similar to GenericVerifiableCredential... maybe replace that one
|
|
||||||
export interface VerifiableCredentialSubject {
|
|
||||||
"@context": string;
|
|
||||||
"@type": string;
|
|
||||||
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WorldProperties {
|
|
||||||
startTime?: string;
|
|
||||||
endTime?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AKA Registration & RegisterAction
|
|
||||||
export interface RegisterVerifiableCredential {
|
|
||||||
"@context": typeof SCHEMA_ORG_CONTEXT;
|
|
||||||
"@type": "RegisterAction";
|
|
||||||
agent: { identifier: string };
|
|
||||||
identifier?: string; // used for invites (when participant ID isn't known)
|
|
||||||
object: string;
|
|
||||||
participant?: { identifier: string }; // used when person is known (not an invite)
|
|
||||||
}
|
|
||||||
|
|
||||||
// now for some of the error & other wrapper types
|
|
||||||
|
|
||||||
export interface ResultWithType {
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SuccessResult extends ResultWithType {
|
|
||||||
type: "success";
|
|
||||||
response: AxiosResponse<ClaimResult>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ErrorResponse {
|
|
||||||
error?: {
|
|
||||||
message?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InternalError {
|
|
||||||
error: string; // for system logging
|
|
||||||
userMessage?: string; // for user display
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ErrorResult extends ResultWithType {
|
|
||||||
type: "error";
|
|
||||||
error: InternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CreateAndSubmitClaimResult = SuccessResult | ErrorResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is similar to Contact but it grew up in different logic paths.
|
|
||||||
* We may want to change this to be a Contact.
|
|
||||||
*/
|
|
||||||
export interface UserInfo {
|
|
||||||
did: string;
|
|
||||||
name: string;
|
|
||||||
publicEncKey: string;
|
|
||||||
registered: boolean;
|
|
||||||
profileImageUrl?: string;
|
|
||||||
nextPublicEncKeyHash?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is used to check for hidden info.
|
// This is used to check for hidden info.
|
||||||
// See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
|
// See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
|
||||||
const HIDDEN_DID = "did:none:HIDDEN";
|
const HIDDEN_DID = "did:none:HIDDEN";
|
||||||
|
|
||||||
export function isDid(did: string) {
|
/**
|
||||||
|
* Validates if a string is a valid DID
|
||||||
|
* @param {string} did - The DID string to validate
|
||||||
|
* @returns {boolean} True if string is a valid DID format
|
||||||
|
*/
|
||||||
|
export function isDid(did: string): boolean {
|
||||||
return did.startsWith("did:");
|
return did.startsWith("did:");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isHiddenDid(did: string) {
|
/**
|
||||||
|
* Checks if a DID is the special hidden DID value
|
||||||
|
* @param {string} did - The DID to check
|
||||||
|
* @returns {boolean} True if DID is hidden
|
||||||
|
*/
|
||||||
|
export function isHiddenDid(did: string): boolean {
|
||||||
return did === HIDDEN_DID;
|
return did === HIDDEN_DID;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEmptyOrHiddenDid(did?: string) {
|
/**
|
||||||
return !did || did === HIDDEN_DID; // catching empty string as well
|
* Checks if a DID is empty or hidden
|
||||||
|
* @param {string} [did] - The DID to check
|
||||||
|
* @returns {boolean} True if DID is empty or hidden
|
||||||
|
*/
|
||||||
|
export function isEmptyOrHiddenDid(did?: string): boolean {
|
||||||
|
return !did || did === HIDDEN_DID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true for any string within this primitive/object/array where func(input) === true
|
* Recursively tests strings within an object/array against a test function
|
||||||
*
|
* @param {Function} func - Test function to apply to strings
|
||||||
* Similar logic is found in endorser-mobile.
|
* @param {any} input - Object/array to recursively test
|
||||||
|
* @returns {boolean} True if any string passes the test function
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* testRecursivelyOnStrings(isDid, { user: { id: "did:example:123" } })
|
||||||
|
* // Returns: true
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
/**
|
||||||
function testRecursivelyOnStrings(func: (arg0: any) => boolean, input: any) {
|
* Recursively tests strings within a nested object/array structure against a test function
|
||||||
|
*
|
||||||
|
* This function traverses through objects and arrays to find all string values and applies
|
||||||
|
* a test function to each string found. It handles:
|
||||||
|
* - Direct string values
|
||||||
|
* - Strings in objects (at any depth)
|
||||||
|
* - Strings in arrays (at any depth)
|
||||||
|
* - Mixed nested structures (objects containing arrays containing objects, etc)
|
||||||
|
*
|
||||||
|
* @param {Function} func - Test function that takes a string and returns boolean
|
||||||
|
* @param {any} input - Value to recursively search (can be string, object, array, or other)
|
||||||
|
* @returns {boolean} True if any string in the structure passes the test function
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Test if any string is a DID
|
||||||
|
* const obj = {
|
||||||
|
* user: {
|
||||||
|
* id: "did:example:123",
|
||||||
|
* details: ["name", "did:example:456"]
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
* testRecursivelyOnStrings(isDid, obj); // Returns: true
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Test for hidden DIDs
|
||||||
|
* const obj = {
|
||||||
|
* visible: "did:example:123",
|
||||||
|
* hidden: ["did:none:HIDDEN"]
|
||||||
|
* };
|
||||||
|
* testRecursivelyOnStrings(isHiddenDid, obj); // Returns: true
|
||||||
|
*/
|
||||||
|
function testRecursivelyOnStrings(
|
||||||
|
func: (arg0: any) => boolean,
|
||||||
|
input: any
|
||||||
|
): boolean {
|
||||||
|
// Test direct string values
|
||||||
if (Object.prototype.toString.call(input) === "[object String]") {
|
if (Object.prototype.toString.call(input) === "[object String]") {
|
||||||
return func(input);
|
return func(input);
|
||||||
} else if (input instanceof Object) {
|
}
|
||||||
|
// Recursively test objects and arrays
|
||||||
|
else if (input instanceof Object) {
|
||||||
if (!Array.isArray(input)) {
|
if (!Array.isArray(input)) {
|
||||||
// it's an object
|
// Handle plain objects
|
||||||
for (const key in input) {
|
for (const key in input) {
|
||||||
if (testRecursivelyOnStrings(func, input[key])) {
|
if (testRecursivelyOnStrings(func, input[key])) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// it's an array
|
// Handle arrays
|
||||||
for (const value of input) {
|
for (const value of input) {
|
||||||
if (testRecursivelyOnStrings(func, value)) {
|
if (testRecursivelyOnStrings(func, value)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -351,6 +200,7 @@ function testRecursivelyOnStrings(func: (arg0: any) => boolean, input: any) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
// Non-string, non-object values can't contain strings
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -617,15 +467,23 @@ export async function getHeaders(
|
|||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache for storing plan data
|
||||||
|
* @constant {LRUCache}
|
||||||
|
*/
|
||||||
const planCache: LRUCache<string, PlanSummaryRecord> = new LRUCache({
|
const planCache: LRUCache<string, PlanSummaryRecord> = new LRUCache({
|
||||||
max: 500,
|
max: 500,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param handleId nullable, in which case "undefined" will be returned
|
* Retrieves plan data from cache or server
|
||||||
* @param requesterDid optional, in which case no private info will be returned
|
* @param {string} handleId - Plan handle ID
|
||||||
* @param axios
|
* @param {Axios} axios - Axios instance
|
||||||
* @param apiServer
|
* @param {string} apiServer - API server URL
|
||||||
|
* @param {string} [requesterDid] - Optional requester DID for private info
|
||||||
|
* @returns {Promise<PlanSummaryRecord|undefined>} Plan data or undefined if not found
|
||||||
|
*
|
||||||
|
* @throws {Error} If server request fails
|
||||||
*/
|
*/
|
||||||
export async function getPlanFromCache(
|
export async function getPlanFromCache(
|
||||||
handleId: string | undefined,
|
handleId: string | undefined,
|
||||||
@@ -649,44 +507,44 @@ export async function getPlanFromCache(
|
|||||||
cred = resp.data.data[0];
|
cred = resp.data.data[0];
|
||||||
planCache.set(handleId, cred);
|
planCache.set(handleId, cred);
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.info(
|
||||||
"Failed to load plan with handle",
|
"[EndorserServer] Plan cache is empty for handle",
|
||||||
handleId,
|
handleId,
|
||||||
" Got data:",
|
" Got data:",
|
||||||
resp.data,
|
JSON.stringify(resp.data),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
"Failed to load plan with handle",
|
"[EndorserServer] Failed to load plan with handle",
|
||||||
handleId,
|
handleId,
|
||||||
" Got error:",
|
" Got error:",
|
||||||
error,
|
JSON.stringify(error),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cred;
|
return cred;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates plan data in cache
|
||||||
|
* @param {string} handleId - Plan handle ID
|
||||||
|
* @param {PlanSummaryRecord} planSummary - Plan data to cache
|
||||||
|
*/
|
||||||
export async function setPlanInCache(
|
export async function setPlanInCache(
|
||||||
handleId: string,
|
handleId: string,
|
||||||
planSummary: PlanSummaryRecord,
|
planSummary: PlanSummaryRecord,
|
||||||
) {
|
): Promise<void> {
|
||||||
planCache.set(handleId, planSummary);
|
planCache.set(handleId, planSummary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Extracts user-friendly message from server error
|
||||||
* @param error that is thrown from an Endorser server call by Axios
|
* @param {any} error - Error thrown from Endorser server call
|
||||||
* @returns user-friendly message, or undefined if none found
|
* @returns {string|undefined} User-friendly message or undefined if none found
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
export function serverMessageForUser(error: any): string | undefined {
|
||||||
export function serverMessageForUser(error: any) {
|
return error?.response?.data?.error?.message;
|
||||||
return (
|
|
||||||
// this is how most user messages are returned
|
|
||||||
error?.response?.data?.error?.message
|
|
||||||
// some are returned as "error" with a string, but those are more for devs and are less helpful to the user
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
89
src/main.capacitor.ts
Normal file
89
src/main.capacitor.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* @file Capacitor Main Entry Point
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*
|
||||||
|
* This file initializes the deep linking system for the TimeSafari app.
|
||||||
|
* It sets up the connection between Capacitor's URL handling and our deep link processor.
|
||||||
|
*
|
||||||
|
* Deep Linking Flow:
|
||||||
|
* 1. Capacitor receives URL open event
|
||||||
|
* 2. Event is passed to DeepLinkHandler
|
||||||
|
* 3. URL is validated and processed
|
||||||
|
* 4. Router navigates to appropriate view
|
||||||
|
*
|
||||||
|
* Integration Points:
|
||||||
|
* - Capacitor App plugin for URL handling
|
||||||
|
* - Vue Router for navigation
|
||||||
|
* - Error handling system
|
||||||
|
* - Logging system
|
||||||
|
*
|
||||||
|
* Type Safety:
|
||||||
|
* - Uses DeepLinkHandler for type-safe parameter processing
|
||||||
|
* - Ensures type safety between Capacitor events and app routing
|
||||||
|
* - Maintains type checking through the entire deep link flow
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // URL open event from OS
|
||||||
|
* timesafari://claim/123?view=details
|
||||||
|
* // Processed and routed to appropriate view with type-safe parameters
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { initializeApp } from "./main.common";
|
||||||
|
import { App } from "./lib/capacitor/app";
|
||||||
|
import router from "./router";
|
||||||
|
import { handleApiError } from "./services/api";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import { DeepLinkHandler } from "./services/deepLinks";
|
||||||
|
import { logConsoleAndDb } from "./db";
|
||||||
|
|
||||||
|
console.log("[Capacitor] Starting initialization");
|
||||||
|
console.log("[Capacitor] Platform:", process.env.VITE_PLATFORM);
|
||||||
|
|
||||||
|
const app = initializeApp();
|
||||||
|
|
||||||
|
// Initialize API error handling for unhandled promise rejections
|
||||||
|
window.addEventListener("unhandledrejection", (event) => {
|
||||||
|
if (event.reason?.response) {
|
||||||
|
handleApiError(event.reason, event.reason.config?.url || "unknown");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const deepLinkHandler = new DeepLinkHandler(router);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles deep link routing for the application
|
||||||
|
* Processes URLs in the format timesafari://<route>/<param>
|
||||||
|
* Maps incoming deep links to corresponding router paths with parameters
|
||||||
|
*
|
||||||
|
* @param {Object} data - Deep link data object
|
||||||
|
* @param {string} data.url - The full deep link URL to process
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Handles URLs like:
|
||||||
|
* // timesafari://claim/01JMAAFZRNSRTQ0EBSD70A8E1H
|
||||||
|
* // timesafari://project/abc123
|
||||||
|
*
|
||||||
|
* @throws {Error} If URL format is invalid
|
||||||
|
*/
|
||||||
|
const handleDeepLink = async (data: { url: string }) => {
|
||||||
|
try {
|
||||||
|
await router.isReady();
|
||||||
|
await deepLinkHandler.handleDeepLink(data.url);
|
||||||
|
} catch (error) {
|
||||||
|
logConsoleAndDb("[DeepLink] Error handling deep link: " + error, true);
|
||||||
|
handleApiError(
|
||||||
|
{
|
||||||
|
message: error instanceof Error ? error.message : String(error),
|
||||||
|
} as AxiosError,
|
||||||
|
"deep-link",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register deep link handler with Capacitor
|
||||||
|
App.addListener("appUrlOpen", handleDeepLink);
|
||||||
|
|
||||||
|
console.log("[Capacitor] Mounting app");
|
||||||
|
app.mount("#app");
|
||||||
|
console.log("[Capacitor] App mounted");
|
||||||
60
src/main.common.ts
Normal file
60
src/main.common.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { createPinia } from "pinia";
|
||||||
|
import { App as VueApp, ComponentPublicInstance, createApp } from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import router from "./router";
|
||||||
|
import axios from "axios";
|
||||||
|
import VueAxios from "vue-axios";
|
||||||
|
import Notifications from "notiwind";
|
||||||
|
import "./assets/styles/tailwind.css";
|
||||||
|
import { FontAwesomeIcon } from "./lib/fontawesome";
|
||||||
|
import Camera from "simple-vue-camera";
|
||||||
|
|
||||||
|
// Global Error Handler
|
||||||
|
function setupGlobalErrorHandler(app: VueApp) {
|
||||||
|
console.log("[App Init] Setting up global error handler");
|
||||||
|
app.config.errorHandler = (
|
||||||
|
err: unknown,
|
||||||
|
instance: ComponentPublicInstance | null,
|
||||||
|
info: string,
|
||||||
|
) => {
|
||||||
|
console.error("[App Error] Global Error Handler:", {
|
||||||
|
error: err,
|
||||||
|
info,
|
||||||
|
component: instance?.$options.name || "unknown",
|
||||||
|
});
|
||||||
|
alert(
|
||||||
|
(err instanceof Error ? err.message : "Something bad happened") +
|
||||||
|
" - Try reloading or restarting the app.",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to initialize the app
|
||||||
|
export function initializeApp() {
|
||||||
|
console.log("[App Init] Starting app initialization");
|
||||||
|
console.log("[App Init] Platform:", process.env.VITE_PLATFORM);
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
console.log("[App Init] Vue app created");
|
||||||
|
|
||||||
|
app.component("FontAwesome", FontAwesomeIcon).component("camera", Camera);
|
||||||
|
console.log("[App Init] Components registered");
|
||||||
|
|
||||||
|
const pinia = createPinia();
|
||||||
|
app.use(pinia);
|
||||||
|
console.log("[App Init] Pinia store initialized");
|
||||||
|
|
||||||
|
app.use(VueAxios, axios);
|
||||||
|
console.log("[App Init] Axios initialized");
|
||||||
|
|
||||||
|
app.use(router);
|
||||||
|
console.log("[App Init] Router initialized");
|
||||||
|
|
||||||
|
app.use(Notifications);
|
||||||
|
console.log("[App Init] Notifications initialized");
|
||||||
|
|
||||||
|
setupGlobalErrorHandler(app);
|
||||||
|
console.log("[App Init] App initialization complete");
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
4
src/main.electron.ts
Normal file
4
src/main.electron.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { initializeApp } from "./main.common";
|
||||||
|
|
||||||
|
const app = initializeApp();
|
||||||
|
app.mount("#app");
|
||||||
4
src/main.pywebview.ts
Normal file
4
src/main.pywebview.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { initializeApp } from "./main.common";
|
||||||
|
|
||||||
|
const app = initializeApp();
|
||||||
|
app.mount("#app");
|
||||||
166
src/main.ts
166
src/main.ts
@@ -6,169 +6,8 @@ import router from "./router";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import VueAxios from "vue-axios";
|
import VueAxios from "vue-axios";
|
||||||
import Notifications from "notiwind";
|
import Notifications from "notiwind";
|
||||||
|
|
||||||
import "./assets/styles/tailwind.css";
|
import "./assets/styles/tailwind.css";
|
||||||
|
import { FontAwesomeIcon } from "./lib/fontawesome";
|
||||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
|
||||||
import {
|
|
||||||
faArrowDown,
|
|
||||||
faArrowLeft,
|
|
||||||
faArrowRight,
|
|
||||||
faArrowRotateBackward,
|
|
||||||
faArrowUpRightFromSquare,
|
|
||||||
faArrowUp,
|
|
||||||
faBan,
|
|
||||||
faBitcoinSign,
|
|
||||||
faBurst,
|
|
||||||
faCalendar,
|
|
||||||
faCamera,
|
|
||||||
faCaretDown,
|
|
||||||
faChair,
|
|
||||||
faCheck,
|
|
||||||
faChevronDown,
|
|
||||||
faChevronLeft,
|
|
||||||
faChevronRight,
|
|
||||||
faChevronUp,
|
|
||||||
faCircle,
|
|
||||||
faCircleCheck,
|
|
||||||
faCircleInfo,
|
|
||||||
faCircleQuestion,
|
|
||||||
faCircleUser,
|
|
||||||
faClock,
|
|
||||||
faCoins,
|
|
||||||
faComment,
|
|
||||||
faCopy,
|
|
||||||
faDollar,
|
|
||||||
faEllipsis,
|
|
||||||
faEllipsisVertical,
|
|
||||||
faEnvelopeOpenText,
|
|
||||||
faEraser,
|
|
||||||
faEye,
|
|
||||||
faEyeSlash,
|
|
||||||
faFileContract,
|
|
||||||
faFileLines,
|
|
||||||
faFilter,
|
|
||||||
faFloppyDisk,
|
|
||||||
faFolderOpen,
|
|
||||||
faForward,
|
|
||||||
faGift,
|
|
||||||
faGlobe,
|
|
||||||
faHammer,
|
|
||||||
faHand,
|
|
||||||
faHandHoldingDollar,
|
|
||||||
faHandHoldingHeart,
|
|
||||||
faHouseChimney,
|
|
||||||
faImagePortrait,
|
|
||||||
faLeftRight,
|
|
||||||
faLightbulb,
|
|
||||||
faLink,
|
|
||||||
faLocationDot,
|
|
||||||
faLongArrowAltLeft,
|
|
||||||
faLongArrowAltRight,
|
|
||||||
faMagnifyingGlass,
|
|
||||||
faMessage,
|
|
||||||
faMinus,
|
|
||||||
faPen,
|
|
||||||
faPersonCircleCheck,
|
|
||||||
faPersonCircleQuestion,
|
|
||||||
faPlus,
|
|
||||||
faQuestion,
|
|
||||||
faQrcode,
|
|
||||||
faRightFromBracket,
|
|
||||||
faRotate,
|
|
||||||
faShareNodes,
|
|
||||||
faSpinner,
|
|
||||||
faSquare,
|
|
||||||
faSquareCaretDown,
|
|
||||||
faSquareCaretUp,
|
|
||||||
faSquarePlus,
|
|
||||||
faTrashCan,
|
|
||||||
faTriangleExclamation,
|
|
||||||
faUser,
|
|
||||||
faUsers,
|
|
||||||
faXmark,
|
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
|
||||||
|
|
||||||
library.add(
|
|
||||||
faArrowDown,
|
|
||||||
faArrowLeft,
|
|
||||||
faArrowRight,
|
|
||||||
faArrowRotateBackward,
|
|
||||||
faArrowUpRightFromSquare,
|
|
||||||
faArrowUp,
|
|
||||||
faBan,
|
|
||||||
faBitcoinSign,
|
|
||||||
faBurst,
|
|
||||||
faCalendar,
|
|
||||||
faCamera,
|
|
||||||
faCaretDown,
|
|
||||||
faChair,
|
|
||||||
faCheck,
|
|
||||||
faChevronDown,
|
|
||||||
faChevronLeft,
|
|
||||||
faChevronRight,
|
|
||||||
faChevronUp,
|
|
||||||
faCircle,
|
|
||||||
faCircleCheck,
|
|
||||||
faCircleInfo,
|
|
||||||
faCircleQuestion,
|
|
||||||
faCircleUser,
|
|
||||||
faClock,
|
|
||||||
faCoins,
|
|
||||||
faComment,
|
|
||||||
faCopy,
|
|
||||||
faDollar,
|
|
||||||
faEllipsis,
|
|
||||||
faEllipsisVertical,
|
|
||||||
faEnvelopeOpenText,
|
|
||||||
faEraser,
|
|
||||||
faEye,
|
|
||||||
faEyeSlash,
|
|
||||||
faFileContract,
|
|
||||||
faFileLines,
|
|
||||||
faFilter,
|
|
||||||
faFloppyDisk,
|
|
||||||
faFolderOpen,
|
|
||||||
faForward,
|
|
||||||
faGift,
|
|
||||||
faGlobe,
|
|
||||||
faHammer,
|
|
||||||
faHand,
|
|
||||||
faHandHoldingDollar,
|
|
||||||
faHandHoldingHeart,
|
|
||||||
faHouseChimney,
|
|
||||||
faImagePortrait,
|
|
||||||
faLeftRight,
|
|
||||||
faLightbulb,
|
|
||||||
faLink,
|
|
||||||
faLocationDot,
|
|
||||||
faLongArrowAltLeft,
|
|
||||||
faLongArrowAltRight,
|
|
||||||
faMagnifyingGlass,
|
|
||||||
faMessage,
|
|
||||||
faMinus,
|
|
||||||
faPen,
|
|
||||||
faPersonCircleCheck,
|
|
||||||
faPersonCircleQuestion,
|
|
||||||
faPlus,
|
|
||||||
faQrcode,
|
|
||||||
faQuestion,
|
|
||||||
faRotate,
|
|
||||||
faRightFromBracket,
|
|
||||||
faShareNodes,
|
|
||||||
faSpinner,
|
|
||||||
faSquare,
|
|
||||||
faSquareCaretDown,
|
|
||||||
faSquareCaretUp,
|
|
||||||
faSquarePlus,
|
|
||||||
faTrashCan,
|
|
||||||
faTriangleExclamation,
|
|
||||||
faUser,
|
|
||||||
faUsers,
|
|
||||||
faXmark,
|
|
||||||
);
|
|
||||||
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
|
||||||
import Camera from "simple-vue-camera";
|
import Camera from "simple-vue-camera";
|
||||||
|
|
||||||
// Can trigger this with a 'throw' inside some top-level function, eg. on the HomeView
|
// Can trigger this with a 'throw' inside some top-level function, eg. on the HomeView
|
||||||
@@ -197,7 +36,7 @@ function setupGlobalErrorHandler(app: VueApp) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// console.log("Bootstrapping Vue app...");
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
.component("fa", FontAwesomeIcon)
|
.component("fa", FontAwesomeIcon)
|
||||||
.component("camera", Camera)
|
.component("camera", Camera)
|
||||||
@@ -209,4 +48,3 @@ const app = createApp(App)
|
|||||||
setupGlobalErrorHandler(app);
|
setupGlobalErrorHandler(app);
|
||||||
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
// console.log("Vue app mounted.");
|
|
||||||
|
|||||||
5
src/main.web.ts
Normal file
5
src/main.web.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { initializeApp } from "./main.common";
|
||||||
|
import "./registerServiceWorker"; // Web PWA support
|
||||||
|
|
||||||
|
const app = initializeApp();
|
||||||
|
app.mount("#app");
|
||||||
@@ -276,8 +276,8 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
component: () => import("../views/TestView.vue"),
|
component: () => import("../views/TestView.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/userProfile/:id?",
|
path: "/user-profile/:id?",
|
||||||
name: "userProfile",
|
name: "user-profile",
|
||||||
component: () => import("../views/UserProfileView.vue"),
|
component: () => import("../views/UserProfileView.vue"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
24
src/services/api.ts
Normal file
24
src/services/api.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
|
export const handleApiError = (error: AxiosError, endpoint: string) => {
|
||||||
|
if (process.env.VITE_PLATFORM === "capacitor") {
|
||||||
|
console.error(`[Capacitor API Error] ${endpoint}:`, {
|
||||||
|
message: error.message,
|
||||||
|
status: error.response?.status,
|
||||||
|
data: error.response?.data,
|
||||||
|
config: {
|
||||||
|
url: error.config?.url,
|
||||||
|
method: error.config?.method,
|
||||||
|
headers: error.config?.headers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specific handling for rate limits
|
||||||
|
if (error.response?.status === 400) {
|
||||||
|
console.warn(`[Rate Limit] ${endpoint}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
145
src/services/deepLinks.ts
Normal file
145
src/services/deepLinks.ts
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
* @file Deep Link Handler Service
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*
|
||||||
|
* This service handles the processing and routing of deep links in the TimeSafari app.
|
||||||
|
* It provides a type-safe interface between the raw deep links and the application router.
|
||||||
|
*
|
||||||
|
* Architecture:
|
||||||
|
* 1. DeepLinkHandler class encapsulates all deep link processing logic
|
||||||
|
* 2. Uses Zod schemas from types/deepLinks for parameter validation
|
||||||
|
* 3. Provides consistent error handling and logging
|
||||||
|
* 4. Maps validated parameters to Vue router calls
|
||||||
|
*
|
||||||
|
* Error Handling Strategy:
|
||||||
|
* - All errors are wrapped in DeepLinkError interface
|
||||||
|
* - Errors include error codes for systematic handling
|
||||||
|
* - Detailed error information is logged for debugging
|
||||||
|
* - Errors are propagated to the global error handler
|
||||||
|
*
|
||||||
|
* Validation Strategy:
|
||||||
|
* - URL structure validation
|
||||||
|
* - Route-specific parameter validation using Zod schemas
|
||||||
|
* - Query parameter validation and sanitization
|
||||||
|
* - Type-safe parameter passing to router
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const handler = new DeepLinkHandler(router);
|
||||||
|
* await handler.handleDeepLink("timesafari://claim/123?view=details");
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Router } from "vue-router";
|
||||||
|
import { deepLinkSchemas, baseUrlSchema } from "../types/deepLinks";
|
||||||
|
import { logConsoleAndDb } from "../db";
|
||||||
|
import type { DeepLinkError } from "../interfaces/deepLinks";
|
||||||
|
|
||||||
|
export class DeepLinkHandler {
|
||||||
|
private router: Router;
|
||||||
|
|
||||||
|
constructor(router: Router) {
|
||||||
|
this.router = router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses deep link URL into path, params and query components
|
||||||
|
*/
|
||||||
|
private parseDeepLink(url: string) {
|
||||||
|
const parts = url.split("://");
|
||||||
|
if (parts.length !== 2) {
|
||||||
|
throw { code: "INVALID_URL", message: "Invalid URL format" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate base URL structure
|
||||||
|
baseUrlSchema.parse({
|
||||||
|
scheme: parts[0],
|
||||||
|
path: parts[1],
|
||||||
|
queryParams: {}, // Will be populated below
|
||||||
|
});
|
||||||
|
|
||||||
|
const [path, queryString] = parts[1].split("?");
|
||||||
|
const [routePath, param] = path.split("/");
|
||||||
|
|
||||||
|
const query: Record<string, string> = {};
|
||||||
|
if (queryString) {
|
||||||
|
new URLSearchParams(queryString).forEach((value, key) => {
|
||||||
|
query[key] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: routePath,
|
||||||
|
params: param ? { id: param } : {},
|
||||||
|
query,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes incoming deep links and routes them appropriately
|
||||||
|
* @param url The deep link URL to process
|
||||||
|
*/
|
||||||
|
async handleDeepLink(url: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
logConsoleAndDb("[DeepLink] Processing URL: " + url, false);
|
||||||
|
const { path, params, query } = this.parseDeepLink(url);
|
||||||
|
// Ensure params is always a Record<string,string> by converting undefined to empty string
|
||||||
|
const sanitizedParams = Object.fromEntries(
|
||||||
|
Object.entries(params).map(([key, value]) => [key, value ?? ""]),
|
||||||
|
);
|
||||||
|
await this.validateAndRoute(path, sanitizedParams, query);
|
||||||
|
} catch (error) {
|
||||||
|
const deepLinkError = error as DeepLinkError;
|
||||||
|
logConsoleAndDb(
|
||||||
|
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.message}`,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
throw {
|
||||||
|
code: deepLinkError.code || "UNKNOWN_ERROR",
|
||||||
|
message: deepLinkError.message,
|
||||||
|
details: deepLinkError.details,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Routes the deep link to appropriate view with validated parameters
|
||||||
|
*/
|
||||||
|
private async validateAndRoute(
|
||||||
|
path: string,
|
||||||
|
params: Record<string, string>,
|
||||||
|
query: Record<string, string>,
|
||||||
|
): Promise<void> {
|
||||||
|
const routeMap: Record<string, string> = {
|
||||||
|
claim: "claim",
|
||||||
|
"claim-cert": "claim-cert",
|
||||||
|
"claim-add-raw": "claim-add-raw",
|
||||||
|
"contact-edit": "contact-edit",
|
||||||
|
"contact-import": "contact-import",
|
||||||
|
project: "project",
|
||||||
|
"invite-one-accept": "invite-one-accept",
|
||||||
|
"offer-details": "offer-details",
|
||||||
|
"confirm-gift": "confirm-gift",
|
||||||
|
};
|
||||||
|
|
||||||
|
const routeName = routeMap[path];
|
||||||
|
if (!routeName) {
|
||||||
|
throw {
|
||||||
|
code: "INVALID_ROUTE",
|
||||||
|
message: `Unsupported route: ${path}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate parameters based on route type
|
||||||
|
const schema = deepLinkSchemas[path as keyof typeof deepLinkSchemas];
|
||||||
|
const validatedParams = await schema.parseAsync({
|
||||||
|
...params,
|
||||||
|
...query,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.router.replace({
|
||||||
|
name: routeName,
|
||||||
|
params: validatedParams,
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/services/plan.ts
Normal file
80
src/services/plan.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
interface PlanResponse {
|
||||||
|
data?: unknown;
|
||||||
|
status?: number;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadPlanWithRetry = async (
|
||||||
|
handle: string,
|
||||||
|
retries = 3,
|
||||||
|
): Promise<PlanResponse> => {
|
||||||
|
try {
|
||||||
|
console.log(`[Plan Service] Loading plan ${handle}, attempt 1/${retries}`);
|
||||||
|
console.log(
|
||||||
|
`[Plan Service] Context: Deep link handle=${handle}, isClaimFlow=${handle.includes("claim")}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Different endpoint if this is a claim flow
|
||||||
|
const response = await loadPlan(handle);
|
||||||
|
console.log(`[Plan Service] Plan ${handle} loaded successfully:`, {
|
||||||
|
status: response?.status,
|
||||||
|
headers: response?.headers,
|
||||||
|
data: response?.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error(`[Plan Service] Error loading plan ${handle}:`, {
|
||||||
|
message: (error as Error).message,
|
||||||
|
status: (error as { response?: { status?: number } })?.response?.status,
|
||||||
|
statusText: (error as { response?: { statusText?: string } })?.response
|
||||||
|
?.statusText,
|
||||||
|
data: (error as { response?: { data?: unknown } })?.response?.data,
|
||||||
|
headers: (error as { response?: { headers?: unknown } })?.response
|
||||||
|
?.headers,
|
||||||
|
config: {
|
||||||
|
url: (error as { config?: { url?: string } })?.config?.url,
|
||||||
|
method: (error as { config?: { method?: string } })?.config?.method,
|
||||||
|
baseURL: (error as { config?: { baseURL?: string } })?.config?.baseURL,
|
||||||
|
headers: (error as { config?: { headers?: unknown } })?.config?.headers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (retries > 1) {
|
||||||
|
console.log(
|
||||||
|
`[Plan Service] Retrying plan ${handle}, ${retries - 1} attempts remaining`,
|
||||||
|
);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
return loadPlanWithRetry(handle, retries - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: `Failed to load plan ${handle} after ${4 - retries} attempts: ${(error as Error).message}`,
|
||||||
|
status: (error as { response?: { status?: number } })?.response?.status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadPlan = async (handle: string): Promise<PlanResponse> => {
|
||||||
|
console.log(`[Plan Service] Making API request for plan ${handle}`);
|
||||||
|
|
||||||
|
const endpoint = handle.includes("claim")
|
||||||
|
? `/api/claims/${handle}`
|
||||||
|
: `/api/plans/${handle}`;
|
||||||
|
|
||||||
|
console.log(`[Plan Service] Using endpoint: ${endpoint}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(endpoint);
|
||||||
|
return response;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error(`[Plan Service] API request failed for ${handle}:`, {
|
||||||
|
endpoint,
|
||||||
|
error: (error as Error).message,
|
||||||
|
response: (error as { response?: { data?: unknown } })?.response?.data,
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
73
src/types/deepLinks.ts
Normal file
73
src/types/deepLinks.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* @file Deep Link Type Definitions and Validation Schemas
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*
|
||||||
|
* This file defines the type system and validation schemas for deep linking in the TimeSafari app.
|
||||||
|
* It uses Zod for runtime validation while providing TypeScript types for compile-time checking.
|
||||||
|
*
|
||||||
|
* Type Strategy:
|
||||||
|
* 1. Define base URL schema to validate the fundamental deep link structure
|
||||||
|
* 2. Define route-specific parameter schemas with exact validation rules
|
||||||
|
* 3. Generate TypeScript types from Zod schemas for type safety
|
||||||
|
* 4. Export both schemas and types for use in deep link handling
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* - Import schemas for runtime validation in deep link handlers
|
||||||
|
* - Import types for type-safe parameter handling in components
|
||||||
|
* - Use DeepLinkParams type for type-safe access to route parameters
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Runtime validation
|
||||||
|
* const params = deepLinkSchemas.claim.parse({ id: "123", view: "details" });
|
||||||
|
*
|
||||||
|
* // Type-safe parameter access
|
||||||
|
* function handleClaimParams(params: DeepLinkParams["claim"]) {
|
||||||
|
* // TypeScript knows params.id exists and params.view is optional
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
// Base URL validation schema
|
||||||
|
export const baseUrlSchema = z.object({
|
||||||
|
scheme: z.literal("timesafari"),
|
||||||
|
path: z.string(),
|
||||||
|
queryParams: z.record(z.string()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Parameter validation schemas for each route type
|
||||||
|
export const deepLinkSchemas = {
|
||||||
|
claim: z.object({
|
||||||
|
id: z.string().min(1),
|
||||||
|
view: z.enum(["details", "certificate", "raw"]).optional(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
contact: z.object({
|
||||||
|
did: z.string().regex(/^did:/),
|
||||||
|
action: z.enum(["edit", "import"]).optional(),
|
||||||
|
jwt: z.string().optional(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
project: z.object({
|
||||||
|
id: z.string().min(1),
|
||||||
|
view: z.enum(["details", "edit"]).optional(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
invite: z.object({
|
||||||
|
jwt: z.string().min(1),
|
||||||
|
type: z.enum(["one", "many"]).optional(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
gift: z.object({
|
||||||
|
id: z.string().min(1),
|
||||||
|
action: z.enum(["confirm", "details"]).optional(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
offer: z.object({
|
||||||
|
id: z.string().min(1),
|
||||||
|
view: z.enum(["details"]).optional(),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeepLinkParams = {
|
||||||
|
[K in keyof typeof deepLinkSchemas]: z.infer<(typeof deepLinkSchemas)[K]>;
|
||||||
|
};
|
||||||
@@ -39,12 +39,15 @@
|
|||||||
:to="{ name: 'contact-qr' }"
|
:to="{ name: 'contact-qr' }"
|
||||||
class="bg-slate-500 text-white px-1.5 py-1 rounded-md"
|
class="bg-slate-500 text-white px-1.5 py-1 rounded-md"
|
||||||
>
|
>
|
||||||
<fa icon="qrcode" class="fa-fw text-xl"></fa>
|
<font-awesome icon="qrcode" class="fa-fw text-xl"></font-awesome>
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
</span>
|
||||||
{{ givenName }}
|
{{ givenName }}
|
||||||
<router-link :to="{ name: 'new-edit-account' }">
|
<router-link :to="{ name: 'new-edit-account' }">
|
||||||
<fa icon="pen" class="text-xs text-blue-500 ml-2 mb-1"></fa>
|
<font-awesome
|
||||||
|
icon="pen"
|
||||||
|
class="text-xs text-blue-500 ml-2 mb-1"
|
||||||
|
></font-awesome>
|
||||||
</router-link>
|
</router-link>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,13 +56,13 @@
|
|||||||
class="block w-full text-center text-md bg-amber-200 border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
|
class="block w-full text-center text-md bg-amber-200 border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
class="inline-block text-md uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
|
||||||
@click="
|
@click="
|
||||||
() =>
|
() =>
|
||||||
(this.$refs.userNameDialog as UserNameDialog).open(
|
($refs.userNameDialog as UserNameDialog).open(
|
||||||
(name) => (this.givenName = name),
|
(name) => (givenName = name),
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
class="inline-block text-md uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
|
|
||||||
>
|
>
|
||||||
Set Your Name
|
Set Your Name
|
||||||
</button>
|
</button>
|
||||||
@@ -69,23 +72,23 @@
|
|||||||
<span v-if="profileImageUrl" class="flex justify-between">
|
<span v-if="profileImageUrl" class="flex justify-between">
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:icon-size="96"
|
:icon-size="96"
|
||||||
:profileImageUrl="profileImageUrl"
|
:profile-image-url="profileImageUrl"
|
||||||
class="inline-block align-text-bottom border border-slate-300 rounded"
|
class="inline-block align-text-bottom border border-slate-300 rounded"
|
||||||
@click="showLargeIdenticonUrl = profileImageUrl"
|
@click="showLargeIdenticonUrl = profileImageUrl"
|
||||||
/>
|
/>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="trash-can"
|
icon="trash-can"
|
||||||
@click="confirmDeleteImage"
|
|
||||||
class="text-red-500 fa-fw ml-8 mt-8 w-12 h-12"
|
class="text-red-500 fa-fw ml-8 mt-8 w-12 h-12"
|
||||||
|
@click="confirmDeleteImage"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<div v-else class="text-center">
|
<div v-else class="text-center">
|
||||||
<div class @click="openImageDialog()">
|
<div class @click="openImageDialog()">
|
||||||
<fa
|
<font-awesome
|
||||||
icon="image-portrait"
|
icon="image-portrait"
|
||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-l"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-l"
|
||||||
/>
|
/>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="camera"
|
icon="camera"
|
||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-r"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-r"
|
||||||
/>
|
/>
|
||||||
@@ -101,8 +104,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:entityId="activeDid"
|
:entity-id="activeDid"
|
||||||
:iconSize="64"
|
:icon-size="64"
|
||||||
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
||||||
@click="showLargeIdenticonId = activeDid"
|
@click="showLargeIdenticonId = activeDid"
|
||||||
/>
|
/>
|
||||||
@@ -116,9 +119,9 @@
|
|||||||
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||||
>
|
>
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:entityId="showLargeIdenticonId"
|
:entity-id="showLargeIdenticonId"
|
||||||
:iconSize="512"
|
:icon-size="512"
|
||||||
:profileImageUrl="showLargeIdenticonUrl"
|
:profile-image-url="showLargeIdenticonUrl"
|
||||||
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
||||||
@click="
|
@click="
|
||||||
showLargeIdenticonId = undefined;
|
showLargeIdenticonId = undefined;
|
||||||
@@ -135,12 +138,12 @@
|
|||||||
>
|
>
|
||||||
<code class="truncate">{{ activeDid }}</code>
|
<code class="truncate">{{ activeDid }}</code>
|
||||||
<button
|
<button
|
||||||
|
class="ml-2"
|
||||||
@click="
|
@click="
|
||||||
doCopyTwoSecRedo(activeDid, () => (showDidCopy = !showDidCopy))
|
doCopyTwoSecRedo(activeDid, () => (showDidCopy = !showDidCopy))
|
||||||
"
|
"
|
||||||
class="ml-2"
|
|
||||||
>
|
>
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
|
<font-awesome icon="copy" class="text-slate-400 fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
<span v-show="showDidCopy">Copied</span>
|
<span v-show="showDidCopy">Copied</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -185,7 +188,7 @@
|
|||||||
<!-- label -->
|
<!-- label -->
|
||||||
<div>
|
<div>
|
||||||
Reminder Notification
|
Reminder Notification
|
||||||
<fa
|
<font-awesome
|
||||||
icon="question-circle"
|
icon="question-circle"
|
||||||
class="text-slate-400 fa-fw ml-2 cursor-pointer"
|
class="text-slate-400 fa-fw ml-2 cursor-pointer"
|
||||||
@click.stop="showReminderNotificationInfo"
|
@click.stop="showReminderNotificationInfo"
|
||||||
@@ -197,7 +200,7 @@
|
|||||||
@click="showReminderNotificationChoice()"
|
@click="showReminderNotificationChoice()"
|
||||||
>
|
>
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input type="checkbox" v-model="notifyingReminder" class="sr-only" />
|
<input v-model="notifyingReminder" type="checkbox" class="sr-only" />
|
||||||
<!-- line -->
|
<!-- line -->
|
||||||
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
||||||
<!-- dot -->
|
<!-- dot -->
|
||||||
@@ -214,7 +217,7 @@
|
|||||||
<!-- label -->
|
<!-- label -->
|
||||||
<div>
|
<div>
|
||||||
New Activity Notification
|
New Activity Notification
|
||||||
<fa
|
<font-awesome
|
||||||
icon="question-circle"
|
icon="question-circle"
|
||||||
class="text-slate-400 fa-fw ml-2 cursor-pointer"
|
class="text-slate-400 fa-fw ml-2 cursor-pointer"
|
||||||
@click.stop="showNewActivityNotificationInfo"
|
@click.stop="showNewActivityNotificationInfo"
|
||||||
@@ -227,8 +230,8 @@
|
|||||||
>
|
>
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="notifyingNewActivity"
|
v-model="notifyingNewActivity"
|
||||||
|
type="checkbox"
|
||||||
class="sr-only"
|
class="sr-only"
|
||||||
/>
|
/>
|
||||||
<!-- line -->
|
<!-- line -->
|
||||||
@@ -256,7 +259,7 @@
|
|||||||
<span class="mb-2 font-bold">Location for Searches</span>
|
<span class="mb-2 font-bold">Location for Searches</span>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'search-area' }"
|
:to="{ name: 'search-area' }"
|
||||||
class="text-m bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mb-2"
|
class="text-m bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2"
|
||||||
>
|
>
|
||||||
{{ isSearchAreasSet ? "Change" : "Set" }} Search Area…
|
{{ isSearchAreasSet ? "Change" : "Set" }} Search Area…
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -268,12 +271,15 @@
|
|||||||
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
||||||
>
|
>
|
||||||
<div v-if="loadingProfile" class="text-center mb-2">
|
<div v-if="loadingProfile" class="text-center mb-2">
|
||||||
<fa icon="spinner" class="fa-spin text-slate-400"></fa> Loading
|
<font-awesome
|
||||||
profile...
|
icon="spinner"
|
||||||
|
class="fa-spin text-slate-400"
|
||||||
|
></font-awesome>
|
||||||
|
Loading profile...
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex items-center mb-2">
|
<div v-else class="flex items-center mb-2">
|
||||||
<span class="font-bold">Public Profile</span>
|
<span class="font-bold">Public Profile</span>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="circle-info"
|
icon="circle-info"
|
||||||
class="text-slate-400 fa-fw ml-2 cursor-pointer"
|
class="text-slate-400 fa-fw ml-2 cursor-pointer"
|
||||||
@click="showProfileInfo"
|
@click="showProfileInfo"
|
||||||
@@ -289,9 +295,9 @@
|
|||||||
|
|
||||||
<div class="flex items-center mb-4" @click="toggleUserProfileLocation">
|
<div class="flex items-center mb-4" @click="toggleUserProfileLocation">
|
||||||
<input
|
<input
|
||||||
|
v-model="includeUserProfileLocation"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
v-model="includeUserProfileLocation"
|
|
||||||
/>
|
/>
|
||||||
<label for="includeUserProfileLocation">Include Location</label>
|
<label for="includeUserProfileLocation">Include Location</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -327,17 +333,16 @@
|
|||||||
<div v-if="!loadingProfile && !savingProfile">
|
<div v-if="!loadingProfile && !savingProfile">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<button
|
<button
|
||||||
@click="saveProfile"
|
|
||||||
class="mt-2 px-4 py-2 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white rounded-md"
|
class="mt-2 px-4 py-2 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white rounded-md"
|
||||||
:disabled="loadingProfile || savingProfile"
|
:disabled="loadingProfile || savingProfile"
|
||||||
:class="{
|
:class="{
|
||||||
'opacity-50 cursor-not-allowed': loadingProfile || savingProfile,
|
'opacity-50 cursor-not-allowed': loadingProfile || savingProfile,
|
||||||
}"
|
}"
|
||||||
|
@click="saveProfile"
|
||||||
>
|
>
|
||||||
Save Profile
|
Save Profile
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="confirmDeleteProfile"
|
|
||||||
class="mt-2 px-4 py-2 bg-gradient-to-b from-red-400 to-red-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white rounded-md"
|
class="mt-2 px-4 py-2 bg-gradient-to-b from-red-400 to-red-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white rounded-md"
|
||||||
:disabled="loadingProfile || savingProfile"
|
:disabled="loadingProfile || savingProfile"
|
||||||
:class="{
|
:class="{
|
||||||
@@ -346,6 +351,7 @@
|
|||||||
savingProfile ||
|
savingProfile ||
|
||||||
(!userProfileDesc && !includeUserProfileLocation),
|
(!userProfileDesc && !includeUserProfileLocation),
|
||||||
}"
|
}"
|
||||||
|
@click="confirmDeleteProfile"
|
||||||
>
|
>
|
||||||
Delete Profile
|
Delete Profile
|
||||||
</button>
|
</button>
|
||||||
@@ -363,7 +369,8 @@
|
|||||||
<div class="mb-2 font-bold">Usage Limits</div>
|
<div class="mb-2 font-bold">Usage Limits</div>
|
||||||
<!-- show spinner if loading limits -->
|
<!-- show spinner if loading limits -->
|
||||||
<div v-if="loadingLimits" class="text-center">
|
<div v-if="loadingLimits" class="text-center">
|
||||||
Checking… <fa icon="spinner" class="fa-spin"></fa>
|
Checking…
|
||||||
|
<font-awesome icon="spinner" class="fa-spin"></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4 text-center">
|
<div class="mb-4 text-center">
|
||||||
{{ limitsMessage }}
|
{{ limitsMessage }}
|
||||||
@@ -419,15 +426,15 @@
|
|||||||
>
|
>
|
||||||
<div class="mb-2 font-bold">Data Export</div>
|
<div class="mb-2 font-bold">Data Export</div>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'seed-backup' }"
|
|
||||||
v-if="activeDid"
|
v-if="activeDid"
|
||||||
|
:to="{ name: 'seed-backup' }"
|
||||||
class="block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2 mt-2"
|
class="block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2 mt-2"
|
||||||
>
|
>
|
||||||
Backup Identifier Seed
|
Backup Identifier Seed
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-bind:class="computedStartDownloadLinkClassNames()"
|
:class="computedStartDownloadLinkClassNames()"
|
||||||
class="block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
class="block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
||||||
@click="exportDatabase()"
|
@click="exportDatabase()"
|
||||||
>
|
>
|
||||||
@@ -437,7 +444,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<a
|
<a
|
||||||
ref="downloadLink"
|
ref="downloadLink"
|
||||||
v-bind:class="computedDownloadLinkClassNames()"
|
:class="computedDownloadLinkClassNames()"
|
||||||
class="block w-full text-center text-md bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
|
class="block w-full text-center text-md bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
|
||||||
>
|
>
|
||||||
If no download happened yet, click again here to download now.
|
If no download happened yet, click again here to download now.
|
||||||
@@ -454,7 +461,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="list-disc list-outside ml-4">
|
<li class="list-disc list-outside ml-4">
|
||||||
On Android: Choose "Open" and then share
|
On Android: Choose "Open" and then share
|
||||||
<fa icon="share-nodes" class="fa-fw" />
|
<font-awesome icon="share-nodes" class="fa-fw" />
|
||||||
to your prefered place.
|
to your prefered place.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -469,7 +476,7 @@
|
|||||||
>
|
>
|
||||||
Advanced
|
Advanced
|
||||||
</h3>
|
</h3>
|
||||||
<div id="sectionAdvanced" v-if="showAdvanced || showGeneralAdvanced">
|
<div v-if="showAdvanced || showGeneralAdvanced" id="sectionAdvanced">
|
||||||
<p class="text-rose-600 mb-8">
|
<p class="text-rose-600 mb-8">
|
||||||
Beware: the features here can be confusing and even change data in ways
|
Beware: the features here can be confusing and even change data in ways
|
||||||
you do not expect. But we support your freedom!
|
you do not expect. But we support your freedom!
|
||||||
@@ -489,12 +496,15 @@
|
|||||||
>
|
>
|
||||||
<code class="truncate">{{ publicBase64 }}</code>
|
<code class="truncate">{{ publicBase64 }}</code>
|
||||||
<button
|
<button
|
||||||
|
class="ml-2"
|
||||||
@click="
|
@click="
|
||||||
doCopyTwoSecRedo(publicBase64, () => (showB64Copy = !showB64Copy))
|
doCopyTwoSecRedo(publicBase64, () => (showB64Copy = !showB64Copy))
|
||||||
"
|
"
|
||||||
class="ml-2"
|
|
||||||
>
|
>
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
|
<font-awesome
|
||||||
|
icon="copy"
|
||||||
|
class="text-slate-400 fa-fw"
|
||||||
|
></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
<span v-show="showB64Copy">Copied</span>
|
<span v-show="showB64Copy">Copied</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -505,12 +515,15 @@
|
|||||||
>
|
>
|
||||||
<code class="truncate">{{ publicHex }}</code>
|
<code class="truncate">{{ publicHex }}</code>
|
||||||
<button
|
<button
|
||||||
|
class="ml-2"
|
||||||
@click="
|
@click="
|
||||||
doCopyTwoSecRedo(publicHex, () => (showPubCopy = !showPubCopy))
|
doCopyTwoSecRedo(publicHex, () => (showPubCopy = !showPubCopy))
|
||||||
"
|
"
|
||||||
class="ml-2"
|
|
||||||
>
|
>
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
|
<font-awesome
|
||||||
|
icon="copy"
|
||||||
|
class="text-slate-400 fa-fw"
|
||||||
|
></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
<span v-show="showPubCopy">Copied</span>
|
<span v-show="showPubCopy">Copied</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -522,15 +535,18 @@
|
|||||||
>
|
>
|
||||||
<code class="truncate">{{ derivationPath }}</code>
|
<code class="truncate">{{ derivationPath }}</code>
|
||||||
<button
|
<button
|
||||||
|
class="ml-2"
|
||||||
@click="
|
@click="
|
||||||
doCopyTwoSecRedo(
|
doCopyTwoSecRedo(
|
||||||
derivationPath,
|
derivationPath,
|
||||||
() => (showDerCopy = !showDerCopy),
|
() => (showDerCopy = !showDerCopy),
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
class="ml-2"
|
|
||||||
>
|
>
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
|
<font-awesome
|
||||||
|
icon="copy"
|
||||||
|
class="text-slate-400 fa-fw"
|
||||||
|
></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
<span v-show="showDerCopy">Copied</span>
|
<span v-show="showDerCopy">Copied</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -557,7 +573,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="ml-4 mt-2">
|
<div class="ml-4 mt-2">
|
||||||
<input type="file" @change="uploadImportFile" class="ml-2" />
|
<input type="file" class="ml-2" @change="uploadImportFile" />
|
||||||
<transition
|
<transition
|
||||||
enter-active-class="transform ease-out duration-300 transition"
|
enter-active-class="transform ease-out duration-300 transition"
|
||||||
enter-from-class="translate-y-2 opacity-0 sm:translate-y-4"
|
enter-from-class="translate-y-2 opacity-0 sm:translate-y-4"
|
||||||
@@ -604,8 +620,8 @@
|
|||||||
<div class="relative ml-2">
|
<div class="relative ml-2">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="showContactGives"
|
v-model="showContactGives"
|
||||||
|
type="checkbox"
|
||||||
name="showContactGives"
|
name="showContactGives"
|
||||||
class="sr-only"
|
class="sr-only"
|
||||||
/>
|
/>
|
||||||
@@ -622,16 +638,20 @@
|
|||||||
<h2 class="text-slate-500 text-sm font-bold mt-4">Claim Server</h2>
|
<h2 class="text-slate-500 text-sm font-bold mt-4">Claim Server</h2>
|
||||||
<div class="px-4 py-4">
|
<div class="px-4 py-4">
|
||||||
<input
|
<input
|
||||||
|
v-model="apiServerInput"
|
||||||
type="text"
|
type="text"
|
||||||
class="block w-full rounded border border-slate-400 px-4 py-2"
|
class="block w-full rounded border border-slate-400 px-4 py-2"
|
||||||
v-model="apiServerInput"
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="apiServerInput != apiServer"
|
v-if="apiServerInput != apiServer"
|
||||||
class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
|
class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
|
||||||
@click="onClickSaveApiServer()"
|
@click="onClickSaveApiServer()"
|
||||||
>
|
>
|
||||||
<fa icon="floppy-disk" class="fa-fw" color="white"></fa>
|
<font-awesome
|
||||||
|
icon="floppy-disk"
|
||||||
|
class="fa-fw"
|
||||||
|
color="white"
|
||||||
|
></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||||
@@ -663,7 +683,7 @@
|
|||||||
<!-- toggle -->
|
<!-- toggle -->
|
||||||
<div class="relative ml-2">
|
<div class="relative ml-2">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input type="checkbox" v-model="warnIfProdServer" class="sr-only" />
|
<input v-model="warnIfProdServer" type="checkbox" class="sr-only" />
|
||||||
<!-- line -->
|
<!-- line -->
|
||||||
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
||||||
<!-- dot -->
|
<!-- dot -->
|
||||||
@@ -683,7 +703,7 @@
|
|||||||
<!-- toggle -->
|
<!-- toggle -->
|
||||||
<div class="relative ml-2">
|
<div class="relative ml-2">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input type="checkbox" v-model="warnIfTestServer" class="sr-only" />
|
<input v-model="warnIfTestServer" type="checkbox" class="sr-only" />
|
||||||
<!-- line -->
|
<!-- line -->
|
||||||
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
||||||
<!-- dot -->
|
<!-- dot -->
|
||||||
@@ -699,16 +719,20 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<div class="px-3 py-4">
|
<div class="px-3 py-4">
|
||||||
<input
|
<input
|
||||||
|
v-model="webPushServerInput"
|
||||||
type="text"
|
type="text"
|
||||||
class="block w-full rounded border border-slate-400 px-3 py-2"
|
class="block w-full rounded border border-slate-400 px-3 py-2"
|
||||||
v-model="webPushServerInput"
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="webPushServerInput != webPushServer"
|
v-if="webPushServerInput != webPushServer"
|
||||||
class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
|
class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
|
||||||
@click="onClickSavePushServer()"
|
@click="onClickSavePushServer()"
|
||||||
>
|
>
|
||||||
<fa icon="floppy-disk" class="fa-fw" color="white"></fa>
|
<font-awesome
|
||||||
|
icon="floppy-disk"
|
||||||
|
class="fa-fw"
|
||||||
|
color="white"
|
||||||
|
></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||||
@@ -729,7 +753,7 @@
|
|||||||
Use Test 2
|
Use Test 2
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span class="px-4 text-sm" v-if="!webPushServerInput">
|
<span v-if="!webPushServerInput" class="px-4 text-sm">
|
||||||
When that setting is blank, this app will use the default web push
|
When that setting is blank, this app will use the default web push
|
||||||
server URL:
|
server URL:
|
||||||
{{ DEFAULT_PUSH_SERVER }}
|
{{ DEFAULT_PUSH_SERVER }}
|
||||||
@@ -738,16 +762,20 @@
|
|||||||
<h2 class="text-slate-500 text-sm font-bold mb-2">Partner Server URL</h2>
|
<h2 class="text-slate-500 text-sm font-bold mb-2">Partner Server URL</h2>
|
||||||
<div class="px-3 py-4">
|
<div class="px-3 py-4">
|
||||||
<input
|
<input
|
||||||
|
v-model="partnerApiServerInput"
|
||||||
type="text"
|
type="text"
|
||||||
class="block w-full rounded border border-slate-400 px-3 py-2"
|
class="block w-full rounded border border-slate-400 px-3 py-2"
|
||||||
v-model="partnerApiServerInput"
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="partnerApiServerInput != partnerApiServer"
|
v-if="partnerApiServerInput != partnerApiServer"
|
||||||
class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
|
class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
|
||||||
@click="onClickSavePartnerServer()"
|
@click="onClickSavePartnerServer()"
|
||||||
>
|
>
|
||||||
<fa icon="floppy-disk" class="fa-fw" color="white"></fa>
|
<font-awesome
|
||||||
|
icon="floppy-disk"
|
||||||
|
class="fa-fw"
|
||||||
|
color="white"
|
||||||
|
></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="px-3 rounded bg-slate-200 border border-slate-400"
|
class="px-3 rounded bg-slate-200 border border-slate-400"
|
||||||
@@ -768,7 +796,7 @@
|
|||||||
Use Local
|
Use Local
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span class="px-4 text-sm" v-if="!partnerApiServerInput">
|
<span v-if="!partnerApiServerInput" class="px-4 text-sm">
|
||||||
When that setting is blank, this app will use the default partner server
|
When that setting is blank, this app will use the default partner server
|
||||||
URL:
|
URL:
|
||||||
{{ DEFAULT_PARTNER_API_SERVER }}
|
{{ DEFAULT_PARTNER_API_SERVER }}
|
||||||
@@ -793,8 +821,8 @@
|
|||||||
<div class="relative ml-2">
|
<div class="relative ml-2">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="hideRegisterPromptOnNewContact"
|
v-model="hideRegisterPromptOnNewContact"
|
||||||
|
type="checkbox"
|
||||||
class="sr-only"
|
class="sr-only"
|
||||||
/>
|
/>
|
||||||
<!-- line -->
|
<!-- line -->
|
||||||
@@ -818,7 +846,7 @@
|
|||||||
<!-- toggle -->
|
<!-- toggle -->
|
||||||
<div class="relative ml-2">
|
<div class="relative ml-2">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input type="checkbox" v-model="showShortcutBvc" class="sr-only" />
|
<input v-model="showShortcutBvc" type="checkbox" class="sr-only" />
|
||||||
<!-- line -->
|
<!-- line -->
|
||||||
<div class="block bg-slate-500 w-14 h-8 rounded-full" />
|
<div class="block bg-slate-500 w-14 h-8 rounded-full" />
|
||||||
<!-- dot -->
|
<!-- dot -->
|
||||||
@@ -851,9 +879,9 @@
|
|||||||
</span>
|
</span>
|
||||||
<div class="relative ml-2">
|
<div class="relative ml-2">
|
||||||
<input
|
<input
|
||||||
|
v-model="passkeyExpirationMinutes"
|
||||||
type="number"
|
type="number"
|
||||||
class="border border-slate-400 rounded px-2 py-2 text-center w-20"
|
class="border border-slate-400 rounded px-2 py-2 text-center w-20"
|
||||||
v-model="passkeyExpirationMinutes"
|
|
||||||
@change="updatePasskeyExpiration"
|
@change="updatePasskeyExpiration"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -872,8 +900,8 @@
|
|||||||
<div class="relative ml-2">
|
<div class="relative ml-2">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="showGeneralAdvanced"
|
v-model="showGeneralAdvanced"
|
||||||
|
type="checkbox"
|
||||||
class="sr-only"
|
class="sr-only"
|
||||||
/>
|
/>
|
||||||
<!-- line -->
|
<!-- line -->
|
||||||
@@ -895,13 +923,13 @@ import { AxiosError } from "axios";
|
|||||||
import { Buffer } from "buffer/";
|
import { Buffer } from "buffer/";
|
||||||
import Dexie from "dexie";
|
import Dexie from "dexie";
|
||||||
import "dexie-export-import";
|
import "dexie-export-import";
|
||||||
import { ImportProgress } from "dexie-export-import/dist/import";
|
import { ImportProgress } from "dexie-export-import";
|
||||||
import { LeafletMouseEvent } from "leaflet";
|
import { LeafletMouseEvent } from "leaflet";
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
||||||
|
|
||||||
@@ -947,6 +975,7 @@ import {
|
|||||||
DIRECT_PUSH_TITLE,
|
DIRECT_PUSH_TITLE,
|
||||||
retrieveAccountMetadata,
|
retrieveAccountMetadata,
|
||||||
} from "../libs/util";
|
} from "../libs/util";
|
||||||
|
import { UserProfile } from "@/libs/partnerServer";
|
||||||
|
|
||||||
const inputImportFileNameRef = ref<Blob>();
|
const inputImportFileNameRef = ref<Blob>();
|
||||||
|
|
||||||
@@ -966,6 +995,8 @@ const inputImportFileNameRef = ref<Blob>();
|
|||||||
})
|
})
|
||||||
export default class AccountViewView extends Vue {
|
export default class AccountViewView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
AppConstants = AppString;
|
AppConstants = AppString;
|
||||||
DEFAULT_PUSH_SERVER = DEFAULT_PUSH_SERVER;
|
DEFAULT_PUSH_SERVER = DEFAULT_PUSH_SERVER;
|
||||||
|
|||||||
@@ -7,17 +7,17 @@
|
|||||||
<h1 class="text-lg text-center font-light relative px-7">
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<button
|
<button
|
||||||
@click="$router.go(-1)"
|
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.go(-1)"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw" />
|
<font-awesome icon="chevron-left" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
Raw Claim
|
Raw Claim
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<textarea rows="20" class="border-2 w-full" v-model="claimStr"></textarea>
|
<textarea v-model="claimStr" rows="20" class="border-2 w-full"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
|
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
|
||||||
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
@@ -38,12 +37,15 @@ import { logConsoleAndDb, retrieveSettingsForActiveAccount } from "../db/index";
|
|||||||
import * as serverUtil from "../libs/endorserServer";
|
import * as serverUtil from "../libs/endorserServer";
|
||||||
import * as libsUtil from "../libs/util";
|
import * as libsUtil from "../libs/util";
|
||||||
import { errorStringForLog } from "../libs/endorserServer";
|
import { errorStringForLog } from "../libs/endorserServer";
|
||||||
|
import { Router, RouteLocationNormalizedLoaded } from "vue-router";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { QuickNav },
|
components: { QuickNav },
|
||||||
})
|
})
|
||||||
export default class ClaimAddRawView extends Vue {
|
export default class ClaimAddRawView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
accountIdentityStr: string = "null";
|
accountIdentityStr: string = "null";
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
@@ -55,7 +57,7 @@ export default class ClaimAddRawView extends Vue {
|
|||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
|
|
||||||
this.claimStr = (this.$route as Router).query["claim"];
|
this.claimStr = (this.$route.query["claim"] as string) || "";
|
||||||
if (this.claimStr) {
|
if (this.claimStr) {
|
||||||
try {
|
try {
|
||||||
const veriClaim = JSON.parse(this.claimStr);
|
const veriClaim = JSON.parse(this.claimStr);
|
||||||
@@ -65,7 +67,7 @@ export default class ClaimAddRawView extends Vue {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// there may be no link that uses this, meaning you'd have to enter it in a browser
|
// there may be no link that uses this, meaning you'd have to enter it in a browser
|
||||||
const claimJwtId = (this.$route as Router).query["claimJwtId"];
|
const claimJwtId = (this.$route.query["claimJwtId"] as string) || "";
|
||||||
if (claimJwtId) {
|
if (claimJwtId) {
|
||||||
const urlPath = libsUtil.isGlobalUri(claimJwtId)
|
const urlPath = libsUtil.isGlobalUri(claimJwtId)
|
||||||
? "/api/claim/byHandle/"
|
? "/api/claim/byHandle/"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
<section id="Content">
|
<section id="Content">
|
||||||
<div class="flex items-center justify-center h-screen">
|
<div class="flex items-center justify-center h-screen">
|
||||||
<div v-if="claimData">
|
<div v-if="claimData">
|
||||||
<router-link :to="'/claim/' + this.claimId">
|
<router-link :to="'/claim/' + claimId">
|
||||||
<canvas class="w-full block mx-auto" ref="claimCanvas"></canvas>
|
<canvas ref="claimCanvas" class="w-full block mx-auto"></canvas>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -14,11 +14,10 @@
|
|||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { nextTick } from "vue";
|
import { nextTick } from "vue";
|
||||||
import QRCode from "qrcode";
|
import QRCode from "qrcode";
|
||||||
|
|
||||||
import { APP_SERVER, NotificationIface } from "../constants/app";
|
import { APP_SERVER, NotificationIface } from "../constants/app";
|
||||||
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
import * as serverUtil from "../libs/endorserServer";
|
import * as serverUtil from "../libs/endorserServer";
|
||||||
|
import { GenericCredWrapper, GenericVerifiableCredential } from "../interfaces";
|
||||||
@Component
|
@Component
|
||||||
export default class ClaimCertificateView extends Vue {
|
export default class ClaimCertificateView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
@@ -81,7 +80,7 @@ export default class ClaimCertificateView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async drawCanvas(
|
async drawCanvas(
|
||||||
claimData: serverUtil.GenericCredWrapper<serverUtil.GenericVerifiableCredential>,
|
claimData: GenericCredWrapper<GenericVerifiableCredential>,
|
||||||
confirmerIds: Array<string>,
|
confirmerIds: Array<string>,
|
||||||
) {
|
) {
|
||||||
await db.open();
|
await db.open();
|
||||||
|
|||||||
@@ -6,16 +6,6 @@
|
|||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
canvas {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { nextTick } from "vue";
|
import { nextTick } from "vue";
|
||||||
@@ -188,3 +178,13 @@ export default class ClaimReportCertificateView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
canvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
<h1 class="text-lg text-center font-light relative px-7">
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<button
|
<button
|
||||||
@click="$router.go(-1)"
|
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.go(-1)"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw" />
|
<font-awesome icon="chevron-left" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
Verifiable Claim Details
|
Verifiable Claim Details
|
||||||
</h1>
|
</h1>
|
||||||
@@ -22,7 +22,9 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class="flex columns-3">
|
<div class="flex columns-3">
|
||||||
<h2 class="text-md font-bold w-full">
|
<h2 class="text-md font-bold w-full">
|
||||||
{{ capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType) }}
|
{{
|
||||||
|
capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType || "")
|
||||||
|
}}
|
||||||
<button
|
<button
|
||||||
v-if="
|
v-if="
|
||||||
['GiveAction', 'Offer', 'PlanAction'].includes(
|
['GiveAction', 'Offer', 'PlanAction'].includes(
|
||||||
@@ -32,11 +34,14 @@
|
|||||||
// but rather than add more Plan-specific logic to detect the agent
|
// but rather than add more Plan-specific logic to detect the agent
|
||||||
// we'll let them click the Project link and edit from there
|
// we'll let them click the Project link and edit from there
|
||||||
"
|
"
|
||||||
@click="onClickEditClaim"
|
|
||||||
title="Edit"
|
title="Edit"
|
||||||
data-testId="editClaimButton"
|
data-testId="editClaimButton"
|
||||||
|
@click="onClickEditClaim"
|
||||||
>
|
>
|
||||||
<fa icon="pen" class="text-sm text-blue-500 ml-2 mb-1" />
|
<font-awesome
|
||||||
|
icon="pen"
|
||||||
|
class="text-sm text-blue-500 ml-2 mb-1"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex justify-center w-full">
|
<div class="flex justify-center w-full">
|
||||||
@@ -45,7 +50,10 @@
|
|||||||
class="text-blue-500 mt-2"
|
class="text-blue-500 mt-2"
|
||||||
title="Printable Certificate"
|
title="Printable Certificate"
|
||||||
>
|
>
|
||||||
<fa icon="square" class="text-white bg-yellow-500 p-1" />
|
<font-awesome
|
||||||
|
icon="square"
|
||||||
|
class="text-white bg-yellow-500 p-1"
|
||||||
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<!-- show link icon to copy this URL to the clipboard -->
|
<!-- show link icon to copy this URL to the clipboard -->
|
||||||
@@ -56,30 +64,37 @@
|
|||||||
copyToClipboard('A link to this page', window.location.href)
|
copyToClipboard('A link to this page', window.location.href)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<fa icon="link" class="text-slate-500" />
|
<font-awesome icon="link" class="text-slate-500" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<div data-testId="description">
|
<div data-testId="description">
|
||||||
<fa icon="message" class="fa-fw text-slate-400" />
|
<font-awesome icon="message" class="fa-fw text-slate-400" />
|
||||||
{{
|
{{
|
||||||
veriClaim.claim?.itemOffered?.description ||
|
(veriClaim.claim?.itemOffered as any)?.description ||
|
||||||
veriClaim.claim?.description
|
(veriClaim.claim as any)?.description ||
|
||||||
|
""
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<fa icon="user" class="fa-fw text-slate-400" />
|
<font-awesome icon="user" class="fa-fw text-slate-400" />
|
||||||
{{ didInfo(veriClaim.issuer) }}
|
{{ didInfo(veriClaim.issuer) }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<fa icon="calendar" class="fa-fw text-slate-400" />
|
<font-awesome icon="calendar" class="fa-fw text-slate-400" />
|
||||||
Recorded
|
Recorded
|
||||||
{{ veriClaim.issuedAt?.replace(/T/, " ").replace(/Z/, " UTC") }}
|
{{ veriClaim.issuedAt?.replace(/T/, " ").replace(/Z/, " UTC") }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="veriClaim.claim.image" class="flex justify-center">
|
<div
|
||||||
<a :href="veriClaim.claim.image" target="_blank">
|
v-if="(veriClaim.claim as any).image"
|
||||||
<img :src="veriClaim.claim.image" class="h-24 rounded-xl" />
|
class="flex justify-center"
|
||||||
|
>
|
||||||
|
<a :href="(veriClaim.claim as any).image" target="_blank">
|
||||||
|
<img
|
||||||
|
:src="(veriClaim.claim as any).image"
|
||||||
|
class="h-24 rounded-xl"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -116,10 +131,10 @@
|
|||||||
>
|
>
|
||||||
<!-- router-link to /claim/ only changes URL path -->
|
<!-- router-link to /claim/ only changes URL path -->
|
||||||
<a
|
<a
|
||||||
|
class="text-blue-500 mt-4 cursor-pointer"
|
||||||
@click="
|
@click="
|
||||||
showDifferentClaimPage(detailsForGive?.fulfillsHandleId)
|
showDifferentClaimPage(detailsForGive?.fulfillsHandleId)
|
||||||
"
|
"
|
||||||
class="text-blue-500 mt-4 cursor-pointer"
|
|
||||||
>
|
>
|
||||||
Fulfills
|
Fulfills
|
||||||
{{
|
{{
|
||||||
@@ -155,15 +170,15 @@
|
|||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<div class="grow overflow-hidden">
|
<div class="grow overflow-hidden">
|
||||||
<a
|
<a
|
||||||
|
class="text-blue-500 mt-4 cursor-pointer"
|
||||||
@click="
|
@click="
|
||||||
provider.identifier.startsWith('did:')
|
provider.identifier.startsWith('did:')
|
||||||
? this.$router.push(
|
? $router.push(
|
||||||
'/did/' +
|
'/did/' +
|
||||||
encodeURIComponent(provider.identifier),
|
encodeURIComponent(provider.identifier),
|
||||||
)
|
)
|
||||||
: showDifferentClaimPage(provider.identifier)
|
: showDifferentClaimPage(provider.identifier)
|
||||||
"
|
"
|
||||||
class="text-blue-500 mt-4 cursor-pointer"
|
|
||||||
>
|
>
|
||||||
an activity...
|
an activity...
|
||||||
</a>
|
</a>
|
||||||
@@ -177,13 +192,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<fa icon="comment" class="text-slate-400" />
|
<font-awesome icon="comment" class="text-slate-400" />
|
||||||
{{ issuerName }} posted that.
|
{{ issuerName }} posted that.
|
||||||
</div>
|
</div>
|
||||||
<!--
|
<!--
|
||||||
<div>
|
<div>
|
||||||
<router-link :to="'/claim-cert/' + encodeURIComponent(veriClaim.id)">
|
<router-link :to="'/claim-cert/' + encodeURIComponent(veriClaim.id)">
|
||||||
<fa icon="file-contract" class="text-slate-400" />
|
<font-awesome icon="file-contract" class="text-slate-400" />
|
||||||
<span class="ml-2 text-blue-500">Printable Certificate</span>
|
<span class="ml-2 text-blue-500">Printable Certificate</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
@@ -192,11 +207,14 @@
|
|||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<button
|
<button
|
||||||
v-if="libsUtil.canFulfillOffer(veriClaim)"
|
v-if="libsUtil.canFulfillOffer(veriClaim)"
|
||||||
@click="openFulfillGiftDialog()"
|
|
||||||
class="col-span-1 block w-fit text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
class="col-span-1 block w-fit text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
||||||
|
@click="openFulfillGiftDialog()"
|
||||||
>
|
>
|
||||||
Affirm Delivery
|
Affirm Delivery
|
||||||
<fa icon="hand-holding-heart" class="ml-2 text-white cursor-pointer" />
|
<font-awesome
|
||||||
|
icon="hand-holding-heart"
|
||||||
|
class="ml-2 text-white cursor-pointer"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<GiftedDialog ref="customGiveDialog" />
|
<GiftedDialog ref="customGiveDialog" />
|
||||||
@@ -204,7 +222,6 @@
|
|||||||
<div v-if="libsUtil.isGiveAction(veriClaim)">
|
<div v-if="libsUtil.isGiveAction(veriClaim)">
|
||||||
<div class="flex columns-3">
|
<div class="flex columns-3">
|
||||||
<button
|
<button
|
||||||
class="col-span-1 bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-4 py-2 rounded-md"
|
|
||||||
v-if="
|
v-if="
|
||||||
libsUtil.isGiveRecordTheUserCanConfirm(
|
libsUtil.isGiveRecordTheUserCanConfirm(
|
||||||
isRegistered,
|
isRegistered,
|
||||||
@@ -213,10 +230,14 @@
|
|||||||
confirmerIdList,
|
confirmerIdList,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
class="col-span-1 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-4 py-2 rounded-md"
|
||||||
@click="confirmConfirmClaim()"
|
@click="confirmConfirmClaim()"
|
||||||
>
|
>
|
||||||
Confirm
|
Confirm
|
||||||
<fa icon="circle-check" class="ml-2 text-white cursor-pointer" />
|
<font-awesome
|
||||||
|
icon="circle-check"
|
||||||
|
class="ml-2 text-white cursor-pointer"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<h2 v-else class="font-bold uppercase text-xl mt-2">Confirmations</h2>
|
<h2 v-else class="font-bold uppercase text-xl mt-2">Confirmations</h2>
|
||||||
|
|
||||||
@@ -276,7 +297,10 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
>
|
>
|
||||||
<fa icon="arrow-up-right-from-square" class="fa-fw" />
|
<font-awesome
|
||||||
|
icon="arrow-up-right-from-square"
|
||||||
|
class="fa-fw"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -314,7 +338,10 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
>
|
>
|
||||||
<fa icon="arrow-up-right-from-square" class="fa-fw" />
|
<font-awesome
|
||||||
|
icon="arrow-up-right-from-square"
|
||||||
|
class="fa-fw"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -345,8 +372,8 @@
|
|||||||
@click="showVeriClaimDump = !showVeriClaimDump"
|
@click="showVeriClaimDump = !showVeriClaimDump"
|
||||||
>
|
>
|
||||||
Details
|
Details
|
||||||
<fa v-if="showVeriClaimDump" icon="chevron-up" />
|
<font-awesome v-if="showVeriClaimDump" icon="chevron-up" />
|
||||||
<fa v-else icon="chevron-right" />
|
<font-awesome v-else icon="chevron-right" />
|
||||||
</h2>
|
</h2>
|
||||||
<div v-if="showVeriClaimDump">
|
<div v-if="showVeriClaimDump">
|
||||||
<div
|
<div
|
||||||
@@ -361,7 +388,7 @@
|
|||||||
<span v-if="canShare">
|
<span v-if="canShare">
|
||||||
You can ask one of your contacts to take a look and see if their
|
You can ask one of your contacts to take a look and see if their
|
||||||
contacts can see more details:
|
contacts can see more details:
|
||||||
<a @click="onClickShareClaim()" class="text-blue-500"
|
<a class="text-blue-500" @click="onClickShareClaim()"
|
||||||
>click to send them this page info</a
|
>click to send them this page info</a
|
||||||
>
|
>
|
||||||
and see if they can make an introduction. Someone is connected to
|
and see if they can make an introduction. Someone is connected to
|
||||||
@@ -372,8 +399,8 @@
|
|||||||
You can ask one of your contacts to take a look and see if their
|
You can ask one of your contacts to take a look and see if their
|
||||||
contacts can see more details:
|
contacts can see more details:
|
||||||
<a
|
<a
|
||||||
@click="copyToClipboard('A link to this page', windowLocation)"
|
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
|
@click="copyToClipboard('A link to this page', windowLocation)"
|
||||||
>click to copy this page info</a
|
>click to copy this page info</a
|
||||||
>
|
>
|
||||||
and see if they can make an introduction. Someone is connected to
|
and see if they can make an introduction. Someone is connected to
|
||||||
@@ -387,7 +414,7 @@
|
|||||||
of your contacts.
|
of your contacts.
|
||||||
<span v-if="canShare">
|
<span v-if="canShare">
|
||||||
If you'd like an introduction,
|
If you'd like an introduction,
|
||||||
<a @click="onClickShareClaim()" class="text-blue-500"
|
<a class="text-blue-500" @click="onClickShareClaim()"
|
||||||
>click to share the information with them and ask if they'll tell
|
>click to share the information with them and ask if they'll tell
|
||||||
you more about the participants.</a
|
you more about the participants.</a
|
||||||
>
|
>
|
||||||
@@ -395,8 +422,8 @@
|
|||||||
<span v-else>
|
<span v-else>
|
||||||
If you'd like an introduction,
|
If you'd like an introduction,
|
||||||
<a
|
<a
|
||||||
@click="copyToClipboard('A link to this page', windowLocation)"
|
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
|
@click="copyToClipboard('A link to this page', windowLocation)"
|
||||||
>share this page with them and ask if they'll tell you more about
|
>share this page with them and ask if they'll tell you more about
|
||||||
about the participants.</a
|
about the participants.</a
|
||||||
>
|
>
|
||||||
@@ -408,7 +435,7 @@
|
|||||||
class="list-disc p-4"
|
class="list-disc p-4"
|
||||||
>
|
>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<fa icon="minus" class="fa-fw" />
|
<font-awesome icon="minus" class="fa-fw" />
|
||||||
The {{ visibleDidPath }} is visible to:
|
The {{ visibleDidPath }} is visible to:
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-12 p-1">
|
<div class="ml-12 p-1">
|
||||||
@@ -427,7 +454,10 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
>
|
>
|
||||||
<fa icon="arrow-up-right-from-square" class="fa-fw" />
|
<font-awesome
|
||||||
|
icon="arrow-up-right-from-square"
|
||||||
|
class="fa-fw"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="veriClaim.publicUrls?.[visDid]"
|
<span v-if="veriClaim.publicUrls?.[visDid]"
|
||||||
@@ -436,7 +466,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
>
|
>
|
||||||
<fa icon="globe" class="fa-fw" />
|
<font-awesome icon="globe" class="fa-fw" />
|
||||||
{{
|
{{
|
||||||
veriClaim.publicUrls[visDid].substring(
|
veriClaim.publicUrls[visDid].substring(
|
||||||
veriClaim.publicUrls[visDid].indexOf("//") + 2,
|
veriClaim.publicUrls[visDid].indexOf("//") + 2,
|
||||||
@@ -476,7 +506,7 @@
|
|||||||
class="text-blue-500 cursor-pointer"
|
class="text-blue-500 cursor-pointer"
|
||||||
@click="showFullClaim(veriClaim.id as string)"
|
@click="showFullClaim(veriClaim.id as string)"
|
||||||
>
|
>
|
||||||
<fa icon="file-lines" class="fa-fw" />
|
<font-awesome icon="file-lines" class="fa-fw" />
|
||||||
Load Full Claim Details
|
Load Full Claim Details
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -492,8 +522,8 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
class="text-blue-500 cursor-pointer"
|
class="text-blue-500 cursor-pointer"
|
||||||
>
|
>
|
||||||
<fa icon="file-lines" class="fa-fw" />
|
<font-awesome icon="file-lines" class="fa-fw" />
|
||||||
<fa icon="arrow-up-right-from-square" class="ml-1 fa-fw" />
|
<font-awesome icon="arrow-up-right-from-square" class="ml-1 fa-fw" />
|
||||||
View on the Public Server
|
View on the Public Server
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -505,9 +535,9 @@ import { AxiosError } from "axios";
|
|||||||
import * as yaml from "js-yaml";
|
import * as yaml from "js-yaml";
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
import { Router, RouteLocationNormalizedLoaded } from "vue-router";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
import { GenericVerifiableCredential } from "../interfaces";
|
||||||
import GiftedDialog from "../components/GiftedDialog.vue";
|
import GiftedDialog from "../components/GiftedDialog.vue";
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
@@ -521,19 +551,17 @@ import * as serverUtil from "../libs/endorserServer";
|
|||||||
import {
|
import {
|
||||||
GenericCredWrapper,
|
GenericCredWrapper,
|
||||||
OfferVerifiableCredential,
|
OfferVerifiableCredential,
|
||||||
} from "../libs/endorserServer";
|
ProviderInfo,
|
||||||
|
} from "../interfaces";
|
||||||
import * as libsUtil from "../libs/util";
|
import * as libsUtil from "../libs/util";
|
||||||
|
|
||||||
interface ProviderInfo {
|
|
||||||
identifier: string; // could be a DID or a handleId
|
|
||||||
linkConfirmed: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { GiftedDialog, QuickNav },
|
components: { GiftedDialog, QuickNav },
|
||||||
})
|
})
|
||||||
export default class ClaimView extends Vue {
|
export default class ClaimView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allMyDids: Array<string> = [];
|
allMyDids: Array<string> = [];
|
||||||
@@ -544,8 +572,12 @@ export default class ClaimView extends Vue {
|
|||||||
confirmerIdList: string[] = []; // list of DIDs that have confirmed this claim excluding the issuer
|
confirmerIdList: string[] = []; // list of DIDs that have confirmed this claim excluding the issuer
|
||||||
confsVisibleErrorMessage = "";
|
confsVisibleErrorMessage = "";
|
||||||
confsVisibleToIdList: string[] = []; // list of DIDs that can see any confirmer
|
confsVisibleToIdList: string[] = []; // list of DIDs that can see any confirmer
|
||||||
detailsForGive = null;
|
detailsForGive: {
|
||||||
detailsForOffer = null;
|
fulfillsPlanHandleId?: string;
|
||||||
|
fulfillsType?: string;
|
||||||
|
fulfillsHandleId?: string;
|
||||||
|
} | null = null;
|
||||||
|
detailsForOffer: { fulfillsPlanHandleId?: string } | null = null;
|
||||||
fullClaim = null;
|
fullClaim = null;
|
||||||
fullClaimDump = "";
|
fullClaimDump = "";
|
||||||
fullClaimMessage = "";
|
fullClaimMessage = "";
|
||||||
@@ -558,7 +590,7 @@ export default class ClaimView extends Vue {
|
|||||||
showVeriClaimDump = false;
|
showVeriClaimDump = false;
|
||||||
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
|
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
|
||||||
veriClaimDump = "";
|
veriClaimDump = "";
|
||||||
veriClaimDidsVisible = {};
|
veriClaimDidsVisible: { [key: string]: string[] } = {};
|
||||||
windowLocation = window.location.href;
|
windowLocation = window.location.href;
|
||||||
|
|
||||||
R = R;
|
R = R;
|
||||||
@@ -585,6 +617,7 @@ export default class ClaimView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
|
console.log("ClaimView created");
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const settings = await retrieveSettingsForActiveAccount();
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
@@ -594,7 +627,6 @@ export default class ClaimView extends Vue {
|
|||||||
try {
|
try {
|
||||||
this.allMyDids = await libsUtil.retrieveAccountDids();
|
this.allMyDids = await libsUtil.retrieveAccountDids();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// continue because we want to see claims, even anonymously
|
|
||||||
logConsoleAndDb(
|
logConsoleAndDb(
|
||||||
"Error retrieving all account DIDs on home page:" + error,
|
"Error retrieving all account DIDs on home page:" + error,
|
||||||
true,
|
true,
|
||||||
@@ -610,10 +642,8 @@ export default class ClaimView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathParam = window.location.pathname.substring("/claim/".length);
|
const claimId = this.$route.params.id as string;
|
||||||
let claimId;
|
if (claimId) {
|
||||||
if (pathParam) {
|
|
||||||
claimId = decodeURIComponent(pathParam);
|
|
||||||
await this.loadClaim(claimId, this.activeDid);
|
await this.loadClaim(claimId, this.activeDid);
|
||||||
} else {
|
} else {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -627,8 +657,6 @@ export default class ClaimView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// When Chrome compatibility is fixed https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API#api.navigator.canshare
|
|
||||||
// then use this truer check: navigator.canShare && navigator.canShare()
|
|
||||||
this.canShare = !!navigator.share;
|
this.canShare = !!navigator.share;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,6 +687,7 @@ export default class ClaimView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadClaim(claimId: string, userDid: string) {
|
async loadClaim(claimId: string, userDid: string) {
|
||||||
|
console.log("[ClaimView] loadClaim called with claimId:", claimId);
|
||||||
const urlPath = libsUtil.isGlobalUri(claimId)
|
const urlPath = libsUtil.isGlobalUri(claimId)
|
||||||
? "/api/claim/byHandle/"
|
? "/api/claim/byHandle/"
|
||||||
: "/api/claim/";
|
: "/api/claim/";
|
||||||
@@ -666,6 +695,7 @@ export default class ClaimView extends Vue {
|
|||||||
const headers = await serverUtil.getHeaders(userDid);
|
const headers = await serverUtil.getHeaders(userDid);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log("[ClaimView] Making API request to:", url);
|
||||||
const resp = await this.axios.get(url, { headers });
|
const resp = await this.axios.get(url, { headers });
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
this.veriClaim = resp.data;
|
this.veriClaim = resp.data;
|
||||||
@@ -887,7 +917,7 @@ export default class ClaimView extends Vue {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const confirmationClaim: serverUtil.GenericVerifiableCredential = {
|
const confirmationClaim: GenericVerifiableCredential = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "AgreeAction",
|
"@type": "AgreeAction",
|
||||||
object: goodClaim,
|
object: goodClaim,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'account' }"
|
:to="{ name: 'account' }"
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
><fa icon="chevron-left" class="fa-fw"></fa
|
><font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
></router-link>
|
</router-link>
|
||||||
|
|
||||||
Confirm Contact
|
Confirm Contact
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
<h1 class="text-lg text-center font-light relative px-7">
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<button
|
<button
|
||||||
@click="$router.go(-1)"
|
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.go(-1)"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw" />
|
<font-awesome icon="chevron-left" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
@@ -32,7 +32,6 @@
|
|||||||
<div v-if="giveDetails && !isLoading">
|
<div v-if="giveDetails && !isLoading">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<button
|
<button
|
||||||
class="col-span-1 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
|
|
||||||
v-if="
|
v-if="
|
||||||
libsUtil.isGiveRecordTheUserCanConfirm(
|
libsUtil.isGiveRecordTheUserCanConfirm(
|
||||||
isRegistered,
|
isRegistered,
|
||||||
@@ -41,18 +40,25 @@
|
|||||||
confirmerIdList,
|
confirmerIdList,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
class="col-span-1 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
|
||||||
@click="confirmConfirmClaim()"
|
@click="confirmConfirmClaim()"
|
||||||
>
|
>
|
||||||
Confirm
|
Confirm
|
||||||
<fa icon="circle-check" class="ml-2 text-white cursor-pointer" />
|
<font-awesome
|
||||||
|
icon="circle-check"
|
||||||
|
class="ml-2 text-white cursor-pointer"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
@click="notifyWhyCannotConfirm()"
|
|
||||||
class="col-span-1 bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
|
class="col-span-1 bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
|
||||||
|
@click="notifyWhyCannotConfirm()"
|
||||||
>
|
>
|
||||||
Confirm
|
Confirm
|
||||||
<fa icon="circle-check" class="ml-2 text-white cursor-pointer" />
|
<font-awesome
|
||||||
|
icon="circle-check"
|
||||||
|
class="ml-2 text-white cursor-pointer"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -62,26 +68,29 @@
|
|||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<div>
|
<div>
|
||||||
<fa icon="arrow-left" class="fa-fw text-slate-400" />
|
<font-awesome icon="arrow-left" class="fa-fw text-slate-400" />
|
||||||
{{ giverName }}
|
{{ giverName }}
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-6">gave</div>
|
<div class="ml-6">gave</div>
|
||||||
<div v-if="giveDetails.amount">
|
<div v-if="giveDetails.amount">
|
||||||
<fa icon="hand-holding-dollar" class="fa-fw text-slate-400" />
|
<font-awesome
|
||||||
|
icon="hand-holding-dollar"
|
||||||
|
class="fa-fw text-slate-400"
|
||||||
|
/>
|
||||||
{{ displayAmount(giveDetails.unit, giveDetails.amount) }}
|
{{ displayAmount(giveDetails.unit, giveDetails.amount) }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="giveDetails.description">
|
<div v-if="giveDetails.description">
|
||||||
<fa icon="message" class="fa-fw text-slate-400" />
|
<font-awesome icon="message" class="fa-fw text-slate-400" />
|
||||||
{{ giveDetails.amount ? "and:" : "" }}
|
{{ giveDetails.amount ? "and:" : "" }}
|
||||||
{{ giveDetails.description }}
|
{{ giveDetails.description }}
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-6">to</div>
|
<div class="ml-6">to</div>
|
||||||
<div>
|
<div>
|
||||||
<fa icon="arrow-right" class="fa-fw text-slate-400" />
|
<font-awesome icon="arrow-right" class="fa-fw text-slate-400" />
|
||||||
{{ recipientName }}
|
{{ recipientName }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<fa icon="calendar" class="fa-fw text-slate-400" />
|
<font-awesome icon="calendar" class="fa-fw text-slate-400" />
|
||||||
on
|
on
|
||||||
{{ giveDetails.issuedAt.substring(0, 10) }}
|
{{ giveDetails.issuedAt.substring(0, 10) }}
|
||||||
</div>
|
</div>
|
||||||
@@ -89,7 +98,7 @@
|
|||||||
<!-- Fullfills Links -->
|
<!-- Fullfills Links -->
|
||||||
|
|
||||||
<!-- fullfills links for a give -->
|
<!-- fullfills links for a give -->
|
||||||
<div class="mt-2" v-if="giveDetails?.fulfillsPlanHandleId">
|
<div v-if="giveDetails?.fulfillsPlanHandleId" class="mt-2">
|
||||||
<router-link
|
<router-link
|
||||||
:to="
|
:to="
|
||||||
'/project/' +
|
'/project/' +
|
||||||
@@ -99,7 +108,10 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
This fulfills a bigger plan
|
This fulfills a bigger plan
|
||||||
<fa icon="arrow-up-right-from-square" class="fa-fw" />
|
<font-awesome
|
||||||
|
icon="arrow-up-right-from-square"
|
||||||
|
class="fa-fw"
|
||||||
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<!-- if there's another, it's probably fulfilling an offer, too -->
|
<!-- if there's another, it's probably fulfilling an offer, too -->
|
||||||
@@ -125,7 +137,10 @@
|
|||||||
giveDetails?.fulfillsType || "",
|
giveDetails?.fulfillsType || "",
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
<fa icon="arrow-up-right-from-square" class="fa-fw" />
|
<font-awesome
|
||||||
|
icon="arrow-up-right-from-square"
|
||||||
|
class="fa-fw"
|
||||||
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -133,7 +148,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<fa icon="comment" class="text-slate-400" />
|
<font-awesome icon="comment" class="text-slate-400" />
|
||||||
{{ issuerName }} posted that.
|
{{ issuerName }} posted that.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -185,7 +200,10 @@
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw" />
|
<font-awesome
|
||||||
|
icon="copy"
|
||||||
|
class="text-slate-400 fa-fw"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -228,7 +246,10 @@
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw" />
|
<font-awesome
|
||||||
|
icon="copy"
|
||||||
|
class="text-slate-400 fa-fw"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -260,8 +281,8 @@
|
|||||||
@click="showVeriClaimDump = !showVeriClaimDump"
|
@click="showVeriClaimDump = !showVeriClaimDump"
|
||||||
>
|
>
|
||||||
Details
|
Details
|
||||||
<fa v-if="showVeriClaimDump" icon="chevron-up" />
|
<font-awesome v-if="showVeriClaimDump" icon="chevron-up" />
|
||||||
<fa v-else icon="chevron-right" />
|
<font-awesome v-else icon="chevron-right" />
|
||||||
</h2>
|
</h2>
|
||||||
<div v-if="showVeriClaimDump">
|
<div v-if="showVeriClaimDump">
|
||||||
<div
|
<div
|
||||||
@@ -276,7 +297,7 @@
|
|||||||
<span v-if="canShare">
|
<span v-if="canShare">
|
||||||
You can ask one of your contacts to take a look and see if their
|
You can ask one of your contacts to take a look and see if their
|
||||||
contacts can see more details:
|
contacts can see more details:
|
||||||
<a @click="onClickShareClaim()" class="text-blue-500"
|
<a class="text-blue-500" @click="onClickShareClaim()"
|
||||||
>click to send them this page info</a
|
>click to send them this page info</a
|
||||||
>
|
>
|
||||||
and see if they can make an introduction. Someone is connected to
|
and see if they can make an introduction. Someone is connected to
|
||||||
@@ -287,8 +308,8 @@
|
|||||||
You can ask one of your contacts to take a look and see if their
|
You can ask one of your contacts to take a look and see if their
|
||||||
contacts can see more details:
|
contacts can see more details:
|
||||||
<a
|
<a
|
||||||
@click="copyToClipboard('A link to this page', windowLocation)"
|
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
|
@click="copyToClipboard('A link to this page', windowLocation)"
|
||||||
>click to copy this page info</a
|
>click to copy this page info</a
|
||||||
>
|
>
|
||||||
and see if they can make an introduction. Someone is connected to
|
and see if they can make an introduction. Someone is connected to
|
||||||
@@ -302,7 +323,7 @@
|
|||||||
some of your contacts.
|
some of your contacts.
|
||||||
<span v-if="canShare">
|
<span v-if="canShare">
|
||||||
If you'd like an introduction,
|
If you'd like an introduction,
|
||||||
<a @click="onClickShareClaim()" class="text-blue-500"
|
<a class="text-blue-500" @click="onClickShareClaim()"
|
||||||
>click to share the information with them and ask if they'll tell
|
>click to share the information with them and ask if they'll tell
|
||||||
you more about the participants.</a
|
you more about the participants.</a
|
||||||
>
|
>
|
||||||
@@ -310,8 +331,8 @@
|
|||||||
<span v-else>
|
<span v-else>
|
||||||
If you'd like an introduction,
|
If you'd like an introduction,
|
||||||
<a
|
<a
|
||||||
@click="copyToClipboard('A link to this page', windowLocation)"
|
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
|
@click="copyToClipboard('A link to this page', windowLocation)"
|
||||||
>share this page with them and ask if they'll tell you more about
|
>share this page with them and ask if they'll tell you more about
|
||||||
about the participants.</a
|
about the participants.</a
|
||||||
>
|
>
|
||||||
@@ -323,7 +344,7 @@
|
|||||||
class="list-disc p-4"
|
class="list-disc p-4"
|
||||||
>
|
>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<fa icon="minus" class="fa-fw" />
|
<font-awesome icon="minus" class="fa-fw" />
|
||||||
The {{ visibleDidPath }} is visible to:
|
The {{ visibleDidPath }} is visible to:
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-12 p-1">
|
<div class="ml-12 p-1">
|
||||||
@@ -342,12 +363,18 @@
|
|||||||
copyToClipboard('The DID of ' + visDid, visDid)
|
copyToClipboard('The DID of ' + visDid, visDid)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw" />
|
<font-awesome
|
||||||
|
icon="copy"
|
||||||
|
class="text-slate-400 fa-fw"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="veriClaim.publicUrls?.[visDid]"
|
<span v-if="veriClaim.publicUrls?.[visDid]"
|
||||||
>, found at
|
>, found at
|
||||||
<fa icon="globe" class="fa-fw text-slate-400" />
|
<font-awesome
|
||||||
|
icon="globe"
|
||||||
|
class="fa-fw text-slate-400"
|
||||||
|
/>
|
||||||
<a
|
<a
|
||||||
:href="veriClaim.publicUrls?.[visDid]"
|
:href="veriClaim.publicUrls?.[visDid]"
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
@@ -372,10 +399,10 @@
|
|||||||
>
|
>
|
||||||
<div class="mt-2 ml-2">
|
<div class="mt-2 ml-2">
|
||||||
<a
|
<a
|
||||||
@click="showClaimPage(veriClaim.id)"
|
|
||||||
class="text-blue-500 cursor-pointer"
|
class="text-blue-500 cursor-pointer"
|
||||||
|
@click="showClaimPage(veriClaim.id)"
|
||||||
>
|
>
|
||||||
<fa icon="file-lines" />
|
<font-awesome icon="file-lines" />
|
||||||
See All Generic Info
|
See All Generic Info
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -385,7 +412,7 @@
|
|||||||
class="text-blue-500 cursor-pointer"
|
class="text-blue-500 cursor-pointer"
|
||||||
:href="urlForNewGive"
|
:href="urlForNewGive"
|
||||||
>
|
>
|
||||||
<fa icon="file-lines" />
|
<font-awesome icon="file-lines" />
|
||||||
Record a Give Similar to the Original
|
Record a Give Similar to the Original
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -394,10 +421,10 @@
|
|||||||
<div v-else-if="!isLoading">This does not have details to confirm.</div>
|
<div v-else-if="!isLoading">This does not have details to confirm.</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
|
|
||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
|
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
|
||||||
>
|
>
|
||||||
<fa icon="spinner" class="fa-spin-pulse"></fa>
|
<font-awesome icon="spinner" class="fa-spin-pulse"></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@@ -408,24 +435,26 @@ import * as yaml from "js-yaml";
|
|||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
import { Router } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
|
import { GenericVerifiableCredential } from "../interfaces";
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import * as serverUtil from "../libs/endorserServer";
|
import * as serverUtil from "../libs/endorserServer";
|
||||||
import { displayAmount, GiveSummaryRecord } from "../libs/endorserServer";
|
import { GiveSummaryRecord } from "../interfaces";
|
||||||
|
import { displayAmount } from "../libs/endorserServer";
|
||||||
import * as libsUtil from "../libs/util";
|
import * as libsUtil from "../libs/util";
|
||||||
import { isGiveAction, retrieveAccountDids } from "../libs/util";
|
import { isGiveAction, retrieveAccountDids } from "../libs/util";
|
||||||
import TopMessage from "../components/TopMessage.vue";
|
import TopMessage from "../components/TopMessage.vue";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
methods: { displayAmount },
|
|
||||||
components: { TopMessage, QuickNav },
|
components: { TopMessage, QuickNav },
|
||||||
})
|
})
|
||||||
export default class ClaimView extends Vue {
|
export default class ConfirmGiftView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allMyDids: Array<string> = [];
|
allMyDids: Array<string> = [];
|
||||||
@@ -447,13 +476,14 @@ export default class ClaimView extends Vue {
|
|||||||
urlForNewGive = "";
|
urlForNewGive = "";
|
||||||
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
|
veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD;
|
||||||
veriClaimDump = "";
|
veriClaimDump = "";
|
||||||
veriClaimDidsVisible = {};
|
veriClaimDidsVisible: { [key: string]: string[] } = {};
|
||||||
windowLocation = window.location.href;
|
windowLocation = window.location.href;
|
||||||
|
|
||||||
R = R;
|
R = R;
|
||||||
yaml = yaml;
|
yaml = yaml;
|
||||||
libsUtil = libsUtil;
|
libsUtil = libsUtil;
|
||||||
serverUtil = serverUtil;
|
serverUtil = serverUtil;
|
||||||
|
displayAmount = displayAmount;
|
||||||
|
|
||||||
resetThisValues() {
|
resetThisValues() {
|
||||||
this.confirmerIdList = [];
|
this.confirmerIdList = [];
|
||||||
@@ -719,7 +749,7 @@ export default class ClaimView extends Vue {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const confirmationClaim: serverUtil.GenericVerifiableCredential = {
|
const confirmationClaim: GenericVerifiableCredential = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "AgreeAction",
|
"@type": "AgreeAction",
|
||||||
object: goodClaim,
|
object: goodClaim,
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'contacts' }"
|
:to="{ name: 'contacts' }"
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
><fa icon="chevron-left" class="fa-fw"></fa
|
><font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
></router-link>
|
</router-link>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4">
|
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4">
|
||||||
@@ -59,10 +59,13 @@
|
|||||||
<div class="font-bold">
|
<div class="font-bold">
|
||||||
{{ displayAmount(record.unit, record.amount) }}
|
{{ displayAmount(record.unit, record.amount) }}
|
||||||
<span v-if="record.amountConfirmed" title="Confirmed">
|
<span v-if="record.amountConfirmed" title="Confirmed">
|
||||||
<fa icon="circle-check" class="text-green-600 fa-fw" />
|
<font-awesome
|
||||||
|
icon="circle-check"
|
||||||
|
class="text-green-600 fa-fw"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<button v-else @click="confirm(record)" title="Unconfirmed">
|
<button v-else title="Unconfirmed" @click="confirm(record)">
|
||||||
<fa icon="circle" class="text-blue-600 fa-fw" />
|
<font-awesome icon="circle" class="text-blue-600 fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="italic text-xs sm:text-sm text-slate-500">
|
<div class="italic text-xs sm:text-sm text-slate-500">
|
||||||
@@ -72,10 +75,10 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="p-1">
|
<td class="p-1">
|
||||||
<span v-if="record.agentDid == contact?.did">
|
<span v-if="record.agentDid == contact?.did">
|
||||||
<fa icon="arrow-left" class="text-slate-400 fa-fw" />
|
<font-awesome icon="arrow-left" class="text-slate-400 fa-fw" />
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<fa icon="arrow-right" class="text-slate-400 fa-fw" />
|
<font-awesome icon="arrow-right" class="text-slate-400 fa-fw" />
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="p-1">
|
<td class="p-1">
|
||||||
@@ -83,14 +86,17 @@
|
|||||||
<div class="font-bold">
|
<div class="font-bold">
|
||||||
{{ displayAmount(record.unit, record.amount) }}
|
{{ displayAmount(record.unit, record.amount) }}
|
||||||
<span v-if="record.amountConfirmed" title="Confirmed">
|
<span v-if="record.amountConfirmed" title="Confirmed">
|
||||||
<fa icon="circle-check" class="text-green-600 fa-fw" />
|
<font-awesome
|
||||||
|
icon="circle-check"
|
||||||
|
class="text-green-600 fa-fw"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
@click="cannotConfirmMessage()"
|
|
||||||
title="Unconfirmed"
|
title="Unconfirmed"
|
||||||
|
@click="cannotConfirmMessage()"
|
||||||
>
|
>
|
||||||
<fa icon="circle" class="text-slate-600 fa-fw" />
|
<font-awesome icon="circle" class="text-slate-600 fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="italic text-xs sm:text-sm text-slate-500">
|
<div class="italic text-xs sm:text-sm text-slate-500">
|
||||||
@@ -108,7 +114,7 @@
|
|||||||
import { AxiosError, AxiosRequestHeaders } from "axios";
|
import { AxiosError, AxiosRequestHeaders } from "axios";
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
@@ -116,11 +122,13 @@ import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
|||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import {
|
import {
|
||||||
AgreeVerifiableCredential,
|
AgreeVerifiableCredential,
|
||||||
|
GiveSummaryRecord,
|
||||||
|
GiveVerifiableCredential,
|
||||||
|
} from "../interfaces";
|
||||||
|
import {
|
||||||
createEndorserJwtVcFromClaim,
|
createEndorserJwtVcFromClaim,
|
||||||
displayAmount,
|
displayAmount,
|
||||||
getHeaders,
|
getHeaders,
|
||||||
GiveSummaryRecord,
|
|
||||||
GiveVerifiableCredential,
|
|
||||||
SCHEMA_ORG_CONTEXT,
|
SCHEMA_ORG_CONTEXT,
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import { retrieveAccountCount } from "../libs/util";
|
import { retrieveAccountCount } from "../libs/util";
|
||||||
@@ -128,6 +136,8 @@ import { retrieveAccountCount } from "../libs/util";
|
|||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class ContactAmountssView extends Vue {
|
export default class ContactAmountssView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
@@ -143,7 +153,7 @@ export default class ContactAmountssView extends Vue {
|
|||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
try {
|
try {
|
||||||
const contactDid = (this.$route as Router).query["contactDid"] as string;
|
const contactDid = this.$route.query["contactDid"] as string;
|
||||||
this.contact = (await db.contacts.get(contactDid)) || null;
|
this.contact = (await db.contacts.get(contactDid)) || null;
|
||||||
|
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const settings = await retrieveSettingsForActiveAccount();
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
<h1 class="text-4xl text-center font-light relative px-7">
|
<h1 class="text-4xl text-center font-light relative px-7">
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<button
|
<button
|
||||||
@click="$router.go(-1)"
|
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.go(-1)"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw" />
|
<font-awesome icon="chevron-left" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
{{ contact.name || AppString.NO_CONTACT_NAME }}
|
{{ contact.name || AppString.NO_CONTACT_NAME }}
|
||||||
</h1>
|
</h1>
|
||||||
@@ -25,9 +25,9 @@
|
|||||||
Name
|
Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
v-model="contactName"
|
||||||
type="text"
|
type="text"
|
||||||
class="block w-full ml-2 mt-1 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
|
class="block w-full ml-2 mt-1 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
v-model="contactName"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -38,9 +38,9 @@
|
|||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
id="contactNotes"
|
id="contactNotes"
|
||||||
|
v-model="contactNotes"
|
||||||
rows="4"
|
rows="4"
|
||||||
class="block w-full mt-1 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
|
class="block w-full mt-1 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
v-model="contactNotes"
|
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -53,60 +53,60 @@
|
|||||||
class="flex mt-2"
|
class="flex mt-2"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
|
||||||
v-model="method.label"
|
v-model="method.label"
|
||||||
|
type="text"
|
||||||
class="block w-1/4 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
|
class="block w-1/4 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
placeholder="Label"
|
placeholder="Label"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="text"
|
|
||||||
v-model="method.type"
|
v-model="method.type"
|
||||||
|
type="text"
|
||||||
class="block ml-2 w-1/4 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
|
class="block ml-2 w-1/4 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
placeholder="Type"
|
placeholder="Type"
|
||||||
/>
|
/>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<button
|
<button
|
||||||
@click="toggleDropdown(index)"
|
|
||||||
class="px-2 py-1 bg-gray-200 rounded-md"
|
class="px-2 py-1 bg-gray-200 rounded-md"
|
||||||
|
@click="toggleDropdown(index)"
|
||||||
>
|
>
|
||||||
<fa icon="caret-down" class="fa-fw" />
|
<font-awesome icon="caret-down" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
v-if="dropdownIndex === index"
|
v-if="dropdownIndex === index"
|
||||||
class="absolute bg-white border border-gray-300 rounded-md mt-1"
|
class="absolute bg-white border border-gray-300 rounded-md mt-1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@click="setMethodType(index, 'CELL')"
|
|
||||||
class="px-4 py-2 hover:bg-gray-100 cursor-pointer"
|
class="px-4 py-2 hover:bg-gray-100 cursor-pointer"
|
||||||
|
@click="setMethodType(index, 'CELL')"
|
||||||
>
|
>
|
||||||
CELL
|
CELL
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@click="setMethodType(index, 'EMAIL')"
|
|
||||||
class="px-4 py-2 hover:bg-gray-100 cursor-pointer"
|
class="px-4 py-2 hover:bg-gray-100 cursor-pointer"
|
||||||
|
@click="setMethodType(index, 'EMAIL')"
|
||||||
>
|
>
|
||||||
EMAIL
|
EMAIL
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@click="setMethodType(index, 'WHATSAPP')"
|
|
||||||
class="px-4 py-2 hover:bg-gray-100 cursor-pointer"
|
class="px-4 py-2 hover:bg-gray-100 cursor-pointer"
|
||||||
|
@click="setMethodType(index, 'WHATSAPP')"
|
||||||
>
|
>
|
||||||
WHATSAPP
|
WHATSAPP
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
|
||||||
v-model="method.value"
|
v-model="method.value"
|
||||||
|
type="text"
|
||||||
class="block ml-2 w-1/2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
|
class="block ml-2 w-1/2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
placeholder="Number, email, etc."
|
placeholder="Number, email, etc."
|
||||||
/>
|
/>
|
||||||
<button @click="removeContactMethod(index)" class="ml-2 text-red-500">
|
<button class="ml-2 text-red-500" @click="removeContactMethod(index)">
|
||||||
<fa icon="trash-can" class="fa-fw" />
|
<font-awesome icon="trash-can" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button @click="addContactMethod" class="mt-2">
|
<button class="mt-2" @click="addContactMethod">
|
||||||
<fa
|
<font-awesome
|
||||||
icon="plus"
|
icon="plus"
|
||||||
class="fa-fw px-2 py-2.5 bg-green-500 text-green-100 rounded-full"
|
class="fa-fw px-2 py-2.5 bg-green-500 text-green-100 rounded-full"
|
||||||
/>
|
/>
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { RouteLocation, Router } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import TopMessage from "../components/TopMessage.vue";
|
import TopMessage from "../components/TopMessage.vue";
|
||||||
@@ -142,6 +142,37 @@ import { AppString, NotificationIface } from "../constants/app";
|
|||||||
import { db } from "../db/index";
|
import { db } from "../db/index";
|
||||||
import { Contact, ContactMethod } from "../db/tables/contacts";
|
import { Contact, ContactMethod } from "../db/tables/contacts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact Edit View Component
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*
|
||||||
|
* This component provides a full-featured contact editing interface with support for:
|
||||||
|
* - Basic contact information (name, notes)
|
||||||
|
* - Multiple contact methods with type selection
|
||||||
|
* - Data validation and persistence
|
||||||
|
*
|
||||||
|
* Workflow:
|
||||||
|
* 1. Component loads with DID from route params
|
||||||
|
* 2. Fetches existing contact data from IndexedDB
|
||||||
|
* 3. Presents editable form with current values
|
||||||
|
* 4. Validates and saves updates back to database
|
||||||
|
*
|
||||||
|
* Contact Method Types:
|
||||||
|
* - CELL: Mobile phone numbers
|
||||||
|
* - EMAIL: Email addresses
|
||||||
|
* - WHATSAPP: WhatsApp contact info
|
||||||
|
*
|
||||||
|
* State Management:
|
||||||
|
* - Maintains separate state for form fields to prevent direct mutation
|
||||||
|
* - Handles array cloning for contact methods to prevent reference issues
|
||||||
|
* - Manages dropdown state for method type selection
|
||||||
|
*
|
||||||
|
* Navigation:
|
||||||
|
* - Back button returns to previous view
|
||||||
|
* - Save redirects to contact detail view
|
||||||
|
* - Cancel returns to previous view
|
||||||
|
* - Invalid DID redirects to contacts list
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
QuickNav,
|
QuickNav,
|
||||||
@@ -149,22 +180,46 @@ import { Contact, ContactMethod } from "../db/tables/contacts";
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class ContactEditView extends Vue {
|
export default class ContactEditView extends Vue {
|
||||||
|
/** Notification function injected by Vue */
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
/** Current route instance */
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
/** Router instance for navigation */
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
|
/** Current contact data */
|
||||||
contact: Contact = {
|
contact: Contact = {
|
||||||
did: "",
|
did: "",
|
||||||
name: "",
|
name: "",
|
||||||
notes: "",
|
notes: "",
|
||||||
};
|
};
|
||||||
|
/** Editable contact name field */
|
||||||
contactName = "";
|
contactName = "";
|
||||||
|
/** Editable contact notes field */
|
||||||
contactNotes = "";
|
contactNotes = "";
|
||||||
|
/** Array of editable contact methods */
|
||||||
contactMethods: Array<ContactMethod> = [];
|
contactMethods: Array<ContactMethod> = [];
|
||||||
|
/** Currently open dropdown index, null if none open */
|
||||||
dropdownIndex: number | null = null;
|
dropdownIndex: number | null = null;
|
||||||
|
|
||||||
|
/** App string constants */
|
||||||
AppString = AppString;
|
AppString = AppString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component lifecycle hook that initializes the contact edit form
|
||||||
|
*
|
||||||
|
* Workflow:
|
||||||
|
* 1. Extracts DID from route parameters
|
||||||
|
* 2. Queries database for existing contact
|
||||||
|
* 3. Populates form fields with contact data
|
||||||
|
* 4. Handles missing contact error case
|
||||||
|
*
|
||||||
|
* @throws Will not throw but redirects on error
|
||||||
|
* @emits Notification on contact not found
|
||||||
|
* @emits Router navigation on error
|
||||||
|
*/
|
||||||
async created() {
|
async created() {
|
||||||
const contactDid = (this.$route as RouteLocation).params.did;
|
const contactDid = this.$route.params.did;
|
||||||
const contact = await db.contacts.get(contactDid || "");
|
const contact = await db.contacts.get(contactDid || "");
|
||||||
if (contact) {
|
if (contact) {
|
||||||
this.contact = contact;
|
this.contact = contact;
|
||||||
@@ -183,29 +238,75 @@ export default class ContactEditView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new empty contact method to the methods array
|
||||||
|
*
|
||||||
|
* Creates a new method object with empty fields for:
|
||||||
|
* - label: Custom label for the method
|
||||||
|
* - type: Communication type (CELL, EMAIL, WHATSAPP)
|
||||||
|
* - value: The contact information value
|
||||||
|
*/
|
||||||
addContactMethod() {
|
addContactMethod() {
|
||||||
this.contactMethods.push({ label: "", type: "", value: "" });
|
this.contactMethods.push({ label: "", type: "", value: "" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a contact method at the specified index
|
||||||
|
*
|
||||||
|
* @param index The array index of the method to remove
|
||||||
|
*/
|
||||||
removeContactMethod(index: number) {
|
removeContactMethod(index: number) {
|
||||||
this.contactMethods.splice(index, 1);
|
this.contactMethods.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the type selection dropdown for a contact method
|
||||||
|
*
|
||||||
|
* If the clicked dropdown is already open, closes it.
|
||||||
|
* If another dropdown is open, closes it and opens the clicked one.
|
||||||
|
*
|
||||||
|
* @param index The array index of the method whose dropdown to toggle
|
||||||
|
*/
|
||||||
toggleDropdown(index: number) {
|
toggleDropdown(index: number) {
|
||||||
this.dropdownIndex = this.dropdownIndex === index ? null : index;
|
this.dropdownIndex = this.dropdownIndex === index ? null : index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the type for a contact method and closes the dropdown
|
||||||
|
*
|
||||||
|
* @param index The array index of the method to update
|
||||||
|
* @param type The new type value (CELL, EMAIL, WHATSAPP)
|
||||||
|
*/
|
||||||
setMethodType(index: number, type: string) {
|
setMethodType(index: number, type: string) {
|
||||||
this.contactMethods[index].type = type;
|
this.contactMethods[index].type = type;
|
||||||
this.dropdownIndex = null;
|
this.dropdownIndex = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the edited contact information to the database
|
||||||
|
*
|
||||||
|
* Workflow:
|
||||||
|
* 1. Clones contact methods array to prevent reference issues
|
||||||
|
* 2. Normalizes method types to uppercase
|
||||||
|
* 3. Checks for changes in method types
|
||||||
|
* 4. Updates database with new values
|
||||||
|
* 5. Notifies user of success
|
||||||
|
* 6. Redirects to contact detail view
|
||||||
|
*
|
||||||
|
* @throws Will not throw but notifies on validation errors
|
||||||
|
* @emits Notification on type changes or success
|
||||||
|
* @emits Router navigation on success
|
||||||
|
*/
|
||||||
async saveEdit() {
|
async saveEdit() {
|
||||||
// without this conversion, "Failed to execute 'put' on 'IDBObjectStore': [object Array] could not be cloned."
|
// without this conversion, "Failed to execute 'put' on 'IDBObjectStore': [object Array] could not be cloned."
|
||||||
const contactMethodsObj = JSON.parse(JSON.stringify(this.contactMethods));
|
const contactMethodsObj = JSON.parse(JSON.stringify(this.contactMethods));
|
||||||
|
|
||||||
|
// Normalize method types to uppercase
|
||||||
const contactMethods = contactMethodsObj.map((method: ContactMethod) =>
|
const contactMethods = contactMethodsObj.map((method: ContactMethod) =>
|
||||||
R.set(R.lensProp("type"), method.type.toUpperCase(), method),
|
R.set(R.lensProp("type"), method.type.toUpperCase(), method),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check for type changes
|
||||||
if (!R.equals(contactMethodsObj, contactMethods)) {
|
if (!R.equals(contactMethodsObj, contactMethods)) {
|
||||||
this.contactMethods = contactMethods;
|
this.contactMethods = contactMethods;
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -219,11 +320,15 @@ export default class ContactEditView extends Vue {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save to database
|
||||||
await db.contacts.update(this.contact.did, {
|
await db.contacts.update(this.contact.did, {
|
||||||
name: this.contactName,
|
name: this.contactName,
|
||||||
notes: this.contactNotes,
|
notes: this.contactNotes,
|
||||||
contactMethods: contactMethods,
|
contactMethods: contactMethods,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Notify success and redirect
|
||||||
this.$notify({
|
this.$notify({
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "success",
|
type: "success",
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'home' }"
|
:to="{ name: 'home' }"
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
><fa icon="chevron-left" class="fa-fw"></fa
|
><font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
></router-link>
|
</router-link>
|
||||||
Given by...
|
Given by...
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,10 +30,10 @@
|
|||||||
<span class="text-right">
|
<span class="text-right">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="openDialog()"
|
|
||||||
class="block w-full text-center text-sm uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
|
class="block w-full text-center text-sm uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
|
||||||
|
@click="openDialog()"
|
||||||
>
|
>
|
||||||
<fa icon="gift" class="fa-fw"></fa>
|
<font-awesome icon="gift" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
<span class="grow font-semibold">
|
<span class="grow font-semibold">
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:contact="contact"
|
:contact="contact"
|
||||||
:iconSize="32"
|
:icon-size="32"
|
||||||
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
||||||
/>
|
/>
|
||||||
{{ contact.name || "(no name)" }}
|
{{ contact.name || "(no name)" }}
|
||||||
@@ -55,23 +55,23 @@
|
|||||||
<span class="text-right">
|
<span class="text-right">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="openDialog(contact)"
|
|
||||||
class="block w-full text-center text-sm uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
|
class="block w-full text-center text-sm uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
|
||||||
|
@click="openDialog(contact)"
|
||||||
>
|
>
|
||||||
<fa icon="gift" class="fa-fw"></fa>
|
<font-awesome icon="gift" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<GiftedDialog ref="customDialog" :toProjectId="projectId" />
|
<GiftedDialog ref="customDialog" :to-project-id="projectId" />
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
|
|
||||||
import GiftedDialog from "../components/GiftedDialog.vue";
|
import GiftedDialog from "../components/GiftedDialog.vue";
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
@@ -86,6 +86,8 @@ import { GiverReceiverInputInfo } from "../libs/util";
|
|||||||
})
|
})
|
||||||
export default class ContactGiftingView extends Vue {
|
export default class ContactGiftingView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
@@ -107,9 +109,8 @@ export default class ContactGiftingView extends Vue {
|
|||||||
(a.name || "").localeCompare(b.name || ""),
|
(a.name || "").localeCompare(b.name || ""),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.projectId = (this.$route as Router).query["projectId"] || "";
|
this.projectId = (this.$route.query["projectId"] as string) || "";
|
||||||
|
this.prompt = (this.$route.query["prompt"] as string) ?? this.prompt;
|
||||||
this.prompt = (this.$route as Router).query["prompt"] ?? this.prompt;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
@@ -3,11 +3,8 @@
|
|||||||
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<div class="text-lg text-center font-light relative px-7">
|
<div class="text-lg text-center font-light relative px-7">
|
||||||
<h1
|
<h1 class="text-lg text-center px-2 py-1 absolute -left-2 -top-1" @click="$router.back()">
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
@click="$router.back()"
|
|
||||||
>
|
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -17,46 +14,31 @@
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div v-if="checkingImports" class="text-center">
|
<div v-if="checkingImports" class="text-center">
|
||||||
<fa icon="spinner" class="animate-spin" />
|
<font-awesome icon="spinner" class="animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<span
|
<span v-if="contactsImporting.length > sameCount" class="flex justify-center">
|
||||||
v-if="contactsImporting.length > sameCount"
|
<input v-model="makeVisible" type="checkbox" class="mr-2" />
|
||||||
class="flex justify-center"
|
|
||||||
>
|
|
||||||
<input type="checkbox" v-model="makeVisible" class="mr-2" />
|
|
||||||
Make my activity visible to these contacts.
|
Make my activity visible to these contacts.
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div v-if="sameCount > 0">
|
<div v-if="sameCount > 0">
|
||||||
<span v-if="sameCount == 1"
|
<span v-if="sameCount == 1">One contact is the same as an existing contact</span>
|
||||||
>One contact is the same as an existing contact</span
|
<span v-else>{{ sameCount }} contacts are the same as existing contacts</span>
|
||||||
>
|
|
||||||
<span v-else
|
|
||||||
>{{ sameCount }} contacts are the same as existing contacts</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Results List -->
|
<!-- Results List -->
|
||||||
<ul
|
<ul v-if="contactsImporting.length > sameCount" class="border-t border-slate-300">
|
||||||
v-if="contactsImporting.length > sameCount"
|
|
||||||
class="border-t border-slate-300"
|
|
||||||
>
|
|
||||||
<li v-for="(contact, index) in contactsImporting" :key="contact.did">
|
<li v-for="(contact, index) in contactsImporting" :key="contact.did">
|
||||||
<div
|
<div v-if="
|
||||||
v-if="
|
!contactsExisting[contact.did] ||
|
||||||
!contactsExisting[contact.did] ||
|
!R.isEmpty(contactDifferences[contact.did])
|
||||||
!R.isEmpty(contactDifferences[contact.did])
|
" class="grow overflow-hidden border-b border-slate-300 pt-2.5 pb-4">
|
||||||
"
|
|
||||||
class="grow overflow-hidden border-b border-slate-300 pt-2.5 pb-4"
|
|
||||||
>
|
|
||||||
<h2 class="text-base font-semibold">
|
<h2 class="text-base font-semibold">
|
||||||
<input type="checkbox" v-model="contactsSelected[index]" />
|
<input v-model="contactsSelected[index]" type="checkbox" />
|
||||||
{{ contact.name || AppString.NO_CONTACT_NAME }}
|
{{ contact.name || AppString.NO_CONTACT_NAME }}
|
||||||
-
|
-
|
||||||
<span v-if="contactsExisting[contact.did]" class="text-orange-500"
|
<span v-if="contactsExisting[contact.did]" class="text-orange-500">Existing</span>
|
||||||
>Existing</span
|
|
||||||
>
|
|
||||||
<span v-else class="text-green-500">New</span>
|
<span v-else class="text-green-500">New</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="text-sm truncate">
|
<div class="text-sm truncate">
|
||||||
@@ -69,13 +51,9 @@
|
|||||||
<div class="font-bold">Old Value</div>
|
<div class="font-bold">Old Value</div>
|
||||||
<div class="font-bold">New Value</div>
|
<div class="font-bold">New Value</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-for="(value, contactField) in contactDifferences[
|
||||||
v-for="(value, contactField) in contactDifferences[
|
contact.did
|
||||||
contact.did
|
]" :key="contactField" class="grid grid-cols-3 border">
|
||||||
]"
|
|
||||||
:key="contactField"
|
|
||||||
class="grid grid-cols-3 border"
|
|
||||||
>
|
|
||||||
<div class="border font-bold p-1">
|
<div class="border font-bold p-1">
|
||||||
{{ capitalizeAndInsertSpacesBeforeCaps(contactField) }}
|
{{ capitalizeAndInsertSpacesBeforeCaps(contactField) }}
|
||||||
</div>
|
</div>
|
||||||
@@ -88,8 +66,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<button
|
<button
|
||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-sm text-white mt-2 px-2 py-1.5 rounded"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-sm text-white mt-2 px-2 py-1.5 rounded"
|
||||||
@click="importContacts"
|
@click="importContacts">
|
||||||
>
|
|
||||||
Import Selected Contacts
|
Import Selected Contacts
|
||||||
</button>
|
</button>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -101,18 +78,10 @@
|
|||||||
get the full text and paste it. (Note that iOS cuts off data in text
|
get the full text and paste it. (Note that iOS cuts off data in text
|
||||||
messages.) Ask the person to send the data a different way, eg. email.
|
messages.) Ask the person to send the data a different way, eg. email.
|
||||||
<div class="mt-4 text-center">
|
<div class="mt-4 text-center">
|
||||||
<textarea
|
<textarea v-model="inputJwt" placeholder="Contact-import data"
|
||||||
v-model="inputJwt"
|
class="mt-4 border-2 border-gray-300 p-2 rounded" cols="30" @input="() => checkContactJwt(inputJwt)" />
|
||||||
placeholder="Contact-import data"
|
|
||||||
class="mt-4 border-2 border-gray-300 p-2 rounded"
|
|
||||||
cols="30"
|
|
||||||
@input="() => checkContactJwt(inputJwt)"
|
|
||||||
/>
|
|
||||||
<br />
|
<br />
|
||||||
<button
|
<button class="ml-2 p-2 bg-blue-500 text-white rounded" @click="() => processContactJwt(inputJwt)">
|
||||||
@click="() => processContactJwt(inputJwt)"
|
|
||||||
class="ml-2 p-2 bg-blue-500 text-white rounded"
|
|
||||||
>
|
|
||||||
Check Import
|
Check Import
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,6 +91,77 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* @file Contact Import View Component
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*
|
||||||
|
* This component handles the import of contacts into the TimeSafari app.
|
||||||
|
* It supports multiple import methods and handles duplicate detection,
|
||||||
|
* contact validation, and visibility settings.
|
||||||
|
*
|
||||||
|
* Import Methods:
|
||||||
|
* 1. Direct URL Query Parameters:
|
||||||
|
* Example: /contact-import?contacts=[{"did":"did:example:123","name":"Alice"}]
|
||||||
|
*
|
||||||
|
* 2. JWT in URL Path:
|
||||||
|
* Example: /contact-import/eyJhbGciOiJFUzI1NksifQ...
|
||||||
|
* - Supports both single and bulk imports
|
||||||
|
* - JWT payload can be either:
|
||||||
|
* a) Array format: { contacts: [{did: "...", name: "..."}, ...] }
|
||||||
|
* b) Single contact: { own: true, did: "...", name: "..." }
|
||||||
|
*
|
||||||
|
* 3. Manual JWT Input:
|
||||||
|
* - Accepts pasted JWT strings
|
||||||
|
* - Validates format and content before processing
|
||||||
|
*
|
||||||
|
* URL Examples:
|
||||||
|
* ```
|
||||||
|
* # Bulk import via query params
|
||||||
|
* /contact-import?contacts=[
|
||||||
|
* {"did":"did:example:123","name":"Alice"},
|
||||||
|
* {"did":"did:example:456","name":"Bob"}
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* # Single contact via JWT
|
||||||
|
* /contact-import/eyJhbGciOiJFUzI1NksifQ.eyJvd24iOnRydWUsImRpZCI6ImRpZDpleGFtcGxlOjEyMyJ9...
|
||||||
|
*
|
||||||
|
* # Bulk import via JWT
|
||||||
|
* /contact-import/eyJhbGciOiJFUzI1NksifQ.eyJjb250YWN0cyI6W3siZGlkIjoiZGlkOmV4YW1wbGU6MTIzIn1dfQ...
|
||||||
|
*
|
||||||
|
* # Redirect to contacts page (single contact)
|
||||||
|
* /contacts?contactJwt=eyJhbGciOiJFUzI1NksifQ...
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Automatic duplicate detection
|
||||||
|
* - Field-by-field comparison for existing contacts
|
||||||
|
* - Batch visibility settings
|
||||||
|
* - Auto-import for single new contacts
|
||||||
|
* - Error handling and validation
|
||||||
|
*
|
||||||
|
* State Management:
|
||||||
|
* - Tracks existing contacts
|
||||||
|
* - Maintains selection state for bulk imports
|
||||||
|
* - Records differences for duplicate contacts
|
||||||
|
* - Manages visibility settings
|
||||||
|
*
|
||||||
|
* Security Considerations:
|
||||||
|
* - JWT validation for imported contacts
|
||||||
|
* - Visibility control per contact
|
||||||
|
* - Error handling for malformed data
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Component usage in router
|
||||||
|
* {
|
||||||
|
* path: "/contact-import/:jwt?",
|
||||||
|
* name: "contact-import",
|
||||||
|
* component: ContactImportView
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @see {@link Contact} for contact data structure
|
||||||
|
* @see {@link setVisibilityUtil} for visibility management
|
||||||
|
*/
|
||||||
|
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
@@ -145,22 +185,75 @@ import {
|
|||||||
import { getContactJwtFromJwtUrl } from "../libs/crypto";
|
import { getContactJwtFromJwtUrl } from "../libs/crypto";
|
||||||
import { decodeEndorserJwt } from "../libs/crypto/vc";
|
import { decodeEndorserJwt } from "../libs/crypto/vc";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact Import View Component
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*
|
||||||
|
* This component handles the secure import of contacts into TimeSafari via JWT tokens.
|
||||||
|
* It supports both single and multiple contact imports with validation and duplicate detection.
|
||||||
|
*
|
||||||
|
* Import Workflows:
|
||||||
|
* 1. JWT in URL Path (/contact-import/[JWT])
|
||||||
|
* - Extracts JWT from path
|
||||||
|
* - Decodes and validates contact data
|
||||||
|
* - Handles both single and multiple contacts
|
||||||
|
*
|
||||||
|
* 2. JWT in Query Parameter (/contacts?contactJwt=[JWT])
|
||||||
|
* - Used for single contact redirects
|
||||||
|
* - Processes JWT from query parameter
|
||||||
|
* - Redirects to appropriate view
|
||||||
|
*
|
||||||
|
* JWT Payload Structure:
|
||||||
|
* ```json
|
||||||
|
* {
|
||||||
|
* "iat": 1740740453,
|
||||||
|
* "contacts": [{
|
||||||
|
* "did": "did:ethr:0x...",
|
||||||
|
* "name": "Optional Name",
|
||||||
|
* "nextPubKeyHashB64": "base64 string",
|
||||||
|
* "publicKeyBase64": "base64 string"
|
||||||
|
* }],
|
||||||
|
* "iss": "did:ethr:0x..."
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Security Features:
|
||||||
|
* - JWT validation
|
||||||
|
* - Issuer verification
|
||||||
|
* - Duplicate detection
|
||||||
|
* - Contact data validation
|
||||||
|
*
|
||||||
|
* @component
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
components: { EntityIcon, OfferDialog, QuickNav },
|
components: { EntityIcon, OfferDialog, QuickNav },
|
||||||
})
|
})
|
||||||
export default class ContactImportView extends Vue {
|
export default class ContactImportView extends Vue {
|
||||||
|
/** Notification function injected by Vue */
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
/** Current route instance */
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
/** Router instance for navigation */
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
|
// Constants
|
||||||
AppString = AppString;
|
AppString = AppString;
|
||||||
capitalizeAndInsertSpacesBeforeCaps = capitalizeAndInsertSpacesBeforeCaps;
|
capitalizeAndInsertSpacesBeforeCaps = capitalizeAndInsertSpacesBeforeCaps;
|
||||||
libsUtil = libsUtil;
|
libsUtil = libsUtil;
|
||||||
R = R;
|
R = R;
|
||||||
|
|
||||||
|
// Component state
|
||||||
|
/** Active user's DID for authentication and visibility settings */
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
|
/** API server URL for backend communication */
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
contactsExisting: Record<string, Contact> = {}; // user's contacts already in the system, keyed by DID
|
/** Map of existing contacts keyed by DID for duplicate detection */
|
||||||
contactsImporting: Array<Contact> = []; // contacts from the import
|
contactsExisting: Record<string, Contact> = {};
|
||||||
contactsSelected: Array<boolean> = []; // whether each contact in contactsImporting is selected
|
/** Array of contacts being imported from JWT */
|
||||||
|
contactsImporting: Array<Contact> = [];
|
||||||
|
/** Selection state for each importing contact */
|
||||||
|
contactsSelected: Array<boolean> = [];
|
||||||
|
/** Differences between existing and importing contacts */
|
||||||
contactDifferences: Record<
|
contactDifferences: Record<
|
||||||
string,
|
string,
|
||||||
Record<
|
Record<
|
||||||
@@ -170,69 +263,117 @@ export default class ContactImportView extends Vue {
|
|||||||
old: string | boolean | Array<ContactMethod> | undefined;
|
old: string | boolean | Array<ContactMethod> | undefined;
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
> = {}; // for existing contacts, it shows the difference between imported and existing contacts for each key
|
> = {};
|
||||||
|
/** Loading state for import operations */
|
||||||
checkingImports = false;
|
checkingImports = false;
|
||||||
|
/** JWT input for manual contact import */
|
||||||
inputJwt: string = "";
|
inputJwt: string = "";
|
||||||
|
/** Visibility setting for imported contacts */
|
||||||
makeVisible = true;
|
makeVisible = true;
|
||||||
|
/** Count of duplicate contacts found */
|
||||||
sameCount = 0;
|
sameCount = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component lifecycle hook that initializes the contact import process
|
||||||
|
*
|
||||||
|
* This method handles three distinct import scenarios:
|
||||||
|
* 1. Query Parameter Import:
|
||||||
|
* - Checks for contacts in URL query parameters
|
||||||
|
* - Parses JSON array of contacts if present
|
||||||
|
*
|
||||||
|
* 2. JWT URL Import:
|
||||||
|
* - Extracts JWT from URL path using regex pattern '/contact-import/(ey.+)$'
|
||||||
|
* - Decodes JWT without validation (supports future-dated QR codes)
|
||||||
|
* - Handles two JWT payload formats:
|
||||||
|
* a. Array format: payload.contacts or direct array
|
||||||
|
* b. Single contact format: redirects to contacts page with JWT
|
||||||
|
*
|
||||||
|
* 3. Auto-Import Logic:
|
||||||
|
* - Automatically imports if exactly one new contact is present
|
||||||
|
* - Only triggers if no existing contacts match
|
||||||
|
*
|
||||||
|
* @throws Will not throw but logs errors during JWT processing
|
||||||
|
* @emits router.push when redirecting for single contact import
|
||||||
|
*/
|
||||||
async created() {
|
async created() {
|
||||||
|
await this.initializeSettings();
|
||||||
|
await this.processQueryParams();
|
||||||
|
await this.processJwtFromPath();
|
||||||
|
await this.handleAutoImport();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes component settings from active account
|
||||||
|
*/
|
||||||
|
private async initializeSettings() {
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const settings = await retrieveSettingsForActiveAccount();
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
|
}
|
||||||
|
|
||||||
// look for any imported contact array from the query parameter
|
/**
|
||||||
const importedContacts = (this.$route as RouteLocationNormalizedLoaded)
|
* Processes contacts from URL query parameters
|
||||||
.query["contacts"] as string;
|
*/
|
||||||
|
private async processQueryParams() {
|
||||||
|
const importedContacts = this.$route.query["contacts"] as string;
|
||||||
if (importedContacts) {
|
if (importedContacts) {
|
||||||
await this.setContactsSelected(JSON.parse(importedContacts));
|
await this.setContactsSelected(JSON.parse(importedContacts));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// look for a JWT after /contact-import/ in the window.location.pathname
|
/**
|
||||||
const jwt = window.location.pathname.match(
|
* Processes JWT from URL path and handles different JWT formats
|
||||||
/\/contact-import\/(ey.+)$/,
|
*/
|
||||||
)?.[1];
|
private async processJwtFromPath() {
|
||||||
|
// JWT tokens always start with 'ey' (base64url encoded header)
|
||||||
|
const JWT_PATTERN = /\/contact-import\/(ey.+)$/;
|
||||||
|
const jwt = window.location.pathname.match(JWT_PATTERN)?.[1];
|
||||||
|
|
||||||
if (jwt) {
|
if (jwt) {
|
||||||
// would prefer to validate but we've got an error with JWTs on QR codes generated in the future
|
|
||||||
// eslint-disable-next-line prettier/prettier
|
|
||||||
// const parsedJwt: Omit<JWTVerified, "didResolutionResult" | "signer" | "jwt"> = await decodeAndVerifyJwt(jwt);
|
|
||||||
// decode the JWT
|
|
||||||
const parsedJwt = decodeEndorserJwt(jwt);
|
const parsedJwt = decodeEndorserJwt(jwt);
|
||||||
|
|
||||||
const contacts: Array<Contact> =
|
const contacts: Array<Contact> =
|
||||||
parsedJwt.payload.contacts || // someday this will be the only payload sent to this page
|
parsedJwt.payload.contacts ||
|
||||||
(Array.isArray(parsedJwt.payload) ? parsedJwt.payload : undefined);
|
(Array.isArray(parsedJwt.payload) ? parsedJwt.payload : undefined);
|
||||||
|
|
||||||
if (!contacts && parsedJwt.payload.own) {
|
if (!contacts && parsedJwt.payload.own) {
|
||||||
// handle this single-contact JWT in the contacts page, better suited to single additions
|
this.$router.push({
|
||||||
(this.$router as Router).push({
|
|
||||||
name: "contacts",
|
name: "contacts",
|
||||||
query: { contactJwt: jwt },
|
query: { contactJwt: jwt },
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contacts) {
|
if (contacts) {
|
||||||
await this.setContactsSelected(contacts);
|
await this.setContactsSelected(contacts);
|
||||||
} else {
|
|
||||||
// no contacts found so default message should be OK
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles automatic import for single new contacts
|
||||||
|
*/
|
||||||
|
private async handleAutoImport() {
|
||||||
if (
|
if (
|
||||||
this.contactsImporting.length === 1 &&
|
this.contactsImporting.length === 1 &&
|
||||||
R.isEmpty(this.contactsExisting)
|
R.isEmpty(this.contactsExisting)
|
||||||
) {
|
) {
|
||||||
// if there is only one contact and it's new, then we will automatically import it
|
|
||||||
this.contactsSelected[0] = true;
|
this.contactsSelected[0] = true;
|
||||||
this.importContacts(); // ... which routes to the contacts list
|
await this.importContacts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes contacts for import and checks for duplicates
|
||||||
|
* @param contacts Array of contacts to process
|
||||||
|
*/
|
||||||
async setContactsSelected(contacts: Array<Contact>) {
|
async setContactsSelected(contacts: Array<Contact>) {
|
||||||
this.contactsImporting = contacts;
|
this.contactsImporting = contacts;
|
||||||
this.contactsSelected = new Array(this.contactsImporting.length).fill(true);
|
this.contactsSelected = new Array(this.contactsImporting.length).fill(true);
|
||||||
|
|
||||||
await db.open();
|
await db.open();
|
||||||
const baseContacts = await db.contacts.toArray();
|
const baseContacts = await db.contacts.toArray();
|
||||||
// set the existing contacts, keyed by DID, if they exist in contactsImporting
|
|
||||||
|
// Check for existing contacts and differences
|
||||||
for (let i = 0; i < this.contactsImporting.length; i++) {
|
for (let i = 0; i < this.contactsImporting.length; i++) {
|
||||||
const contactIn = this.contactsImporting[i];
|
const contactIn = this.contactsImporting[i];
|
||||||
const existingContact = baseContacts.find(
|
const existingContact = baseContacts.find(
|
||||||
@@ -241,6 +382,7 @@ export default class ContactImportView extends Vue {
|
|||||||
if (existingContact) {
|
if (existingContact) {
|
||||||
this.contactsExisting[contactIn.did] = existingContact;
|
this.contactsExisting[contactIn.did] = existingContact;
|
||||||
|
|
||||||
|
// Compare contact fields for differences
|
||||||
const differences: Record<
|
const differences: Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
@@ -249,7 +391,6 @@ export default class ContactImportView extends Vue {
|
|||||||
}
|
}
|
||||||
> = {};
|
> = {};
|
||||||
Object.keys(contactIn).forEach((key) => {
|
Object.keys(contactIn).forEach((key) => {
|
||||||
// eslint-disable-next-line prettier/prettier
|
|
||||||
if (!R.equals(contactIn[key as keyof Contact], existingContact[key as keyof Contact])) {
|
if (!R.equals(contactIn[key as keyof Contact], existingContact[key as keyof Contact])) {
|
||||||
differences[key] = {
|
differences[key] = {
|
||||||
old: existingContact[key as keyof Contact],
|
old: existingContact[key as keyof Contact],
|
||||||
@@ -262,13 +403,16 @@ export default class ContactImportView extends Vue {
|
|||||||
this.sameCount++;
|
this.sameCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't automatically import previous data
|
// Don't auto-select duplicates
|
||||||
this.contactsSelected[i] = false;
|
this.contactsSelected[i] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the contact-import JWT
|
/**
|
||||||
|
* Validates contact import JWT format
|
||||||
|
* @param jwtInput JWT string to validate
|
||||||
|
*/
|
||||||
async checkContactJwt(jwtInput: string) {
|
async checkContactJwt(jwtInput: string) {
|
||||||
if (
|
if (
|
||||||
jwtInput.endsWith(APP_SERVER) ||
|
jwtInput.endsWith(APP_SERVER) ||
|
||||||
@@ -288,14 +432,15 @@ export default class ContactImportView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// process the invite JWT and/or text message containing the URL with the JWT
|
/**
|
||||||
|
* Processes contact import JWT and updates contacts
|
||||||
|
* @param jwtInput JWT string containing contact data
|
||||||
|
*/
|
||||||
async processContactJwt(jwtInput: string) {
|
async processContactJwt(jwtInput: string) {
|
||||||
this.checkingImports = true;
|
this.checkingImports = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// (For another approach used with invites, see InviteOneAcceptView.processInvite)
|
|
||||||
const jwt: string = getContactJwtFromJwtUrl(jwtInput);
|
const jwt: string = getContactJwtFromJwtUrl(jwtInput);
|
||||||
// JWT format: { header, payload, signature, data }
|
|
||||||
const payload = decodeEndorserJwt(jwt).payload;
|
const payload = decodeEndorserJwt(jwt).payload;
|
||||||
|
|
||||||
if (Array.isArray(payload.contacts)) {
|
if (Array.isArray(payload.contacts)) {
|
||||||
@@ -319,10 +464,16 @@ export default class ContactImportView extends Vue {
|
|||||||
this.checkingImports = false;
|
this.checkingImports = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports selected contacts and sets visibility if requested
|
||||||
|
* Updates existing contacts or adds new ones
|
||||||
|
*/
|
||||||
async importContacts() {
|
async importContacts() {
|
||||||
this.checkingImports = true;
|
this.checkingImports = true;
|
||||||
let importedCount = 0,
|
let importedCount = 0,
|
||||||
updatedCount = 0;
|
updatedCount = 0;
|
||||||
|
|
||||||
|
// Process selected contacts
|
||||||
for (let i = 0; i < this.contactsImporting.length; i++) {
|
for (let i = 0; i < this.contactsImporting.length; i++) {
|
||||||
if (this.contactsSelected[i]) {
|
if (this.contactsSelected[i]) {
|
||||||
const contact = this.contactsImporting[i];
|
const contact = this.contactsImporting[i];
|
||||||
@@ -338,6 +489,8 @@ export default class ContactImportView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set visibility if requested
|
||||||
if (this.makeVisible) {
|
if (this.makeVisible) {
|
||||||
const failedVisibileToContacts = [];
|
const failedVisibileToContacts = [];
|
||||||
for (let i = 0; i < this.contactsImporting.length; i++) {
|
for (let i = 0; i < this.contactsImporting.length; i++) {
|
||||||
@@ -364,9 +517,8 @@ export default class ContactImportView extends Vue {
|
|||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Visibility Error",
|
title: "Visibility Error",
|
||||||
text: `Failed to set visibility for ${failedVisibileToContacts.length} contact${
|
text: `Failed to set visibility for ${failedVisibileToContacts.length} contact${failedVisibileToContacts.length == 1 ? "" : "s"
|
||||||
failedVisibileToContacts.length == 1 ? "" : "s"
|
}. You must set them individually: ${failedVisibileToContacts.map((c) => c.name).join(", ")}`,
|
||||||
}. You must set them individually: ${failedVisibileToContacts.map((c) => c.name).join(", ")}`,
|
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -375,6 +527,7 @@ export default class ContactImportView extends Vue {
|
|||||||
|
|
||||||
this.checkingImports = false;
|
this.checkingImports = false;
|
||||||
|
|
||||||
|
// Show success notification
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -386,7 +539,7 @@ export default class ContactImportView extends Vue {
|
|||||||
},
|
},
|
||||||
3000,
|
3000,
|
||||||
);
|
);
|
||||||
(this.$router as Router).push({ name: "contacts" });
|
this.$router.push({ name: "contacts" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
@click="$router.back()"
|
@click="$router.back()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw" />
|
<font-awesome icon="chevron-left" class="fa-fw" />
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -26,10 +26,8 @@
|
|||||||
You aren't sharing your name, so quickly
|
You aren't sharing your name, so quickly
|
||||||
<br />
|
<br />
|
||||||
<span
|
<span
|
||||||
@click="
|
|
||||||
() => $refs.userNameDialog.open((name) => (this.givenName = name))
|
|
||||||
"
|
|
||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 rounded-md"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 rounded-md"
|
||||||
|
@click="() => $refs.userNameDialog.open((name) => (givenName = name))"
|
||||||
>
|
>
|
||||||
click here to set it for them.
|
click here to set it for them.
|
||||||
</span>
|
</span>
|
||||||
@@ -38,18 +36,18 @@
|
|||||||
<UserNameDialog ref="userNameDialog" />
|
<UserNameDialog ref="userNameDialog" />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@click="onCopyUrlToClipboard()"
|
|
||||||
v-if="activeDid && activeDid.startsWith(ETHR_DID_PREFIX)"
|
v-if="activeDid && activeDid.startsWith(ETHR_DID_PREFIX)"
|
||||||
class="text-center"
|
class="text-center"
|
||||||
|
@click="onCopyUrlToClipboard()"
|
||||||
>
|
>
|
||||||
<!--
|
<!--
|
||||||
Play with display options: https://qr-code-styling.com/
|
Play with display options: https://qr-code-styling.com/
|
||||||
See docs: https://www.npmjs.com/package/qr-code-generator-vue3
|
See docs: https://www.npmjs.com/package/qr-code-generator-vue3
|
||||||
-->
|
-->
|
||||||
<QRCodeVue3
|
<QRCodeVue3
|
||||||
:value="this.qrValue"
|
:value="qrValue"
|
||||||
:cornersSquareOptions="{ type: 'extra-rounded' }"
|
:corners-square-options="{ type: 'extra-rounded' }"
|
||||||
:dotsOptions="{ type: 'square' }"
|
:dots-options="{ type: 'square' }"
|
||||||
class="flex justify-center"
|
class="flex justify-center"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
@@ -58,14 +56,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else-if="activeDid" class="text-center">
|
<div v-else-if="activeDid" class="text-center">
|
||||||
<!-- Not an ETHR DID so force them to paste it. (Passkey Peer DIDs are too big.) -->
|
<!-- Not an ETHR DID so force them to paste it. (Passkey Peer DIDs are too big.) -->
|
||||||
<span @click="onCopyDidToClipboard()" class="text-blue-500">
|
<span class="text-blue-500" @click="onCopyDidToClipboard()">
|
||||||
Click here to copy your DID to your clipboard.
|
Click here to copy your DID to your clipboard.
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
Then give it to them so they can paste it in their list of People.
|
Then give it to them so they can paste it in their list of People.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center" v-else>
|
<div v-else class="text-center">
|
||||||
You have no identitifiers yet, so
|
You have no identitifiers yet, so
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'start' }"
|
:to="{ name: 'start' }"
|
||||||
@@ -110,6 +108,7 @@ import {
|
|||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import { decodeEndorserJwt, ETHR_DID_PREFIX } from "../libs/crypto/vc";
|
import { decodeEndorserJwt, ETHR_DID_PREFIX } from "../libs/crypto/vc";
|
||||||
import { retrieveAccountMetadata } from "../libs/util";
|
import { retrieveAccountMetadata } from "../libs/util";
|
||||||
|
import { Router } from "vue-router";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -121,6 +120,7 @@ import { retrieveAccountMetadata } from "../libs/util";
|
|||||||
})
|
})
|
||||||
export default class ContactQRScanShow extends Vue {
|
export default class ContactQRScanShow extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'account' }"
|
:to="{ name: 'account' }"
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
><fa icon="chevron-left" class="fa-fw"></fa
|
><font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
></router-link>
|
</router-link>
|
||||||
|
|
||||||
Scan Contact
|
Scan Contact
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -23,26 +23,26 @@
|
|||||||
|
|
||||||
<!-- New Contact -->
|
<!-- New Contact -->
|
||||||
<div id="formAddNewContact" class="mt-4 mb-4 flex items-stretch">
|
<div id="formAddNewContact" class="mt-4 mb-4 flex items-stretch">
|
||||||
<span class="flex" v-if="isRegistered">
|
<span v-if="isRegistered" class="flex">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'invite-one' }"
|
:to="{ name: 'invite-one' }"
|
||||||
class="flex items-center bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 mr-1 rounded-md"
|
class="flex items-center bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 mr-1 rounded-md"
|
||||||
>
|
>
|
||||||
<fa icon="envelope-open-text" class="fa-fw text-2xl" />
|
<font-awesome icon="envelope-open-text" class="fa-fw text-2xl" />
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="showOnboardMeetingDialog()"
|
|
||||||
class="flex items-center bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 mr-1 rounded-md"
|
class="flex items-center bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 mr-1 rounded-md"
|
||||||
|
@click="showOnboardMeetingDialog()"
|
||||||
>
|
>
|
||||||
<fa icon="chair" class="fa-fw text-2xl" />
|
<font-awesome icon="chair" class="fa-fw text-2xl" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="flex">
|
<span v-else class="flex">
|
||||||
<span
|
<span
|
||||||
class="flex items-center bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 mr-1 rounded-md"
|
class="flex items-center bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 mr-1 rounded-md"
|
||||||
>
|
>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="envelope-open-text"
|
icon="envelope-open-text"
|
||||||
class="fa-fw text-2xl"
|
class="fa-fw text-2xl"
|
||||||
@click="
|
@click="
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
<span
|
<span
|
||||||
class="flex items-center bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 mr-1 rounded-md"
|
class="flex items-center bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 mr-1 rounded-md"
|
||||||
>
|
>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="chair"
|
icon="chair"
|
||||||
class="fa-fw text-2xl"
|
class="fa-fw text-2xl"
|
||||||
@click="
|
@click="
|
||||||
@@ -73,53 +73,56 @@
|
|||||||
:to="{ name: 'contact-qr' }"
|
:to="{ name: 'contact-qr' }"
|
||||||
class="flex items-center bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 mr-1 rounded-md"
|
class="flex items-center bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 mr-1 rounded-md"
|
||||||
>
|
>
|
||||||
<fa icon="qrcode" class="fa-fw text-2xl" />
|
<font-awesome icon="qrcode" class="fa-fw text-2xl" />
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
|
v-model="contactInput"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="New URL or DID, Name, Public Key, Next Public Key Hash"
|
placeholder="New URL or DID, Name, Public Key, Next Public Key Hash"
|
||||||
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2 h-10"
|
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2 h-10"
|
||||||
v-model="contactInput"
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="px-4 rounded-r bg-green-200 border border-l-0 border-green-400"
|
class="px-4 rounded-r bg-green-200 border border-l-0 border-green-400"
|
||||||
@click="onClickNewContact()"
|
@click="onClickNewContact()"
|
||||||
>
|
>
|
||||||
<fa icon="plus" class="fa-fw" />
|
<font-awesome icon="plus" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-between" v-if="contacts.length > 0">
|
<div v-if="contacts.length > 0" class="flex justify-between">
|
||||||
<div class="w-full text-left">
|
<div class="w-full text-left">
|
||||||
<div v-if="!showGiveNumbers">
|
<div v-if="!showGiveNumbers">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:checked="contactsSelected.length === contacts.length"
|
:checked="contactsSelected.length === contacts.length"
|
||||||
|
class="align-middle ml-2 h-6 w-6"
|
||||||
|
data-testId="contactCheckAllTop"
|
||||||
@click="
|
@click="
|
||||||
contactsSelected.length === contacts.length
|
contactsSelected.length === contacts.length
|
||||||
? (contactsSelected = [])
|
? (contactsSelected = [])
|
||||||
: (contactsSelected = contacts.map((contact) => contact.did))
|
: (contactsSelected = contacts.map((contact) => contact.did))
|
||||||
"
|
"
|
||||||
class="align-middle ml-2 h-6 w-6"
|
|
||||||
data-testId="contactCheckAllTop"
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
v-if="!showGiveNumbers"
|
||||||
href=""
|
href=""
|
||||||
class="text-md bg-gradient-to-b shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-3 px-3 py-1.5 rounded-md"
|
class="text-md bg-gradient-to-b shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-2 px-1 py-1 rounded-md"
|
||||||
:style="
|
:style="
|
||||||
contactsSelected.length > 0
|
contactsSelected.length > 0
|
||||||
? 'background-image: linear-gradient(to bottom, #3b82f6, #1e40af);'
|
? 'background-image: linear-gradient(to bottom, #3b82f6, #1e40af);'
|
||||||
: 'background-image: linear-gradient(to bottom, #94a3b8, #374151);'
|
: 'background-image: linear-gradient(to bottom, #94a3b8, #374151);'
|
||||||
"
|
"
|
||||||
@click="copySelectedContacts()"
|
|
||||||
v-if="!showGiveNumbers"
|
|
||||||
data-testId="copySelectedContactsButtonTop"
|
data-testId="copySelectedContactsButtonTop"
|
||||||
|
@click="copySelectedContacts()"
|
||||||
>
|
>
|
||||||
Copy Selections
|
Copy Selections
|
||||||
</button>
|
</button>
|
||||||
<button @click="showCopySelectionsInfo()">
|
<button @click="showCopySelectionsInfo()">
|
||||||
<fa icon="circle-info" class="text-xl text-blue-500 ml-4" />
|
<font-awesome
|
||||||
|
icon="circle-info"
|
||||||
|
class="text-xl text-blue-500 ml-4"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,7 +130,7 @@
|
|||||||
<div class="w-full text-right">
|
<div class="w-full text-right">
|
||||||
<button
|
<button
|
||||||
href=""
|
href=""
|
||||||
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
|
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 py-1 rounded-md"
|
||||||
@click="toggleShowContactAmounts()"
|
@click="toggleShowContactAmounts()"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
@@ -136,20 +139,20 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between mt-1" v-if="showGiveNumbers">
|
<div v-if="showGiveNumbers" class="flex justify-between mt-1">
|
||||||
<div class="w-full text-right">
|
<div class="w-full text-right">
|
||||||
In the following, only the most recent hours are included. To see more,
|
In the following, only the most recent hours are included. To see more,
|
||||||
click
|
click
|
||||||
<span
|
<span
|
||||||
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 py-1 rounded-md"
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 py-1 rounded-md"
|
||||||
>
|
>
|
||||||
<fa icon="file-lines" class="fa-fw" />
|
<font-awesome icon="file-lines" class="fa-fw" />
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<button
|
<button
|
||||||
href=""
|
href=""
|
||||||
class="text-md bg-gradient-to-b shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 rounded-md mt-1"
|
class="text-md bg-gradient-to-b shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 rounded-md mt-1"
|
||||||
v-bind:class="showGiveAmountsClassNames()"
|
:class="showGiveAmountsClassNames()"
|
||||||
@click="toggleShowGiveTotals()"
|
@click="toggleShowGiveTotals()"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
@@ -159,29 +162,38 @@
|
|||||||
? "Confirmed Amounts"
|
? "Confirmed Amounts"
|
||||||
: "Unconfirmed Amounts"
|
: "Unconfirmed Amounts"
|
||||||
}}
|
}}
|
||||||
<fa icon="left-right" class="fa-fw" />
|
<font-awesome icon="left-right" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Results List -->
|
<!-- Results List -->
|
||||||
<ul
|
<ul
|
||||||
id="listContacts"
|
|
||||||
v-if="contacts.length > 0"
|
v-if="contacts.length > 0"
|
||||||
|
id="listContacts"
|
||||||
class="border-t border-slate-300 mt-1"
|
class="border-t border-slate-300 mt-1"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
class="border-b border-slate-300 pt-1 pb-1"
|
|
||||||
v-for="contact in filteredContacts()"
|
v-for="contact in filteredContacts()"
|
||||||
:key="contact.did"
|
:key="contact.did"
|
||||||
|
class="border-b border-slate-300 pt-1 pb-1"
|
||||||
data-testId="contactListItem"
|
data-testId="contactListItem"
|
||||||
>
|
>
|
||||||
<div class="grow overflow-hidden">
|
<div class="grow overflow-hidden">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center">
|
||||||
|
<EntityIcon
|
||||||
|
:contact="contact"
|
||||||
|
:icon-size="24"
|
||||||
|
class="inline-block align-text-bottom border border-slate-300 rounded cursor-pointer"
|
||||||
|
@click="showLargeIdenticon = contact"
|
||||||
|
/>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-if="!showGiveNumbers"
|
v-if="!showGiveNumbers"
|
||||||
|
type="checkbox"
|
||||||
:checked="contactsSelected.includes(contact.did)"
|
:checked="contactsSelected.includes(contact.did)"
|
||||||
|
class="ml-2 h-6 w-6 flex-shrink-0"
|
||||||
|
data-testId="contactCheckOne"
|
||||||
@click="
|
@click="
|
||||||
contactsSelected.includes(contact.did)
|
contactsSelected.includes(contact.did)
|
||||||
? contactsSelected.splice(
|
? contactsSelected.splice(
|
||||||
@@ -190,130 +202,128 @@
|
|||||||
)
|
)
|
||||||
: contactsSelected.push(contact.did)
|
: contactsSelected.push(contact.did)
|
||||||
"
|
"
|
||||||
class="ml-2 h-6 w-6 flex-shrink-0"
|
|
||||||
data-testId="contactCheckOne"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EntityIcon
|
<h2
|
||||||
:contact="contact"
|
class="text-base font-semibold ml-2 w-1/3 truncate flex-shrink-0"
|
||||||
:iconSize="48"
|
>
|
||||||
class="inline-block align-text-bottom border border-slate-300 rounded cursor-pointer overflow-hidden"
|
|
||||||
@click="showLargeIdenticon = contact"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<h2 class="text-base font-semibold w-1/3 truncate flex-shrink-0">
|
|
||||||
{{ contactNameNonBreakingSpace(contact.name) }}
|
{{ contactNameNonBreakingSpace(contact.name) }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
<div class="flex gap-2 items-center">
|
<div class="flex items-center">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
path: '/did/' + encodeURIComponent(contact.did),
|
path: '/did/' + encodeURIComponent(contact.did),
|
||||||
}"
|
}"
|
||||||
title="See more about this person"
|
title="See more about this person"
|
||||||
>
|
>
|
||||||
<fa icon="circle-info" class="text-xl text-blue-500" />
|
<font-awesome
|
||||||
|
icon="circle-info"
|
||||||
|
class="text-xl text-blue-500 ml-4"
|
||||||
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<span class="text-sm overflow-hidden">{{
|
<span class="ml-4 text-sm overflow-hidden">{{
|
||||||
libsUtil.shortDid(contact.did)
|
libsUtil.shortDid(contact.did)
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm">
|
<div class="ml-4 text-sm">
|
||||||
{{ contact.notes }}
|
{{ contact.notes }}
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div id="ContactActions" class="flex gap-1.5 mt-2">
|
||||||
v-if="showGiveNumbers && contact.did != activeDid"
|
<div
|
||||||
class="ml-auto flex gap-1.5 mt-2"
|
v-if="showGiveNumbers && contact.did != activeDid"
|
||||||
>
|
class="ml-auto flex gap-1.5"
|
||||||
<button
|
|
||||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-l-md"
|
|
||||||
@click="confirmShowGiftedDialog(contact.did, activeDid)"
|
|
||||||
:title="givenToMeDescriptions[contact.did] || ''"
|
|
||||||
>
|
>
|
||||||
From:
|
<button
|
||||||
<br />
|
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-l-md"
|
||||||
{{
|
:title="givenToMeDescriptions[contact.did] || ''"
|
||||||
/* eslint-disable prettier/prettier */
|
@click="confirmShowGiftedDialog(contact.did, activeDid)"
|
||||||
this.showGiveTotals
|
>
|
||||||
? ((givenToMeConfirmed[contact.did] || 0)
|
From:
|
||||||
+ (givenToMeUnconfirmed[contact.did] || 0))
|
<br />
|
||||||
: this.showGiveConfirmed
|
{{
|
||||||
? (givenToMeConfirmed[contact.did] || 0)
|
/* eslint-disable prettier/prettier */
|
||||||
: (givenToMeUnconfirmed[contact.did] || 0)
|
showGiveTotals
|
||||||
/* eslint-enable prettier/prettier */
|
? ((givenToMeConfirmed[contact.did] || 0)
|
||||||
}}
|
+ (givenToMeUnconfirmed[contact.did] || 0))
|
||||||
</button>
|
: showGiveConfirmed
|
||||||
|
? (givenToMeConfirmed[contact.did] || 0)
|
||||||
|
: (givenToMeUnconfirmed[contact.did] || 0)
|
||||||
|
/* eslint-enable prettier/prettier */
|
||||||
|
}}
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white -ml-1.5 px-2 py-1.5 rounded-r-md border-l"
|
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white -ml-1.5 px-2 py-1.5 rounded-r-md border-l"
|
||||||
@click="confirmShowGiftedDialog(activeDid, contact.did)"
|
:title="givenByMeDescriptions[contact.did] || ''"
|
||||||
:title="givenByMeDescriptions[contact.did] || ''"
|
@click="confirmShowGiftedDialog(activeDid, contact.did)"
|
||||||
>
|
>
|
||||||
To:
|
To:
|
||||||
<br />
|
<br />
|
||||||
{{
|
{{
|
||||||
/* eslint-disable prettier/prettier */
|
/* eslint-disable prettier/prettier */
|
||||||
this.showGiveTotals
|
showGiveTotals
|
||||||
? ((givenByMeConfirmed[contact.did] || 0)
|
? ((givenByMeConfirmed[contact.did] || 0)
|
||||||
+ (givenByMeUnconfirmed[contact.did] || 0))
|
+ (givenByMeUnconfirmed[contact.did] || 0))
|
||||||
: this.showGiveConfirmed
|
: showGiveConfirmed
|
||||||
? (givenByMeConfirmed[contact.did] || 0)
|
? (givenByMeConfirmed[contact.did] || 0)
|
||||||
: (givenByMeUnconfirmed[contact.did] || 0)
|
: (givenByMeUnconfirmed[contact.did] || 0)
|
||||||
/* eslint-enable prettier/prettier */
|
/* eslint-enable prettier/prettier */
|
||||||
}}
|
}}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md border border-blue-400"
|
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md border border-blue-400"
|
||||||
@click="openOfferDialog(contact.did, contact.name)"
|
data-testId="offerButton"
|
||||||
data-testId="offerButton"
|
@click="openOfferDialog(contact.did, contact.name)"
|
||||||
>
|
>
|
||||||
Offer
|
Offer
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
name: 'contact-amounts',
|
name: 'contact-amounts',
|
||||||
query: { contactDid: contact.did },
|
query: { contactDid: contact.did },
|
||||||
}"
|
}"
|
||||||
class="text-sm bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md border border-slate-400"
|
class="text-sm bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md border border-slate-400"
|
||||||
title="See more given activity"
|
title="See more given activity"
|
||||||
>
|
>
|
||||||
<fa icon="file-lines" class="fa-fw" />
|
<font-awesome icon="file-lines" class="fa-fw" />
|
||||||
</router-link>
|
</router-link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p v-else>There are no contacts.</p>
|
<p v-else>There are no contacts.</p>
|
||||||
|
|
||||||
<div class="mt-2 w-full text-left" v-if="contacts.length > 0">
|
<div v-if="contacts.length > 0" class="mt-2 w-full text-left">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-if="!showGiveNumbers"
|
v-if="!showGiveNumbers"
|
||||||
|
type="checkbox"
|
||||||
:checked="contactsSelected.length === contacts.length"
|
:checked="contactsSelected.length === contacts.length"
|
||||||
|
class="align-middle ml-2 h-6 w-6"
|
||||||
|
data-testId="contactCheckAllBottom"
|
||||||
@click="
|
@click="
|
||||||
contactsSelected.length === contacts.length
|
contactsSelected.length === contacts.length
|
||||||
? (contactsSelected = [])
|
? (contactsSelected = [])
|
||||||
: (contactsSelected = contacts.map((contact) => contact.did))
|
: (contactsSelected = contacts.map((contact) => contact.did))
|
||||||
"
|
"
|
||||||
class="align-middle ml-2 h-6 w-6"
|
|
||||||
data-testId="contactCheckAllBottom"
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
v-if="!showGiveNumbers"
|
||||||
href=""
|
href=""
|
||||||
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-3 px-3 py-1.5 rounded-md"
|
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-2 px-1 py-1 rounded-md"
|
||||||
:style="
|
:style="
|
||||||
contactsSelected.length > 0
|
contactsSelected.length > 0
|
||||||
? 'background-image: linear-gradient(to bottom, #3b82f6, #1e40af);'
|
? 'background-image: linear-gradient(to bottom, #3b82f6, #1e40af);'
|
||||||
: 'background-image: linear-gradient(to bottom, #94a3b8, #374151);'
|
: 'background-image: linear-gradient(to bottom, #94a3b8, #374151);'
|
||||||
"
|
"
|
||||||
@click="copySelectedContacts()"
|
@click="copySelectedContacts()"
|
||||||
v-if="!showGiveNumbers"
|
|
||||||
>
|
>
|
||||||
Copy Selections
|
Copy Selections
|
||||||
</button>
|
</button>
|
||||||
@@ -329,7 +339,7 @@
|
|||||||
>
|
>
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:contact="showLargeIdenticon"
|
:contact="showLargeIdenticon"
|
||||||
:iconSize="512"
|
:icon-size="512"
|
||||||
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
||||||
@click="showLargeIdenticon = undefined"
|
@click="showLargeIdenticon = undefined"
|
||||||
/>
|
/>
|
||||||
@@ -395,6 +405,8 @@ import { generateSaveAndActivateIdentity } from "../libs/util";
|
|||||||
})
|
})
|
||||||
export default class ContactsView extends Vue {
|
export default class ContactsView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
@@ -461,8 +473,7 @@ export default class ContactsView extends Vue {
|
|||||||
//
|
//
|
||||||
// For external links, use /contact-import/:jwt with a JWT that has an array of contacts
|
// For external links, use /contact-import/:jwt with a JWT that has an array of contacts
|
||||||
// because that will do better error checking for things like missing data on iOS platforms.
|
// because that will do better error checking for things like missing data on iOS platforms.
|
||||||
const importedContactJwt = (this.$route as RouteLocationNormalizedLoaded)
|
const importedContactJwt = this.$route.query["contactJwt"] as string;
|
||||||
.query["contactJwt"] as string;
|
|
||||||
if (importedContactJwt) {
|
if (importedContactJwt) {
|
||||||
// really should fully verify contents
|
// really should fully verify contents
|
||||||
const { payload } = decodeEndorserJwt(importedContactJwt);
|
const { payload } = decodeEndorserJwt(importedContactJwt);
|
||||||
@@ -477,14 +488,13 @@ export default class ContactsView extends Vue {
|
|||||||
} as Contact;
|
} as Contact;
|
||||||
await this.addContact(newContact);
|
await this.addContact(newContact);
|
||||||
// if we're here, they haven't redirected anywhere, so we'll redirect here without a query parameter
|
// if we're here, they haven't redirected anywhere, so we'll redirect here without a query parameter
|
||||||
(this.$router as Router).push({ path: "/contacts" });
|
this.$router.push({ path: "/contacts" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async processInviteJwt() {
|
private async processInviteJwt() {
|
||||||
// handle an invite JWT sent via URL
|
// handle an invite JWT sent via URL
|
||||||
const importedInviteJwt = (this.$route as RouteLocationNormalizedLoaded)
|
const importedInviteJwt = this.$route.query["inviteJwt"] as string;
|
||||||
.query["inviteJwt"] as string;
|
|
||||||
if (importedInviteJwt === "") {
|
if (importedInviteJwt === "") {
|
||||||
// this happens when a platform (eg iOS) doesn't include anything after the "=" in a shared link.
|
// this happens when a platform (eg iOS) doesn't include anything after the "=" in a shared link.
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -586,7 +596,7 @@ export default class ContactsView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// if we're here, they haven't redirected anywhere, so we'll redirect here without a query parameter
|
// if we're here, they haven't redirected anywhere, so we'll redirect here without a query parameter
|
||||||
(this.$router as Router).push({ path: "/contacts" });
|
this.$router.push({ path: "/contacts" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,7 +636,7 @@ export default class ContactsView extends Vue {
|
|||||||
title: "They're Added To Your List",
|
title: "They're Added To Your List",
|
||||||
text: "Would you like to go to the main page now?",
|
text: "Would you like to go to the main page now?",
|
||||||
onYes: async () => {
|
onYes: async () => {
|
||||||
(this.$router as Router).push({ name: "home" });
|
this.$router.push({ name: "home" });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
@@ -763,9 +773,7 @@ export default class ContactsView extends Vue {
|
|||||||
|
|
||||||
if (contactInput.includes(CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI)) {
|
if (contactInput.includes(CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI)) {
|
||||||
const jwt = getContactJwtFromJwtUrl(contactInput);
|
const jwt = getContactJwtFromJwtUrl(contactInput);
|
||||||
(this.$router as Router).push({
|
this.$router.push({ path: "/contact-import/" + jwt });
|
||||||
path: "/contact-import/" + jwt,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -873,7 +881,7 @@ export default class ContactsView extends Vue {
|
|||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const contacts = JSON.parse(jsonContactInput);
|
const contacts = JSON.parse(jsonContactInput);
|
||||||
(this.$router as Router).push({
|
this.$router.push({
|
||||||
name: "contact-import",
|
name: "contact-import",
|
||||||
query: { contacts: JSON.stringify(contacts) },
|
query: { contacts: JSON.stringify(contacts) },
|
||||||
});
|
});
|
||||||
@@ -1199,7 +1207,7 @@ export default class ContactsView extends Vue {
|
|||||||
this.showGiftedDialog(giverDid, recipientDid);
|
this.showGiftedDialog(giverDid, recipientDid);
|
||||||
},
|
},
|
||||||
onYes: async () => {
|
onYes: async () => {
|
||||||
(this.$router as Router).push({
|
this.$router.push({
|
||||||
name: "contact-amounts",
|
name: "contact-amounts",
|
||||||
query: { contactDid: giverDid },
|
query: { contactDid: giverDid },
|
||||||
});
|
});
|
||||||
@@ -1399,10 +1407,10 @@ export default class ContactsView extends Vue {
|
|||||||
|
|
||||||
if (hostResponse.data.data) {
|
if (hostResponse.data.data) {
|
||||||
// They're the host, take them to setup
|
// They're the host, take them to setup
|
||||||
(this.$router as Router).push({ name: "onboard-meeting-setup" });
|
this.$router.push({ name: "onboard-meeting-setup" });
|
||||||
} else {
|
} else {
|
||||||
// They're not the host, take them to list
|
// They're not the host, take them to list
|
||||||
(this.$router as Router).push({ name: "onboard-meeting-list" });
|
this.$router.push({ name: "onboard-meeting-list" });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// They're not in a meeting, show the dialog
|
// They're not in a meeting, show the dialog
|
||||||
@@ -1413,11 +1421,11 @@ export default class ContactsView extends Vue {
|
|||||||
title: "Onboarding Meeting",
|
title: "Onboarding Meeting",
|
||||||
text: "Would you like to start a new meeting?",
|
text: "Would you like to start a new meeting?",
|
||||||
onYes: async () => {
|
onYes: async () => {
|
||||||
(this.$router as Router).push({ name: "onboard-meeting-setup" });
|
this.$router.push({ name: "onboard-meeting-setup" });
|
||||||
},
|
},
|
||||||
yesText: "Start New Meeting",
|
yesText: "Start New Meeting",
|
||||||
onNo: async () => {
|
onNo: async () => {
|
||||||
(this.$router as Router).push({ name: "onboard-meeting-list" });
|
this.$router.push({ name: "onboard-meeting-list" });
|
||||||
},
|
},
|
||||||
noText: "Join Existing Meeting",
|
noText: "Join Existing Meeting",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
<h1 id="ViewHeading" class="text-lg text-center font-light relative px-7">
|
<h1 id="ViewHeading" class="text-lg text-center font-light relative px-7">
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<button
|
<button
|
||||||
@click="$router.go(-1)"
|
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.go(-1)"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
Identifier Details
|
Identifier Details
|
||||||
</h1>
|
</h1>
|
||||||
@@ -29,16 +29,20 @@
|
|||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'contact-edit', params: { did: contactFromDid?.did } }"
|
:to="{ name: 'contact-edit', params: { did: contactFromDid?.did } }"
|
||||||
>
|
>
|
||||||
<fa icon="pen" class="text-sm text-blue-500 ml-2 mb-1" />
|
<font-awesome icon="pen" class="text-sm text-blue-500 ml-2 mb-1" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
@click="showDidDetails = !showDidDetails"
|
|
||||||
class="ml-2 mr-2 mt-4"
|
class="ml-2 mr-2 mt-4"
|
||||||
|
@click="showDidDetails = !showDidDetails"
|
||||||
>
|
>
|
||||||
Details
|
Details
|
||||||
<fa v-if="showDidDetails" icon="chevron-down" class="text-blue-400" />
|
<font-awesome
|
||||||
<fa v-else icon="chevron-right" class="text-blue-400" />
|
v-if="showDidDetails"
|
||||||
|
icon="chevron-down"
|
||||||
|
class="text-blue-400"
|
||||||
|
/>
|
||||||
|
<font-awesome v-else icon="chevron-right" class="text-blue-400" />
|
||||||
</button>
|
</button>
|
||||||
<!-- Keep the dump contents directly between > and < to avoid weird spacing. -->
|
<!-- Keep the dump contents directly between > and < to avoid weird spacing. -->
|
||||||
<pre
|
<pre
|
||||||
@@ -54,7 +58,7 @@
|
|||||||
>
|
>
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:icon-size="96"
|
:icon-size="96"
|
||||||
:profileImageUrl="contactFromDid?.profileImageUrl"
|
:profile-image-url="contactFromDid?.profileImageUrl"
|
||||||
class="inline-block align-text-bottom border border-slate-300 rounded"
|
class="inline-block align-text-bottom border border-slate-300 rounded"
|
||||||
@click="showLargeIdenticonUrl = contactFromDid?.profileImageUrl"
|
@click="showLargeIdenticonUrl = contactFromDid?.profileImageUrl"
|
||||||
/>
|
/>
|
||||||
@@ -69,61 +73,65 @@
|
|||||||
contactFromDid?.seesMe && contactFromDid.did !== activeDid
|
contactFromDid?.seesMe && contactFromDid.did !== activeDid
|
||||||
"
|
"
|
||||||
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
||||||
@click="confirmSetVisibility(contactFromDid, false)"
|
|
||||||
title="They can see you"
|
title="They can see you"
|
||||||
|
@click="confirmSetVisibility(contactFromDid, false)"
|
||||||
>
|
>
|
||||||
<fa icon="eye" class="fa-fw" />
|
<font-awesome icon="eye" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else-if="
|
v-else-if="
|
||||||
!contactFromDid?.seesMe && contactFromDid?.did !== activeDid
|
!contactFromDid?.seesMe && contactFromDid?.did !== activeDid
|
||||||
"
|
"
|
||||||
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
||||||
@click="confirmSetVisibility(contactFromDid, true)"
|
|
||||||
title="They cannot see you"
|
title="They cannot see you"
|
||||||
|
@click="confirmSetVisibility(contactFromDid, true)"
|
||||||
>
|
>
|
||||||
<fa icon="eye-slash" class="fa-fw" />
|
<font-awesome icon="eye-slash" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
|
||||||
@click="checkVisibility(contactFromDid)"
|
|
||||||
title="Check Visibility"
|
|
||||||
v-if="contactFromDid?.did !== activeDid"
|
v-if="contactFromDid?.did !== activeDid"
|
||||||
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
||||||
|
title="Check Visibility"
|
||||||
|
@click="checkVisibility(contactFromDid)"
|
||||||
>
|
>
|
||||||
<fa icon="rotate" class="fa-fw" />
|
<font-awesome icon="rotate" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="confirmRegister(contactFromDid)"
|
|
||||||
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-6 mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
|
||||||
v-if="contactFromDid?.did !== activeDid"
|
v-if="contactFromDid?.did !== activeDid"
|
||||||
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-6 mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
||||||
title="Registration"
|
title="Registration"
|
||||||
|
@click="confirmRegister(contactFromDid)"
|
||||||
>
|
>
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="contactFromDid?.registered"
|
v-if="contactFromDid?.registered"
|
||||||
icon="person-circle-check"
|
icon="person-circle-check"
|
||||||
class="fa-fw"
|
class="fa-fw"
|
||||||
/>
|
/>
|
||||||
<fa v-else icon="person-circle-question" class="fa-fw" />
|
<font-awesome
|
||||||
|
v-else
|
||||||
|
icon="person-circle-question"
|
||||||
|
class="fa-fw"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="confirmDeleteContact(contactFromDid)"
|
|
||||||
class="text-sm uppercase bg-gradient-to-b from-rose-500 to-rose-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-6 mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
class="text-sm uppercase bg-gradient-to-b from-rose-500 to-rose-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-6 mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
||||||
title="Delete"
|
title="Delete"
|
||||||
|
@click="confirmDeleteContact(contactFromDid)"
|
||||||
>
|
>
|
||||||
<fa icon="trash-can" class="fa-fw" />
|
<font-awesome icon="trash-can" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!contactFromDid?.profileImageUrl">
|
<div v-if="!contactFromDid?.profileImageUrl">
|
||||||
<div>Auto-Generated Icon</div>
|
<div>Auto-Generated Icon</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:entityId="viewingDid"
|
:entity-id="viewingDid"
|
||||||
:iconSize="64"
|
:icon-size="64"
|
||||||
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
||||||
@click="showLargeIdenticonId = viewingDid"
|
@click="showLargeIdenticonId = viewingDid"
|
||||||
/>
|
/>
|
||||||
@@ -138,9 +146,9 @@
|
|||||||
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||||
>
|
>
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:entityId="showLargeIdenticonId"
|
:entity-id="showLargeIdenticonId"
|
||||||
:iconSize="512"
|
:icon-size="512"
|
||||||
:profileImageUrl="showLargeIdenticonUrl"
|
:profile-image-url="showLargeIdenticonUrl"
|
||||||
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
||||||
@click="
|
@click="
|
||||||
showLargeIdenticonId = undefined;
|
showLargeIdenticonId = undefined;
|
||||||
@@ -161,10 +169,10 @@
|
|||||||
|
|
||||||
<!-- Loading Animation -->
|
<!-- Loading Animation -->
|
||||||
<div
|
<div
|
||||||
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
|
|
||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
|
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
|
||||||
>
|
>
|
||||||
<fa icon="spinner" class="fa-spin-pulse"></fa>
|
<font-awesome icon="spinner" class="fa-spin-pulse"></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
<!-- Results List -->
|
<!-- Results List -->
|
||||||
<div v-if="claims.length > 0" class="mt-4">
|
<div v-if="claims.length > 0" class="mt-4">
|
||||||
@@ -175,9 +183,9 @@
|
|||||||
<InfiniteScroll @reached-bottom="loadMoreData">
|
<InfiniteScroll @reached-bottom="loadMoreData">
|
||||||
<ul>
|
<ul>
|
||||||
<li
|
<li
|
||||||
class="border-b border-slate-300"
|
|
||||||
v-for="claim in claims"
|
v-for="claim in claims"
|
||||||
:key="claim.handleId"
|
:key="claim.handleId"
|
||||||
|
class="border-b border-slate-300"
|
||||||
>
|
>
|
||||||
<div class="grid grid-cols-12 gap-4">
|
<div class="grid grid-cols-12 gap-4">
|
||||||
<span class="col-span-2">
|
<span class="col-span-2">
|
||||||
@@ -193,8 +201,11 @@
|
|||||||
{{ claimDescription(claim) }}
|
{{ claimDescription(claim) }}
|
||||||
</span>
|
</span>
|
||||||
<span class="col-span-1">
|
<span class="col-span-1">
|
||||||
<a @click="onClickLoadClaim(claim.id)" class="cursor-pointer">
|
<a class="cursor-pointer" @click="onClickLoadClaim(claim.id)">
|
||||||
<fa icon="file-lines" class="pl-2 pt-1 text-blue-500" />
|
<font-awesome
|
||||||
|
icon="file-lines"
|
||||||
|
class="pl-2 pt-1 text-blue-500"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -216,7 +227,7 @@
|
|||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import * as yaml from "js-yaml";
|
import * as yaml from "js-yaml";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import InfiniteScroll from "../components/InfiniteScroll.vue";
|
import InfiniteScroll from "../components/InfiniteScroll.vue";
|
||||||
@@ -226,14 +237,16 @@ import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
|||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import { BoundingBox } from "../db/tables/settings";
|
import { BoundingBox } from "../db/tables/settings";
|
||||||
import {
|
import {
|
||||||
capitalizeAndInsertSpacesBeforeCaps,
|
|
||||||
didInfoForContact,
|
|
||||||
displayAmount,
|
|
||||||
getHeaders,
|
|
||||||
GenericCredWrapper,
|
GenericCredWrapper,
|
||||||
GenericVerifiableCredential,
|
GenericVerifiableCredential,
|
||||||
GiveVerifiableCredential,
|
GiveVerifiableCredential,
|
||||||
OfferVerifiableCredential,
|
OfferVerifiableCredential,
|
||||||
|
} from "../interfaces";
|
||||||
|
import {
|
||||||
|
capitalizeAndInsertSpacesBeforeCaps,
|
||||||
|
didInfoForContact,
|
||||||
|
displayAmount,
|
||||||
|
getHeaders,
|
||||||
register,
|
register,
|
||||||
setVisibilityUtil,
|
setVisibilityUtil,
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
@@ -250,6 +263,8 @@ import EntityIcon from "../components/EntityIcon.vue";
|
|||||||
})
|
})
|
||||||
export default class DIDView extends Vue {
|
export default class DIDView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
libsUtil = libsUtil;
|
libsUtil = libsUtil;
|
||||||
yaml = yaml;
|
yaml = yaml;
|
||||||
@@ -352,7 +367,7 @@ export default class DIDView extends Vue {
|
|||||||
},
|
},
|
||||||
3000,
|
3000,
|
||||||
);
|
);
|
||||||
(this.$router as Router).push({ name: "contacts" });
|
this.$router.push({ name: "contacts" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// confirm to register a new contact
|
// confirm to register a new contact
|
||||||
@@ -505,7 +520,7 @@ export default class DIDView extends Vue {
|
|||||||
const route = {
|
const route = {
|
||||||
path: "/claim/" + encodeURIComponent(jwtId),
|
path: "/claim/" + encodeURIComponent(jwtId),
|
||||||
};
|
};
|
||||||
(this.$router as Router).push(route);
|
this.$router.push(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
public claimAmount(claim: GenericVerifiableCredential) {
|
public claimAmount(claim: GenericVerifiableCredential) {
|
||||||
|
|||||||
@@ -18,17 +18,17 @@
|
|||||||
:style="{ visibility: isSearchVisible ? 'visible' : 'hidden' }"
|
:style="{ visibility: isSearchVisible ? 'visible' : 'hidden' }"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
|
||||||
v-model="searchTerms"
|
v-model="searchTerms"
|
||||||
|
type="text"
|
||||||
placeholder="Search…"
|
placeholder="Search…"
|
||||||
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2"
|
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2"
|
||||||
v-on:keyup.enter="searchSelected()"
|
@keyup.enter="searchSelected()"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
|
class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
|
||||||
@click="searchSelected()"
|
@click="searchSelected()"
|
||||||
>
|
>
|
||||||
<fa icon="magnifying-glass" class="fa-fw"></fa>
|
<font-awesome icon="magnifying-glass" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
:class="computedProjectsTabStyleClassNames()"
|
||||||
@click="
|
@click="
|
||||||
projects = [];
|
projects = [];
|
||||||
userProfiles = [];
|
userProfiles = [];
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
isPeopleActive = false;
|
isPeopleActive = false;
|
||||||
searchSelected();
|
searchSelected();
|
||||||
"
|
"
|
||||||
v-bind:class="computedProjectsTabStyleClassNames()"
|
|
||||||
>
|
>
|
||||||
Projects
|
Projects
|
||||||
</a>
|
</a>
|
||||||
@@ -54,6 +54,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
:class="computedPeopleTabStyleClassNames()"
|
||||||
@click="
|
@click="
|
||||||
projects = [];
|
projects = [];
|
||||||
userProfiles = [];
|
userProfiles = [];
|
||||||
@@ -61,7 +62,6 @@
|
|||||||
isPeopleActive = true;
|
isPeopleActive = true;
|
||||||
searchSelected();
|
searchSelected();
|
||||||
"
|
"
|
||||||
v-bind:class="computedPeopleTabStyleClassNames()"
|
|
||||||
>
|
>
|
||||||
People
|
People
|
||||||
</a>
|
</a>
|
||||||
@@ -75,6 +75,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
:class="computedLocalTabStyleClassNames()"
|
||||||
@click="
|
@click="
|
||||||
projects = [];
|
projects = [];
|
||||||
userProfiles = [];
|
userProfiles = [];
|
||||||
@@ -85,7 +86,6 @@
|
|||||||
tempSearchBox = null;
|
tempSearchBox = null;
|
||||||
searchLocal();
|
searchLocal();
|
||||||
"
|
"
|
||||||
v-bind:class="computedLocalTabStyleClassNames()"
|
|
||||||
>
|
>
|
||||||
Nearby
|
Nearby
|
||||||
<!-- restore when the links don't jump around for different numbers
|
<!-- restore when the links don't jump around for different numbers
|
||||||
@@ -101,6 +101,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
:class="computedMappedTabStyleClassNames()"
|
||||||
@click="
|
@click="
|
||||||
projects = [];
|
projects = [];
|
||||||
userProfiles = [];
|
userProfiles = [];
|
||||||
@@ -111,7 +112,6 @@
|
|||||||
searchTerms = '';
|
searchTerms = '';
|
||||||
tempSearchBox = null;
|
tempSearchBox = null;
|
||||||
"
|
"
|
||||||
v-bind:class="computedMappedTabStyleClassNames()"
|
|
||||||
>
|
>
|
||||||
<!-- search is triggered when map component gets to "ready" state -->
|
<!-- search is triggered when map component gets to "ready" state -->
|
||||||
Mapped
|
Mapped
|
||||||
@@ -120,6 +120,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
:class="computedRemoteTabStyleClassNames()"
|
||||||
@click="
|
@click="
|
||||||
projects = [];
|
projects = [];
|
||||||
userProfiles = [];
|
userProfiles = [];
|
||||||
@@ -130,7 +131,6 @@
|
|||||||
tempSearchBox = null;
|
tempSearchBox = null;
|
||||||
searchAll();
|
searchAll();
|
||||||
"
|
"
|
||||||
v-bind:class="computedRemoteTabStyleClassNames()"
|
|
||||||
>
|
>
|
||||||
Anywhere
|
Anywhere
|
||||||
<!-- restore when the links don't jump around for different numbers
|
<!-- restore when the links don't jump around for different numbers
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
class="ml-2 mt-2 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
class="ml-2 mt-2 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
||||||
@click="$router.push({ name: 'search-area' })"
|
@click="$router.push({ name: 'search-area' })"
|
||||||
>
|
>
|
||||||
<fa icon="location-dot" class="fa-fw" />
|
<font-awesome icon="location-dot" class="fa-fw" />
|
||||||
Select a {{ searchBox ? "Different" : "" }} Location for Nearby Search
|
Select a {{ searchBox ? "Different" : "" }} Location for Nearby Search
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -179,10 +179,10 @@
|
|||||||
|
|
||||||
<!-- Loading Animation -->
|
<!-- Loading Animation -->
|
||||||
<div
|
<div
|
||||||
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
|
|
||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
|
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
|
||||||
>
|
>
|
||||||
<fa icon="spinner" class="fa-spin-pulse"></fa>
|
<font-awesome icon="spinner" class="fa-spin-pulse"></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="projects.length === 0 && userProfiles.length === 0"
|
v-else-if="projects.length === 0 && userProfiles.length === 0"
|
||||||
@@ -205,19 +205,19 @@
|
|||||||
<!-- Projects List -->
|
<!-- Projects List -->
|
||||||
<template v-if="isProjectsActive">
|
<template v-if="isProjectsActive">
|
||||||
<li
|
<li
|
||||||
class="border-b border-slate-300"
|
|
||||||
v-for="project in projects"
|
v-for="project in projects"
|
||||||
:key="project.handleId"
|
:key="project.handleId"
|
||||||
|
class="border-b border-slate-300"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@click="onClickLoadItem(project.handleId)"
|
|
||||||
class="block py-4 flex gap-4 cursor-pointer"
|
class="block py-4 flex gap-4 cursor-pointer"
|
||||||
|
@click="onClickLoadItem(project.handleId)"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<ProjectIcon
|
<ProjectIcon
|
||||||
:entityId="project.handleId"
|
:entity-id="project.handleId"
|
||||||
:iconSize="48"
|
:icon-size="48"
|
||||||
:imageUrl="project.image"
|
:image-url="project.image"
|
||||||
class="block border border-slate-300 rounded-md max-h-12 max-w-12"
|
class="block border border-slate-300 rounded-md max-h-12 max-w-12"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -225,7 +225,10 @@
|
|||||||
<div class="grow">
|
<div class="grow">
|
||||||
<h2 class="text-base font-semibold">{{ project.name }}</h2>
|
<h2 class="text-base font-semibold">{{ project.name }}</h2>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
<font-awesome
|
||||||
|
icon="user"
|
||||||
|
class="fa-fw text-slate-400"
|
||||||
|
></font-awesome>
|
||||||
{{
|
{{
|
||||||
didInfo(
|
didInfo(
|
||||||
project.issuerDid,
|
project.issuerDid,
|
||||||
@@ -243,17 +246,20 @@
|
|||||||
<!-- Profiles List -->
|
<!-- Profiles List -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<li
|
<li
|
||||||
class="border-b border-slate-300"
|
|
||||||
v-for="profile in userProfiles"
|
v-for="profile in userProfiles"
|
||||||
:key="profile.issuerDid"
|
:key="profile.issuerDid"
|
||||||
|
class="border-b border-slate-300"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@click="onClickLoadItem(profile?.rowId || '')"
|
|
||||||
class="block py-4 flex gap-4 cursor-pointer"
|
class="block py-4 flex gap-4 cursor-pointer"
|
||||||
|
@click="onClickLoadItem(profile?.rowId || '')"
|
||||||
>
|
>
|
||||||
<div class="grow">
|
<div class="grow">
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
<font-awesome
|
||||||
|
icon="user"
|
||||||
|
class="fa-fw text-slate-400"
|
||||||
|
></font-awesome>
|
||||||
{{
|
{{
|
||||||
didInfo(
|
didInfo(
|
||||||
profile.issuerDid,
|
profile.issuerDid,
|
||||||
@@ -273,7 +279,10 @@
|
|||||||
v-if="isAnywhereActive && profile.locLat && profile.locLon"
|
v-if="isAnywhereActive && profile.locLat && profile.locLon"
|
||||||
class="mt-1 text-xs text-slate-500"
|
class="mt-1 text-xs text-slate-500"
|
||||||
>
|
>
|
||||||
<fa icon="location-dot" class="fa-fw"></fa>
|
<font-awesome
|
||||||
|
icon="location-dot"
|
||||||
|
class="fa-fw"
|
||||||
|
></font-awesome>
|
||||||
{{
|
{{
|
||||||
(profile.locLat > 0 ? "North" : "South") +
|
(profile.locLat > 0 ? "North" : "South") +
|
||||||
" in " +
|
" in " +
|
||||||
@@ -313,11 +322,11 @@ import {
|
|||||||
} from "../db/index";
|
} from "../db/index";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import { BoundingBox } from "../db/tables/settings";
|
import { BoundingBox } from "../db/tables/settings";
|
||||||
|
import { PlanData } from "../interfaces";
|
||||||
import {
|
import {
|
||||||
didInfo,
|
didInfo,
|
||||||
errorStringForLog,
|
errorStringForLog,
|
||||||
getHeaders,
|
getHeaders,
|
||||||
PlanData,
|
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import { OnboardPage, retrieveAccountDids } from "../libs/util";
|
import { OnboardPage, retrieveAccountDids } from "../libs/util";
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
@click="cancelBack()"
|
@click="cancelBack()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -44,9 +44,9 @@
|
|||||||
>
|
>
|
||||||
</h1>
|
</h1>
|
||||||
<textarea
|
<textarea
|
||||||
|
v-model="description"
|
||||||
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||||
placeholder="What was received"
|
placeholder="What was received"
|
||||||
v-model="description"
|
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-row justify-center">
|
<div class="flex flex-row justify-center">
|
||||||
<span
|
<span
|
||||||
@@ -59,18 +59,18 @@
|
|||||||
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
@click="amountInput === '0' ? null : decrement()"
|
@click="amountInput === '0' ? null : decrement()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" />
|
<font-awesome icon="chevron-left" />
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
v-model="amountInput"
|
||||||
type="number"
|
type="number"
|
||||||
class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20"
|
class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20"
|
||||||
v-model="amountInput"
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
@click="increment()"
|
@click="increment()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-right" />
|
<font-awesome icon="chevron-right" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -79,14 +79,14 @@
|
|||||||
<a :href="imageUrl" target="_blank">
|
<a :href="imageUrl" target="_blank">
|
||||||
<img :src="imageUrl" class="h-24 rounded-xl" />
|
<img :src="imageUrl" class="h-24 rounded-xl" />
|
||||||
</a>
|
</a>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="trash-can"
|
icon="trash-can"
|
||||||
@click="confirmDeleteImage"
|
|
||||||
class="text-red-500 fa-fw ml-8 mt-10"
|
class="text-red-500 fa-fw ml-8 mt-10"
|
||||||
|
@click="confirmDeleteImage"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="camera"
|
icon="camera"
|
||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md"
|
||||||
@click="openImageDialog"
|
@click="openImageDialog"
|
||||||
@@ -101,11 +101,11 @@
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<input
|
<input
|
||||||
v-if="giverDid && !providedByProject"
|
v-if="giverDid && !providedByProject"
|
||||||
|
v-model="providedByGiver"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="h-6 w-6 mr-2"
|
class="h-6 w-6 mr-2"
|
||||||
v-model="providedByGiver"
|
|
||||||
/>
|
/>
|
||||||
<fa
|
<font-awesome
|
||||||
v-else
|
v-else
|
||||||
icon="square"
|
icon="square"
|
||||||
class="mr-2 bg-white text-white h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
class="mr-2 bg-white text-white h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
: "No named individual gave."
|
: "No named individual gave."
|
||||||
}}
|
}}
|
||||||
</label>
|
</label>
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="!giverDid || providedByProject"
|
v-if="!giverDid || providedByProject"
|
||||||
icon="info-circle"
|
icon="info-circle"
|
||||||
class="-mt-1 bg-white text-slate-500 h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
class="-mt-1 bg-white text-slate-500 h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
||||||
@@ -128,11 +128,11 @@
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<input
|
<input
|
||||||
v-if="providerProjectId && !providedByGiver"
|
v-if="providerProjectId && !providedByGiver"
|
||||||
|
v-model="providedByProject"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="h-6 w-6 mr-2"
|
class="h-6 w-6 mr-2"
|
||||||
v-model="providedByProject"
|
|
||||||
/>
|
/>
|
||||||
<fa
|
<font-awesome
|
||||||
v-else
|
v-else
|
||||||
icon="square"
|
icon="square"
|
||||||
class="mr-2 bg-white text-white h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
class="mr-2 bg-white text-white h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
: "This was not provided by a project."
|
: "This was not provided by a project."
|
||||||
}}
|
}}
|
||||||
</label>
|
</label>
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="!providerProjectId || providedByGiver"
|
v-if="!providerProjectId || providedByGiver"
|
||||||
icon="info-circle"
|
icon="info-circle"
|
||||||
class="-mt-1 bg-white text-slate-500 h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
class="-mt-1 bg-white text-slate-500 h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-shrink flex justify-center items-center">
|
<div class="flex-shrink flex justify-center items-center">
|
||||||
<fa icon="arrow-right" class="fa-fw h-7" />
|
<font-awesome icon="arrow-right" class="fa-fw h-7" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Third Column for Recipient -->
|
<!-- Third Column for Recipient -->
|
||||||
@@ -162,11 +162,11 @@
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<input
|
<input
|
||||||
v-if="recipientDid && !givenToProject"
|
v-if="recipientDid && !givenToProject"
|
||||||
|
v-model="givenToRecipient"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="h-6 w-6 mr-2"
|
class="h-6 w-6 mr-2"
|
||||||
v-model="givenToRecipient"
|
|
||||||
/>
|
/>
|
||||||
<fa
|
<font-awesome
|
||||||
v-else
|
v-else
|
||||||
icon="square"
|
icon="square"
|
||||||
class="mr-2 bg-white text-white h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
class="mr-2 bg-white text-white h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
: "No individual benefitted."
|
: "No individual benefitted."
|
||||||
}}
|
}}
|
||||||
</label>
|
</label>
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="!recipientDid || givenToProject"
|
v-if="!recipientDid || givenToProject"
|
||||||
icon="info-circle"
|
icon="info-circle"
|
||||||
class="-mt-1 bg-white text-slate-500 h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
class="-mt-1 bg-white text-slate-500 h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
||||||
@@ -189,11 +189,11 @@
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<input
|
<input
|
||||||
v-if="fulfillsProjectId && !givenToRecipient"
|
v-if="fulfillsProjectId && !givenToRecipient"
|
||||||
|
v-model="givenToProject"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="h-6 w-6 mr-2"
|
class="h-6 w-6 mr-2"
|
||||||
v-model="givenToProject"
|
|
||||||
/>
|
/>
|
||||||
<fa
|
<font-awesome
|
||||||
v-else
|
v-else
|
||||||
icon="square"
|
icon="square"
|
||||||
class="mr-2 bg-white text-white h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
class="mr-2 bg-white text-white h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
||||||
@@ -205,7 +205,7 @@
|
|||||||
: "No project benefitted."
|
: "No project benefitted."
|
||||||
}}
|
}}
|
||||||
</label>
|
</label>
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="!fulfillsProjectId || givenToRecipient"
|
v-if="!fulfillsProjectId || givenToRecipient"
|
||||||
icon="info-circle"
|
icon="info-circle"
|
||||||
class="-mt-1 bg-white text-slate-500 h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
class="-mt-1 bg-white text-slate-500 h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
||||||
@@ -216,7 +216,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 flex">
|
<div class="mt-8 flex">
|
||||||
<input type="checkbox" class="h-6 w-6 mr-2" v-model="isTrade" />
|
<input v-model="isTrade" type="checkbox" class="h-6 w-6 mr-2" />
|
||||||
<label class="text-sm mt-1">This was a trade (not a gift)</label>
|
<label class="text-sm mt-1">This was a trade (not a gift)</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
|
|
||||||
<p class="text-center mb-2 mt-6 italic">
|
<p class="text-center mb-2 mt-6 italic">
|
||||||
Sign & Send to publish to the world
|
Sign & Send to publish to the world
|
||||||
<fa
|
<font-awesome
|
||||||
icon="circle-info"
|
icon="circle-info"
|
||||||
class="pl-2 text-blue-500 cursor-pointer"
|
class="pl-2 text-blue-500 cursor-pointer"
|
||||||
@click="explainData()"
|
@click="explainData()"
|
||||||
@@ -261,21 +261,20 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
|
|
||||||
import ImageMethodDialog from "../components/ImageMethodDialog.vue";
|
import ImageMethodDialog from "../components/ImageMethodDialog.vue";
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import TopMessage from "../components/TopMessage.vue";
|
import TopMessage from "../components/TopMessage.vue";
|
||||||
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "../constants/app";
|
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "../constants/app";
|
||||||
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
|
import { GenericCredWrapper, GiveVerifiableCredential } from "../interfaces";
|
||||||
import {
|
import {
|
||||||
createAndSubmitGive,
|
createAndSubmitGive,
|
||||||
didInfo,
|
didInfo,
|
||||||
editAndSubmitGive,
|
editAndSubmitGive,
|
||||||
GenericCredWrapper,
|
|
||||||
getHeaders,
|
getHeaders,
|
||||||
getPlanFromCache,
|
getPlanFromCache,
|
||||||
GiveVerifiableCredential,
|
|
||||||
hydrateGive,
|
hydrateGive,
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import * as libsUtil from "../libs/util";
|
import * as libsUtil from "../libs/util";
|
||||||
@@ -290,6 +289,8 @@ import { retrieveAccountDids } from "../libs/util";
|
|||||||
})
|
})
|
||||||
export default class GiftedDetails extends Vue {
|
export default class GiftedDetails extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
@@ -322,9 +323,9 @@ export default class GiftedDetails extends Vue {
|
|||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
try {
|
try {
|
||||||
this.prevCredToEdit = (this.$route as Router).query["prevCredToEdit"]
|
this.prevCredToEdit = (this.$route.query["prevCredToEdit"] as string)
|
||||||
? (JSON.parse(
|
? (JSON.parse(
|
||||||
(this.$route as Router).query["prevCredToEdit"],
|
this.$route.query["prevCredToEdit"] as string,
|
||||||
) as GenericCredWrapper<GiveVerifiableCredential>)
|
) as GenericCredWrapper<GiveVerifiableCredential>)
|
||||||
: undefined;
|
: undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -341,24 +342,23 @@ export default class GiftedDetails extends Vue {
|
|||||||
|
|
||||||
const prevAmount = this.prevCredToEdit?.claim?.object?.amountOfThisGood;
|
const prevAmount = this.prevCredToEdit?.claim?.object?.amountOfThisGood;
|
||||||
this.amountInput =
|
this.amountInput =
|
||||||
(this.$route as Router).query["amountInput"] ||
|
(this.$route.query["amountInput"] as string) ||
|
||||||
(prevAmount ? String(prevAmount) : "") ||
|
(prevAmount ? String(prevAmount) : "") ||
|
||||||
this.amountInput;
|
this.amountInput;
|
||||||
this.description =
|
this.description =
|
||||||
(this.$route as Router).query["description"] ||
|
(this.$route.query["description"] as string) ||
|
||||||
this.prevCredToEdit?.claim?.description ||
|
this.prevCredToEdit?.claim?.description ||
|
||||||
this.description;
|
this.description;
|
||||||
this.destinationPathAfter = (this.$route as Router).query[
|
this.destinationPathAfter =
|
||||||
"destinationPathAfter"
|
(this.$route.query["destinationPathAfter"] as string) || "";
|
||||||
];
|
this.giverDid = ((this.$route.query["giverDid"] as string) ||
|
||||||
this.giverDid = ((this.$route as Router).query["giverDid"] ||
|
(this.prevCredToEdit?.claim?.agent as unknown as { identifier: string })
|
||||||
this.prevCredToEdit?.claim?.agent?.identifier ||
|
?.identifier ||
|
||||||
this.giverDid) as string;
|
this.giverDid) as string;
|
||||||
this.giverName =
|
this.giverName = (this.$route.query["giverName"] as string) || "";
|
||||||
((this.$route as Router).query["giverName"] as string) || "";
|
|
||||||
this.hideBackButton =
|
this.hideBackButton =
|
||||||
(this.$route as Router).query["hideBackButton"] === "true";
|
(this.$route.query["hideBackButton"] as string) === "true";
|
||||||
this.message = ((this.$route as Router).query["message"] as string) || "";
|
this.message = (this.$route.query["message"] as string) || "";
|
||||||
|
|
||||||
// find any offer ID
|
// find any offer ID
|
||||||
const fulfills = this.prevCredToEdit?.claim?.fulfills;
|
const fulfills = this.prevCredToEdit?.claim?.fulfills;
|
||||||
@@ -368,7 +368,7 @@ export default class GiftedDetails extends Vue {
|
|||||||
? [fulfills]
|
? [fulfills]
|
||||||
: [];
|
: [];
|
||||||
const offer = fulfillsArray.find((rec) => rec["@type"] === "Offer");
|
const offer = fulfillsArray.find((rec) => rec["@type"] === "Offer");
|
||||||
this.offerId = ((this.$route as Router).query["offerId"] ||
|
this.offerId = ((this.$route.query["offerId"] as string) ||
|
||||||
offer?.identifier ||
|
offer?.identifier ||
|
||||||
this.offerId) as string;
|
this.offerId) as string;
|
||||||
|
|
||||||
@@ -378,7 +378,7 @@ export default class GiftedDetails extends Vue {
|
|||||||
);
|
);
|
||||||
// eslint-disable-next-line prettier/prettier
|
// eslint-disable-next-line prettier/prettier
|
||||||
this.fulfillsProjectId =
|
this.fulfillsProjectId =
|
||||||
((this.$route as Router).query["fulfillsProjectId"] ||
|
((this.$route.query["fulfillsProjectId"] as string) ||
|
||||||
fulfillsProject?.identifier ||
|
fulfillsProject?.identifier ||
|
||||||
this.fulfillsProjectId) as string;
|
this.fulfillsProjectId) as string;
|
||||||
|
|
||||||
@@ -392,40 +392,38 @@ export default class GiftedDetails extends Vue {
|
|||||||
const providerProject = providerArray.find(
|
const providerProject = providerArray.find(
|
||||||
(rec) => rec["@type"] === "PlanAction",
|
(rec) => rec["@type"] === "PlanAction",
|
||||||
);
|
);
|
||||||
this.providerProjectId = ((this.$route as Router).query[
|
this.providerProjectId = ((this.$route.query[
|
||||||
"providerProjectId"
|
"providerProjectId"
|
||||||
] ||
|
] as string) ||
|
||||||
providerProject?.identifier ||
|
providerProject?.identifier ||
|
||||||
this.providerProjectId) as string;
|
this.providerProjectId) as string;
|
||||||
|
|
||||||
this.recipientDid = ((this.$route as Router).query["recipientDid"] ||
|
this.recipientDid = ((this.$route.query["recipientDid"] as string) ||
|
||||||
this.prevCredToEdit?.claim?.recipient?.identifier) as string;
|
this.prevCredToEdit?.claim?.recipient?.identifier) as string;
|
||||||
this.recipientName =
|
this.recipientName = (this.$route.query["recipientName"] as string) || "";
|
||||||
((this.$route as Router).query["recipientName"] as string) || "";
|
this.unitCode = ((this.$route.query["unitCode"] as string) ||
|
||||||
this.unitCode = ((this.$route as Router).query["unitCode"] ||
|
|
||||||
this.prevCredToEdit?.claim?.object?.unitCode ||
|
this.prevCredToEdit?.claim?.object?.unitCode ||
|
||||||
this.unitCode) as string;
|
this.unitCode) as string;
|
||||||
|
|
||||||
this.imageUrl =
|
this.imageUrl = ((this.$route.query["imageUrl"] as string) ||
|
||||||
((this.$route as Router).query["imageUrl"] as string) ||
|
|
||||||
this.prevCredToEdit?.claim?.image ||
|
this.prevCredToEdit?.claim?.image ||
|
||||||
localStorage.getItem("imageUrl") ||
|
localStorage.getItem("imageUrl") ||
|
||||||
this.imageUrl;
|
this.imageUrl) as string;
|
||||||
|
|
||||||
// this is an endpoint for sharing project info to highlight something given
|
// this is an endpoint for sharing project info to highlight something given
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target
|
// https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target
|
||||||
if ((this.$route as Router).query["shareTitle"]) {
|
if (this.$route.query["shareTitle"] as string) {
|
||||||
this.description =
|
this.description =
|
||||||
((this.$route as Router).query["shareTitle"] as string) +
|
((this.$route.query["shareTitle"] as string) || "") +
|
||||||
(this.description ? "\n" + this.description : "");
|
(this.description ? "\n" + this.description : "");
|
||||||
}
|
}
|
||||||
if ((this.$route as Router).query["shareText"]) {
|
if (this.$route.query["shareText"] as string) {
|
||||||
this.description =
|
this.description =
|
||||||
(this.description ? this.description + "\n" : "") +
|
(this.description ? this.description + "\n" : "") +
|
||||||
((this.$route as Router).query["shareText"] as string);
|
((this.$route.query["shareText"] as string) || "");
|
||||||
}
|
}
|
||||||
if ((this.$route as Router).query["shareUrl"]) {
|
if (this.$route.query["shareUrl"] as string) {
|
||||||
this.imageUrl = (this.$route as Router).query["shareUrl"] as string;
|
this.imageUrl = this.$route.query["shareUrl"] as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const settings = await retrieveSettingsForActiveAccount();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
@click="$router.back()"
|
@click="$router.back()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
@click="$router.back()"
|
@click="$router.back()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -30,8 +30,8 @@
|
|||||||
<p>
|
<p>
|
||||||
If this works then you're all set.
|
If this works then you're all set.
|
||||||
<button
|
<button
|
||||||
@click="sendTestWebPushMessage(true)"
|
|
||||||
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2"
|
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2"
|
||||||
|
@click="sendTestWebPushMessage(true)"
|
||||||
>
|
>
|
||||||
Send Yourself a Test Web Push Message (Through Push Server but
|
Send Yourself a Test Web Push Message (Through Push Server but
|
||||||
Skipping Client Filter)
|
Skipping Client Filter)
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
here <fa icon="arrow-up-right-from-square" class="fa-fw" />
|
here <font-awesome icon="arrow-up-right-from-square" class="fa-fw" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@
|
|||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
here <fa icon="arrow-up-right-from-square" class="fa-fw" />
|
here <font-awesome icon="arrow-up-right-from-square" class="fa-fw" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -199,7 +199,7 @@
|
|||||||
<p>
|
<p>
|
||||||
Of course, you'll want to back up all your data first -- all seeds as
|
Of course, you'll want to back up all your data first -- all seeds as
|
||||||
well as the contacts & settings -- on the Profile
|
well as the contacts & settings -- on the Profile
|
||||||
<fa icon="circle-user" /> page.
|
<font-awesome icon="circle-user" /> page.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Here are instructions to uninstall the app and clear out caches and storage.
|
Here are instructions to uninstall the app and clear out caches and storage.
|
||||||
@@ -246,8 +246,8 @@
|
|||||||
|
|
||||||
<h2 class="text-xl font-semibold mt-4">Tests</h2>
|
<h2 class="text-xl font-semibold mt-4">Tests</h2>
|
||||||
<button
|
<button
|
||||||
@click="showTestNotification()"
|
|
||||||
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4 mb-2"
|
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4 mb-2"
|
||||||
|
@click="showTestNotification()"
|
||||||
>
|
>
|
||||||
Send Test Notification Directly to Device (Not Through Push Server)
|
Send Test Notification Directly to Device (Not Through Push Server)
|
||||||
</button>
|
</button>
|
||||||
@@ -259,8 +259,8 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="alertWebPushSubscription()"
|
|
||||||
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4 mb-2"
|
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4 mb-2"
|
||||||
|
@click="alertWebPushSubscription()"
|
||||||
>
|
>
|
||||||
Show Web Push Subscription Info
|
Show Web Push Subscription Info
|
||||||
</button>
|
</button>
|
||||||
@@ -272,8 +272,8 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="sendTestWebPushMessage(true)"
|
|
||||||
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4 mb-2"
|
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4 mb-2"
|
||||||
|
@click="sendTestWebPushMessage(true)"
|
||||||
>
|
>
|
||||||
Send Yourself a Test Web Push Message (Through Push Server but Skipping
|
Send Yourself a Test Web Push Message (Through Push Server but Skipping
|
||||||
Client Filter)
|
Client Filter)
|
||||||
@@ -285,8 +285,8 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="sendTestWebPushMessage()"
|
|
||||||
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4 mb-2"
|
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mt-4 mb-2"
|
||||||
|
@click="sendTestWebPushMessage()"
|
||||||
>
|
>
|
||||||
Send Yourself a Test Web Push Message (Through Push Server and Client
|
Send Yourself a Test Web Push Message (Through Push Server and Client
|
||||||
Filter)
|
Filter)
|
||||||
@@ -313,11 +313,12 @@ import { DIRECT_PUSH_TITLE, sendTestThroughPushServer } from "../libs/util";
|
|||||||
import PushNotificationPermission from "../components/PushNotificationPermission.vue";
|
import PushNotificationPermission from "../components/PushNotificationPermission.vue";
|
||||||
import { db } from "../db/index";
|
import { db } from "../db/index";
|
||||||
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
||||||
|
import { Router } from "vue-router";
|
||||||
|
|
||||||
@Component({ components: { PushNotificationPermission, QuickNav } })
|
@Component({ components: { PushNotificationPermission, QuickNav } })
|
||||||
export default class HelpNotificationsView extends Vue {
|
export default class HelpNotificationsView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
subscriptionJSON?: PushSubscriptionJSON;
|
subscriptionJSON?: PushSubscriptionJSON;
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
|||||||
@@ -19,13 +19,14 @@
|
|||||||
:to="{ name: 'invite-one' }"
|
:to="{ name: 'invite-one' }"
|
||||||
class="bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
class="bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
||||||
>
|
>
|
||||||
<fa icon="envelope-open-text" class="fa-fw text-xl"
|
<font-awesome icon="envelope-open-text" class="fa-fw text-xl"
|
||||||
/></router-link>
|
/></router-link>
|
||||||
</p>
|
</p>
|
||||||
<p>Then watch that page to see when they accept their invite.</p>
|
<p>Then watch that page to see when they accept their invite.</p>
|
||||||
<p>
|
<p>
|
||||||
(That page is also reachable from the Contacts <fa icon="users" /> page
|
(That page is also reachable from the Contacts
|
||||||
though the invitation <fa icon="envelope-open-text" /> icon.)
|
<font-awesome icon="users" /> page though the invitation
|
||||||
|
<font-awesome icon="envelope-open-text" /> icon.)
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1 class="mt-4 font-bold text-xl">Next Steps</h1>
|
<h1 class="mt-4 font-bold text-xl">Next Steps</h1>
|
||||||
@@ -35,12 +36,13 @@
|
|||||||
<h1 class="font-bold text-xl">Without a backup, you can lose data.</h1>
|
<h1 class="font-bold text-xl">Without a backup, you can lose data.</h1>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
Exporting backups (from the Account <fa icon="circle-user" /> screen)
|
Exporting backups (from the Account
|
||||||
is important for the case where they lose their device. This is
|
<font-awesome icon="circle-user" /> screen) is important for the case
|
||||||
especially true for the Identifier Seed: that is theirs and and theirs
|
where they lose their device. This is especially true for the
|
||||||
alone, and currently nobody else can recover it if they lose it. The
|
Identifier Seed: that is theirs and and theirs alone, and currently
|
||||||
good thing is that anyone can create a new account and simply inform
|
nobody else can recover it if they lose it. The good thing is that
|
||||||
their network of their new ID.
|
anyone can create a new account and simply inform their network of
|
||||||
|
their new ID.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,7 +56,7 @@
|
|||||||
<h1 class="font-bold text-xl">Add Contact & Register</h1>
|
<h1 class="font-bold text-xl">Add Contact & Register</h1>
|
||||||
<p>
|
<p>
|
||||||
You share even more information such as your picture and name when
|
You share even more information such as your picture and name when
|
||||||
you share with your QR code at these links: <fa icon="qrcode" />
|
you share with your QR code at these links: <font-awesome icon="qrcode" />
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Scanning
|
Scanning
|
||||||
@@ -70,14 +72,14 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
2) Scan their QR, or have them tap on it to copy their info and send it to you.
|
2) Scan their QR, or have them tap on it to copy their info and send it to you.
|
||||||
Then you can add them to your Contacts <fa icon="users" />
|
Then you can add them to your Contacts <font-awesome icon="users" />
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
3) You can register them at their info page <fa icon="circle-info" />
|
3) You can register them at their info page <font-awesome icon="circle-info" />
|
||||||
and click on the register button <fa icon="person-circle-question" />
|
and click on the register button <font-awesome icon="person-circle-question" />
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
4) Add yourself to their Contacts <fa icon="users" />
|
4) Add yourself to their Contacts <font-awesome icon="users" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -94,7 +96,7 @@
|
|||||||
<h1 class="font-bold text-xl">Enable Notifications</h1>
|
<h1 class="font-bold text-xl">Enable Notifications</h1>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
Enable notifications from the Account page <fa icon="circle-user" />.
|
Enable notifications from the Account page <font-awesome icon="circle-user" />.
|
||||||
Those notifications might show up on the device depending on your settings.
|
Those notifications might show up on the device depending on your settings.
|
||||||
For the most reliable habits, set an alarm or do some other ritual to record gratitude every day.
|
For the most reliable habits, set an alarm or do some other ritual to record gratitude every day.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
@click="$router.back()"
|
@click="$router.back()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw" />
|
<font-awesome icon="chevron-left" class="fa-fw" />
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -30,8 +30,8 @@
|
|||||||
<p class="ml-4">
|
<p class="ml-4">
|
||||||
If you'd like to see the page-by-page help,
|
If you'd like to see the page-by-page help,
|
||||||
<span
|
<span
|
||||||
@click="unsetFinishedOnboarding()"
|
|
||||||
class="text-blue-500 cursor-pointer"
|
class="text-blue-500 cursor-pointer"
|
||||||
|
@click="unsetFinishedOnboarding()"
|
||||||
>click here</span>.
|
>click here</span>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<h2 class="text-xl font-semibold">I want to know more because...</h2>
|
<h2 class="text-xl font-semibold">I want to know more because...</h2>
|
||||||
<ul class="list-disc list-outside ml-4">
|
<ul class="list-disc list-outside ml-4">
|
||||||
<li class="p-2">
|
<li class="p-2">
|
||||||
<div @click="showAlpha = !showAlpha" class="text-blue-500">... I'm a member of Alpha chat.</div>
|
<div class="text-blue-500" @click="showAlpha = !showAlpha">... I'm a member of Alpha chat.</div>
|
||||||
<div v-if="showAlpha">
|
<div v-if="showAlpha">
|
||||||
<p>
|
<p>
|
||||||
This is a project for public benefit. You are invited to add your gratitude
|
This is a project for public benefit. You are invited to add your gratitude
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="p-2">
|
<li class="p-2">
|
||||||
<div @click="showGroup = !showGroup" class="text-blue-500">... I want to find a group I'll enjoy working with.</div>
|
<div class="text-blue-500" @click="showGroup = !showGroup">... I want to find a group I'll enjoy working with.</div>
|
||||||
<div v-if="showGroup">
|
<div v-if="showGroup">
|
||||||
<p>
|
<p>
|
||||||
This app encourages people to offer small bits of time to one another. It's a way to
|
This app encourages people to offer small bits of time to one another. It's a way to
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="p-2">
|
<li class="p-2">
|
||||||
<div @click="showCommunity = !showCommunity" class="text-blue-500">... I want to participate in community projects.</div>
|
<div class="text-blue-500" @click="showCommunity = !showCommunity">... I want to participate in community projects.</div>
|
||||||
<div v-if="showCommunity">
|
<div v-if="showCommunity">
|
||||||
<p>
|
<p>
|
||||||
These are mostly at the beginning stages, so any of them will appreciate your offers that show interest.
|
These are mostly at the beginning stages, so any of them will appreciate your offers that show interest.
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="p-2">
|
<li class="p-2">
|
||||||
<div @click="showVerifiable = !showVerifiable" class="text-blue-500">... I want to build with verifiable, private data.</div>
|
<div class="text-blue-500" @click="showVerifiable = !showVerifiable">... I want to build with verifiable, private data.</div>
|
||||||
<div v-if="showVerifiable">
|
<div v-if="showVerifiable">
|
||||||
<p>
|
<p>
|
||||||
Make your claims and get others to confirm them. Then you can use the API to pull your copy of all that
|
Make your claims and get others to confirm them. Then you can use the API to pull your copy of all that
|
||||||
@@ -153,7 +153,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="p-2">
|
<li class="p-2">
|
||||||
<div @click="showGovernance = !showGovernance" class="text-blue-500">... I want to build governance organically.</div>
|
<div class="text-blue-500" @click="showGovernance = !showGovernance">... I want to build governance organically.</div>
|
||||||
<div v-if="showGovernance">
|
<div v-if="showGovernance">
|
||||||
<p>
|
<p>
|
||||||
This requires motivated, dedicated citizens. The good thing is that dedication the primary ingredient;
|
This requires motivated, dedicated citizens. The good thing is that dedication the primary ingredient;
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="p-2">
|
<li class="p-2">
|
||||||
<div @click="showBasics = !showBasics" class="text-blue-500">... I want to supply life's basics freely.</div>
|
<div class="text-blue-500" @click="showBasics = !showBasics">... I want to supply life's basics freely.</div>
|
||||||
<div v-if="showBasics">
|
<div v-if="showBasics">
|
||||||
<p>
|
<p>
|
||||||
This platform is not optimal for balancing needs and resources at this point,
|
This platform is not optimal for balancing needs and resources at this point,
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
<h2 class="text-xl font-semibold">How do I get started?</h2>
|
<h2 class="text-xl font-semibold">How do I get started?</h2>
|
||||||
<p>
|
<p>
|
||||||
Someone -- like the person who told you about this app -- needs to register you
|
Someone -- like the person who told you about this app -- needs to register you
|
||||||
on the Contacts <fa icon="users" class="fa-fw" /> page.
|
on the Contacts <font-awesome icon="users" class="fa-fw" /> page.
|
||||||
If you heard about this from our outreach, feel free to contact us (below) for a chat.
|
If you heard about this from our outreach, feel free to contact us (below) for a chat.
|
||||||
After someone registers you, you can register others.
|
After someone registers you, you can register others.
|
||||||
</p>
|
</p>
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If they are not nearby to scan QR codes, you each can tap on the QR code
|
If they are not nearby to scan QR codes, you each can tap on the QR code
|
||||||
and paste it into the text box on the Contacts <fa icon="users" class="fa-fw" /> page.
|
and paste it into the text box on the Contacts <font-awesome icon="users" class="fa-fw" /> page.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 class="text-xl font-semibold">
|
<h2 class="text-xl font-semibold">
|
||||||
@@ -244,7 +244,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<ul class="list-disc list-outside ml-4">
|
<ul class="list-disc list-outside ml-4">
|
||||||
<li>
|
<li>
|
||||||
Go to Your Identity <fa icon="circle-user" class="fa-fw" /> page.
|
Go to Your Identity <font-awesome icon="circle-user" class="fa-fw" /> page.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Click on "Backup Identifier Seed" and follow the instructions.
|
Click on "Backup Identifier Seed" and follow the instructions.
|
||||||
@@ -260,7 +260,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<ul class="list-disc list-outside ml-4">
|
<ul class="list-disc list-outside ml-4">
|
||||||
<li>
|
<li>
|
||||||
Go to Your Identity <fa icon="circle-user" class="fa-fw" /> page.
|
Go to Your Identity <font-awesome icon="circle-user" class="fa-fw" /> page.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Click on "Download Settings...". That will save a file to your
|
Click on "Download Settings...". That will save a file to your
|
||||||
@@ -274,7 +274,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<ul class="list-disc list-outside ml-4">
|
<ul class="list-disc list-outside ml-4">
|
||||||
<li>
|
<li>
|
||||||
Go to Your Identity <fa icon="circle-user" class="fa-fw" /> page,
|
Go to Your Identity <font-awesome icon="circle-user" class="fa-fw" /> page,
|
||||||
tap on your image, and save it.
|
tap on your image, and save it.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -315,7 +315,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<ul class="list-disc list-outside ml-4">
|
<ul class="list-disc list-outside ml-4">
|
||||||
<li>
|
<li>
|
||||||
Go to Your Identity <fa icon="circle-user" class="fa-fw" /> page,
|
Go to Your Identity <font-awesome icon="circle-user" class="fa-fw" /> page,
|
||||||
click Advanced, and follow the instructions for the Contacts & Settings Database "Import".
|
click Advanced, and follow the instructions for the Contacts & Settings Database "Import".
|
||||||
Beware that this will erase your existing contact & settings.
|
Beware that this will erase your existing contact & settings.
|
||||||
</li>
|
</li>
|
||||||
@@ -384,7 +384,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
There is an "Advanced" section at the bottom of the Profile
|
There is an "Advanced" section at the bottom of the Profile
|
||||||
<fa icon="circle-user" /> page.
|
<font-awesome icon="circle-user" /> page.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
There is even more functionality in a mobile app (and more
|
There is even more functionality in a mobile app (and more
|
||||||
@@ -402,8 +402,8 @@
|
|||||||
because they have not given you permission to see their information. Ask
|
because they have not given you permission to see their information. Ask
|
||||||
them to add you to their contact list, and ask specifically to make sure
|
them to add you to their contact list, and ask specifically to make sure
|
||||||
the eye next to your name is open like this
|
the eye next to your name is open like this
|
||||||
<fa icon="eye" class="fa-fw" /> and not closed like this
|
<font-awesome icon="eye" class="fa-fw" /> and not closed like this
|
||||||
<fa icon="eye-slash" class="fa-fw" />.
|
<font-awesome icon="eye-slash" class="fa-fw" />.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Sometimes the reason you don't see something is because the search
|
Sometimes the reason you don't see something is because the search
|
||||||
@@ -444,7 +444,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
There may be a problem with your identity. Go to the Identity
|
There may be a problem with your identity. Go to the Identity
|
||||||
<fa icon="circle-user" class="fa-fw" /> page, then "Advanced", and "Switch Identifier"
|
<font-awesome icon="circle-user" class="fa-fw" /> page, then "Advanced", and "Switch Identifier"
|
||||||
and you may see helpful info there. If it shows a problem, try adding your identifier again.
|
and you may see helpful info there. If it shows a problem, try adding your identifier again.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -505,7 +505,7 @@
|
|||||||
<ul class="list-disc list-outside ml-4">
|
<ul class="list-disc list-outside ml-4">
|
||||||
<li>
|
<li>
|
||||||
If using notifications, a server stores push token data. That can be revoked at any time
|
If using notifications, a server stores push token data. That can be revoked at any time
|
||||||
by disabling notifications on the Profile <fa icon="circle-user" class="fa-fw" /> page.
|
by disabling notifications on the Profile <font-awesome icon="circle-user" class="fa-fw" /> page.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
If sending images, a server stores them, too. They can be removed by editing the claim
|
If sending images, a server stores them, too. They can be removed by editing the claim
|
||||||
@@ -529,17 +529,17 @@
|
|||||||
If you have skills, contact us below.
|
If you have skills, contact us below.
|
||||||
If you have Bitcoin, donate to
|
If you have Bitcoin, donate to
|
||||||
<button
|
<button
|
||||||
|
class="text-blue-500 ml-2"
|
||||||
@click="
|
@click="
|
||||||
doCopyTwoSecRedo(
|
doCopyTwoSecRedo(
|
||||||
'bc1q90v4ted6cpt63tjfh2lvd5xzfc67sd4g9w8xma',
|
'bc1q90v4ted6cpt63tjfh2lvd5xzfc67sd4g9w8xma',
|
||||||
() => (showDidCopy = !showDidCopy)
|
() => (showDidCopy = !showDidCopy)
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
class="text-blue-500 ml-2"
|
|
||||||
>
|
>
|
||||||
bc1q90v4ted6cpt63tjfh2lvd5xzfc67sd4g9w8xma
|
bc1q90v4ted6cpt63tjfh2lvd5xzfc67sd4g9w8xma
|
||||||
<fa v-show="!showDidCopy" icon="copy" class="text-sm text-slate-400 fa-fw" />
|
<font-awesome v-show="!showDidCopy" icon="copy" class="text-sm text-slate-400 fa-fw" />
|
||||||
<fa v-show="showDidCopy" icon="circle-check" class="text-sm text-green-500 fa-fw"/>
|
<font-awesome v-show="showDidCopy" icon="circle-check" class="text-sm text-green-500 fa-fw"/>
|
||||||
</button>
|
</button>
|
||||||
You can donate online via
|
You can donate online via
|
||||||
<a href="https://www.patreon.com/TimeSafari" target="_blank" class="text-blue-500">Patreon here</a>.
|
<a href="https://www.patreon.com/TimeSafari" target="_blank" class="text-blue-500">Patreon here</a>.
|
||||||
@@ -588,6 +588,7 @@ import {
|
|||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class Help extends Vue {
|
export default class Help extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
package = Package;
|
package = Package;
|
||||||
commitHash = import.meta.env.VITE_GIT_HASH;
|
commitHash = import.meta.env.VITE_GIT_HASH;
|
||||||
@@ -614,7 +615,7 @@ export default class Help extends Vue {
|
|||||||
finishedOnboarding: false,
|
finishedOnboarding: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
(this.$router as Router).push({ name: "home" });
|
this.$router.push({ name: "home" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<TopMessage />
|
<TopMessage />
|
||||||
|
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
<section id="Content" class="p-2 pb-24 max-w-3xl mx-auto">
|
||||||
<h1 id="ViewHeading" class="text-4xl text-center font-light mb-8">
|
<h1 id="ViewHeading" class="text-4xl text-center font-light mb-8">
|
||||||
{{ AppString.APP_NAME }}
|
{{ AppString.APP_NAME }}
|
||||||
</h1>
|
</h1>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
width="30"
|
width="30"
|
||||||
style="display: inline; margin: 0 5px; vertical-align: middle"
|
style="display: inline; margin: 0 5px; vertical-align: middle"
|
||||||
/>and then "Add to Home Screen"
|
/>and then "Add to Home Screen"
|
||||||
<fa icon="square-plus" title="Apple 'Add' icon" />
|
<font-awesome icon="square-plus" title="Apple 'Add' icon" />
|
||||||
and go click on that new app.
|
and go click on that new app.
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
@@ -36,12 +36,8 @@
|
|||||||
>
|
>
|
||||||
You should see a prompt to install, or you can click on the
|
You should see a prompt to install, or you can click on the
|
||||||
top-right dots
|
top-right dots
|
||||||
<fa
|
<font-awesome icon="ellipsis-vertical" title="vertical ellipsis" />
|
||||||
icon="ellipsis-vertical"
|
/> and then "Install"<img
|
||||||
title="vertical ellipsis"
|
|
||||||
class="fa-fw"
|
|
||||||
/>
|
|
||||||
and then "Install"<img
|
|
||||||
src="../assets/help/install-android-chrome.png"
|
src="../assets/help/install-android-chrome.png"
|
||||||
alt="Android 'install' icon"
|
alt="Android 'install' icon"
|
||||||
width="30"
|
width="30"
|
||||||
@@ -73,7 +69,7 @@
|
|||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<div v-if="isCreatingIdentifier">
|
<div v-if="isCreatingIdentifier">
|
||||||
<p class="text-slate-500 text-center italic mt-4 mb-4">
|
<p class="text-slate-500 text-center italic mt-4 mb-4">
|
||||||
<fa icon="spinner" class="fa-spin-pulse" />
|
<font-awesome icon="spinner" class="fa-spin-pulse" />
|
||||||
Loading…
|
Loading…
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,8 +87,8 @@
|
|||||||
To share, someone must register you.
|
To share, someone must register you.
|
||||||
<div class="block text-center">
|
<div class="block text-center">
|
||||||
<button
|
<button
|
||||||
@click="showNameThenIdDialog()"
|
|
||||||
class="text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
|
class="text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
|
||||||
|
@click="showNameThenIdDialog()"
|
||||||
>
|
>
|
||||||
Show them {{ PASSKEYS_ENABLED ? "default" : "your" }} identifier
|
Show them {{ PASSKEYS_ENABLED ? "default" : "your" }} identifier
|
||||||
info
|
info
|
||||||
@@ -116,10 +112,10 @@
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<h2 class="text-xl font-bold">What have you seen someone do?</h2>
|
<h2 class="text-xl font-bold">What have you seen someone do?</h2>
|
||||||
<button
|
<button
|
||||||
@click="openGiftedPrompts()"
|
|
||||||
class="ml-2 block text-xs text-center bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 rounded-md"
|
class="ml-2 block text-xs text-center bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 rounded-md"
|
||||||
|
@click="openGiftedPrompts()"
|
||||||
>
|
>
|
||||||
<fa icon="lightbulb" class="fa-fw" />
|
<font-awesome icon="lightbulb" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -147,7 +143,7 @@
|
|||||||
>
|
>
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:contact="contact"
|
:contact="contact"
|
||||||
:iconSize="64"
|
:icon-size="64"
|
||||||
class="mx-auto border border-blue-500 rounded-md mb-1 cursor-pointer"
|
class="mx-auto border border-blue-500 rounded-md mb-1 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<h3
|
<h3
|
||||||
@@ -181,7 +177,7 @@
|
|||||||
class="absolute right-6 bottom-0 transform translate-y-1/2 text-center text-4xl leading-none bg-green-600 text-white w-14 py-2.5 rounded-full"
|
class="absolute right-6 bottom-0 transform translate-y-1/2 text-center text-4xl leading-none bg-green-600 text-white w-14 py-2.5 rounded-full"
|
||||||
@click="openDialog()"
|
@click="openDialog()"
|
||||||
>
|
>
|
||||||
<fa icon="plus" class="fa-fw" />
|
<font-awesome icon="plus" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -192,12 +188,12 @@
|
|||||||
Latest Activity
|
Latest Activity
|
||||||
<button @click="openFeedFilters()">
|
<button @click="openFeedFilters()">
|
||||||
<span class="text-xs text-white">
|
<span class="text-xs text-white">
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="resultsAreFiltered()"
|
v-if="resultsAreFiltered()"
|
||||||
icon="filter"
|
icon="filter"
|
||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-1 py-1.5 rounded-md"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-1 py-1.5 rounded-md"
|
||||||
/>
|
/>
|
||||||
<fa
|
<font-awesome
|
||||||
v-else
|
v-else
|
||||||
icon="filter"
|
icon="filter"
|
||||||
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-1 py-1.5 rounded-md"
|
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-1 py-1.5 rounded-md"
|
||||||
@@ -208,8 +204,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@click="goToActivityToUserPage()"
|
|
||||||
class="border-t p-2 border-slate-300"
|
class="border-t p-2 border-slate-300"
|
||||||
|
@click="goToActivityToUserPage()"
|
||||||
>
|
>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<div
|
<div
|
||||||
@@ -251,13 +247,13 @@
|
|||||||
<InfiniteScroll @reached-bottom="loadMoreGives">
|
<InfiniteScroll @reached-bottom="loadMoreGives">
|
||||||
<ul id="listLatestActivity" class="border-t border-slate-300">
|
<ul id="listLatestActivity" class="border-t border-slate-300">
|
||||||
<li
|
<li
|
||||||
class="border-b border-slate-300 py-2"
|
|
||||||
v-for="record in feedData"
|
v-for="record in feedData"
|
||||||
:key="record.jwtId"
|
:key="record.jwtId"
|
||||||
|
class="border-b border-slate-300 py-2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="border-b border-slate-300 text-orange-400 pb-2 mb-2 font-bold text-sm"
|
|
||||||
v-if="record.jwtId == feedLastViewedClaimId"
|
v-if="record.jwtId == feedLastViewedClaimId"
|
||||||
|
class="border-b border-slate-300 text-orange-400 pb-2 mb-2 font-bold text-sm"
|
||||||
>
|
>
|
||||||
You've already seen all the following
|
You've already seen all the following
|
||||||
</div>
|
</div>
|
||||||
@@ -265,7 +261,7 @@
|
|||||||
<div class="grid grid-cols-12">
|
<div class="grid grid-cols-12">
|
||||||
<span class="pt-1 col-span-1 justify-self-start">
|
<span class="pt-1 col-span-1 justify-self-start">
|
||||||
<span>
|
<span>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="circle-user"
|
icon="circle-user"
|
||||||
:class="
|
:class="
|
||||||
computeKnownPersonIconStyleClassNames(
|
computeKnownPersonIconStyleClassNames(
|
||||||
@@ -274,7 +270,7 @@
|
|||||||
"
|
"
|
||||||
@click="toastUser('This involves your contacts.')"
|
@click="toastUser('This involves your contacts.')"
|
||||||
/>
|
/>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="gift"
|
icon="gift"
|
||||||
class="pl-3 text-slate-500"
|
class="pl-3 text-slate-500"
|
||||||
@click="toastUser('This is a gift.')"
|
@click="toastUser('This is a gift.')"
|
||||||
@@ -295,7 +291,7 @@
|
|||||||
:profile-image-url="record.giver.profileImageUrl"
|
:profile-image-url="record.giver.profileImageUrl"
|
||||||
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
||||||
/>
|
/>
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="
|
v-if="
|
||||||
record.agentDid !== activeDid &&
|
record.agentDid !== activeDid &&
|
||||||
record.recipientDid !== activeDid &&
|
record.recipientDid !== activeDid &&
|
||||||
@@ -319,7 +315,7 @@
|
|||||||
{{ giveDescription(record) }}
|
{{ giveDescription(record) }}
|
||||||
</span>
|
</span>
|
||||||
<a @click="onClickLoadClaim(record.jwtId)">
|
<a @click="onClickLoadClaim(record.jwtId)">
|
||||||
<fa
|
<font-awesome
|
||||||
icon="file-lines"
|
icon="file-lines"
|
||||||
class="pl-2 text-slate-500 cursor-pointer"
|
class="pl-2 text-slate-500 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
@@ -333,7 +329,7 @@
|
|||||||
encodeURIComponent(record.fulfillsPlanHandleId)
|
encodeURIComponent(record.fulfillsPlanHandleId)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<fa icon="hammer" class="text-blue-500" />
|
<font-awesome icon="hammer" class="text-blue-500" />
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="record.providerPlanHandleId"
|
v-if="record.providerPlanHandleId"
|
||||||
@@ -342,7 +338,7 @@
|
|||||||
encodeURIComponent(record.providerPlanHandleId)
|
encodeURIComponent(record.providerPlanHandleId)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<fa icon="hammer" class="text-blue-500" />
|
<font-awesome icon="hammer" class="text-blue-500" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -364,7 +360,7 @@
|
|||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
<div v-if="isFeedLoading">
|
<div v-if="isFeedLoading">
|
||||||
<p class="text-slate-500 text-center italic mt-4 mb-4">
|
<p class="text-slate-500 text-center italic mt-4 mb-4">
|
||||||
<fa icon="spinner" class="fa-spin-pulse" /> Loading…
|
<font-awesome icon="spinner" class="fa-spin-pulse" /> Loading…
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isFeedLoading && feedData.length === 0">
|
<div v-if="!isFeedLoading && feedData.length === 0">
|
||||||
@@ -378,9 +374,9 @@
|
|||||||
<ChoiceButtonDialog ref="choiceButtonDialog" />
|
<ChoiceButtonDialog ref="choiceButtonDialog" />
|
||||||
|
|
||||||
<ImageViewer
|
<ImageViewer
|
||||||
|
v-model:is-open="isImageViewerOpen"
|
||||||
:image-url="selectedImage"
|
:image-url="selectedImage"
|
||||||
:image-data="selectedImageData"
|
:image-data="selectedImageData"
|
||||||
v-model:is-open="isImageViewerOpen"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -438,6 +434,7 @@ import {
|
|||||||
} from "../libs/util";
|
} from "../libs/util";
|
||||||
|
|
||||||
interface GiveRecordWithContactInfo extends GiveSummaryRecord {
|
interface GiveRecordWithContactInfo extends GiveSummaryRecord {
|
||||||
|
jwtId: string;
|
||||||
giver: {
|
giver: {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
known: boolean;
|
known: boolean;
|
||||||
@@ -731,6 +728,7 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
const newRecord: GiveRecordWithContactInfo = {
|
const newRecord: GiveRecordWithContactInfo = {
|
||||||
...record,
|
...record,
|
||||||
|
jwtId: record.jwtId,
|
||||||
giver: didInfoForContact(
|
giver: didInfoForContact(
|
||||||
giverDid,
|
giverDid,
|
||||||
this.activeDid,
|
this.activeDid,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'account' }"
|
:to="{ name: 'account' }"
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
><fa icon="chevron-left" class="fa-fw"></fa>
|
><font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
Switch Identity
|
Switch Identity
|
||||||
@@ -22,7 +22,10 @@
|
|||||||
v-if="activeDid && !activeDidInIdentities"
|
v-if="activeDid && !activeDidInIdentities"
|
||||||
class="block bg-slate-100 rounded-md flex items-center px-4 py-3 mb-4"
|
class="block bg-slate-100 rounded-md flex items-center px-4 py-3 mb-4"
|
||||||
>
|
>
|
||||||
<fa icon="circle-check" class="fa-fw text-red-600 text-xl mr-3"></fa>
|
<font-awesome
|
||||||
|
icon="circle-check"
|
||||||
|
class="fa-fw text-red-600 text-xl mr-3"
|
||||||
|
></font-awesome>
|
||||||
<div class="text-sm text-slate-500">
|
<div class="text-sm text-slate-500">
|
||||||
<div class="overflow-hidden truncate">
|
<div class="overflow-hidden truncate">
|
||||||
<b>ID:</b> <code>{{ activeDid }}</code>
|
<b>ID:</b> <code>{{ activeDid }}</code>
|
||||||
@@ -45,12 +48,12 @@
|
|||||||
class="flex flex-grow items-center bg-slate-100 rounded-md px-4 py-3 mb-2 truncate cursor-pointer"
|
class="flex flex-grow items-center bg-slate-100 rounded-md px-4 py-3 mb-2 truncate cursor-pointer"
|
||||||
@click="switchAccount(ident.did)"
|
@click="switchAccount(ident.did)"
|
||||||
>
|
>
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="ident.did === activeDid"
|
v-if="ident.did === activeDid"
|
||||||
icon="circle-check"
|
icon="circle-check"
|
||||||
class="fa-fw text-blue-600 text-xl mr-3"
|
class="fa-fw text-blue-600 text-xl mr-3"
|
||||||
/>
|
/>
|
||||||
<fa
|
<font-awesome
|
||||||
v-else
|
v-else
|
||||||
icon="circle"
|
icon="circle"
|
||||||
class="fa-fw text-slate-400 text-xl mr-3"
|
class="fa-fw text-slate-400 text-xl mr-3"
|
||||||
@@ -62,13 +65,13 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="ident.did === activeDid"
|
v-if="ident.did === activeDid"
|
||||||
icon="trash-can"
|
icon="trash-can"
|
||||||
class="text-slate-400 text-xl ml-2 mr-2 cursor-pointer"
|
class="text-slate-400 text-xl ml-2 mr-2 cursor-pointer"
|
||||||
@click="notifyCannotDelete()"
|
@click="notifyCannotDelete()"
|
||||||
/>
|
/>
|
||||||
<fa
|
<font-awesome
|
||||||
v-else
|
v-else
|
||||||
icon="trash-can"
|
icon="trash-can"
|
||||||
class="text-red-600 text-xl ml-2 mr-2 cursor-pointer"
|
class="text-red-600 text-xl ml-2 mr-2 cursor-pointer"
|
||||||
@@ -114,6 +117,7 @@ import { retrieveAllAccountsMetadata } from "../libs/util";
|
|||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class IdentitySwitcherView extends Vue {
|
export default class IdentitySwitcherView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
public activeDid = "";
|
public activeDid = "";
|
||||||
public activeDidInIdentities = false;
|
public activeDidInIdentities = false;
|
||||||
@@ -131,7 +135,10 @@ export default class IdentitySwitcherView extends Vue {
|
|||||||
const accounts = await retrieveAllAccountsMetadata();
|
const accounts = await retrieveAllAccountsMetadata();
|
||||||
for (let n = 0; n < accounts.length; n++) {
|
for (let n = 0; n < accounts.length; n++) {
|
||||||
const acct = accounts[n];
|
const acct = accounts[n];
|
||||||
this.otherIdentities.push({ id: acct.id as string, did: acct.did });
|
this.otherIdentities.push({
|
||||||
|
id: (acct.id ?? 0).toString(),
|
||||||
|
did: acct.did,
|
||||||
|
});
|
||||||
if (acct.did && this.activeDid === acct.did) {
|
if (acct.did && this.activeDid === acct.did) {
|
||||||
this.activeDidInIdentities = true;
|
this.activeDidInIdentities = true;
|
||||||
}
|
}
|
||||||
@@ -159,7 +166,7 @@ export default class IdentitySwitcherView extends Vue {
|
|||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
activeDid: did,
|
activeDid: did,
|
||||||
});
|
});
|
||||||
(this.$router as Router).push({ name: "account" });
|
this.$router.push({ name: "account" });
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteAccount(id: string) {
|
async deleteAccount(id: string) {
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<h1 class="text-lg text-center font-light relative px-7">
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
<!-- Cancel -->
|
<!-- Cancel -->
|
||||||
<button
|
<button
|
||||||
@click="$router.go(-1)"
|
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.go(-1)"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left"></fa>
|
<font-awesome icon="chevron-left"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
Import Existing Identifier
|
Import Existing Identifier
|
||||||
</h1>
|
</h1>
|
||||||
@@ -20,10 +20,10 @@
|
|||||||
<!-- id used by puppeteer test script -->
|
<!-- id used by puppeteer test script -->
|
||||||
<textarea
|
<textarea
|
||||||
id="seed-input"
|
id="seed-input"
|
||||||
|
v-model="mnemonic"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Seed Phrase"
|
placeholder="Seed Phrase"
|
||||||
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
v-model="mnemonic"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h3
|
<h3
|
||||||
@@ -35,22 +35,22 @@
|
|||||||
<div v-if="showAdvanced">
|
<div v-if="showAdvanced">
|
||||||
Enter a custom derivation path
|
Enter a custom derivation path
|
||||||
<input
|
<input
|
||||||
|
v-model="derivationPath"
|
||||||
type="text"
|
type="text"
|
||||||
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||||
v-model="derivationPath"
|
|
||||||
/>
|
/>
|
||||||
<span class="ml-4">
|
<span class="ml-4">
|
||||||
For previous uPort or Endorser users,
|
For previous uPort or Endorser users,
|
||||||
<a
|
<a
|
||||||
@click="derivationPath = UPORT_DERIVATION_PATH"
|
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
|
@click="derivationPath = UPORT_DERIVATION_PATH"
|
||||||
>
|
>
|
||||||
click here to use that value.
|
click here to use that value.
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="mt-4" v-if="numAccounts == 1">
|
<div v-if="numAccounts == 1" class="mt-4">
|
||||||
<input type="checkbox" class="mr-2" v-model="shouldErase" />
|
<input v-model="shouldErase" type="checkbox" class="mr-2" />
|
||||||
<label>Erase the previous identifier.</label>
|
<label>Erase the previous identifier.</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -65,15 +65,15 @@
|
|||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||||
<button
|
<button
|
||||||
@click="fromMnemonic()"
|
|
||||||
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
|
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
|
||||||
|
@click="fromMnemonic()"
|
||||||
>
|
>
|
||||||
Import
|
Import
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="onCancelClick()"
|
|
||||||
type="button"
|
type="button"
|
||||||
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
||||||
|
@click="onCancelClick()"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -111,6 +111,7 @@ export default class ImportAccountView extends Vue {
|
|||||||
AppString = AppString;
|
AppString = AppString;
|
||||||
|
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
address = "";
|
address = "";
|
||||||
@@ -130,7 +131,7 @@ export default class ImportAccountView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onCancelClick() {
|
public onCancelClick() {
|
||||||
(this.$router as Router).back();
|
this.$router.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
public isNotProdServer() {
|
public isNotProdServer() {
|
||||||
@@ -170,7 +171,7 @@ export default class ImportAccountView extends Vue {
|
|||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
activeDid: newId.did,
|
activeDid: newId.did,
|
||||||
});
|
});
|
||||||
(this.$router as Router).push({ name: "account" });
|
this.$router.push({ name: "account" });
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("Error saving mnemonic & updating settings:", err);
|
console.error("Error saving mnemonic & updating settings:", err);
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<h1 class="text-lg text-center font-light relative px-7">
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
<!-- Cancel -->
|
<!-- Cancel -->
|
||||||
<button
|
<button
|
||||||
@click="$router.go(-1)"
|
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.go(-1)"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left"></fa>
|
<font-awesome icon="chevron-left"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
Derive from Existing Identity
|
Derive from Existing Identity
|
||||||
</h1>
|
</h1>
|
||||||
@@ -25,21 +25,21 @@
|
|||||||
</p>
|
</p>
|
||||||
<ul class="mb-4">
|
<ul class="mb-4">
|
||||||
<li
|
<li
|
||||||
class="block bg-slate-100 rounded-md flex items-center px-4 py-3 mb-2"
|
|
||||||
v-for="dids in didArrays"
|
v-for="dids in didArrays"
|
||||||
:key="dids[0]"
|
:key="dids[0]"
|
||||||
|
class="block bg-slate-100 rounded-md flex items-center px-4 py-3 mb-2"
|
||||||
@click="switchAccount(dids[0])"
|
@click="switchAccount(dids[0])"
|
||||||
>
|
>
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="dids[0] == selectedArrayFirstDid"
|
v-if="dids[0] == selectedArrayFirstDid"
|
||||||
icon="circle"
|
icon="circle"
|
||||||
class="fa-fw text-blue-500 text-xl mr-3"
|
class="fa-fw text-blue-500 text-xl mr-3"
|
||||||
></fa>
|
></font-awesome>
|
||||||
<fa
|
<font-awesome
|
||||||
v-else
|
v-else
|
||||||
icon="circle"
|
icon="circle"
|
||||||
class="fa-fw text-slate-400 text-xl mr-3"
|
class="fa-fw text-slate-400 text-xl mr-3"
|
||||||
></fa>
|
></font-awesome>
|
||||||
<span class="overflow-hidden">
|
<span class="overflow-hidden">
|
||||||
<div class="text-sm text-slate-500 truncate">
|
<div class="text-sm text-slate-500 truncate">
|
||||||
<code>{{ dids.join(",") }}</code>
|
<code>{{ dids.join(",") }}</code>
|
||||||
@@ -51,15 +51,15 @@
|
|||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||||
<button
|
<button
|
||||||
@click="incrementDerivation()"
|
|
||||||
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
|
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
|
||||||
|
@click="incrementDerivation()"
|
||||||
>
|
>
|
||||||
Increment and Import
|
Increment and Import
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="onCancelClick()"
|
|
||||||
type="button"
|
type="button"
|
||||||
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
||||||
|
@click="onCancelClick()"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
import { Router, RouteLocationNormalizedLoaded } from "vue-router";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_ROOT_DERIVATION_PATH,
|
DEFAULT_ROOT_DERIVATION_PATH,
|
||||||
@@ -86,6 +86,9 @@ import { retrieveAllFullyDecryptedAccounts } from "../libs/util";
|
|||||||
components: {},
|
components: {},
|
||||||
})
|
})
|
||||||
export default class ImportAccountView extends Vue {
|
export default class ImportAccountView extends Vue {
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
derivationPath = DEFAULT_ROOT_DERIVATION_PATH;
|
derivationPath = DEFAULT_ROOT_DERIVATION_PATH;
|
||||||
didArrays: Array<Array<string>> = [];
|
didArrays: Array<Array<string>> = [];
|
||||||
selectedArrayFirstDid = "";
|
selectedArrayFirstDid = "";
|
||||||
@@ -102,7 +105,7 @@ export default class ImportAccountView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onCancelClick() {
|
public onCancelClick() {
|
||||||
(this.$router as Router).back();
|
this.$router.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
public switchAccount(did: string) {
|
public switchAccount(did: string) {
|
||||||
@@ -151,7 +154,7 @@ export default class ImportAccountView extends Vue {
|
|||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
activeDid: newId.did,
|
activeDid: newId.did,
|
||||||
});
|
});
|
||||||
(this.$router as Router).push({ name: "account" });
|
this.$router.push({ name: "account" });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error saving mnemonic & updating settings:", err);
|
console.error("Error saving mnemonic & updating settings:", err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
v-if="checkingInvite"
|
v-if="checkingInvite"
|
||||||
class="text-lg text-center font-light relative px-7"
|
class="text-lg text-center font-light relative px-7"
|
||||||
>
|
>
|
||||||
<fa icon="spinner" class="fa-spin-pulse" />
|
<font-awesome icon="spinner" class="fa-spin-pulse" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-center mt-4">
|
<div v-else class="text-center mt-4">
|
||||||
<p>That invitation did not work.</p>
|
<p>That invitation did not work.</p>
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<button
|
<button
|
||||||
@click="() => processInvite(inputJwt, true)"
|
|
||||||
class="ml-2 p-2 bg-blue-500 text-white rounded"
|
class="ml-2 p-2 bg-blue-500 text-white rounded"
|
||||||
|
@click="() => processInvite(inputJwt, true)"
|
||||||
>
|
>
|
||||||
Accept
|
Accept
|
||||||
</button>
|
</button>
|
||||||
@@ -55,6 +55,7 @@ import { generateSaveAndActivateIdentity } from "../libs/util";
|
|||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class InviteOneAcceptView extends Vue {
|
export default class InviteOneAcceptView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
activeDid: string = "";
|
activeDid: string = "";
|
||||||
apiServer: string = "";
|
apiServer: string = "";
|
||||||
@@ -123,7 +124,7 @@ export default class InviteOneAcceptView extends Vue {
|
|||||||
|
|
||||||
// That's good enough for an initial check.
|
// That's good enough for an initial check.
|
||||||
// Send them to the contacts page to finish, with inviteJwt in the query string.
|
// Send them to the contacts page to finish, with inviteJwt in the query string.
|
||||||
(this.$router as Router).push({
|
this.$router.push({
|
||||||
name: "contacts",
|
name: "contacts",
|
||||||
query: { inviteJwt: jwt },
|
query: { inviteJwt: jwt },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
@click="$router.back()"
|
@click="$router.back()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
class="fixed right-6 top-12 text-center text-4xl leading-none bg-green-600 text-white w-14 py-2.5 rounded-full"
|
class="fixed right-6 top-12 text-center text-4xl leading-none bg-green-600 text-white w-14 py-2.5 rounded-full"
|
||||||
@click="createInvite()"
|
@click="createInvite()"
|
||||||
>
|
>
|
||||||
<fa icon="plus" class="fa-fw"></fa>
|
<font-awesome icon="plus" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<InviteDialog ref="inviteDialog" />
|
<InviteDialog ref="inviteDialog" />
|
||||||
@@ -72,16 +72,18 @@
|
|||||||
!invite.redeemedAt &&
|
!invite.redeemedAt &&
|
||||||
invite.expiresAt > new Date().toISOString()
|
invite.expiresAt > new Date().toISOString()
|
||||||
"
|
"
|
||||||
|
class="text-center text-blue-500 cursor-pointer"
|
||||||
|
:title="inviteLink(invite.jwt)"
|
||||||
@click="
|
@click="
|
||||||
copyInviteAndNotify(invite.inviteIdentifier, invite.jwt)
|
copyInviteAndNotify(invite.inviteIdentifier, invite.jwt)
|
||||||
"
|
"
|
||||||
class="text-center text-blue-500 cursor-pointer"
|
|
||||||
:title="inviteLink(invite.jwt)"
|
|
||||||
>
|
>
|
||||||
{{ getTruncatedInviteId(invite.inviteIdentifier) }}
|
{{ getTruncatedInviteId(invite.inviteIdentifier) }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else
|
v-else
|
||||||
|
class="text-center text-slate-500 cursor-pointer"
|
||||||
|
:title="inviteLink(invite.jwt)"
|
||||||
@click="
|
@click="
|
||||||
showInvite(
|
showInvite(
|
||||||
invite.inviteIdentifier,
|
invite.inviteIdentifier,
|
||||||
@@ -89,8 +91,6 @@
|
|||||||
invite.expiresAt < new Date().toISOString(),
|
invite.expiresAt < new Date().toISOString(),
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
class="text-center text-slate-500 cursor-pointer"
|
|
||||||
:title="inviteLink(invite.jwt)"
|
|
||||||
>
|
>
|
||||||
{{ getTruncatedInviteId(invite.inviteIdentifier) }}
|
{{ getTruncatedInviteId(invite.inviteIdentifier) }}
|
||||||
</span>
|
</span>
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
<br />
|
<br />
|
||||||
{{ getTruncatedRedeemedBy(invite.redeemedBy) }}
|
{{ getTruncatedRedeemedBy(invite.redeemedBy) }}
|
||||||
<br />
|
<br />
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="invite.redeemedBy && !contactsRedeemed[invite.redeemedBy]"
|
v-if="invite.redeemedBy && !contactsRedeemed[invite.redeemedBy]"
|
||||||
icon="plus"
|
icon="plus"
|
||||||
class="bg-green-600 text-white px-1 py-1 rounded-full cursor-pointer"
|
class="bg-green-600 text-white px-1 py-1 rounded-full cursor-pointer"
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="trash-can"
|
icon="trash-can"
|
||||||
class="text-red-600 text-xl ml-2 mr-2 cursor-pointer"
|
class="text-red-600 text-xl ml-2 mr-2 cursor-pointer"
|
||||||
@click="deleteInvite(invite.inviteIdentifier, invite.notes)"
|
@click="deleteInvite(invite.inviteIdentifier, invite.notes)"
|
||||||
@@ -132,6 +132,7 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
import { Router } from "vue-router";
|
||||||
|
|
||||||
import ContactNameDialog from "../components/ContactNameDialog.vue";
|
import ContactNameDialog from "../components/ContactNameDialog.vue";
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
@@ -156,6 +157,7 @@ interface Invite {
|
|||||||
})
|
})
|
||||||
export default class InviteOneView extends Vue {
|
export default class InviteOneView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
invites: Invite[] = [];
|
invites: Invite[] = [];
|
||||||
activeDid: string = "";
|
activeDid: string = "";
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
<div id="ViewBreadcrumb" class="mb-8">
|
<div id="ViewBreadcrumb" class="mb-8">
|
||||||
<h1 class="text-lg text-center font-light relative px-7">
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<fa
|
<font-awesome
|
||||||
icon="chevron-left"
|
icon="chevron-left"
|
||||||
@click="$router.back()"
|
|
||||||
class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.back()"
|
||||||
/>
|
/>
|
||||||
New Activity For You
|
New Activity For You
|
||||||
</h1>
|
</h1>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<span class="text-lg font-medium ml-4"
|
<span class="text-lg font-medium ml-4"
|
||||||
>New Offer{{ newOffersToUser.length === 1 ? "" : "s" }} To You</span
|
>New Offer{{ newOffersToUser.length === 1 ? "" : "s" }} To You</span
|
||||||
>
|
>
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="newOffersToUser.length > 0"
|
v-if="newOffersToUser.length > 0"
|
||||||
:icon="showOffersDetails ? 'chevron-down' : 'chevron-right'"
|
:icon="showOffersDetails ? 'chevron-down' : 'chevron-right'"
|
||||||
class="cursor-pointer ml-4 mr-4 text-lg"
|
class="cursor-pointer ml-4 mr-4 text-lg"
|
||||||
@@ -59,12 +59,15 @@
|
|||||||
:to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }"
|
:to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }"
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
>
|
>
|
||||||
<fa icon="file-lines" class="pl-2 text-blue-500 cursor-pointer" />
|
<font-awesome
|
||||||
|
icon="file-lines"
|
||||||
|
class="pl-2 text-blue-500 cursor-pointer"
|
||||||
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
<!-- New line that appears on hover or when the offer is clicked -->
|
<!-- New line that appears on hover or when the offer is clicked -->
|
||||||
<div
|
<div
|
||||||
@click="markOffersAsReadStartingWith(offer.jwtId)"
|
|
||||||
class="absolute left-0 w-full text-left text-gray-500 text-sm hidden group-hover:flex cursor-pointer items-center"
|
class="absolute left-0 w-full text-left text-gray-500 text-sm hidden group-hover:flex cursor-pointer items-center"
|
||||||
|
@click="markOffersAsReadStartingWith(offer.jwtId)"
|
||||||
>
|
>
|
||||||
<span class="inline-block w-8 h-px bg-gray-500 mr-2" />
|
<span class="inline-block w-8 h-px bg-gray-500 mr-2" />
|
||||||
Click to keep all above as new offers
|
Click to keep all above as new offers
|
||||||
@@ -87,7 +90,7 @@
|
|||||||
>New Offer{{ newOffersToUserProjects.length === 1 ? "" : "s" }} To
|
>New Offer{{ newOffersToUserProjects.length === 1 ? "" : "s" }} To
|
||||||
Your Projects</span
|
Your Projects</span
|
||||||
>
|
>
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="newOffersToUserProjects.length > 0"
|
v-if="newOffersToUserProjects.length > 0"
|
||||||
:icon="
|
:icon="
|
||||||
showOffersToUserProjectsDetails ? 'chevron-down' : 'chevron-right'
|
showOffersToUserProjectsDetails ? 'chevron-down' : 'chevron-right'
|
||||||
@@ -125,12 +128,15 @@
|
|||||||
:to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }"
|
:to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }"
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
>
|
>
|
||||||
<fa icon="file-lines" class="pl-2 text-blue-500 cursor-pointer" />
|
<font-awesome
|
||||||
|
icon="file-lines"
|
||||||
|
class="pl-2 text-blue-500 cursor-pointer"
|
||||||
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
<!-- New line that appears on hover -->
|
<!-- New line that appears on hover -->
|
||||||
<div
|
<div
|
||||||
@click="markOffersToUserProjectsAsReadStartingWith(offer.jwtId)"
|
|
||||||
class="absolute left-0 w-full text-left text-gray-500 text-sm hidden group-hover:flex cursor-pointer items-center"
|
class="absolute left-0 w-full text-left text-gray-500 text-sm hidden group-hover:flex cursor-pointer items-center"
|
||||||
|
@click="markOffersToUserProjectsAsReadStartingWith(offer.jwtId)"
|
||||||
>
|
>
|
||||||
<span class="inline-block w-8 h-px bg-gray-500 mr-2" />
|
<span class="inline-block w-8 h-px bg-gray-500 mr-2" />
|
||||||
Click to keep all above as new offers
|
Click to keep all above as new offers
|
||||||
@@ -154,13 +160,13 @@ import {
|
|||||||
updateAccountSettings,
|
updateAccountSettings,
|
||||||
} from "../db/index";
|
} from "../db/index";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
|
import { Router } from "vue-router";
|
||||||
|
import { OfferSummaryRecord, OfferToPlanSummaryRecord } from "../interfaces";
|
||||||
import {
|
import {
|
||||||
didInfo,
|
didInfo,
|
||||||
displayAmount,
|
displayAmount,
|
||||||
getNewOffersToUser,
|
getNewOffersToUser,
|
||||||
getNewOffersToUserProjects,
|
getNewOffersToUserProjects,
|
||||||
OfferSummaryRecord,
|
|
||||||
OfferToPlanSummaryRecord,
|
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import { retrieveAccountDids } from "../libs/util";
|
import { retrieveAccountDids } from "../libs/util";
|
||||||
|
|
||||||
@@ -169,7 +175,7 @@ import { retrieveAccountDids } from "../libs/util";
|
|||||||
})
|
})
|
||||||
export default class NewActivityView extends Vue {
|
export default class NewActivityView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
allMyDids: string[] = [];
|
allMyDids: string[] = [];
|
||||||
|
|||||||
@@ -5,20 +5,20 @@
|
|||||||
<h1 class="text-lg text-center font-light relative px-7">
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
<!-- Cancel -->
|
<!-- Cancel -->
|
||||||
<button
|
<button
|
||||||
@click="$router.go(-1)"
|
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.back()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
Edit Identity
|
Edit Identity
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
v-model="givenName"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
v-model="givenName"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
@@ -54,6 +54,8 @@ import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
|||||||
components: {},
|
components: {},
|
||||||
})
|
})
|
||||||
export default class NewEditAccountView extends Vue {
|
export default class NewEditAccountView extends Vue {
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
givenName = "";
|
givenName = "";
|
||||||
|
|
||||||
// 'created' hook runs when the Vue instance is first created
|
// 'created' hook runs when the Vue instance is first created
|
||||||
@@ -69,11 +71,11 @@ export default class NewEditAccountView extends Vue {
|
|||||||
firstName: this.givenName,
|
firstName: this.givenName,
|
||||||
lastName: "", // deprecated, pre v 0.1.3
|
lastName: "", // deprecated, pre v 0.1.3
|
||||||
});
|
});
|
||||||
(this.$router as Router).back();
|
this.$router.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickCancel() {
|
onClickCancel() {
|
||||||
(this.$router as Router).back();
|
this.$router.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
<!-- Cancel -->
|
<!-- Cancel -->
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<button
|
<button
|
||||||
@click="$router.go(-1)"
|
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.back()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
Edit Project Idea
|
Edit Project Idea
|
||||||
</h1>
|
</h1>
|
||||||
@@ -25,10 +25,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
v-model="fullClaim.name"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Idea Name"
|
placeholder="Idea Name"
|
||||||
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
v-model="fullClaim.name"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex justify-center mt-4">
|
<div class="flex justify-center mt-4">
|
||||||
@@ -36,14 +36,14 @@
|
|||||||
<a :href="imageUrl" target="_blank" class="text-blue-500 ml-4">
|
<a :href="imageUrl" target="_blank" class="text-blue-500 ml-4">
|
||||||
<img :src="imageUrl" class="h-24 rounded-xl" />
|
<img :src="imageUrl" class="h-24 rounded-xl" />
|
||||||
</a>
|
</a>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="trash-can"
|
icon="trash-can"
|
||||||
@click="confirmDeleteImage"
|
|
||||||
class="text-red-500 fa-fw ml-8 mt-10"
|
class="text-red-500 fa-fw ml-8 mt-10"
|
||||||
|
@click="confirmDeleteImage"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="camera"
|
icon="camera"
|
||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md"
|
||||||
@click="openImageDialog"
|
@click="openImageDialog"
|
||||||
@@ -53,27 +53,27 @@
|
|||||||
<ImageMethodDialog ref="imageDialog" />
|
<ImageMethodDialog ref="imageDialog" />
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
v-model="agentDid"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Other Authorized Representative"
|
placeholder="Other Authorized Representative"
|
||||||
class="mt-4 block w-full rounded border border-slate-400 px-3 py-2"
|
class="mt-4 block w-full rounded border border-slate-400 px-3 py-2"
|
||||||
v-model="agentDid"
|
|
||||||
/>
|
/>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<p v-if="activeDid != projectIssuerDid && agentDid != projectIssuerDid">
|
<p v-if="activeDid != projectIssuerDid && agentDid != projectIssuerDid">
|
||||||
<span class="text-red-500">Beware!</span>
|
<span class="text-red-500">Beware!</span>
|
||||||
If you save this, the original project owner will no longer be able to
|
If you save this, the original project owner will no longer be able to
|
||||||
edit it.
|
edit it.
|
||||||
<button @click="agentDid = projectIssuerDid" class="text-blue-500">
|
<button class="text-blue-500" @click="agentDid = projectIssuerDid">
|
||||||
Click here to make the original owner an authorized representative.
|
Click here to make the original owner an authorized representative.
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
|
v-model="fullClaim.description"
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
class="block w-full rounded border border-slate-400 px-3 py-2"
|
class="block w-full rounded border border-slate-400 px-3 py-2"
|
||||||
rows="5"
|
rows="5"
|
||||||
v-model="fullClaim.description"
|
|
||||||
maxlength="5000"
|
maxlength="5000"
|
||||||
></textarea>
|
></textarea>
|
||||||
<div class="text-xs text-slate-500 italic">
|
<div class="text-xs text-slate-500 italic">
|
||||||
@@ -102,9 +102,9 @@
|
|||||||
class="rounded border border-slate-400 px-3 py-2"
|
class="rounded border border-slate-400 px-3 py-2"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
|
v-model="startTimeInput"
|
||||||
:disabled="!startDateInput"
|
:disabled="!startDateInput"
|
||||||
placeholder="Start Time"
|
placeholder="Start Time"
|
||||||
v-model="startTimeInput"
|
|
||||||
type="time"
|
type="time"
|
||||||
class="rounded border border-slate-400 ml-2 px-3 py-2"
|
class="rounded border border-slate-400 ml-2 px-3 py-2"
|
||||||
/>
|
/>
|
||||||
@@ -127,9 +127,9 @@
|
|||||||
class="ml-2 rounded border border-slate-400 px-3 py-2"
|
class="ml-2 rounded border border-slate-400 px-3 py-2"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
|
v-model="endTimeInput"
|
||||||
:disabled="!endDateInput"
|
:disabled="!endDateInput"
|
||||||
placeholder="End Time"
|
placeholder="End Time"
|
||||||
v-model="endTimeInput"
|
|
||||||
type="time"
|
type="time"
|
||||||
class="rounded border border-slate-400 ml-2 px-3 py-2"
|
class="rounded border border-slate-400 ml-2 px-3 py-2"
|
||||||
/>
|
/>
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
class="flex items-center mt-4"
|
class="flex items-center mt-4"
|
||||||
@click="includeLocation = !includeLocation"
|
@click="includeLocation = !includeLocation"
|
||||||
>
|
>
|
||||||
<input type="checkbox" class="mr-2" v-model="includeLocation" />
|
<input v-model="includeLocation" type="checkbox" class="mr-2" />
|
||||||
<label for="includeLocation">Include Location</label>
|
<label for="includeLocation">Include Location</label>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="includeLocation" class="mb-4 aspect-video">
|
<div v-if="includeLocation" class="mb-4 aspect-video">
|
||||||
@@ -179,9 +179,9 @@
|
|||||||
class="items-center mb-4"
|
class="items-center mb-4"
|
||||||
>
|
>
|
||||||
<div class="flex" @click="sendToTrustroots = !sendToTrustroots">
|
<div class="flex" @click="sendToTrustroots = !sendToTrustroots">
|
||||||
<input type="checkbox" class="mr-2" v-model="sendToTrustroots" />
|
<input v-model="sendToTrustroots" type="checkbox" class="mr-2" />
|
||||||
<label>Send to Trustroots</label>
|
<label>Send to Trustroots</label>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="circle-info"
|
icon="circle-info"
|
||||||
class="text-blue-500 ml-2 cursor-pointer"
|
class="text-blue-500 ml-2 cursor-pointer"
|
||||||
@click.stop="showNostrPartnerInfo"
|
@click.stop="showNostrPartnerInfo"
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
<div class="flex" @click="sendToTripHopping = !sendToTripHopping">
|
<div class="flex" @click="sendToTripHopping = !sendToTripHopping">
|
||||||
<input type="checkbox" class="mr-2" v-model="sendToTripHopping" />
|
<input type="checkbox" class="mr-2" v-model="sendToTripHopping" />
|
||||||
<label>Send to TripHopping</label>
|
<label>Send to TripHopping</label>
|
||||||
<fa icon="circle-info" class="text-blue-500 ml-2 cursor-pointer" @click.stop="showNostrPartnerInfo" />
|
<font-awesome icon="circle-info" class="text-blue-500 ml-2 cursor-pointer" @click.stop="showNostrPartnerInfo" />
|
||||||
</div>
|
</div>
|
||||||
-->
|
-->
|
||||||
</div>
|
</div>
|
||||||
@@ -229,10 +229,11 @@
|
|||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import { AxiosError, AxiosRequestHeaders } from "axios";
|
import { AxiosError, AxiosRequestHeaders } from "axios";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import { finalizeEvent, serializeEvent } from "nostr-tools";
|
import { finalizeEvent } from "nostr-tools/lib/esm/index.js";
|
||||||
// these core imports could also be included as "import type ..."
|
import {
|
||||||
import { EventTemplate, UnsignedEvent, VerifiedEvent } from "nostr-tools/core";
|
accountFromExtendedKey,
|
||||||
import * as nip06 from "nostr-tools/nip06";
|
extendedKeysFromSeedWords,
|
||||||
|
} from "nostr-tools/lib/esm/nip06.js";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
||||||
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
@@ -245,21 +246,30 @@ import {
|
|||||||
NotificationIface,
|
NotificationIface,
|
||||||
} from "../constants/app";
|
} from "../constants/app";
|
||||||
import { retrieveSettingsForActiveAccount } from "../db/index";
|
import { retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
|
import { PlanVerifiableCredential } from "../interfaces";
|
||||||
import {
|
import {
|
||||||
createEndorserJwtVcFromClaim,
|
createEndorserJwtVcFromClaim,
|
||||||
getHeaders,
|
getHeaders,
|
||||||
PlanVerifiableCredential,
|
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import {
|
import {
|
||||||
retrieveAccountCount,
|
retrieveAccountCount,
|
||||||
retrieveFullyDecryptedAccount,
|
retrieveFullyDecryptedAccount,
|
||||||
} from "../libs/util";
|
} from "../libs/util";
|
||||||
|
import {
|
||||||
|
EventTemplate,
|
||||||
|
UnsignedEvent,
|
||||||
|
VerifiedEvent,
|
||||||
|
} from "nostr-tools/lib/esm/index.js";
|
||||||
|
import { serializeEvent } from "nostr-tools/lib/esm/index.js";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { ImageMethodDialog, LMap, LMarker, LTileLayer, QuickNav },
|
components: { ImageMethodDialog, LMap, LMarker, LTileLayer, QuickNav },
|
||||||
})
|
})
|
||||||
export default class NewEditProjectView extends Vue {
|
export default class NewEditProjectView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
errNote(message: string) {
|
errNote(message: string) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{ group: "alert", type: "danger", title: "Error", text: message },
|
{ group: "alert", type: "danger", title: "Error", text: message },
|
||||||
@@ -305,8 +315,7 @@ export default class NewEditProjectView extends Vue {
|
|||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.showGeneralAdvanced = !!settings.showGeneralAdvanced;
|
this.showGeneralAdvanced = !!settings.showGeneralAdvanced;
|
||||||
|
|
||||||
this.projectId =
|
this.projectId = (this.$route.query["projectId"] as string) || "";
|
||||||
(this.$route as RouteLocationNormalizedLoaded).query["projectId"] || "";
|
|
||||||
|
|
||||||
if (this.projectId) {
|
if (this.projectId) {
|
||||||
if (this.numAccounts === 0) {
|
if (this.numAccounts === 0) {
|
||||||
@@ -591,7 +600,7 @@ export default class NewEditProjectView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(this.$router as Router).push({ path: "/project/" + projectPath });
|
this.$router.push({ path: "/project/" + projectPath });
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error(
|
||||||
"Got unexpected 'data' inside response from server",
|
"Got unexpected 'data' inside response from server",
|
||||||
@@ -668,7 +677,7 @@ export default class NewEditProjectView extends Vue {
|
|||||||
// remove any trailing '
|
// remove any trailing '
|
||||||
const finalDerNumNoApostrophe = finalDerNum?.replace(/'/g, "");
|
const finalDerNumNoApostrophe = finalDerNum?.replace(/'/g, "");
|
||||||
const accountNum = Number(finalDerNumNoApostrophe || 0);
|
const accountNum = Number(finalDerNumNoApostrophe || 0);
|
||||||
const extPubPri = nip06.extendedKeysFromSeedWords(
|
const extPubPri = extendedKeysFromSeedWords(
|
||||||
account?.mnemonic as string,
|
account?.mnemonic as string,
|
||||||
"",
|
"",
|
||||||
accountNum,
|
accountNum,
|
||||||
@@ -676,7 +685,7 @@ export default class NewEditProjectView extends Vue {
|
|||||||
const publicExtendedKey: string = extPubPri?.publicExtendedKey;
|
const publicExtendedKey: string = extPubPri?.publicExtendedKey;
|
||||||
const privateExtendedKey = extPubPri?.privateExtendedKey;
|
const privateExtendedKey = extPubPri?.privateExtendedKey;
|
||||||
const privateBytes: Uint8Array =
|
const privateBytes: Uint8Array =
|
||||||
nip06.accountFromExtendedKey(privateExtendedKey).privateKey;
|
accountFromExtendedKey(privateExtendedKey).privateKey;
|
||||||
// No real content is necessary, we just want something signed,
|
// No real content is necessary, we just want something signed,
|
||||||
// so we might as well use nostr libs for nostr functions.
|
// so we might as well use nostr libs for nostr functions.
|
||||||
// Besides: someday we may create real content that we can relay.
|
// Besides: someday we may create real content that we can relay.
|
||||||
@@ -710,8 +719,7 @@ export default class NewEditProjectView extends Vue {
|
|||||||
const endorserPartnerUrl = partnerServer + "/api/partner/link";
|
const endorserPartnerUrl = partnerServer + "/api/partner/link";
|
||||||
const timeSafariUrl = window.location.origin + "/claim/" + jwtId;
|
const timeSafariUrl = window.location.origin + "/claim/" + jwtId;
|
||||||
const content = this.fullClaim.name + " - see " + timeSafariUrl;
|
const content = this.fullClaim.name + " - see " + timeSafariUrl;
|
||||||
const publicKeyHex =
|
const publicKeyHex = accountFromExtendedKey(publicExtendedKey).publicKey;
|
||||||
nip06.accountFromExtendedKey(publicExtendedKey).publicKey;
|
|
||||||
const unsignedPayload: UnsignedEvent = {
|
const unsignedPayload: UnsignedEvent = {
|
||||||
// why doesn't "...signedPayload" work?
|
// why doesn't "...signedPayload" work?
|
||||||
kind: signedPayload.kind,
|
kind: signedPayload.kind,
|
||||||
@@ -810,7 +818,7 @@ export default class NewEditProjectView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onCancelClick() {
|
public onCancelClick() {
|
||||||
(this.$router as Router).back();
|
this.$router.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
public showNostrPartnerInfo() {
|
public showNostrPartnerInfo() {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
@click="$router.back()"
|
@click="$router.back()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -25,16 +25,16 @@
|
|||||||
<div />
|
<div />
|
||||||
<div v-if="loading">
|
<div v-if="loading">
|
||||||
<span class="text-xl">Creating... </span>
|
<span class="text-xl">Creating... </span>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="spinner"
|
icon="spinner"
|
||||||
class="fa-spin fa-spin-pulse"
|
class="fa-spin fa-spin-pulse"
|
||||||
color="green"
|
color="green"
|
||||||
size="128"
|
size="128"
|
||||||
></fa>
|
></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<span class="text-xl">Created!</span>
|
<span class="text-xl">Created!</span>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="burst"
|
icon="burst"
|
||||||
class="fa-beat px-12"
|
class="fa-beat px-12"
|
||||||
color="green"
|
color="green"
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
--fa-animation-iteration-count: 1;
|
--fa-animation-iteration-count: 1;
|
||||||
--fa-beat-scale: 6;
|
--fa-beat-scale: 6;
|
||||||
"
|
"
|
||||||
></fa>
|
></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
</div>
|
</div>
|
||||||
@@ -62,12 +62,13 @@ import QuickNav from "../components/QuickNav.vue";
|
|||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class NewIdentifierView extends Vue {
|
export default class NewIdentifierView extends Vue {
|
||||||
loading = true;
|
loading = true;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await generateSaveAndActivateIdentity();
|
await generateSaveAndActivateIdentity();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
(this.$router as Router).push({ name: "home" });
|
this.$router.push({ name: "home" });
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
@click="cancelBack()"
|
@click="cancelBack()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -33,9 +33,9 @@
|
|||||||
>
|
>
|
||||||
</h1>
|
</h1>
|
||||||
<textarea
|
<textarea
|
||||||
|
v-model="descriptionOfItem"
|
||||||
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||||
placeholder="What is offered"
|
placeholder="What is offered"
|
||||||
v-model="descriptionOfItem"
|
|
||||||
data-testId="itemDescription"
|
data-testId="itemDescription"
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-row justify-center">
|
<div class="flex flex-row justify-center">
|
||||||
@@ -49,19 +49,19 @@
|
|||||||
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
@click="amountInput === '0' ? null : decrement()"
|
@click="amountInput === '0' ? null : decrement()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" />
|
<font-awesome icon="chevron-left" />
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
v-model="amountInput"
|
||||||
type="number"
|
type="number"
|
||||||
class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20"
|
class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20"
|
||||||
v-model="amountInput"
|
|
||||||
data-testId="inputOfferAmount"
|
data-testId="inputOfferAmount"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
@click="increment()"
|
@click="increment()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-right" />
|
<font-awesome icon="chevron-right" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -72,9 +72,9 @@
|
|||||||
Conditions
|
Conditions
|
||||||
</span>
|
</span>
|
||||||
<textarea
|
<textarea
|
||||||
|
v-model="descriptionOfCondition"
|
||||||
class="w-full border border-slate-400 px-3 py-2 rounded-r"
|
class="w-full border border-slate-400 px-3 py-2 rounded-r"
|
||||||
placeholder="Prerequisites, other people to include, etc."
|
placeholder="Prerequisites, other people to include, etc."
|
||||||
v-model="descriptionOfCondition"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -94,11 +94,11 @@
|
|||||||
<div class="h-7 mt-4 flex">
|
<div class="h-7 mt-4 flex">
|
||||||
<input
|
<input
|
||||||
v-if="projectId && !offeredToRecipient"
|
v-if="projectId && !offeredToRecipient"
|
||||||
|
v-model="offeredToProject"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="h-6 w-6 mr-2"
|
class="h-6 w-6 mr-2"
|
||||||
v-model="offeredToProject"
|
|
||||||
/>
|
/>
|
||||||
<fa
|
<font-awesome
|
||||||
v-else
|
v-else
|
||||||
icon="square"
|
icon="square"
|
||||||
class="bg-slate-500 text-slate-500 h-5 w-5 px-0.5 py-0.5 mr-2 rounded"
|
class="bg-slate-500 text-slate-500 h-5 w-5 px-0.5 py-0.5 mr-2 rounded"
|
||||||
@@ -116,11 +116,11 @@
|
|||||||
<div class="h-7 mt-4 flex">
|
<div class="h-7 mt-4 flex">
|
||||||
<input
|
<input
|
||||||
v-if="recipientDid && !offeredToProject"
|
v-if="recipientDid && !offeredToProject"
|
||||||
|
v-model="offeredToRecipient"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="h-6 w-6 mr-2"
|
class="h-6 w-6 mr-2"
|
||||||
v-model="offeredToRecipient"
|
|
||||||
/>
|
/>
|
||||||
<fa
|
<font-awesome
|
||||||
v-else
|
v-else
|
||||||
icon="square"
|
icon="square"
|
||||||
class="bg-slate-500 text-slate-500 h-5 w-5 px-0.5 py-0.5 mr-2 rounded"
|
class="bg-slate-500 text-slate-500 h-5 w-5 px-0.5 py-0.5 mr-2 rounded"
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
|
|
||||||
<p class="text-center mb-2 mt-6 italic">
|
<p class="text-center mb-2 mt-6 italic">
|
||||||
Sign & Send to publish to the world
|
Sign & Send to publish to the world
|
||||||
<fa
|
<font-awesome
|
||||||
icon="circle-info"
|
icon="circle-info"
|
||||||
class="pl-2 text-blue-500 cursor-pointer"
|
class="pl-2 text-blue-500 cursor-pointer"
|
||||||
@click="explainData()"
|
@click="explainData()"
|
||||||
@@ -176,20 +176,19 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import TopMessage from "../components/TopMessage.vue";
|
import TopMessage from "../components/TopMessage.vue";
|
||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
|
import { GenericCredWrapper, OfferVerifiableCredential } from "../interfaces";
|
||||||
import {
|
import {
|
||||||
createAndSubmitOffer,
|
createAndSubmitOffer,
|
||||||
didInfo,
|
didInfo,
|
||||||
editAndSubmitOffer,
|
editAndSubmitOffer,
|
||||||
GenericCredWrapper,
|
|
||||||
getPlanFromCache,
|
getPlanFromCache,
|
||||||
hydrateOffer,
|
hydrateOffer,
|
||||||
OfferVerifiableCredential,
|
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import * as libsUtil from "../libs/util";
|
import * as libsUtil from "../libs/util";
|
||||||
import { retrieveAccountDids } from "../libs/util";
|
import { retrieveAccountDids } from "../libs/util";
|
||||||
@@ -202,6 +201,8 @@ import { retrieveAccountDids } from "../libs/util";
|
|||||||
})
|
})
|
||||||
export default class OfferDetailsView extends Vue {
|
export default class OfferDetailsView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
@@ -229,9 +230,9 @@ export default class OfferDetailsView extends Vue {
|
|||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
try {
|
try {
|
||||||
this.prevCredToEdit = (this.$route as Router).query["prevCredToEdit"]
|
this.prevCredToEdit = (this.$route.query["prevCredToEdit"] as string)
|
||||||
? (JSON.parse(
|
? (JSON.parse(
|
||||||
(this.$route as Router).query["prevCredToEdit"],
|
this.$route.query["prevCredToEdit"] as string,
|
||||||
) as GenericCredWrapper<OfferVerifiableCredential>)
|
) as GenericCredWrapper<OfferVerifiableCredential>)
|
||||||
: undefined;
|
: undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -249,28 +250,28 @@ export default class OfferDetailsView extends Vue {
|
|||||||
const prevAmount =
|
const prevAmount =
|
||||||
this.prevCredToEdit?.claim?.includesObject?.amountOfThisGood;
|
this.prevCredToEdit?.claim?.includesObject?.amountOfThisGood;
|
||||||
this.amountInput =
|
this.amountInput =
|
||||||
(this.$route as Router).query["amountInput"] ||
|
(this.$route.query["amountInput"] as string) ||
|
||||||
(prevAmount ? String(prevAmount) : "") ||
|
(prevAmount ? String(prevAmount) : "") ||
|
||||||
this.amountInput;
|
this.amountInput;
|
||||||
this.unitCode = ((this.$route as Router).query["unitCode"] ||
|
this.unitCode = ((this.$route.query["unitCode"] as string) ||
|
||||||
this.prevCredToEdit?.claim?.includesObject?.unitCode ||
|
this.prevCredToEdit?.claim?.includesObject?.unitCode ||
|
||||||
this.unitCode) as string;
|
this.unitCode) as string;
|
||||||
|
|
||||||
this.descriptionOfCondition =
|
this.descriptionOfCondition =
|
||||||
this.prevCredToEdit?.claim?.description || this.descriptionOfCondition;
|
this.prevCredToEdit?.claim?.description || this.descriptionOfCondition;
|
||||||
this.descriptionOfItem =
|
this.descriptionOfItem =
|
||||||
(this.$route as Router).query["description"] ||
|
(this.$route.query["description"] as string) ||
|
||||||
this.prevCredToEdit?.claim?.itemOffered?.description ||
|
this.prevCredToEdit?.claim?.itemOffered?.description ||
|
||||||
this.descriptionOfItem;
|
this.descriptionOfItem;
|
||||||
this.destinationPathAfter = (this.$route as Router).query[
|
this.destinationPathAfter =
|
||||||
"destinationPathAfter"
|
(this.$route.query["destinationPathAfter"] as string) || "";
|
||||||
];
|
this.offererDid = ((this.$route.query["offererDid"] as string) ||
|
||||||
this.offererDid = ((this.$route as Router).query["offererDid"] ||
|
(this.prevCredToEdit?.claim?.agent as unknown as { identifier: string })
|
||||||
this.prevCredToEdit?.claim?.agent?.identifier ||
|
?.identifier ||
|
||||||
this.offererDid) as string;
|
this.offererDid) as string;
|
||||||
this.hideBackButton =
|
this.hideBackButton =
|
||||||
(this.$route as Router).query["hideBackButton"] === "true";
|
(this.$route.query["hideBackButton"] as string) === "true";
|
||||||
this.message = ((this.$route as Router).query["message"] as string) || "";
|
this.message = (this.$route.query["message"] as string) || "";
|
||||||
|
|
||||||
// find any project ID
|
// find any project ID
|
||||||
let project;
|
let project;
|
||||||
@@ -280,17 +281,16 @@ export default class OfferDetailsView extends Vue {
|
|||||||
) {
|
) {
|
||||||
project = this.prevCredToEdit?.claim?.itemOffered?.isPartOf;
|
project = this.prevCredToEdit?.claim?.itemOffered?.isPartOf;
|
||||||
}
|
}
|
||||||
this.projectId = ((this.$route as Router).query["projectId"] ||
|
this.projectId = ((this.$route.query["projectId"] as string) ||
|
||||||
project?.identifier ||
|
project?.identifier ||
|
||||||
this.projectId) as string;
|
this.projectId) as string;
|
||||||
this.projectName = ((this.$route as Router).query["projectName"] ||
|
this.projectName = ((this.$route.query["projectName"] as string) ||
|
||||||
project?.name ||
|
project?.name ||
|
||||||
this.projectName) as string;
|
this.projectName) as string;
|
||||||
|
|
||||||
this.recipientDid = ((this.$route as Router).query["recipientDid"] ||
|
this.recipientDid = ((this.$route.query["recipientDid"] as string) ||
|
||||||
this.prevCredToEdit?.claim?.recipient?.identifier) as string;
|
this.prevCredToEdit?.claim?.recipient?.identifier) as string;
|
||||||
this.recipientName =
|
this.recipientName = (this.$route.query["recipientName"] as string) || "";
|
||||||
((this.$route as Router).query["recipientName"] as string) || "";
|
|
||||||
|
|
||||||
this.validThroughDateInput =
|
this.validThroughDateInput =
|
||||||
this.prevCredToEdit?.claim?.validThrough || this.validThroughDateInput;
|
this.prevCredToEdit?.claim?.validThrough || this.validThroughDateInput;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
<div v-if="isLoading" class="flex justify-center items-center py-8">
|
<div v-if="isLoading" class="flex justify-center items-center py-8">
|
||||||
<fa icon="spinner" class="fa-spin-pulse" />
|
<font-awesome icon="spinner" class="fa-spin-pulse" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="attendingMeeting">
|
<div v-else-if="attendingMeeting">
|
||||||
@@ -22,11 +22,11 @@
|
|||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<h2 class="text-xl font-medium">{{ attendingMeeting.name }}</h2>
|
<h2 class="text-xl font-medium">{{ attendingMeeting.name }}</h2>
|
||||||
<button
|
<button
|
||||||
@click.stop="leaveMeeting"
|
|
||||||
class="text-red-600 hover:text-red-700 p-2"
|
class="text-red-600 hover:text-red-700 p-2"
|
||||||
title="Leave Meeting"
|
title="Leave Meeting"
|
||||||
|
@click.stop="leaveMeeting"
|
||||||
>
|
>
|
||||||
<fa icon="right-from-bracket" />
|
<font-awesome icon="right-from-bracket" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,14 +65,14 @@
|
|||||||
/>
|
/>
|
||||||
<div class="flex justify-end space-x-4">
|
<div class="flex justify-end space-x-4">
|
||||||
<button
|
<button
|
||||||
@click="cancelPasswordDialog"
|
|
||||||
class="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
|
class="px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
|
||||||
|
@click="cancelPasswordDialog"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="submitPassword"
|
|
||||||
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
||||||
|
@click="submitPassword"
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
@@ -120,6 +120,7 @@ export default class OnboardMeetingListView extends Vue {
|
|||||||
},
|
},
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
) => void;
|
) => void;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
@@ -257,7 +258,7 @@ export default class OnboardMeetingListView extends Vue {
|
|||||||
|
|
||||||
if (postResult.data && postResult.data.success) {
|
if (postResult.data && postResult.data.success) {
|
||||||
// Navigate to members view with password and groupId
|
// Navigate to members view with password and groupId
|
||||||
(this.$router as Router).push({
|
this.$router.push({
|
||||||
name: "onboard-meeting-members",
|
name: "onboard-meeting-members",
|
||||||
params: {
|
params: {
|
||||||
groupId: this.selectedMeeting.groupId.toString(),
|
groupId: this.selectedMeeting.groupId.toString(),
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
|
|
||||||
<!-- Loading Animation -->
|
<!-- Loading Animation -->
|
||||||
<div
|
<div
|
||||||
class="mt-16 text-center text-4xl bg-slate-400 text-white w-14 py-2.5 rounded-full mx-auto"
|
|
||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
|
class="mt-16 text-center text-4xl bg-slate-400 text-white w-14 py-2.5 rounded-full mx-auto"
|
||||||
>
|
>
|
||||||
<fa icon="spinner" class="fa-spin-pulse"></fa>
|
<font-awesome icon="spinner" class="fa-spin-pulse"></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error State -->
|
<!-- Error State -->
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { RouteLocation } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import TopMessage from "../components/TopMessage.vue";
|
import TopMessage from "../components/TopMessage.vue";
|
||||||
@@ -69,17 +69,17 @@ export default class OnboardMeetingMembersView extends Vue {
|
|||||||
firstName = "";
|
firstName = "";
|
||||||
isRegistered = false;
|
isRegistered = false;
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
$refs!: {
|
userNameDialog!: InstanceType<typeof UserNameDialog>;
|
||||||
userNameDialog: InstanceType<typeof UserNameDialog>;
|
|
||||||
};
|
|
||||||
|
|
||||||
get groupId(): string {
|
get groupId(): string {
|
||||||
return (this.$route as RouteLocation).params.groupId as string;
|
return (this.$route.params.groupId as string) || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
get password(): string {
|
get password(): string {
|
||||||
return (this.$route as RouteLocation).query.password as string;
|
return (this.$route.query.password as string) || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
|
|||||||
@@ -17,24 +17,24 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<h2 class="text-2xl">Current Meeting</h2>
|
<h2 class="text-2xl">Current Meeting</h2>
|
||||||
<button
|
<button
|
||||||
@click="startEditing"
|
|
||||||
class="mb-4 text-blue-600 hover:text-blue-800 transition-colors duration-200 ml-2"
|
class="mb-4 text-blue-600 hover:text-blue-800 transition-colors duration-200 ml-2"
|
||||||
title="Edit Meeting"
|
title="Edit Meeting"
|
||||||
|
@click="startEditing"
|
||||||
>
|
>
|
||||||
<fa icon="pen" class="fa-fw" />
|
<font-awesome icon="pen" class="fa-fw" />
|
||||||
<span class="sr-only">{{
|
<span class="sr-only">{{
|
||||||
isInCreateMode() ? "Create Meeting" : "Edit Meeting"
|
isInCreateMode() ? "Create Meeting" : "Edit Meeting"
|
||||||
}}</span>
|
}}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@click="confirmDelete"
|
|
||||||
class="text-red-600 hover:text-red-800 transition-colors duration-200"
|
class="text-red-600 hover:text-red-800 transition-colors duration-200"
|
||||||
:disabled="isDeleting"
|
:disabled="isDeleting"
|
||||||
:class="{ 'opacity-50 cursor-not-allowed': isDeleting }"
|
:class="{ 'opacity-50 cursor-not-allowed': isDeleting }"
|
||||||
title="Delete Meeting"
|
title="Delete Meeting"
|
||||||
|
@click="confirmDelete"
|
||||||
>
|
>
|
||||||
<fa icon="trash-can" class="fa-fw" />
|
<font-awesome icon="trash-can" class="fa-fw" />
|
||||||
<span class="sr-only">{{
|
<span class="sr-only">{{
|
||||||
isDeleting ? "Deleting..." : "Delete Meeting"
|
isDeleting ? "Deleting..." : "Delete Meeting"
|
||||||
}}</span>
|
}}</span>
|
||||||
@@ -72,14 +72,14 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="flex justify-between space-x-4">
|
<div class="flex justify-between space-x-4">
|
||||||
<button
|
<button
|
||||||
@click="showDeleteConfirm = false"
|
|
||||||
class="px-4 py-2 bg-slate-500 text-white rounded hover:bg-slate-700"
|
class="px-4 py-2 bg-slate-500 text-white rounded hover:bg-slate-700"
|
||||||
|
@click="showDeleteConfirm = false"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="deleteMeeting"
|
|
||||||
class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
|
class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
|
||||||
|
@click="deleteMeeting"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
@@ -101,8 +101,8 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<!-- This is my first form. Not sure if I like it; will see if the browser benefits extend to the native app. -->
|
<!-- This is my first form. Not sure if I like it; will see if the browser benefits extend to the native app. -->
|
||||||
<form
|
<form
|
||||||
@submit.prevent="isInCreateMode() ? createMeeting() : updateMeeting()"
|
|
||||||
class="space-y-4"
|
class="space-y-4"
|
||||||
|
@submit.prevent="isInCreateMode() ? createMeeting() : updateMeeting()"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
@@ -182,8 +182,8 @@
|
|||||||
<button
|
<button
|
||||||
v-if="isInEditOrCreateMode()"
|
v-if="isInEditOrCreateMode()"
|
||||||
type="button"
|
type="button"
|
||||||
@click="cancelEditing"
|
|
||||||
class="w-full bg-slate-500 text-white px-4 py-2 rounded-md hover:bg-slate-600"
|
class="w-full bg-slate-500 text-white px-4 py-2 rounded-md hover:bg-slate-600"
|
||||||
|
@click="cancelEditing"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -204,20 +204,21 @@
|
|||||||
class="inline-block text-blue-600"
|
class="inline-block text-blue-600"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
• Open shortcut page for members <fa icon="external-link" />
|
• Open shortcut page for members
|
||||||
|
<font-awesome icon="external-link" />
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<MembersList
|
<MembersList
|
||||||
:password="currentMeeting.password || ''"
|
:password="currentMeeting.password || ''"
|
||||||
:show-organizer-tools="true"
|
:show-organizer-tools="true"
|
||||||
@error="handleMembersError"
|
|
||||||
class="mt-4"
|
class="mt-4"
|
||||||
|
@error="handleMembersError"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="isLoading">
|
<div v-else-if="isLoading">
|
||||||
<div class="flex justify-center items-center h-full">
|
<div class="flex justify-center items-center h-full">
|
||||||
<fa icon="spinner" class="fa-spin-pulse" />
|
<font-awesome icon="spinner" class="fa-spin-pulse" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
<h1 class="text-center text-lg font-light relative px-7">
|
<h1 class="text-center text-lg font-light relative px-7">
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<button
|
<button
|
||||||
@click="$router.go(-1)"
|
|
||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.back()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
Project Idea
|
Project Idea
|
||||||
</h1>
|
</h1>
|
||||||
@@ -21,11 +21,11 @@
|
|||||||
{{ name }}
|
{{ name }}
|
||||||
<button
|
<button
|
||||||
v-if="activeDid === issuer || activeDid === agentDid"
|
v-if="activeDid === issuer || activeDid === agentDid"
|
||||||
@click="onEditClick()"
|
|
||||||
title="Edit"
|
title="Edit"
|
||||||
data-testId="editClaimButton"
|
data-testId="editClaimButton"
|
||||||
|
@click="onEditClick()"
|
||||||
>
|
>
|
||||||
<fa icon="pen" class="text-sm text-blue-500 ml-2 mb-1" />
|
<font-awesome icon="pen" class="text-sm text-blue-500 ml-2 mb-1" />
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,10 +37,10 @@
|
|||||||
<div class="pb-4 flex gap-4">
|
<div class="pb-4 flex gap-4">
|
||||||
<div class="pt-1">
|
<div class="pt-1">
|
||||||
<ProjectIcon
|
<ProjectIcon
|
||||||
:entityId="projectId"
|
:entity-id="projectId"
|
||||||
:iconSize="64"
|
:icon-size="64"
|
||||||
:imageUrl="imageUrl"
|
:image-url="imageUrl"
|
||||||
:linkToFull="true"
|
:link-to-full="true"
|
||||||
class="block border border-slate-300 rounded-md max-h-16 max-w-16"
|
class="block border border-slate-300 rounded-md max-h-16 max-w-16"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,7 +48,10 @@
|
|||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
<div class="text-sm mb-3">
|
<div class="text-sm mb-3">
|
||||||
<div class="truncate">
|
<div class="truncate">
|
||||||
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
<font-awesome
|
||||||
|
icon="user"
|
||||||
|
class="fa-fw text-slate-400"
|
||||||
|
></font-awesome>
|
||||||
{{ issuerInfoObject?.displayName }}
|
{{ issuerInfoObject?.displayName }}
|
||||||
<span v-if="!serverUtil.isEmptyOrHiddenDid(issuer)">
|
<span v-if="!serverUtil.isEmptyOrHiddenDid(issuer)">
|
||||||
<a
|
<a
|
||||||
@@ -56,11 +59,14 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
>
|
>
|
||||||
<fa icon="arrow-up-right-from-square" class="fa-fw" />
|
<font-awesome
|
||||||
|
icon="arrow-up-right-from-square"
|
||||||
|
class="fa-fw"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="serverUtil.isHiddenDid(issuer)">
|
<span v-else-if="serverUtil.isHiddenDid(issuer)">
|
||||||
<fa
|
<font-awesome
|
||||||
icon="info-circle"
|
icon="info-circle"
|
||||||
class="fa-fw text-blue-500 cursor-pointer"
|
class="fa-fw text-blue-500 cursor-pointer"
|
||||||
@click="openHiddenDidDialog()"
|
@click="openHiddenDidDialog()"
|
||||||
@@ -68,35 +74,50 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="startTime">
|
<div v-if="startTime">
|
||||||
<fa icon="calendar" class="fa-fw text-slate-400"></fa>
|
<font-awesome
|
||||||
|
icon="calendar"
|
||||||
|
class="fa-fw text-slate-400"
|
||||||
|
></font-awesome>
|
||||||
Starts {{ startTime }}
|
Starts {{ startTime }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="endTime">
|
<div v-if="endTime">
|
||||||
<fa icon="calendar" class="fa-fw text-slate-400"></fa>
|
<font-awesome
|
||||||
|
icon="calendar"
|
||||||
|
class="fa-fw text-slate-400"
|
||||||
|
></font-awesome>
|
||||||
Ends {{ endTime }}
|
Ends {{ endTime }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="latitude || longitude">
|
<div v-if="latitude || longitude">
|
||||||
<fa icon="location-dot" class="fa-fw text-slate-400"></fa>
|
<font-awesome
|
||||||
|
icon="location-dot"
|
||||||
|
class="fa-fw text-slate-400"
|
||||||
|
></font-awesome>
|
||||||
<a
|
<a
|
||||||
:href="getOpenStreetMapUrl()"
|
:href="getOpenStreetMapUrl()"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="underline text-blue-500"
|
class="underline text-blue-500"
|
||||||
>Map View
|
>Map View
|
||||||
<fa
|
<font-awesome
|
||||||
icon="arrow-up-right-from-square"
|
icon="arrow-up-right-from-square"
|
||||||
class="fa-fw text-blue-500"
|
class="fa-fw text-blue-500"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="url">
|
<div v-if="url">
|
||||||
<fa icon="globe" class="fa-fw text-slate-400"></fa>
|
<font-awesome
|
||||||
|
icon="globe"
|
||||||
|
class="fa-fw text-slate-400"
|
||||||
|
></font-awesome>
|
||||||
<a
|
<a
|
||||||
:href="addScheme(url)"
|
:href="addScheme(url)"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="underline text-blue-500"
|
class="underline text-blue-500"
|
||||||
>
|
>
|
||||||
{{ domainForWebsite(this.url) }}
|
{{ domainForWebsite(url) }}
|
||||||
<fa icon="arrow-up-right-from-square" class="fa-fw" />
|
<font-awesome
|
||||||
|
icon="arrow-up-right-from-square"
|
||||||
|
class="fa-fw"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,23 +129,23 @@
|
|||||||
{{ truncatedDesc }}
|
{{ truncatedDesc }}
|
||||||
<a
|
<a
|
||||||
v-if="description.length >= truncateLength"
|
v-if="description.length >= truncateLength"
|
||||||
@click="expandText"
|
|
||||||
class="uppercase text-xs font-semibold text-slate-700"
|
class="uppercase text-xs font-semibold text-slate-700"
|
||||||
|
@click="expandText"
|
||||||
>... Read More</a
|
>... Read More</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
{{ description }}
|
{{ description }}
|
||||||
<a
|
<a
|
||||||
@click="collapseText"
|
|
||||||
class="uppercase text-xs font-semibold text-slate-700"
|
class="uppercase text-xs font-semibold text-slate-700"
|
||||||
|
@click="collapseText"
|
||||||
>- Read Less</a
|
>- Read Less</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a @click="onClickLoadClaim(projectId)" class="cursor-pointer">
|
<a class="cursor-pointer" @click="onClickLoadClaim(projectId)">
|
||||||
<fa icon="file-lines" class="pl-2 pt-1 text-blue-500" />
|
<font-awesome icon="file-lines" class="pl-2 pt-1 text-blue-500" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,8 +163,8 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div v-for="plan in fulfillersToThis" :key="plan.handleId">
|
<div v-for="plan in fulfillersToThis" :key="plan.handleId">
|
||||||
<button
|
<button
|
||||||
@click="onClickLoadProject(plan.handleId)"
|
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
|
@click="onClickLoadProject(plan.handleId)"
|
||||||
>
|
>
|
||||||
{{ plan.name }}
|
{{ plan.name }}
|
||||||
</button>
|
</button>
|
||||||
@@ -163,8 +184,8 @@
|
|||||||
<!-- centering because long, wrapped project names didn't left align with blank or "text-left" -->
|
<!-- centering because long, wrapped project names didn't left align with blank or "text-left" -->
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<button
|
<button
|
||||||
@click="onClickLoadProject(fulfilledByThis.handleId)"
|
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
|
@click="onClickLoadProject(fulfilledByThis.handleId)"
|
||||||
>
|
>
|
||||||
{{ fulfilledByThis.name }}
|
{{ fulfilledByThis.name }}
|
||||||
</button>
|
</button>
|
||||||
@@ -181,7 +202,10 @@
|
|||||||
class="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 gap-x-3 gap-y-5 text-center mb-5 mt-2"
|
class="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 gap-x-3 gap-y-5 text-center mb-5 mt-2"
|
||||||
>
|
>
|
||||||
<li @click="openGiftDialogToProject({ name: 'you', did: activeDid })">
|
<li @click="openGiftDialogToProject({ name: 'you', did: activeDid })">
|
||||||
<fa icon="hand" class="fa-fw text-blue-500 text-5xl cursor-pointer" />
|
<font-awesome
|
||||||
|
icon="hand"
|
||||||
|
class="fa-fw text-blue-500 text-5xl cursor-pointer"
|
||||||
|
/>
|
||||||
<h3
|
<h3
|
||||||
class="mt-5 text-xs text-blue-500 font-medium text-ellipsis whitespace-nowrap overflow-hidden cursor-pointer"
|
class="mt-5 text-xs text-blue-500 font-medium text-ellipsis whitespace-nowrap overflow-hidden cursor-pointer"
|
||||||
>
|
>
|
||||||
@@ -206,7 +230,7 @@
|
|||||||
>
|
>
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:contact="contact"
|
:contact="contact"
|
||||||
:iconSize="64"
|
:icon-size="64"
|
||||||
class="mx-auto border border-blue-300 rounded-md mb-1 cursor-pointer"
|
class="mx-auto border border-blue-300 rounded-md mb-1 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<h3
|
<h3
|
||||||
@@ -218,14 +242,14 @@
|
|||||||
<li>
|
<li>
|
||||||
<span
|
<span
|
||||||
v-if="allContacts.length >= 5"
|
v-if="allContacts.length >= 5"
|
||||||
@click="onClickAllContactsGifting()"
|
|
||||||
class="flex align-bottom text-xs text-blue-500 mt-12 cursor-pointer"
|
class="flex align-bottom text-xs text-blue-500 mt-12 cursor-pointer"
|
||||||
|
@click="onClickAllContactsGifting()"
|
||||||
>
|
>
|
||||||
... or someone else...
|
... or someone else...
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<GiftedDialog ref="giveDialogToThis" :toProjectId="this.projectId" />
|
<GiftedDialog ref="giveDialogToThis" :to-project-id="projectId" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Offers & Gifts to & from this -->
|
<!-- Offers & Gifts to & from this -->
|
||||||
@@ -236,8 +260,8 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<button
|
<button
|
||||||
data-testId="offerButton"
|
data-testId="offerButton"
|
||||||
|
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 rounded-md"
|
||||||
@click="openOfferDialog()"
|
@click="openOfferDialog()"
|
||||||
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 text-sm leading-tight rounded-md"
|
|
||||||
>
|
>
|
||||||
Offer to this (maybe with conditions)...
|
Offer to this (maybe with conditions)...
|
||||||
</button>
|
</button>
|
||||||
@@ -245,15 +269,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<OfferDialog
|
<OfferDialog
|
||||||
ref="customOfferDialog"
|
ref="customOfferDialog"
|
||||||
:projectId="this.projectId"
|
:project-id="projectId"
|
||||||
:projectName="this.name"
|
:project-name="name"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h3 class="text-lg font-bold mb-3 mt-4">Offered To This Idea</h3>
|
<h3 class="text-lg font-bold mb-3 mt-4">Offered To This Idea</h3>
|
||||||
|
|
||||||
<div v-if="offersToThis.length === 0">
|
<div v-if="offersToThis.length === 0">
|
||||||
(None yet. Wanna
|
(None yet. Wanna
|
||||||
<span @click="openOfferDialog()" class="cursor-pointer text-blue-500"
|
<span class="cursor-pointer text-blue-500" @click="openOfferDialog()"
|
||||||
>offer something... especially if others join you</span
|
>offer something... especially if others join you</span
|
||||||
>?)
|
>?)
|
||||||
</div>
|
</div>
|
||||||
@@ -266,7 +290,10 @@
|
|||||||
>
|
>
|
||||||
<div class="flex justify-between gap-4">
|
<div class="flex justify-between gap-4">
|
||||||
<span>
|
<span>
|
||||||
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
<font-awesome
|
||||||
|
icon="user"
|
||||||
|
class="fa-fw text-slate-400"
|
||||||
|
></font-awesome>
|
||||||
{{
|
{{
|
||||||
serverUtil.didInfo(
|
serverUtil.didInfo(
|
||||||
offer.offeredByDid,
|
offer.offeredByDid,
|
||||||
@@ -277,28 +304,31 @@
|
|||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="offer.amount" class="whitespace-nowrap">
|
<span v-if="offer.amount" class="whitespace-nowrap">
|
||||||
<fa
|
<font-awesome
|
||||||
:icon="libsUtil.iconForUnitCode(offer.unit)"
|
:icon="libsUtil.iconForUnitCode(offer.unit)"
|
||||||
class="fa-fw text-slate-400"
|
class="fa-fw text-slate-400"
|
||||||
/>{{ offer.amount }}
|
/>{{ offer.amount }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="offer.objectDescription" class="text-slate-500">
|
<div v-if="offer.objectDescription" class="text-slate-500">
|
||||||
<fa icon="comment" class="fa-fw text-slate-400" />
|
<font-awesome icon="comment" class="fa-fw text-slate-400" />
|
||||||
{{ offer.objectDescription }}
|
{{ offer.objectDescription }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<a
|
<a
|
||||||
@click="onClickLoadClaim(offer.jwtId as string)"
|
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
|
@click="onClickLoadClaim(offer.jwtId as string)"
|
||||||
>
|
>
|
||||||
<fa icon="file-lines" class="pl-2 pt-1 text-blue-500" />
|
<font-awesome
|
||||||
|
icon="file-lines"
|
||||||
|
class="pl-2 pt-1 text-blue-500"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
v-if="checkIsFulfillable(offer)"
|
v-if="checkIsFulfillable(offer)"
|
||||||
@click="onClickFulfillGiveToOffer(offer)"
|
@click="onClickFulfillGiveToOffer(offer)"
|
||||||
>
|
>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="hand-holding-heart"
|
icon="hand-holding-heart"
|
||||||
class="text-blue-500 cursor-pointer"
|
class="text-blue-500 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
@@ -317,147 +347,91 @@
|
|||||||
<div v-if="activeDid && isRegistered">
|
<div v-if="activeDid && isRegistered">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<button
|
<button
|
||||||
|
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1rounded-md"
|
||||||
@click="openGiftDialogToProject()"
|
@click="openGiftDialogToProject()"
|
||||||
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 text-sm leading-tight rounded-md"
|
|
||||||
>
|
>
|
||||||
Given To This...
|
Given To This...
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="text-lg font-bold mt-4">Given To This Idea</h3>
|
<h3 class="text-lg font-bold mb-3 mt-4">Given To This Idea</h3>
|
||||||
|
|
||||||
<div v-if="givesToThis.length === 0" class="text-sm">
|
<div v-if="givesToThis.length === 0">
|
||||||
(None yet. If you've seen something, say something by clicking a
|
(None yet. If you've seen something, say something by clicking a
|
||||||
contact above.)
|
contact above.)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="mt-1 text-sm">
|
<ul v-else class="text-sm border-t border-slate-300">
|
||||||
<!-- Totals section -->
|
<li
|
||||||
<div class="mt-1 flex items-center min-h-[1.5rem]">
|
v-for="give in givesToThis"
|
||||||
<div v-if="loadingTotals" class="flex-1">
|
:key="give.id"
|
||||||
<fa icon="spinner" class="fa-spin-pulse text-blue-500" />
|
class="py-1.5 border-b border-slate-300"
|
||||||
</div>
|
>
|
||||||
<div v-else-if="givesTotalsByUnit.length > 0" class="flex-1">
|
<div class="flex justify-between gap-4">
|
||||||
<span class="font-semibold mr-2 shrink-0">Totals</span>
|
<span>
|
||||||
<span class="whitespace-nowrap overflow-hidden text-ellipsis">
|
<font-awesome icon="user" class="fa-fw text-slate-400" />
|
||||||
<a
|
{{
|
||||||
@click="totalsExpanded = !totalsExpanded"
|
serverUtil.didInfo(
|
||||||
class="cursor-pointer text-blue-500"
|
give.agentDid,
|
||||||
>
|
activeDid,
|
||||||
<!-- just show the hours, or alternatively whatever is first -->
|
allMyDids,
|
||||||
<span v-if="givenTotalHours() > 0">
|
allContacts,
|
||||||
{{ givenTotalHours() }} {{ libsUtil.UNIT_SHORT["HUR"] }}
|
)
|
||||||
</span>
|
|
||||||
<span v-else>
|
|
||||||
{{ givesTotalsByUnit[0].amount }}
|
|
||||||
{{ libsUtil.UNIT_SHORT[givesTotalsByUnit[0].unit] }}
|
|
||||||
</span>
|
|
||||||
<span v-if="givesTotalsByUnit.length > 1">...</span>
|
|
||||||
<span>
|
|
||||||
<fa
|
|
||||||
:icon="totalsExpanded ? 'chevron-up' : 'chevron-right'"
|
|
||||||
class="fa-fw text-xs ml-1"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<!-- show the full list when expanded -->
|
|
||||||
<div v-if="totalsExpanded">
|
|
||||||
<div
|
|
||||||
v-for="total in givesTotalsByUnit"
|
|
||||||
:key="total.unit"
|
|
||||||
class="ml-2"
|
|
||||||
>
|
|
||||||
<fa
|
|
||||||
:icon="libsUtil.iconForUnitCode(total.unit)"
|
|
||||||
class="fa-fw text-slate-400 mr-1"
|
|
||||||
/>
|
|
||||||
{{ total.amount }} {{ libsUtil.UNIT_LONG[total.unit] }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<span class="font-semibold mr-2 shrink-0">
|
|
||||||
{{ givesToThis.length }}{{ givesHitLimit ? "+" : "" }} record{{
|
|
||||||
givesToThis.length === 1 ? "" : "s"
|
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
|
<span v-if="give.amount" class="whitespace-nowrap">
|
||||||
|
<font-awesome
|
||||||
|
:icon="libsUtil.iconForUnitCode(give.unit)"
|
||||||
|
class="fa-fw text-slate-400"
|
||||||
|
/>{{ give.amount }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="text-slate-500">
|
||||||
|
<font-awesome icon="calendar" class="fa-fw text-slate-400" />
|
||||||
|
{{ give.issuedAt?.substring(0, 10) }}
|
||||||
|
</div>
|
||||||
|
<div v-if="give.description" class="text-slate-500">
|
||||||
|
<font-awesome icon="comment" class="fa-fw text-slate-400" />
|
||||||
|
{{ give.description }}
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<a @click="onClickLoadClaim(give.jwtId)">
|
||||||
|
<font-awesome
|
||||||
|
icon="file-lines"
|
||||||
|
class="text-blue-500 cursor-pointer"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
|
||||||
<!-- List of gives -->
|
<a
|
||||||
<ul class="mt-2 text-sm border-t border-slate-300">
|
v-if="
|
||||||
<li
|
checkIsConfirmable(give) &&
|
||||||
v-for="give in givesToThis"
|
!recentlyCheckedAndUnconfirmableJwts.includes(give.jwtId)
|
||||||
:key="give.id"
|
"
|
||||||
class="py-1.5 border-b border-slate-300"
|
@click="deepCheckConfirmable(give)"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between gap-4">
|
<font-awesome
|
||||||
<span>
|
icon="circle-check"
|
||||||
<fa icon="user" class="fa-fw text-slate-400" />
|
class="text-blue-500 cursor-pointer"
|
||||||
{{
|
/>
|
||||||
serverUtil.didInfo(
|
</a>
|
||||||
give.agentDid,
|
<a v-else-if="checkingConfirmationForJwtId === give.jwtId">
|
||||||
activeDid,
|
<font-awesome icon="spinner" class="fa-spin-pulse" />
|
||||||
allMyDids,
|
</a>
|
||||||
allContacts,
|
<a v-else @click="shallowNotifyWhyCannotConfirm(give)">
|
||||||
)
|
<font-awesome
|
||||||
}}
|
icon="circle-check"
|
||||||
</span>
|
class="text-slate-500 cursor-pointer"
|
||||||
<span v-if="give.amount" class="whitespace-nowrap">
|
/>
|
||||||
<fa
|
</a>
|
||||||
:icon="libsUtil.iconForUnitCode(give.unit)"
|
</div>
|
||||||
class="fa-fw text-slate-400"
|
<div v-if="give.fullClaim.image" class="flex justify-center">
|
||||||
/>{{ give.amount }}
|
<a :href="give.fullClaim.image" target="_blank">
|
||||||
</span>
|
<img :src="give.fullClaim.image" class="h-24 mt-2 rounded-xl" />
|
||||||
</div>
|
</a>
|
||||||
<div class="text-slate-500">
|
</div>
|
||||||
<fa icon="calendar" class="fa-fw text-slate-400" />
|
</li>
|
||||||
{{ give.issuedAt?.substring(0, 10) }}
|
</ul>
|
||||||
</div>
|
|
||||||
<div v-if="give.description" class="text-slate-500">
|
|
||||||
<fa icon="comment" class="fa-fw text-slate-400" />
|
|
||||||
{{ give.description }}
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<a @click="onClickLoadClaim(give.jwtId)">
|
|
||||||
<fa icon="file-lines" class="text-blue-500 cursor-pointer" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
v-if="
|
|
||||||
checkIsConfirmable(give) &&
|
|
||||||
!recentlyCheckedAndUnconfirmableJwts.includes(give.jwtId)
|
|
||||||
"
|
|
||||||
@click="deepCheckConfirmable(give)"
|
|
||||||
>
|
|
||||||
<fa
|
|
||||||
icon="circle-check"
|
|
||||||
class="text-blue-500 cursor-pointer"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<a v-else-if="checkingConfirmationForJwtId === give.jwtId">
|
|
||||||
<fa icon="spinner" class="fa-spin-pulse" />
|
|
||||||
</a>
|
|
||||||
<a v-else @click="shallowNotifyWhyCannotConfirm(give)">
|
|
||||||
<fa
|
|
||||||
icon="circle-check"
|
|
||||||
class="text-slate-500 cursor-pointer"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div v-if="give.fullClaim.image" class="flex justify-center">
|
|
||||||
<a :href="give.fullClaim.image" target="_blank">
|
|
||||||
<img
|
|
||||||
:src="give.fullClaim.image"
|
|
||||||
class="h-24 mt-2 rounded-xl"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div v-if="givesHitLimit" class="text-center text-blue-500">
|
<div v-if="givesHitLimit" class="text-center text-blue-500">
|
||||||
<button @click="loadGives()">Load More</button>
|
<button @click="loadGives()">Load More</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -469,17 +443,14 @@
|
|||||||
<div v-if="activeDid && isRegistered">
|
<div v-if="activeDid && isRegistered">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<button
|
<button
|
||||||
|
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 rounded-md"
|
||||||
@click="openGiftDialogFromProject()"
|
@click="openGiftDialogFromProject()"
|
||||||
class="block w-full bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 text-sm leading-tight rounded-md"
|
|
||||||
>
|
>
|
||||||
Given By This...
|
Given By This...
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<GiftedDialog
|
<GiftedDialog ref="giveDialogFromThis" :from-project-id="projectId" />
|
||||||
ref="giveDialogFromThis"
|
|
||||||
:fromProjectId="this.projectId"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<h3 class="text-lg font-bold mb-3 mt-4">
|
<h3 class="text-lg font-bold mb-3 mt-4">
|
||||||
Benefitted From This Project
|
Benefitted From This Project
|
||||||
@@ -505,23 +476,26 @@
|
|||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="give.amount" class="whitespace-nowrap">
|
<span v-if="give.amount" class="whitespace-nowrap">
|
||||||
<fa
|
<font-awesome
|
||||||
:icon="libsUtil.iconForUnitCode(give.unit)"
|
:icon="libsUtil.iconForUnitCode(give.unit)"
|
||||||
class="fa-fw text-slate-400"
|
class="fa-fw text-slate-400"
|
||||||
/>{{ give.amount }}
|
/>{{ give.amount }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-slate-500">
|
<div class="text-slate-500">
|
||||||
<fa icon="calendar" class="fa-fw text-slate-400" />
|
<font-awesome icon="calendar" class="fa-fw text-slate-400" />
|
||||||
{{ give.issuedAt?.substring(0, 10) }}
|
{{ give.issuedAt?.substring(0, 10) }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="give.description" class="text-slate-500">
|
<div v-if="give.description" class="text-slate-500">
|
||||||
<fa icon="comment" class="fa-fw text-slate-400" />
|
<font-awesome icon="comment" class="fa-fw text-slate-400" />
|
||||||
{{ give.description }}
|
{{ give.description }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<a @click="onClickLoadClaim(give.jwtId)">
|
<a @click="onClickLoadClaim(give.jwtId)">
|
||||||
<fa icon="file-lines" class="text-blue-500 cursor-pointer" />
|
<font-awesome
|
||||||
|
icon="file-lines"
|
||||||
|
class="text-blue-500 cursor-pointer"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
@@ -531,13 +505,19 @@
|
|||||||
"
|
"
|
||||||
@click="deepCheckConfirmable(give)"
|
@click="deepCheckConfirmable(give)"
|
||||||
>
|
>
|
||||||
<fa icon="circle-check" class="text-blue-500 cursor-pointer" />
|
<font-awesome
|
||||||
|
icon="circle-check"
|
||||||
|
class="text-blue-500 cursor-pointer"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a v-else-if="checkingConfirmationForJwtId === give.jwtId">
|
<a v-else-if="checkingConfirmationForJwtId === give.jwtId">
|
||||||
<fa icon="spinner" class="fa-spin-pulse" />
|
<font-awesome icon="spinner" class="fa-spin-pulse" />
|
||||||
</a>
|
</a>
|
||||||
<a v-else @click="shallowNotifyWhyCannotConfirm(give)">
|
<a v-else @click="shallowNotifyWhyCannotConfirm(give)">
|
||||||
<fa icon="circle-check" class="text-slate-500 cursor-pointer" />
|
<font-awesome
|
||||||
|
icon="circle-check"
|
||||||
|
class="text-slate-500 cursor-pointer"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="give.fullClaim.image" class="flex justify-center">
|
<div v-if="give.fullClaim.image" class="flex justify-center">
|
||||||
@@ -561,7 +541,15 @@
|
|||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
import { Router } from "vue-router";
|
||||||
|
import {
|
||||||
|
GenericVerifiableCredential,
|
||||||
|
GenericCredWrapper,
|
||||||
|
GiveSummaryRecord,
|
||||||
|
GiveVerifiableCredential,
|
||||||
|
OfferSummaryRecord,
|
||||||
|
OfferVerifiableCredential,
|
||||||
|
PlanSummaryRecord,
|
||||||
|
} from "../interfaces";
|
||||||
import GiftedDialog from "../components/GiftedDialog.vue";
|
import GiftedDialog from "../components/GiftedDialog.vue";
|
||||||
import OfferDialog from "../components/OfferDialog.vue";
|
import OfferDialog from "../components/OfferDialog.vue";
|
||||||
import TopMessage from "../components/TopMessage.vue";
|
import TopMessage from "../components/TopMessage.vue";
|
||||||
@@ -576,18 +564,41 @@ import {
|
|||||||
} from "../db/index";
|
} from "../db/index";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import * as libsUtil from "../libs/util";
|
import * as libsUtil from "../libs/util";
|
||||||
import {
|
|
||||||
GenericCredWrapper,
|
|
||||||
GiveSummaryRecord,
|
|
||||||
GiveVerifiableCredential,
|
|
||||||
OfferSummaryRecord,
|
|
||||||
OfferVerifiableCredential,
|
|
||||||
PlanSummaryRecord,
|
|
||||||
} from "../libs/endorserServer";
|
|
||||||
import * as serverUtil from "../libs/endorserServer";
|
import * as serverUtil from "../libs/endorserServer";
|
||||||
import { retrieveAccountDids } from "../libs/util";
|
import { retrieveAccountDids } from "../libs/util";
|
||||||
import HiddenDidDialog from "../components/HiddenDidDialog.vue";
|
import HiddenDidDialog from "../components/HiddenDidDialog.vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project View Component
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*
|
||||||
|
* This component displays detailed project information and manages interactions including:
|
||||||
|
* - Project metadata (name, description, dates, location)
|
||||||
|
* - Issuer information and verification
|
||||||
|
* - Project contributions and fulfillments
|
||||||
|
* - Offers and gifts tracking
|
||||||
|
* - Contact interactions
|
||||||
|
*
|
||||||
|
* Data Flow:
|
||||||
|
* 1. Component loads with project ID from route
|
||||||
|
* 2. Fetches project data, contacts, and account settings
|
||||||
|
* 3. Loads related data (offers, gifts, fulfillments)
|
||||||
|
* 4. Updates UI with paginated results
|
||||||
|
*
|
||||||
|
* Security Features:
|
||||||
|
* - DID visibility controls
|
||||||
|
* - JWT validation for imports
|
||||||
|
* - Permission checks for actions
|
||||||
|
*
|
||||||
|
* State Management:
|
||||||
|
* - Maintains separate loading states for different data types
|
||||||
|
* - Handles pagination limits
|
||||||
|
* - Tracks confirmation states
|
||||||
|
*
|
||||||
|
* @see GiftedDialog for gift creation
|
||||||
|
* @see OfferDialog for offer creation
|
||||||
|
* @see HiddenDidDialog for DID privacy explanations
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
EntityIcon,
|
EntityIcon,
|
||||||
@@ -600,52 +611,103 @@ import HiddenDidDialog from "../components/HiddenDidDialog.vue";
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class ProjectViewView extends Vue {
|
export default class ProjectViewView extends Vue {
|
||||||
|
/** Notification function injected by Vue */
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
/** Router instance for navigation */
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
|
// Account and Settings State
|
||||||
|
/** Currently active DID */
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
|
/** Project agent DID */
|
||||||
agentDid = "";
|
agentDid = "";
|
||||||
|
/** DIDs that can see the agent DID */
|
||||||
agentDidVisibleToDids: Array<string> = [];
|
agentDidVisibleToDids: Array<string> = [];
|
||||||
|
/** All DIDs associated with current account */
|
||||||
allMyDids: Array<string> = [];
|
allMyDids: Array<string> = [];
|
||||||
|
/** All known contacts */
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
|
/** API server endpoint */
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
checkingConfirmationForJwtId = "";
|
/** Registration status of current user */
|
||||||
description = "";
|
|
||||||
endTime = "";
|
|
||||||
expanded = false;
|
|
||||||
fulfilledByThis: PlanSummaryRecord | null = null;
|
|
||||||
fulfillersToThis: Array<PlanSummaryRecord> = [];
|
|
||||||
fulfillersToHitLimit = false;
|
|
||||||
givesToThis: Array<GiveSummaryRecord> = [];
|
|
||||||
givesHitLimit = false;
|
|
||||||
givesProvidedByThis: Array<GiveSummaryRecord> = [];
|
|
||||||
givesProvidedByHitLimit = false;
|
|
||||||
givesTotalsByUnit: Array<{ unit: string; amount: number }> = [];
|
|
||||||
imageUrl = "";
|
|
||||||
isRegistered = false;
|
isRegistered = false;
|
||||||
|
|
||||||
|
// Project Data
|
||||||
|
/** Project description */
|
||||||
|
description = "";
|
||||||
|
/** Project end time */
|
||||||
|
endTime = "";
|
||||||
|
/** Text expansion state */
|
||||||
|
expanded = false;
|
||||||
|
/** Project fulfilled by this project */
|
||||||
|
fulfilledByThis: PlanSummaryRecord | null = null;
|
||||||
|
/** Projects fulfilling this project */
|
||||||
|
fulfillersToThis: Array<PlanSummaryRecord> = [];
|
||||||
|
/** Flag for fulfiller pagination */
|
||||||
|
fulfillersToHitLimit = false;
|
||||||
|
/** Project image URL */
|
||||||
|
imageUrl = "";
|
||||||
|
/** Project issuer DID */
|
||||||
issuer = "";
|
issuer = "";
|
||||||
|
/** Cached issuer information */
|
||||||
issuerInfoObject: {
|
issuerInfoObject: {
|
||||||
known: boolean;
|
known: boolean;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
profileImageUrl?: string;
|
profileImageUrl?: string;
|
||||||
} | null = null;
|
} | null = null;
|
||||||
|
/** DIDs that can see issuer information */
|
||||||
issuerVisibleToDids: Array<string> = [];
|
issuerVisibleToDids: Array<string> = [];
|
||||||
|
/** Project location data */
|
||||||
latitude = 0;
|
latitude = 0;
|
||||||
loadingTotals = false;
|
|
||||||
longitude = 0;
|
longitude = 0;
|
||||||
|
/** Project name */
|
||||||
name = "";
|
name = "";
|
||||||
offersToThis: Array<OfferSummaryRecord> = [];
|
/** Project ID (handle) */
|
||||||
offersHitLimit = false;
|
projectId = "";
|
||||||
projectId = ""; // handle ID
|
/** Project start time */
|
||||||
recentlyCheckedAndUnconfirmableJwts: string[] = [];
|
|
||||||
startTime = "";
|
startTime = "";
|
||||||
totalsExpanded = false;
|
/** Project URL */
|
||||||
truncatedDesc = "";
|
|
||||||
truncateLength = 40;
|
|
||||||
url = "";
|
url = "";
|
||||||
|
|
||||||
|
// Interaction Data
|
||||||
|
/** Gifts to this project */
|
||||||
|
givesToThis: Array<GiveSummaryRecord> = [];
|
||||||
|
/** Flag for gifts pagination */
|
||||||
|
givesHitLimit = false;
|
||||||
|
/** Gifts from this project */
|
||||||
|
givesProvidedByThis: Array<GiveSummaryRecord> = [];
|
||||||
|
/** Flag for provided gifts pagination */
|
||||||
|
givesProvidedByHitLimit = false;
|
||||||
|
/** Offers to this project */
|
||||||
|
offersToThis: Array<OfferSummaryRecord> = [];
|
||||||
|
/** Flag for offers pagination */
|
||||||
|
offersHitLimit = false;
|
||||||
|
|
||||||
|
// UI State
|
||||||
|
/** JWT being checked for confirmation */
|
||||||
|
checkingConfirmationForJwtId = "";
|
||||||
|
/** Recently checked unconfirmable JWTs */
|
||||||
|
recentlyCheckedAndUnconfirmableJwts: string[] = [];
|
||||||
|
truncatedDesc = "";
|
||||||
|
/** Truncation length */
|
||||||
|
truncateLength = 40;
|
||||||
|
|
||||||
|
// Utility References
|
||||||
libsUtil = libsUtil;
|
libsUtil = libsUtil;
|
||||||
serverUtil = serverUtil;
|
serverUtil = serverUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component lifecycle hook that initializes the project view
|
||||||
|
*
|
||||||
|
* Workflow:
|
||||||
|
* 1. Loads account settings and contacts
|
||||||
|
* 2. Retrieves all account DIDs
|
||||||
|
* 3. Extracts project ID from URL
|
||||||
|
* 4. Initializes project data loading
|
||||||
|
*
|
||||||
|
* @throws Logs errors but continues loading
|
||||||
|
* @emits Notification on profile loading errors
|
||||||
|
*/
|
||||||
async created() {
|
async created() {
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const settings = await retrieveSettingsForActiveAccount();
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
@@ -677,26 +739,21 @@ export default class ProjectViewView extends Vue {
|
|||||||
this.projectId = decodeURIComponent(pathParam);
|
this.projectId = decodeURIComponent(pathParam);
|
||||||
}
|
}
|
||||||
this.loadProject(this.projectId, this.activeDid);
|
this.loadProject(this.projectId, this.activeDid);
|
||||||
this.loadTotals();
|
|
||||||
}
|
|
||||||
|
|
||||||
onEditClick() {
|
|
||||||
const route = {
|
|
||||||
name: "new-edit-project",
|
|
||||||
query: { projectId: this.projectId },
|
|
||||||
};
|
|
||||||
(this.$router as Router).push(route);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Isn't there a better way to make this available to the template?
|
|
||||||
expandText() {
|
|
||||||
this.expanded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
collapseText() {
|
|
||||||
this.expanded = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads project data and related information
|
||||||
|
*
|
||||||
|
* Workflow:
|
||||||
|
* 1. Fetches project details from API
|
||||||
|
* 2. Updates component state with project data
|
||||||
|
* 3. Initializes related data loading (gifts, offers, fulfillments)
|
||||||
|
*
|
||||||
|
* @param projectId Project handle ID
|
||||||
|
* @param userDid Active user's DID
|
||||||
|
* @throws Logs errors and notifies user
|
||||||
|
* @emits Notification on loading errors
|
||||||
|
*/
|
||||||
async loadProject(projectId: string, userDid: string) {
|
async loadProject(projectId: string, userDid: string) {
|
||||||
this.projectId = projectId;
|
this.projectId = projectId;
|
||||||
|
|
||||||
@@ -783,6 +840,15 @@ export default class ProjectViewView extends Vue {
|
|||||||
this.loadPlanFulfilledBy();
|
this.loadPlanFulfilledBy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads gifts made to this project
|
||||||
|
*
|
||||||
|
* Handles pagination and updates component state with results.
|
||||||
|
* Uses beforeId for pagination based on last loaded gift.
|
||||||
|
*
|
||||||
|
* @throws Logs errors and notifies user
|
||||||
|
* @emits Notification on loading errors
|
||||||
|
*/
|
||||||
async loadGives() {
|
async loadGives() {
|
||||||
const givesUrl =
|
const givesUrl =
|
||||||
this.apiServer +
|
this.apiServer +
|
||||||
@@ -830,6 +896,15 @@ export default class ProjectViewView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads gifts provided by this project
|
||||||
|
*
|
||||||
|
* Similar to loadGives but for outgoing gifts.
|
||||||
|
* Maintains separate pagination state.
|
||||||
|
*
|
||||||
|
* @throws Logs errors and notifies user
|
||||||
|
* @emits Notification on loading errors
|
||||||
|
*/
|
||||||
async loadGivesProvidedBy() {
|
async loadGivesProvidedBy() {
|
||||||
const providedByUrl =
|
const providedByUrl =
|
||||||
this.apiServer +
|
this.apiServer +
|
||||||
@@ -880,6 +955,15 @@ export default class ProjectViewView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads offers made to this project
|
||||||
|
*
|
||||||
|
* Handles pagination and filtering of valid offers.
|
||||||
|
* Updates component state with results.
|
||||||
|
*
|
||||||
|
* @throws Logs errors and notifies user
|
||||||
|
* @emits Notification on loading errors
|
||||||
|
*/
|
||||||
async loadOffers() {
|
async loadOffers() {
|
||||||
const offersUrl =
|
const offersUrl =
|
||||||
this.apiServer +
|
this.apiServer +
|
||||||
@@ -927,6 +1011,14 @@ export default class ProjectViewView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads projects that fulfill this project
|
||||||
|
*
|
||||||
|
* Manages pagination state and updates component with results.
|
||||||
|
*
|
||||||
|
* @throws Logs errors and notifies user
|
||||||
|
* @emits Notification on loading errors
|
||||||
|
*/
|
||||||
async loadPlanFulfillersTo() {
|
async loadPlanFulfillersTo() {
|
||||||
const fulfillsUrl =
|
const fulfillsUrl =
|
||||||
this.apiServer +
|
this.apiServer +
|
||||||
@@ -975,6 +1067,14 @@ export default class ProjectViewView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads project that this project fulfills
|
||||||
|
*
|
||||||
|
* Updates fulfilledByThis state with result.
|
||||||
|
*
|
||||||
|
* @throws Logs errors and notifies user
|
||||||
|
* @emits Notification on loading errors
|
||||||
|
*/
|
||||||
async loadPlanFulfilledBy() {
|
async loadPlanFulfilledBy() {
|
||||||
const fulfilledByUrl =
|
const fulfilledByUrl =
|
||||||
this.apiServer +
|
this.apiServer +
|
||||||
@@ -1014,6 +1114,23 @@ export default class ProjectViewView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onEditClick() {
|
||||||
|
const route = {
|
||||||
|
name: "new-edit-project",
|
||||||
|
query: { projectId: this.projectId },
|
||||||
|
};
|
||||||
|
this.$router.push(route);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Isn't there a better way to make this available to the template?
|
||||||
|
expandText() {
|
||||||
|
this.expanded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
collapseText() {
|
||||||
|
this.expanded = false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle clicking on a project entry found in the list
|
* Handle clicking on a project entry found in the list
|
||||||
* @param id of the project
|
* @param id of the project
|
||||||
@@ -1022,7 +1139,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
const route = {
|
const route = {
|
||||||
path: "/project/" + encodeURIComponent(projectId),
|
path: "/project/" + encodeURIComponent(projectId),
|
||||||
};
|
};
|
||||||
(this.$router as Router).push(route);
|
this.$router.push(route);
|
||||||
this.loadProject(projectId, this.activeDid);
|
this.loadProject(projectId, this.activeDid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1069,14 +1186,14 @@ export default class ProjectViewView extends Vue {
|
|||||||
projectId: this.projectId,
|
projectId: this.projectId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
(this.$router as Router).push(route);
|
this.$router.push(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickLoadClaim(jwtId: string) {
|
onClickLoadClaim(jwtId: string) {
|
||||||
const route = {
|
const route = {
|
||||||
path: "/claim/" + encodeURIComponent(jwtId),
|
path: "/claim/" + encodeURIComponent(jwtId),
|
||||||
};
|
};
|
||||||
(this.$router as Router).push(route);
|
this.$router.push(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkIsFulfillable(offer: OfferSummaryRecord) {
|
checkIsFulfillable(offer: OfferSummaryRecord) {
|
||||||
@@ -1225,7 +1342,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const confirmationClaim: serverUtil.GenericVerifiableCredential = {
|
const confirmationClaim: GenericVerifiableCredential = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "AgreeAction",
|
"@type": "AgreeAction",
|
||||||
object: goodClaim,
|
object: goodClaim,
|
||||||
@@ -1276,56 +1393,5 @@ export default class ProjectViewView extends Vue {
|
|||||||
this.allMyDids,
|
this.allMyDids,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadTotals() {
|
|
||||||
this.loadingTotals = true;
|
|
||||||
const url =
|
|
||||||
this.apiServer +
|
|
||||||
"/api/v2/report/givesToPlans?planIds=" +
|
|
||||||
encodeURIComponent(JSON.stringify([this.projectId]));
|
|
||||||
const headers = await serverUtil.getHeaders(this.activeDid);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resp = await this.axios.get(url, { headers });
|
|
||||||
if (resp.status === 200 && resp.data.data) {
|
|
||||||
// Calculate totals by unit
|
|
||||||
const totals: { [key: string]: number } = {};
|
|
||||||
resp.data.data.forEach((give: GiveSummaryRecord) => {
|
|
||||||
const amount = give.fullClaim.object?.amountOfThisGood;
|
|
||||||
const unit = give.fullClaim.object?.unitCode;
|
|
||||||
if (amount && unit) {
|
|
||||||
totals[unit] = (totals[unit] || 0) + amount;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Convert totals object to array format
|
|
||||||
this.givesTotalsByUnit = Object.entries(totals).map(
|
|
||||||
([unit, amount]) => ({
|
|
||||||
unit,
|
|
||||||
amount,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error loading totals:", error);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "Failed to load totals for this project.",
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
this.loadingTotals = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
givenTotalHours(): number {
|
|
||||||
return (
|
|
||||||
this.givesTotalsByUnit.find((total) => total.unit === "HUR")?.amount || 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
:class="computedOfferTabClassNames()"
|
||||||
@click="
|
@click="
|
||||||
offers = [];
|
offers = [];
|
||||||
projects = [];
|
projects = [];
|
||||||
@@ -23,7 +24,6 @@
|
|||||||
showProjects = false;
|
showProjects = false;
|
||||||
loadOffers();
|
loadOffers();
|
||||||
"
|
"
|
||||||
v-bind:class="computedOfferTabClassNames()"
|
|
||||||
>
|
>
|
||||||
Offers
|
Offers
|
||||||
</a>
|
</a>
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
:class="computedProjectTabClassNames()"
|
||||||
@click="
|
@click="
|
||||||
offers = [];
|
offers = [];
|
||||||
projects = [];
|
projects = [];
|
||||||
@@ -38,7 +39,6 @@
|
|||||||
showProjects = true;
|
showProjects = true;
|
||||||
loadProjects();
|
loadProjects();
|
||||||
"
|
"
|
||||||
v-bind:class="computedProjectTabClassNames()"
|
|
||||||
>
|
>
|
||||||
Projects
|
Projects
|
||||||
</a>
|
</a>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<button
|
<button
|
||||||
class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
|
class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
|
||||||
>
|
>
|
||||||
<fa icon="magnifying-glass" class="fa-fw"></fa>
|
<font-awesome icon="magnifying-glass" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
-->
|
-->
|
||||||
@@ -68,15 +68,15 @@
|
|||||||
class="fixed right-6 top-24 text-center text-4xl leading-none bg-green-600 text-white w-14 py-2.5 rounded-full"
|
class="fixed right-6 top-24 text-center text-4xl leading-none bg-green-600 text-white w-14 py-2.5 rounded-full"
|
||||||
@click="onClickNewProject()"
|
@click="onClickNewProject()"
|
||||||
>
|
>
|
||||||
<fa icon="plus" class="fa-fw"></fa>
|
<font-awesome icon="plus" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Loading Animation -->
|
<!-- Loading Animation -->
|
||||||
<div
|
<div
|
||||||
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
|
|
||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
|
class="fixed left-6 bottom-24 text-center text-4xl leading-none bg-slate-400 text-white w-14 py-2.5 rounded-full"
|
||||||
>
|
>
|
||||||
<fa icon="spinner" class="fa-spin-pulse"></fa>
|
<font-awesome icon="spinner" class="fa-spin-pulse"></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Offer Results List -->
|
<!-- Offer Results List -->
|
||||||
@@ -90,22 +90,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul id="listOffers" class="border-t border-slate-300">
|
<ul id="listOffers" class="border-t border-slate-300">
|
||||||
<li
|
<li
|
||||||
class="border-b border-slate-300"
|
|
||||||
v-for="offer in offers"
|
v-for="offer in offers"
|
||||||
:key="offer.handleId"
|
:key="offer.handleId"
|
||||||
|
class="border-b border-slate-300"
|
||||||
>
|
>
|
||||||
<div class="block py-4 flex gap-4">
|
<div class="block py-4 flex gap-4">
|
||||||
<div v-if="offer.fulfillsPlanHandleId" class="flex-none">
|
<div v-if="offer.fulfillsPlanHandleId" class="flex-none">
|
||||||
<ProjectIcon
|
<ProjectIcon
|
||||||
:entityId="offer.fulfillsPlanHandleId"
|
:entity-id="offer.fulfillsPlanHandleId"
|
||||||
:iconSize="48"
|
:icon-size="48"
|
||||||
class="inline-block align-middle border border-slate-300 rounded-md max-h-12 max-w-12"
|
class="inline-block align-middle border border-slate-300 rounded-md max-h-12 max-w-12"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="offer.recipientDid" class="flex-none w-12">
|
<div v-if="offer.recipientDid" class="flex-none w-12">
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:entityId="offer.recipientDid"
|
:entity-id="offer.recipientDid"
|
||||||
:iconSize="48"
|
:icon-size="48"
|
||||||
class="inline-block align-middle border border-slate-300 rounded-md"
|
class="inline-block align-middle border border-slate-300 rounded-md"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -130,17 +130,20 @@
|
|||||||
|
|
||||||
<span class="text-sm">
|
<span class="text-sm">
|
||||||
<span v-if="offer.amount">
|
<span v-if="offer.amount">
|
||||||
<fa
|
<font-awesome
|
||||||
:icon="libsUtil.iconForUnitCode(offer.unit)"
|
:icon="libsUtil.iconForUnitCode(offer.unit)"
|
||||||
class="fa-fw text-slate-400"
|
class="fa-fw text-slate-400"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span v-if="offer.amountGiven >= offer.amount">
|
<span v-if="offer.amountGiven >= offer.amount">
|
||||||
<fa icon="check-circle" class="fa-fw text-green-500" />
|
<font-awesome
|
||||||
|
icon="check-circle"
|
||||||
|
class="fa-fw text-green-500"
|
||||||
|
/>
|
||||||
All {{ offer.amount }} given
|
All {{ offer.amount }} given
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="triangle-exclamation"
|
icon="triangle-exclamation"
|
||||||
class="fa-fw text-yellow-500"
|
class="fa-fw text-yellow-500"
|
||||||
/>
|
/>
|
||||||
@@ -162,7 +165,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<!-- only show icon if there's not already a warning -->
|
<!-- only show icon if there's not already a warning -->
|
||||||
<fa
|
<font-awesome
|
||||||
v-if="offer.amountGiven >= offer.amount"
|
v-if="offer.amountGiven >= offer.amount"
|
||||||
icon="triangle-exclamation"
|
icon="triangle-exclamation"
|
||||||
class="fa-fw text-yellow-300"
|
class="fa-fw text-yellow-300"
|
||||||
@@ -176,13 +179,16 @@
|
|||||||
<span v-else>
|
<span v-else>
|
||||||
<!-- Non-amount offer -->
|
<!-- Non-amount offer -->
|
||||||
<span v-if="offer.nonAmountGivenConfirmed">
|
<span v-if="offer.nonAmountGivenConfirmed">
|
||||||
<fa icon="check-circle" class="fa-fw text-green-500" />
|
<font-awesome
|
||||||
|
icon="check-circle"
|
||||||
|
class="fa-fw text-green-500"
|
||||||
|
/>
|
||||||
{{ offer.nonAmountGivenConfirmed }}
|
{{ offer.nonAmountGivenConfirmed }}
|
||||||
{{ offer.nonAmountGivenConfirmed == 1 ? "give" : "gives" }}
|
{{ offer.nonAmountGivenConfirmed == 1 ? "give" : "gives" }}
|
||||||
are confirmed.
|
are confirmed.
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<fa
|
<font-awesome
|
||||||
icon="triangle-exclamation"
|
icon="triangle-exclamation"
|
||||||
class="fa-fw text-yellow-500"
|
class="fa-fw text-yellow-500"
|
||||||
/>
|
/>
|
||||||
@@ -191,10 +197,10 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<a @click="onClickLoadClaim(offer.jwtId)">
|
<a @click="onClickLoadClaim(offer.jwtId)">
|
||||||
<fa
|
<font-awesome
|
||||||
icon="file-lines"
|
icon="file-lines"
|
||||||
class="pl-2 text-blue-500 cursor-pointer"
|
class="pl-2 text-blue-500 cursor-pointer"
|
||||||
></fa>
|
></font-awesome>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -209,7 +215,7 @@
|
|||||||
You have not announced any projects.
|
You have not announced any projects.
|
||||||
<div v-if="isRegistered">
|
<div v-if="isRegistered">
|
||||||
Hit the big
|
Hit the big
|
||||||
<fa
|
<font-awesome
|
||||||
icon="plus"
|
icon="plus"
|
||||||
class="bg-green-600 text-white px-1.5 py-1 rounded-full"
|
class="bg-green-600 text-white px-1.5 py-1 rounded-full"
|
||||||
/>
|
/>
|
||||||
@@ -217,8 +223,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<button
|
<button
|
||||||
@click="showNameThenIdDialog()"
|
|
||||||
class="text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
|
class="text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
|
||||||
|
@click="showNameThenIdDialog()"
|
||||||
>
|
>
|
||||||
Get someone to onboard you.
|
Get someone to onboard you.
|
||||||
</button>
|
</button>
|
||||||
@@ -227,19 +233,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul id="listProjects" class="border-t border-slate-300">
|
<ul id="listProjects" class="border-t border-slate-300">
|
||||||
<li
|
<li
|
||||||
class="border-b border-slate-300"
|
|
||||||
v-for="project in projects"
|
v-for="project in projects"
|
||||||
:key="project.handleId"
|
:key="project.handleId"
|
||||||
|
class="border-b border-slate-300"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
class="block py-4 flex gap-4"
|
||||||
@click="onClickLoadProject(project.handleId)"
|
@click="onClickLoadProject(project.handleId)"
|
||||||
class="block py-4 flex gap-4 cursor-pointer"
|
|
||||||
>
|
>
|
||||||
<div class="flex-none">
|
<div class="flex-none">
|
||||||
<ProjectIcon
|
<ProjectIcon
|
||||||
:entityId="project.handleId"
|
:entity-id="project.handleId"
|
||||||
:iconSize="48"
|
:icon-size="48"
|
||||||
:imageUrl="project.image"
|
:image-url="project.image"
|
||||||
class="inline-block align-middle border border-slate-300 rounded-md max-h-12 max-w-12"
|
class="inline-block align-middle border border-slate-300 rounded-md max-h-12 max-w-12"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -295,7 +301,9 @@ import { OnboardPage } from "../libs/util";
|
|||||||
})
|
})
|
||||||
export default class ProjectsView extends Vue {
|
export default class ProjectsView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
errNote(message) {
|
$router!: Router;
|
||||||
|
|
||||||
|
errNote(message: string) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{ group: "alert", type: "danger", title: "Error", text: message },
|
{ group: "alert", type: "danger", title: "Error", text: message },
|
||||||
5000,
|
5000,
|
||||||
@@ -417,7 +425,7 @@ export default class ProjectsView extends Vue {
|
|||||||
const route = {
|
const route = {
|
||||||
path: "/project/" + encodeURIComponent(id),
|
path: "/project/" + encodeURIComponent(id),
|
||||||
};
|
};
|
||||||
(this.$router as Router).push(route);
|
this.$router.push(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -427,14 +435,14 @@ export default class ProjectsView extends Vue {
|
|||||||
const route = {
|
const route = {
|
||||||
name: "new-edit-project",
|
name: "new-edit-project",
|
||||||
};
|
};
|
||||||
(this.$router as Router).push(route);
|
this.$router.push(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickLoadClaim(jwtId: string) {
|
onClickLoadClaim(jwtId: string) {
|
||||||
const route = {
|
const route = {
|
||||||
path: "/claim/" + encodeURIComponent(jwtId),
|
path: "/claim/" + encodeURIComponent(jwtId),
|
||||||
};
|
};
|
||||||
(this.$router as Router).push(route);
|
this.$router.push(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -537,10 +545,10 @@ export default class ProjectsView extends Vue {
|
|||||||
text: "If so, we'll use those with QR codes to share.",
|
text: "If so, we'll use those with QR codes to share.",
|
||||||
onCancel: async () => {},
|
onCancel: async () => {},
|
||||||
onNo: async () => {
|
onNo: async () => {
|
||||||
(this.$router as Router).push({ name: "share-my-contact-info" });
|
this.$router.push({ name: "share-my-contact-info" });
|
||||||
},
|
},
|
||||||
onYes: async () => {
|
onYes: async () => {
|
||||||
(this.$router as Router).push({ name: "contact-qr" });
|
this.$router.push({ name: "contact-qr" });
|
||||||
},
|
},
|
||||||
noText: "we will share another way",
|
noText: "we will share another way",
|
||||||
yesText: "we are nearby with cameras",
|
yesText: "we are nearby with cameras",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
@click="$router.back()"
|
@click="$router.back()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -22,17 +22,17 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 class="text-2xl m-2">You're Here</h2>
|
<h2 class="text-2xl m-2">You're Here</h2>
|
||||||
<div class="m-2 flex">
|
<div class="m-2 flex">
|
||||||
<input type="checkbox" v-model="attended" class="h-6 w-6" />
|
<input v-model="attended" type="checkbox" class="h-6 w-6" />
|
||||||
<span class="pb-2 pl-2 pr-2">Attended</span>
|
<span class="pb-2 pl-2 pr-2">Attended</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-2 flex">
|
<div class="m-2 flex">
|
||||||
<input type="checkbox" v-model="gaveTime" class="h-6 w-6" />
|
<input v-model="gaveTime" type="checkbox" class="h-6 w-6" />
|
||||||
<span class="pb-2 pl-2 pr-2">Spent Time</span>
|
<span class="pb-2 pl-2 pr-2">Spent Time</span>
|
||||||
<span v-if="gaveTime">
|
<span v-if="gaveTime">
|
||||||
<input
|
<input
|
||||||
|
v-model="hoursStr"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="How much time"
|
placeholder="How much time"
|
||||||
v-model="hoursStr"
|
|
||||||
size="1"
|
size="1"
|
||||||
class="border border-slate-400 h-6 px-2"
|
class="border border-slate-400 h-6 px-2"
|
||||||
/>
|
/>
|
||||||
@@ -48,8 +48,8 @@
|
|||||||
class="flex justify-center mt-4"
|
class="flex justify-center mt-4"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@click="record()"
|
|
||||||
class="block text-center text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md w-56"
|
class="block text-center text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md w-56"
|
||||||
|
@click="record()"
|
||||||
>
|
>
|
||||||
Sign & Send
|
Sign & Send
|
||||||
</button>
|
</button>
|
||||||
@@ -90,7 +90,7 @@ import * as libsUtil from "../libs/util";
|
|||||||
})
|
})
|
||||||
export default class QuickActionBvcBeginView extends Vue {
|
export default class QuickActionBvcBeginView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
attended = true;
|
attended = true;
|
||||||
gaveTime = true;
|
gaveTime = true;
|
||||||
hoursStr = "1";
|
hoursStr = "1";
|
||||||
@@ -201,7 +201,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
|||||||
},
|
},
|
||||||
3000,
|
3000,
|
||||||
);
|
);
|
||||||
(this.$router as Router).push({ path: "/quick-action-bvc" });
|
this.$router.push({ path: "/quick-action-bvc" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
@click="$router.back()"
|
@click="$router.back()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -22,16 +22,16 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 class="text-2xl m-2">Confirm</h2>
|
<h2 class="text-2xl m-2">Confirm</h2>
|
||||||
<div v-if="loadingConfirms" class="flex justify-center">
|
<div v-if="loadingConfirms" class="flex justify-center">
|
||||||
<fa icon="spinner" class="fa-spin-pulse" />
|
<font-awesome icon="spinner" class="fa-spin-pulse" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="claimsToConfirm.length === 0">
|
<div v-else-if="claimsToConfirm.length === 0">
|
||||||
There are no claims yet today for you to confirm.
|
There are no claims yet today for you to confirm.
|
||||||
</div>
|
</div>
|
||||||
<ul class="border-t border-slate-300 m-2">
|
<ul class="border-t border-slate-300 m-2">
|
||||||
<li
|
<li
|
||||||
class="border-b border-slate-300 py-2"
|
|
||||||
v-for="record in claimsToConfirm"
|
v-for="record in claimsToConfirm"
|
||||||
:key="record.id"
|
:key="record.id"
|
||||||
|
class="border-b border-slate-300 py-2"
|
||||||
>
|
>
|
||||||
<div class="grid grid-cols-12">
|
<div class="grid grid-cols-12">
|
||||||
<span class="col-span-11 justify-self-start">
|
<span class="col-span-11 justify-self-start">
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:checked="claimsToConfirmSelected.includes(record.id)"
|
:checked="claimsToConfirmSelected.includes(record.id)"
|
||||||
|
class="mr-2 h-6 w-6"
|
||||||
@click="
|
@click="
|
||||||
claimsToConfirmSelected.includes(record.id)
|
claimsToConfirmSelected.includes(record.id)
|
||||||
? claimsToConfirmSelected.splice(
|
? claimsToConfirmSelected.splice(
|
||||||
@@ -47,7 +48,6 @@
|
|||||||
)
|
)
|
||||||
: claimsToConfirmSelected.push(record.id)
|
: claimsToConfirmSelected.push(record.id)
|
||||||
"
|
"
|
||||||
class="mr-2 h-6 w-6"
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
{{
|
{{
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
<a @click="onClickLoadClaim(record.id)">
|
<a @click="onClickLoadClaim(record.id)">
|
||||||
<fa
|
<font-awesome
|
||||||
icon="file-lines"
|
icon="file-lines"
|
||||||
class="pl-2 text-blue-500 cursor-pointer"
|
class="pl-2 text-blue-500 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
}}
|
}}
|
||||||
so if you expected but do not see details from someone then ask them to
|
so if you expected but do not see details from someone then ask them to
|
||||||
check that their activity is visible to you on their Contacts
|
check that their activity is visible to you on their Contacts
|
||||||
<fa icon="users" class="text-slate-500" />
|
<font-awesome icon="users" class="text-slate-500" />
|
||||||
page.
|
page.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,18 +96,18 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 class="text-2xl m-2">Anything else?</h2>
|
<h2 class="text-2xl m-2">Anything else?</h2>
|
||||||
<div class="m-2 flex">
|
<div class="m-2 flex">
|
||||||
<input type="checkbox" v-model="someoneGave" class="h-6 w-6" />
|
<input v-model="someoneGave" type="checkbox" class="h-6 w-6" />
|
||||||
<span class="pb-2 pl-2 pr-2">The group provided</span>
|
<span class="pb-2 pl-2 pr-2">The group provided</span>
|
||||||
<span v-if="someoneGave">
|
<span v-if="someoneGave">
|
||||||
<input
|
<input
|
||||||
type="text"
|
|
||||||
v-model="description"
|
v-model="description"
|
||||||
|
type="text"
|
||||||
size="20"
|
size="20"
|
||||||
class="border border-slate-400 h-6 px-2"
|
class="border border-slate-400 h-6 px-2"
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
(Everyone likes personalized messages! 😁 ... and for a pic:
|
(Everyone likes personalized messages! 😁 ... and for a pic:
|
||||||
<input type="checkbox" v-model="supplyGiftDetails" />)
|
<input v-model="supplyGiftDetails" type="checkbox" />)
|
||||||
</span>
|
</span>
|
||||||
<!-- This is to match input height to avoid shifting when hiding & showing. -->
|
<!-- This is to match input height to avoid shifting when hiding & showing. -->
|
||||||
<span v-else class="h-6">...</span>
|
<span v-else class="h-6">...</span>
|
||||||
@@ -119,8 +119,8 @@
|
|||||||
class="flex justify-center mt-4"
|
class="flex justify-center mt-4"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@click="record()"
|
|
||||||
class="block text-center text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md w-56"
|
class="block text-center text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md w-56"
|
||||||
|
@click="record()"
|
||||||
>
|
>
|
||||||
Sign & Send
|
Sign & Send
|
||||||
</button>
|
</button>
|
||||||
@@ -151,16 +151,18 @@ import {
|
|||||||
retrieveSettingsForActiveAccount,
|
retrieveSettingsForActiveAccount,
|
||||||
} from "../db/index";
|
} from "../db/index";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
|
import {
|
||||||
|
GenericCredWrapper,
|
||||||
|
GenericVerifiableCredential,
|
||||||
|
ErrorResult,
|
||||||
|
} from "../interfaces";
|
||||||
import {
|
import {
|
||||||
BVC_MEETUPS_PROJECT_CLAIM_ID,
|
BVC_MEETUPS_PROJECT_CLAIM_ID,
|
||||||
claimSpecialDescription,
|
claimSpecialDescription,
|
||||||
containsHiddenDid,
|
containsHiddenDid,
|
||||||
createAndSubmitConfirmation,
|
createAndSubmitConfirmation,
|
||||||
createAndSubmitGive,
|
createAndSubmitGive,
|
||||||
GenericCredWrapper,
|
|
||||||
GenericVerifiableCredential,
|
|
||||||
getHeaders,
|
getHeaders,
|
||||||
ErrorResult,
|
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
@click="$router.back()"
|
@click="$router.back()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -41,12 +41,14 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import TopMessage from "../components/TopMessage.vue";
|
import TopMessage from "../components/TopMessage.vue";
|
||||||
|
import { Router } from "vue-router";
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
QuickNav,
|
QuickNav,
|
||||||
TopMessage,
|
TopMessage,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class QuickActionBvcView extends Vue {}
|
export default class QuickActionBvcView extends Vue {
|
||||||
|
$router!: Router;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
<div id="ViewBreadcrumb" class="mb-8">
|
<div id="ViewBreadcrumb" class="mb-8">
|
||||||
<h1 class="text-lg text-center font-light relative px-7">
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<fa
|
<font-awesome
|
||||||
icon="chevron-left"
|
icon="chevron-left"
|
||||||
@click="$router.back()"
|
|
||||||
class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.back()"
|
||||||
/>
|
/>
|
||||||
Offers to Your Projects
|
Offers to Your Projects
|
||||||
</h1>
|
</h1>
|
||||||
@@ -20,13 +20,13 @@
|
|||||||
<p class="mt-2">
|
<p class="mt-2">
|
||||||
Maybe there are already some projects you can help on the
|
Maybe there are already some projects you can help on the
|
||||||
<router-link to="/discover" class="text-blue-500">
|
<router-link to="/discover" class="text-blue-500">
|
||||||
Discover page <fa icon="search" />
|
Discover page <font-awesome icon="search" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-2">
|
<p class="mt-2">
|
||||||
You can announce more of your own on
|
You can announce more of your own on
|
||||||
<router-link to="/contacts" class="text-blue-500">
|
<router-link to="/contacts" class="text-blue-500">
|
||||||
Your Ideas page <fa icon="hand" />
|
Your Ideas page <font-awesome icon="hand" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
class="mt-4 relative group"
|
class="mt-4 relative group"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="border-b border-slate-300 text-orange-400 pb-2 mb-2 font-bold text-sm"
|
|
||||||
v-if="offer.jwtId == lastAckedOfferToUserProjectsJwtId"
|
v-if="offer.jwtId == lastAckedOfferToUserProjectsJwtId"
|
||||||
|
class="border-b border-slate-300 text-orange-400 pb-2 mb-2 font-bold text-sm"
|
||||||
>
|
>
|
||||||
You've already seen all the following
|
You've already seen all the following
|
||||||
</div>
|
</div>
|
||||||
@@ -65,7 +65,10 @@
|
|||||||
:to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }"
|
:to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }"
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
>
|
>
|
||||||
<fa icon="file-lines" class="pl-2 text-blue-500 cursor-pointer" />
|
<font-awesome
|
||||||
|
icon="file-lines"
|
||||||
|
class="pl-2 text-blue-500 cursor-pointer"
|
||||||
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -83,11 +86,12 @@ import QuickNav from "../components/QuickNav.vue";
|
|||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
|
import { Router } from "vue-router";
|
||||||
|
import { OfferToPlanSummaryRecord } from "../interfaces";
|
||||||
import {
|
import {
|
||||||
didInfo,
|
didInfo,
|
||||||
displayAmount,
|
displayAmount,
|
||||||
getNewOffersToUserProjects,
|
getNewOffersToUserProjects,
|
||||||
OfferToPlanSummaryRecord,
|
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import { retrieveAccountDids } from "../libs/util";
|
import { retrieveAccountDids } from "../libs/util";
|
||||||
|
|
||||||
@@ -96,7 +100,7 @@ import { retrieveAccountDids } from "../libs/util";
|
|||||||
})
|
})
|
||||||
export default class RecentOffersToUserView extends Vue {
|
export default class RecentOffersToUserView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
allMyDids: string[] = [];
|
allMyDids: string[] = [];
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
<div id="ViewBreadcrumb" class="mb-8">
|
<div id="ViewBreadcrumb" class="mb-8">
|
||||||
<h1 class="text-lg text-center font-light relative px-7">
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<fa
|
<font-awesome
|
||||||
icon="chevron-left"
|
icon="chevron-left"
|
||||||
@click="$router.back()"
|
|
||||||
class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
@click="$router.back()"
|
||||||
/>
|
/>
|
||||||
Offers to You
|
Offers to You
|
||||||
</h1>
|
</h1>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<p class="mt-2">
|
<p class="mt-2">
|
||||||
You can start the cycle on the
|
You can start the cycle on the
|
||||||
<router-link to="/contacts" class="text-blue-500">
|
<router-link to="/contacts" class="text-blue-500">
|
||||||
Contacts page <fa icon="users" />
|
Contacts page <font-awesome icon="users" />
|
||||||
</router-link>
|
</router-link>
|
||||||
with an "Offer" directly to someone. Hopefully you'll find a common
|
with an "Offer" directly to someone. Hopefully you'll find a common
|
||||||
interest!
|
interest!
|
||||||
@@ -37,8 +37,8 @@
|
|||||||
class="mt-4 relative group"
|
class="mt-4 relative group"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="border-b border-slate-300 text-orange-400 pb-2 mb-2 font-bold text-sm"
|
|
||||||
v-if="offer.jwtId == lastAckedOfferToUserJwtId"
|
v-if="offer.jwtId == lastAckedOfferToUserJwtId"
|
||||||
|
class="border-b border-slate-300 text-orange-400 pb-2 mb-2 font-bold text-sm"
|
||||||
>
|
>
|
||||||
You've already seen all the following
|
You've already seen all the following
|
||||||
</div>
|
</div>
|
||||||
@@ -58,7 +58,10 @@
|
|||||||
:to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }"
|
:to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }"
|
||||||
class="text-blue-500"
|
class="text-blue-500"
|
||||||
>
|
>
|
||||||
<fa icon="file-lines" class="pl-2 text-blue-500 cursor-pointer" />
|
<font-awesome
|
||||||
|
icon="file-lines"
|
||||||
|
class="pl-2 text-blue-500 cursor-pointer"
|
||||||
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -68,7 +71,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
import { Router } from "vue-router";
|
||||||
import GiftedDialog from "../components/GiftedDialog.vue";
|
import GiftedDialog from "../components/GiftedDialog.vue";
|
||||||
import EntityIcon from "../components/EntityIcon.vue";
|
import EntityIcon from "../components/EntityIcon.vue";
|
||||||
import InfiniteScroll from "../components/InfiniteScroll.vue";
|
import InfiniteScroll from "../components/InfiniteScroll.vue";
|
||||||
@@ -76,11 +79,11 @@ import QuickNav from "../components/QuickNav.vue";
|
|||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
|
import { OfferSummaryRecord } from "../interfaces";
|
||||||
import {
|
import {
|
||||||
didInfo,
|
didInfo,
|
||||||
displayAmount,
|
displayAmount,
|
||||||
getNewOffersToUser,
|
getNewOffersToUser,
|
||||||
OfferSummaryRecord,
|
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import { retrieveAccountDids } from "../libs/util";
|
import { retrieveAccountDids } from "../libs/util";
|
||||||
|
|
||||||
@@ -89,7 +92,7 @@ import { retrieveAccountDids } from "../libs/util";
|
|||||||
})
|
})
|
||||||
export default class RecentOffersToUserView extends Vue {
|
export default class RecentOffersToUserView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
allMyDids: string[] = [];
|
allMyDids: string[] = [];
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
@click="$router.back()"
|
@click="$router.back()"
|
||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
||||||
@click="storeSearchBox"
|
@click="storeSearchBox"
|
||||||
>
|
>
|
||||||
<fa icon="save" class="fa-fw" />
|
<font-awesome icon="save" class="fa-fw" />
|
||||||
Store This Location for Nearby Search
|
Store This Location for Nearby Search
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
||||||
@click="forgetSearchBox"
|
@click="forgetSearchBox"
|
||||||
>
|
>
|
||||||
<fa icon="trash-can" class="fa-fw" />
|
<font-awesome icon="trash-can" class="fa-fw" />
|
||||||
Delete Stored Location
|
Delete Stored Location
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
||||||
@click="resetLatLong"
|
@click="resetLatLong"
|
||||||
>
|
>
|
||||||
<fa icon="rotate" class="fa-fw" />
|
<font-awesome icon="rotate" class="fa-fw" />
|
||||||
Reset To Original
|
Reset To Original
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
class="m-4 px-4 py-2 rounded-md bg-blue-200 text-blue-500"
|
||||||
@click="isNewMarkerSet = false"
|
@click="isNewMarkerSet = false"
|
||||||
>
|
>
|
||||||
<fa icon="eraser" class="fa-fw" />
|
<font-awesome icon="eraser" class="fa-fw" />
|
||||||
Erase Marker
|
Erase Marker
|
||||||
</button>
|
</button>
|
||||||
<div v-if="isNewMarkerSet">
|
<div v-if="isNewMarkerSet">
|
||||||
@@ -71,9 +71,9 @@
|
|||||||
<div class="aspect-video">
|
<div class="aspect-video">
|
||||||
<l-map
|
<l-map
|
||||||
ref="map"
|
ref="map"
|
||||||
|
v-model:zoom="localZoom"
|
||||||
:center="[localCenterLat, localCenterLong]"
|
:center="[localCenterLat, localCenterLong]"
|
||||||
class="!z-40 rounded-md"
|
class="!z-40 rounded-md"
|
||||||
v-model:zoom="localZoom"
|
|
||||||
@click="setMapPoint"
|
@click="setMapPoint"
|
||||||
>
|
>
|
||||||
<l-tile-layer
|
<l-tile-layer
|
||||||
@@ -131,6 +131,7 @@ const DEFAULT_ZOOM = 2;
|
|||||||
})
|
})
|
||||||
export default class SearchAreaView extends Vue {
|
export default class SearchAreaView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
$router!: Router;
|
||||||
|
|
||||||
isChoosingSearchBox = false;
|
isChoosingSearchBox = false;
|
||||||
isNewMarkerSet = false;
|
isNewMarkerSet = false;
|
||||||
@@ -219,7 +220,7 @@ export default class SearchAreaView extends Vue {
|
|||||||
},
|
},
|
||||||
7000,
|
7000,
|
||||||
);
|
);
|
||||||
(this.$router as Router).back();
|
this.$router.back();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user