Compare commits
10 Commits
gifting-pe
...
deep-link
| Author | SHA1 | Date | |
|---|---|---|---|
| c1477d0266 | |||
| 33ce6bdb72 | |||
| dc21e8dac3 | |||
| a9a8ba217c | |||
| b0d99e7c1e | |||
| a96cc8155c | |||
| 861408c7bc | |||
| 1b283a0045 | |||
| afd407e178 | |||
|
|
59b13823c8 |
@@ -362,7 +362,7 @@ Prerequisites: macOS with Xcode installed
|
|||||||
4. Bump the version to match Android & package.json:
|
4. Bump the version to match Android & package.json:
|
||||||
|
|
||||||
```
|
```
|
||||||
cd ios/App && xcrun agvtool new-version 35 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.0.2;/g" App.xcodeproj/project.pbxproj && cd -
|
cd ios/App && xcrun agvtool new-version 36 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.0.3;/g" App.xcodeproj/project.pbxproj && cd -
|
||||||
# Unfortunately this edits Info.plist directly.
|
# Unfortunately this edits Info.plist directly.
|
||||||
#xcrun agvtool new-marketing-version 0.4.5
|
#xcrun agvtool new-marketing-version 0.4.5
|
||||||
```
|
```
|
||||||
@@ -385,11 +385,12 @@ Prerequisites: macOS with Xcode installed
|
|||||||
* This will trigger a build and take time, needing user's "login" keychain password (user's login password), repeatedly.
|
* This will trigger a build and take time, needing user's "login" keychain password (user's login password), repeatedly.
|
||||||
* If it fails with `building for 'iOS', but linking in dylib (.../.pkgx/zlib.net/v1.3.0/lib/libz.1.3.dylib) built for 'macOS'` then run XCode outside that terminal (ie. not with `npx cap open ios`).
|
* If it fails with `building for 'iOS', but linking in dylib (.../.pkgx/zlib.net/v1.3.0/lib/libz.1.3.dylib) built for 'macOS'` then run XCode outside that terminal (ie. not with `npx cap open ios`).
|
||||||
* Click Distribute -> App Store Connect
|
* Click Distribute -> App Store Connect
|
||||||
* In AppStoreConnect, add the build to the distribution: remove the current build with the "-" when you hover over it, then "Add Build" with the new build.
|
* In AppStoreConnect, add the build to the distribution. You may have to remove the current build with the "-" when you hover over it, then "Add Build" with the new build.
|
||||||
* May have to go to App Review, click Submission, then hover over the build and click "-".
|
* May have to go to App Review, click Submission, then hover over the build and click "-".
|
||||||
* It can take 15 minutes for the build to show up in the list of builds.
|
* It can take 15 minutes for the build to show up in the list of builds.
|
||||||
* You'll probably have to "Manage" something about encryption, disallowed in France.
|
* You'll probably have to "Manage" something about encryption, disallowed in France.
|
||||||
* Then "Save" and "Add to Review" and "Resubmit to App Review".
|
* Then "Save" and "Add to Review" and "Resubmit to App Review".
|
||||||
|
* Eventually it'll be "Ready for Distribution" which means
|
||||||
|
|
||||||
### Android Build
|
### Android Build
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.3] - 2025.07.12
|
||||||
|
### Changed
|
||||||
|
- Photo is pinned to profile mode
|
||||||
|
### Fixed
|
||||||
|
- Deep link URLs (and other prod settings)
|
||||||
|
- Error in BVC begin view
|
||||||
|
|
||||||
|
|
||||||
## [1.0.2] - 2025.06.20 - 276e0a741bc327de3380c4e508cccb7fee58c06d
|
## [1.0.2] - 2025.06.20 - 276e0a741bc327de3380c4e508cccb7fee58c06d
|
||||||
### Added
|
### Added
|
||||||
- Version on feed title
|
- Version on feed title
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ See [TESTING.md](test-playwright/TESTING.md) for detailed test instructions.
|
|||||||
|
|
||||||
Application icons are in the `assets` directory, processed by the `capacitor-assets` command.
|
Application icons are in the `assets` directory, processed by the `capacitor-assets` command.
|
||||||
|
|
||||||
To add a Font Awesome icon, add to main.ts and reference with `font-awesome` element and `icon` attribute with the hyphenated name.
|
To add a Font Awesome icon, add to fontawesome.ts and reference with `font-awesome` element and `icon` attribute with the hyphenated name.
|
||||||
|
|
||||||
## Other
|
## Other
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ android {
|
|||||||
applicationId "app.timesafari.app"
|
applicationId "app.timesafari.app"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 35
|
versionCode 36
|
||||||
versionName "1.0.2"
|
versionName "1.0.3"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/title_activity_main"
|
android:label="@string/title_activity_main"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/AppTheme.NoActionBarLaunch">
|
android:theme="@style/AppTheme.NoActionBarLaunch">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|||||||
@@ -403,7 +403,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 35;
|
CURRENT_PROJECT_VERSION = 36;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
@@ -413,7 +413,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.2;
|
MARKETING_VERSION = 1.0.3;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -430,7 +430,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 35;
|
CURRENT_PROJECT_VERSION = 36;
|
||||||
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
DEVELOPMENT_TEAM = GM3FS5JQPH;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
@@ -440,7 +440,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0.2;
|
MARKETING_VERSION = 1.0.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||||
|
|||||||
@@ -37,8 +37,6 @@
|
|||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "timesafari",
|
"name": "timesafari",
|
||||||
"version": "1.0.3-beta",
|
"version": "1.0.4-beta",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "timesafari",
|
"name": "timesafari",
|
||||||
"version": "1.0.3-beta",
|
"version": "1.0.4-beta",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor-community/sqlite": "6.0.2",
|
"@capacitor-community/sqlite": "6.0.2",
|
||||||
"@capacitor-mlkit/barcode-scanning": "^6.0.0",
|
"@capacitor-mlkit/barcode-scanning": "^6.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "timesafari",
|
"name": "timesafari",
|
||||||
"version": "1.0.3-beta",
|
"version": "1.0.4-beta",
|
||||||
"description": "Time Safari Application",
|
"description": "Time Safari Application",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Time Safari Team"
|
"name": "Time Safari Team"
|
||||||
|
|||||||
@@ -1,534 +1,99 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="visible" class="dialog-overlay">
|
<div v-if="visible" class="dialog-overlay">
|
||||||
<div class="dialog">
|
<div class="dialog">
|
||||||
<!-- Step 1: Giver -->
|
<h1 class="text-xl font-bold text-center mb-4">
|
||||||
<div v-show="firstStep" id="sectionGiftedGiver">
|
{{ customTitle }}
|
||||||
<label class="block font-bold mb-4">
|
</h1>
|
||||||
{{
|
<input
|
||||||
stepType === "recipient"
|
v-model="description"
|
||||||
? "Choose who received the gift:"
|
type="text"
|
||||||
: showProjects
|
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||||
? "Choose a project benefitted from:"
|
:placeholder="prompt || 'What was given?'"
|
||||||
: "Choose a person received from:"
|
/>
|
||||||
}}
|
<div class="flex flex-row justify-center">
|
||||||
</label>
|
<span
|
||||||
|
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 text-center text-blue-500 px-2 py-2 w-20"
|
||||||
<!-- Unified Quick-pick grid for People and Projects -->
|
@click="changeUnitCode()"
|
||||||
<ul
|
|
||||||
:class="
|
|
||||||
shouldShowProjects
|
|
||||||
? 'grid grid-cols-3 md:grid-cols-4 gap-x-2 gap-y-4 text-center mb-4'
|
|
||||||
: 'grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 gap-x-2 gap-y-4 text-center mb-4'
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<template v-if="shouldShowProjects">
|
{{ libsUtil.UNIT_SHORT[unitCode] || unitCode }}
|
||||||
<!-- show projects -->
|
</span>
|
||||||
<li
|
<div
|
||||||
v-for="project in projects.slice(0, 7)"
|
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
:key="project.handleId"
|
@click="amountInput === '0' ? null : decrement()"
|
||||||
class="cursor-pointer"
|
>
|
||||||
@click="
|
<font-awesome icon="chevron-left" />
|
||||||
stepType === 'recipient'
|
</div>
|
||||||
? selectRecipientProject(project)
|
<input
|
||||||
: selectProject(project)
|
id="inputGivenAmount"
|
||||||
"
|
v-model="amountInput"
|
||||||
>
|
type="number"
|
||||||
<div class="relative w-fit mx-auto mb-1">
|
class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20"
|
||||||
<ProjectIcon
|
/>
|
||||||
:entity-id="project.handleId"
|
<div
|
||||||
:icon-size="48"
|
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
||||||
:image-url="project.image"
|
@click="increment()"
|
||||||
class="!size-[3rem] mx-auto border border-slate-300 bg-white overflow-hidden rounded-full"
|
>
|
||||||
/>
|
<font-awesome icon="chevron-right" />
|
||||||
</div>
|
</div>
|
||||||
<h3
|
</div>
|
||||||
class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"
|
<div class="mt-4 flex justify-center">
|
||||||
>
|
<span>
|
||||||
{{ project.name }}
|
<router-link
|
||||||
</h3>
|
:to="{
|
||||||
<div class="text-xs text-slate-500 truncate">
|
name: 'gifted-details',
|
||||||
<font-awesome icon="user" class="fa-fw text-slate-400" />
|
query: {
|
||||||
{{
|
amountInput,
|
||||||
didInfo(project.issuerDid, activeDid, allMyDids, allContacts)
|
description,
|
||||||
}}
|
giverDid: giver?.did,
|
||||||
</div>
|
giverName: giver?.name,
|
||||||
</li>
|
offerId,
|
||||||
<li
|
fulfillsProjectId: toProjectId,
|
||||||
v-if="projects.length === 0"
|
providerProjectId: fromProjectId,
|
||||||
class="text-xs text-slate-500 italic col-span-full"
|
recipientDid: receiver?.did,
|
||||||
>
|
recipientName: receiver?.name,
|
||||||
(No projects found.)
|
unitCode,
|
||||||
</li>
|
},
|
||||||
<li v-if="projects.length > 0">
|
}"
|
||||||
<router-link :to="{ name: 'discover' }" class="cursor-pointer">
|
class="text-blue-500"
|
||||||
<font-awesome
|
>
|
||||||
icon="circle-right"
|
Photo & more options ...
|
||||||
class="text-blue-500 text-5xl mb-1"
|
</router-link>
|
||||||
/>
|
</span>
|
||||||
<h3
|
</div>
|
||||||
class="text-xs text-slate-400 font-medium italic text-ellipsis whitespace-nowrap overflow-hidden"
|
<p class="text-center mb-2 mt-6 italic">
|
||||||
>
|
Sign & Send to publish to the world
|
||||||
Show All
|
<font-awesome
|
||||||
</h3>
|
icon="circle-info"
|
||||||
</router-link>
|
class="pl-2 text-blue-500 cursor-pointer"
|
||||||
</li>
|
@click="explainData()"
|
||||||
</template>
|
/>
|
||||||
<template v-else>
|
</p>
|
||||||
<!-- show people (contacts) -->
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||||
<li
|
|
||||||
v-if="
|
|
||||||
stepType === 'recipient' ||
|
|
||||||
(stepType === 'giver' && isFromProjectView)
|
|
||||||
"
|
|
||||||
:class="{
|
|
||||||
'cursor-pointer': !wouldCreateConflict(activeDid),
|
|
||||||
'cursor-not-allowed opacity-50': wouldCreateConflict(activeDid)
|
|
||||||
}"
|
|
||||||
@click="
|
|
||||||
!wouldCreateConflict(activeDid) &&
|
|
||||||
(stepType === 'recipient'
|
|
||||||
? selectRecipient({ did: activeDid, name: 'You' })
|
|
||||||
: selectGiver({ did: activeDid, name: 'You' }))
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<font-awesome
|
|
||||||
:class="{
|
|
||||||
'text-blue-500 text-5xl mb-1': !wouldCreateConflict(activeDid),
|
|
||||||
'text-slate-400 text-5xl mb-1': wouldCreateConflict(activeDid)
|
|
||||||
}"
|
|
||||||
icon="hand"
|
|
||||||
/>
|
|
||||||
<h3
|
|
||||||
:class="{
|
|
||||||
'text-xs text-blue-500 font-medium text-ellipsis whitespace-nowrap overflow-hidden': !wouldCreateConflict(activeDid),
|
|
||||||
'text-xs text-slate-400 font-medium text-ellipsis whitespace-nowrap overflow-hidden': wouldCreateConflict(activeDid)
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
You
|
|
||||||
</h3>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
class="cursor-pointer"
|
|
||||||
@click="
|
|
||||||
stepType === 'recipient' ? selectRecipient() : selectGiver()
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<font-awesome
|
|
||||||
icon="circle-question"
|
|
||||||
class="text-slate-400 text-5xl mb-1"
|
|
||||||
/>
|
|
||||||
<h3
|
|
||||||
class="text-xs text-slate-400 font-medium italic text-ellipsis whitespace-nowrap overflow-hidden"
|
|
||||||
>
|
|
||||||
(Unnamed)
|
|
||||||
</h3>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-if="allContacts.length === 0"
|
|
||||||
class="text-xs text-slate-500 italic col-span-full"
|
|
||||||
>
|
|
||||||
(Add friends to see more people worthy of recognition.)
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-for="contact in allContacts.slice(0, 10)"
|
|
||||||
:key="contact.did"
|
|
||||||
:class="{
|
|
||||||
'cursor-pointer': !wouldCreateConflict(contact.did),
|
|
||||||
'cursor-not-allowed opacity-50': wouldCreateConflict(contact.did)
|
|
||||||
}"
|
|
||||||
@click="
|
|
||||||
!wouldCreateConflict(contact.did) &&
|
|
||||||
(stepType === 'recipient'
|
|
||||||
? selectRecipient(contact)
|
|
||||||
: selectGiver(contact))
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="relative w-fit mx-auto mb-1">
|
|
||||||
<EntityIcon
|
|
||||||
:contact="contact"
|
|
||||||
class="!size-[3rem] mx-auto border border-slate-300 bg-white overflow-hidden rounded-full"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="rounded-full bg-slate-400 absolute bottom-0 right-0 p-1 translate-x-1/3"
|
|
||||||
>
|
|
||||||
<font-awesome
|
|
||||||
icon="clock"
|
|
||||||
class="block text-white text-xs w-[1em]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h3
|
|
||||||
:class="{
|
|
||||||
'text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden': !wouldCreateConflict(contact.did) && contact.name,
|
|
||||||
'text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden text-slate-400 italic': !contact.name,
|
|
||||||
'text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden text-slate-400': wouldCreateConflict(contact.did) && contact.name
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ contact.name || "(No name)" }}
|
|
||||||
</h3>
|
|
||||||
</li>
|
|
||||||
<li v-if="allContacts.length > 0" class="cursor-pointer">
|
|
||||||
<router-link
|
|
||||||
:to="{
|
|
||||||
name: 'contact-gift',
|
|
||||||
query: {
|
|
||||||
stepType: stepType,
|
|
||||||
giverEntityType: giverEntityType,
|
|
||||||
recipientEntityType: recipientEntityType,
|
|
||||||
...(stepType === 'giver'
|
|
||||||
? {
|
|
||||||
recipientProjectId: toProjectId,
|
|
||||||
recipientProjectName: receiver?.name,
|
|
||||||
recipientProjectImage: receiver?.image,
|
|
||||||
recipientProjectHandleId: receiver?.handleId,
|
|
||||||
recipientDid: receiver?.did,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
giverProjectId: fromProjectId,
|
|
||||||
giverProjectName: giver?.name,
|
|
||||||
giverProjectImage: giver?.image,
|
|
||||||
giverProjectHandleId: giver?.handleId,
|
|
||||||
giverDid: giver?.did,
|
|
||||||
}),
|
|
||||||
fromProjectId: fromProjectId,
|
|
||||||
toProjectId: toProjectId,
|
|
||||||
showProjects: (showProjects || false).toString(),
|
|
||||||
isFromProjectView: (isFromProjectView || false).toString(),
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<font-awesome
|
|
||||||
icon="circle-right"
|
|
||||||
class="text-blue-500 text-5xl mb-1"
|
|
||||||
/>
|
|
||||||
<h3
|
|
||||||
class="text-xs text-slate-400 font-medium italic text-ellipsis whitespace-nowrap overflow-hidden"
|
|
||||||
>
|
|
||||||
Show All
|
|
||||||
</h3>
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<button
|
<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-lg"
|
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="confirm"
|
||||||
|
>
|
||||||
|
Sign & Send
|
||||||
|
</button>
|
||||||
|
<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"
|
||||||
@click="cancel"
|
@click="cancel"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 2: Gift -->
|
|
||||||
<div v-show="!firstStep" id="sectionGiftedGift">
|
|
||||||
<div class="grid grid-cols-2 gap-2 mb-4">
|
|
||||||
<!-- Giver Button -->
|
|
||||||
<button
|
|
||||||
v-if="
|
|
||||||
(giverEntityType === 'person' || giverEntityType === 'project') &&
|
|
||||||
!(isFromProjectView && giverEntityType === 'project')
|
|
||||||
"
|
|
||||||
class="flex-1 flex items-center gap-2 bg-slate-100 border border-slate-300 rounded-md p-2"
|
|
||||||
@click="goBackToStep1('giver')"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<template v-if="giverEntityType === 'project'">
|
|
||||||
<ProjectIcon
|
|
||||||
v-if="giver?.handleId"
|
|
||||||
:entity-id="giver.handleId"
|
|
||||||
:icon-size="32"
|
|
||||||
:image-url="giver.image"
|
|
||||||
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<EntityIcon
|
|
||||||
v-if="giver?.did"
|
|
||||||
:contact="giver"
|
|
||||||
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
||||||
/>
|
|
||||||
<font-awesome
|
|
||||||
v-else
|
|
||||||
icon="circle-question"
|
|
||||||
class="text-slate-400 text-3xl"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-start min-w-0">
|
|
||||||
<p class="text-xs text-slate-500 leading-1 -mb-1 uppercase">
|
|
||||||
{{
|
|
||||||
giverEntityType === "project"
|
|
||||||
? "Benefited from:"
|
|
||||||
: "Received from:"
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
<h3
|
|
||||||
v-if="giver?.name && giver.name !== giver.did"
|
|
||||||
class="font-semibold truncate"
|
|
||||||
>
|
|
||||||
{{ giver.name }}
|
|
||||||
</h3>
|
|
||||||
<h3
|
|
||||||
v-if="giver?.name && giver.name === giver.did"
|
|
||||||
class="font-semibold truncate text-slate-400 italic"
|
|
||||||
>
|
|
||||||
(No name)
|
|
||||||
</h3>
|
|
||||||
<h3
|
|
||||||
v-else-if="!giver?.name"
|
|
||||||
class="font-semibold truncate text-slate-400 italic"
|
|
||||||
>
|
|
||||||
(Unnamed)
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="ms-auto text-sm text-blue-500 pe-1">
|
|
||||||
<font-awesome icon="pen" title="Change" />
|
|
||||||
</p>
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="flex-1 flex items-center gap-2 bg-slate-100 border border-slate-300 rounded-md p-2"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<template v-if="giverEntityType === 'project'">
|
|
||||||
<ProjectIcon
|
|
||||||
v-if="giver?.handleId"
|
|
||||||
:entity-id="giver.handleId"
|
|
||||||
:icon-size="32"
|
|
||||||
:image-url="giver.image"
|
|
||||||
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<EntityIcon
|
|
||||||
v-if="giver?.did"
|
|
||||||
:contact="giver"
|
|
||||||
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
||||||
/>
|
|
||||||
<font-awesome
|
|
||||||
v-else
|
|
||||||
icon="circle-question"
|
|
||||||
class="text-slate-400 text-3xl"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-start min-w-0">
|
|
||||||
<p class="text-xs text-slate-500 leading-1 -mb-1 uppercase">
|
|
||||||
{{
|
|
||||||
giverEntityType === "project"
|
|
||||||
? "Benefited from:"
|
|
||||||
: "Received from:"
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
<h3
|
|
||||||
v-if="giver?.name && giver.name !== giver.did"
|
|
||||||
class="font-semibold truncate"
|
|
||||||
>
|
|
||||||
{{ giver.name }}
|
|
||||||
</h3>
|
|
||||||
<h3
|
|
||||||
v-if="giver?.name && giver.name === giver.did"
|
|
||||||
class="font-semibold truncate text-slate-400 italic"
|
|
||||||
>
|
|
||||||
(No name)
|
|
||||||
</h3>
|
|
||||||
<h3
|
|
||||||
v-else-if="!giver?.name"
|
|
||||||
class="font-semibold truncate text-slate-400 italic"
|
|
||||||
>
|
|
||||||
(Unnamed)
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="ms-auto text-sm text-slate-400 pe-1">
|
|
||||||
<font-awesome icon="lock" title="Can't be changed" />
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Recipient Button -->
|
|
||||||
<button
|
|
||||||
v-if="recipientEntityType === 'person'"
|
|
||||||
class="flex-1 flex items-center gap-2 bg-slate-100 border border-slate-300 rounded-md p-2"
|
|
||||||
@click="goBackToStep1('recipient')"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<EntityIcon
|
|
||||||
v-if="receiver?.did"
|
|
||||||
:contact="receiver"
|
|
||||||
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
||||||
/>
|
|
||||||
<font-awesome
|
|
||||||
v-else
|
|
||||||
icon="circle-question"
|
|
||||||
class="text-slate-400 text-3xl"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-start min-w-0">
|
|
||||||
<p class="text-xs text-slate-500 leading-1 -mb-1 uppercase">
|
|
||||||
Given to:
|
|
||||||
</p>
|
|
||||||
<h3
|
|
||||||
v-if="receiver?.name && receiver.name !== receiver.did"
|
|
||||||
class="font-semibold truncate"
|
|
||||||
>
|
|
||||||
{{ receiver.name }}
|
|
||||||
</h3>
|
|
||||||
<h3
|
|
||||||
v-if="receiver?.name && receiver.name === receiver.did"
|
|
||||||
class="font-semibold truncate text-slate-400 italic"
|
|
||||||
>
|
|
||||||
(No name)
|
|
||||||
</h3>
|
|
||||||
<h3
|
|
||||||
v-else-if="!receiver?.name"
|
|
||||||
class="font-semibold truncate text-slate-400 italic"
|
|
||||||
>
|
|
||||||
(Unnamed)
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="ms-auto text-sm text-blue-500 pe-1">
|
|
||||||
<font-awesome icon="pen" title="Change" />
|
|
||||||
</p>
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
v-else-if="recipientEntityType === 'project'"
|
|
||||||
class="flex-1 flex items-center gap-2 bg-slate-100 border border-slate-300 rounded-md p-2"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<ProjectIcon
|
|
||||||
v-if="receiver?.handleId"
|
|
||||||
:entity-id="receiver.handleId"
|
|
||||||
:icon-size="32"
|
|
||||||
:image-url="receiver.image"
|
|
||||||
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-start min-w-0">
|
|
||||||
<p class="text-xs text-slate-500 leading-1 -mb-1 uppercase">
|
|
||||||
Given to project:
|
|
||||||
</p>
|
|
||||||
<h3
|
|
||||||
v-if="receiver?.name"
|
|
||||||
class="font-semibold truncate"
|
|
||||||
>
|
|
||||||
{{ receiver.name }}
|
|
||||||
</h3>
|
|
||||||
<h3
|
|
||||||
v-else
|
|
||||||
class="font-semibold truncate text-slate-400 italic"
|
|
||||||
>
|
|
||||||
(Unnamed)
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="ms-auto text-sm text-slate-400 pe-1">
|
|
||||||
<font-awesome icon="lock" title="Can't be changed" />
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
v-model="description"
|
|
||||||
type="text"
|
|
||||||
class="block w-full rounded border border-slate-400 px-3 py-2 mb-4 placeholder:italic"
|
|
||||||
:placeholder="prompt || 'What was given?'"
|
|
||||||
/>
|
|
||||||
<div class="flex mb-4">
|
|
||||||
<button
|
|
||||||
class="rounded-s border border-e-0 border-slate-400 bg-slate-200 px-4 py-2"
|
|
||||||
@click="amountInput === '0' ? null : decrement()"
|
|
||||||
>
|
|
||||||
<font-awesome icon="chevron-left" />
|
|
||||||
</button>
|
|
||||||
<input
|
|
||||||
id="inputGivenAmount"
|
|
||||||
v-model="amountInput"
|
|
||||||
type="number"
|
|
||||||
class="flex-1 border border-e-0 border-slate-400 px-2 py-2 text-center w-[1px]"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="rounded-e border border-slate-400 bg-slate-200 px-4 py-2"
|
|
||||||
@click="increment()"
|
|
||||||
>
|
|
||||||
<font-awesome icon="chevron-right" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<select
|
|
||||||
v-model="unitCode"
|
|
||||||
class="flex-1 rounded border border-slate-400 ms-2 px-3 py-2"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
v-for="(displayName, code) in unitOptions"
|
|
||||||
:key="code"
|
|
||||||
:value="code"
|
|
||||||
>
|
|
||||||
{{ displayName }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<router-link
|
|
||||||
:to="{
|
|
||||||
name: 'gifted-details',
|
|
||||||
query: giftedDetailsQuery,
|
|
||||||
}"
|
|
||||||
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-lg mb-4"
|
|
||||||
>
|
|
||||||
Photo & more options…
|
|
||||||
</router-link>
|
|
||||||
<p class="text-center text-sm mb-4">
|
|
||||||
<b class="font-medium">Sign & Send</b> to publish to the world
|
|
||||||
<font-awesome
|
|
||||||
icon="circle-info"
|
|
||||||
class="fa-fw text-blue-500 text-base cursor-pointer"
|
|
||||||
@click="explainData()"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- Conflict warning -->
|
|
||||||
<div v-if="hasPersonConflict" class="mb-4 p-3 bg-red-50 border border-red-200 rounded-md">
|
|
||||||
<p class="text-red-700 text-sm text-center">
|
|
||||||
<font-awesome icon="exclamation-triangle" class="fa-fw mr-1" />
|
|
||||||
Cannot record: Same person selected as both giver and recipient
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
|
||||||
<button
|
|
||||||
:disabled="hasPersonConflict"
|
|
||||||
:class="{
|
|
||||||
'block w-full text-center text-md uppercase 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-1.5 py-2 rounded-lg': !hasPersonConflict,
|
|
||||||
'block w-full text-center text-md uppercase font-bold bg-gradient-to-b from-slate-300 to-slate-500 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-slate-400 px-1.5 py-2 rounded-lg cursor-not-allowed': hasPersonConflict
|
|
||||||
}"
|
|
||||||
@click="confirm"
|
|
||||||
>
|
|
||||||
Sign & Send
|
|
||||||
</button>
|
|
||||||
<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-lg"
|
|
||||||
@click="cancel"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component, Prop, Watch } from "vue-facing-decorator";
|
import { Vue, Component, Prop } from "vue-facing-decorator";
|
||||||
|
|
||||||
import { NotificationIface, USE_DEXIE_DB } from "../constants/app";
|
import { NotificationIface, USE_DEXIE_DB } from "../constants/app";
|
||||||
import {
|
import {
|
||||||
createAndSubmitGive,
|
createAndSubmitGive,
|
||||||
didInfo,
|
didInfo,
|
||||||
serverMessageForUser,
|
serverMessageForUser,
|
||||||
getHeaders,
|
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import * as libsUtil from "../libs/util";
|
import * as libsUtil from "../libs/util";
|
||||||
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
@@ -537,38 +102,13 @@ import * as databaseUtil from "../db/databaseUtil";
|
|||||||
import { retrieveAccountDids } from "../libs/util";
|
import { retrieveAccountDids } from "../libs/util";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||||
import EntityIcon from "../components/EntityIcon.vue";
|
|
||||||
import ProjectIcon from "../components/ProjectIcon.vue";
|
|
||||||
import { PlanData } from "../interfaces/records";
|
|
||||||
|
|
||||||
@Component({
|
@Component
|
||||||
components: {
|
|
||||||
EntityIcon,
|
|
||||||
ProjectIcon,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
export default class GiftedDialog extends Vue {
|
export default class GiftedDialog extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
@Prop() fromProjectId = "";
|
@Prop() fromProjectId = "";
|
||||||
@Prop() toProjectId = "";
|
@Prop() toProjectId = "";
|
||||||
@Prop({ default: false }) showProjects = false;
|
|
||||||
@Prop() isFromProjectView = false;
|
|
||||||
|
|
||||||
@Watch("showProjects")
|
|
||||||
onShowProjectsChange() {
|
|
||||||
this.updateEntityTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Watch("fromProjectId")
|
|
||||||
onFromProjectIdChange() {
|
|
||||||
this.updateEntityTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Watch("toProjectId")
|
|
||||||
onToProjectIdChange() {
|
|
||||||
this.updateEntityTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
@@ -579,7 +119,6 @@ export default class GiftedDialog extends Vue {
|
|||||||
callbackOnSuccess?: (amount: number) => void = () => {};
|
callbackOnSuccess?: (amount: number) => void = () => {};
|
||||||
customTitle?: string;
|
customTitle?: string;
|
||||||
description = "";
|
description = "";
|
||||||
firstStep = true; // true = Step 1 (giver/recipient selection), false = Step 2 (amount/description)
|
|
||||||
giver?: libsUtil.GiverReceiverInputInfo; // undefined means no identified giver agent
|
giver?: libsUtil.GiverReceiverInputInfo; // undefined means no identified giver agent
|
||||||
offerId = "";
|
offerId = "";
|
||||||
prompt = "";
|
prompt = "";
|
||||||
@@ -589,80 +128,6 @@ export default class GiftedDialog extends Vue {
|
|||||||
|
|
||||||
libsUtil = libsUtil;
|
libsUtil = libsUtil;
|
||||||
|
|
||||||
projects: PlanData[] = [];
|
|
||||||
|
|
||||||
didInfo = didInfo;
|
|
||||||
|
|
||||||
// Computed property to help debug template logic
|
|
||||||
get shouldShowProjects() {
|
|
||||||
const result =
|
|
||||||
(this.stepType === "giver" && this.giverEntityType === "project") ||
|
|
||||||
(this.stepType === "recipient" && this.recipientEntityType === "project");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computed property to check if current selection would create a conflict
|
|
||||||
get hasPersonConflict() {
|
|
||||||
// Only check for conflicts when both entities are persons
|
|
||||||
if (this.giverEntityType !== "person" || this.recipientEntityType !== "person") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if giver and recipient are the same person
|
|
||||||
if (this.giver?.did && this.receiver?.did && this.giver.did === this.receiver.did) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computed property to check if a contact would create a conflict when selected
|
|
||||||
wouldCreateConflict(contactDid: string) {
|
|
||||||
// Only check for conflicts when both entities are persons
|
|
||||||
if (this.giverEntityType !== "person" || this.recipientEntityType !== "person") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.stepType === "giver") {
|
|
||||||
// If selecting as giver, check if it conflicts with current recipient
|
|
||||||
return this.receiver?.did === contactDid;
|
|
||||||
} else if (this.stepType === "recipient") {
|
|
||||||
// If selecting as recipient, check if it conflicts with current giver
|
|
||||||
return this.giver?.did === contactDid;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
stepType = "giver";
|
|
||||||
giverEntityType = "person" as "person" | "project";
|
|
||||||
recipientEntityType = "person" as "person" | "project";
|
|
||||||
|
|
||||||
updateEntityTypes() {
|
|
||||||
// Reset and set entity types based on current context
|
|
||||||
this.giverEntityType = "person";
|
|
||||||
this.recipientEntityType = "person";
|
|
||||||
|
|
||||||
// Determine entity types based on current context
|
|
||||||
if (this.showProjects) {
|
|
||||||
// HomeView "Project" button or ProjectViewView "Given by This"
|
|
||||||
this.giverEntityType = "project";
|
|
||||||
this.recipientEntityType = "person";
|
|
||||||
} else if (this.fromProjectId) {
|
|
||||||
// ProjectViewView "Given by This" button (project is giver)
|
|
||||||
this.giverEntityType = "project";
|
|
||||||
this.recipientEntityType = "person";
|
|
||||||
} else if (this.toProjectId) {
|
|
||||||
// ProjectViewView "Given to This" button (project is recipient)
|
|
||||||
this.giverEntityType = "person";
|
|
||||||
this.recipientEntityType = "project";
|
|
||||||
} else {
|
|
||||||
// HomeView "Person" button
|
|
||||||
this.giverEntityType = "person";
|
|
||||||
this.recipientEntityType = "person";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async open(
|
async open(
|
||||||
giver?: libsUtil.GiverReceiverInputInfo,
|
giver?: libsUtil.GiverReceiverInputInfo,
|
||||||
receiver?: libsUtil.GiverReceiverInputInfo,
|
receiver?: libsUtil.GiverReceiverInputInfo,
|
||||||
@@ -675,14 +140,10 @@ export default class GiftedDialog extends Vue {
|
|||||||
this.giver = giver;
|
this.giver = giver;
|
||||||
this.prompt = prompt || "";
|
this.prompt = prompt || "";
|
||||||
this.receiver = receiver;
|
this.receiver = receiver;
|
||||||
|
// if we show "given to user" selection, default checkbox to true
|
||||||
this.amountInput = "0";
|
this.amountInput = "0";
|
||||||
this.callbackOnSuccess = callbackOnSuccess;
|
this.callbackOnSuccess = callbackOnSuccess;
|
||||||
this.offerId = offerId || "";
|
this.offerId = offerId || "";
|
||||||
this.firstStep = !giver;
|
|
||||||
this.stepType = "giver";
|
|
||||||
|
|
||||||
// Update entity types based on current props
|
|
||||||
this.updateEntityTypes();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
let settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
||||||
@@ -713,16 +174,7 @@ export default class GiftedDialog extends Vue {
|
|||||||
this.allContacts,
|
this.allContacts,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
if (
|
|
||||||
this.giverEntityType === "project" ||
|
|
||||||
this.recipientEntityType === "project"
|
|
||||||
) {
|
|
||||||
await this.loadProjects();
|
|
||||||
} else {
|
|
||||||
// Clear projects array when not needed
|
|
||||||
this.projects = [];
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
logger.error("Error retrieving settings from database:", err);
|
logger.error("Error retrieving settings from database:", err);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -772,7 +224,6 @@ export default class GiftedDialog extends Vue {
|
|||||||
this.amountInput = "0";
|
this.amountInput = "0";
|
||||||
this.prompt = "";
|
this.prompt = "";
|
||||||
this.unitCode = "HUR";
|
this.unitCode = "HUR";
|
||||||
this.firstStep = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirm() {
|
async confirm() {
|
||||||
@@ -814,20 +265,6 @@ export default class GiftedDialog extends Vue {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for person conflict
|
|
||||||
if (this.hasPersonConflict) {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "You cannot select the same person as both giver and recipient.",
|
|
||||||
},
|
|
||||||
3000,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -867,52 +304,20 @@ export default class GiftedDialog extends Vue {
|
|||||||
unitCode: string = "HUR",
|
unitCode: string = "HUR",
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
// Determine the correct parameters based on entity types
|
|
||||||
let fromDid: string | undefined;
|
|
||||||
let toDid: string | undefined;
|
|
||||||
let fulfillsProjectHandleId: string | undefined;
|
|
||||||
let providerPlanHandleId: string | undefined;
|
|
||||||
|
|
||||||
if (this.giverEntityType === "project" && this.recipientEntityType === "person") {
|
|
||||||
// Project-to-person gift
|
|
||||||
fromDid = undefined; // No person giver
|
|
||||||
toDid = recipientDid as string; // Person recipient
|
|
||||||
fulfillsProjectHandleId = undefined; // No project recipient
|
|
||||||
providerPlanHandleId = this.giver?.handleId; // Project giver
|
|
||||||
} else if (this.giverEntityType === "person" && this.recipientEntityType === "project") {
|
|
||||||
// Person-to-project gift
|
|
||||||
fromDid = giverDid as string; // Person giver
|
|
||||||
toDid = undefined; // No person recipient
|
|
||||||
fulfillsProjectHandleId = this.toProjectId; // Project recipient
|
|
||||||
providerPlanHandleId = undefined; // No project giver
|
|
||||||
} else if (this.giverEntityType === "project" && this.recipientEntityType === "project") {
|
|
||||||
// Project-to-project gift
|
|
||||||
fromDid = undefined; // No person giver
|
|
||||||
toDid = undefined; // No person recipient
|
|
||||||
fulfillsProjectHandleId = this.toProjectId; // Project recipient
|
|
||||||
providerPlanHandleId = this.giver?.handleId; // Project giver
|
|
||||||
} else {
|
|
||||||
// Person-to-person gift
|
|
||||||
fromDid = giverDid as string;
|
|
||||||
toDid = recipientDid as string;
|
|
||||||
fulfillsProjectHandleId = undefined;
|
|
||||||
providerPlanHandleId = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await createAndSubmitGive(
|
const result = await createAndSubmitGive(
|
||||||
this.axios,
|
this.axios,
|
||||||
this.apiServer,
|
this.apiServer,
|
||||||
this.activeDid,
|
this.activeDid,
|
||||||
fromDid,
|
giverDid as string,
|
||||||
toDid,
|
recipientDid as string,
|
||||||
description,
|
description,
|
||||||
amount,
|
amount,
|
||||||
unitCode,
|
unitCode,
|
||||||
fulfillsProjectHandleId,
|
this.toProjectId,
|
||||||
this.offerId,
|
this.offerId,
|
||||||
false,
|
false,
|
||||||
undefined,
|
undefined,
|
||||||
providerPlanHandleId,
|
this.fromProjectId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
@@ -973,118 +378,6 @@ export default class GiftedDialog extends Vue {
|
|||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectGiver(contact?: Contact) {
|
|
||||||
if (contact) {
|
|
||||||
this.giver = {
|
|
||||||
did: contact.did,
|
|
||||||
name: contact.name || contact.did,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this.giver = {
|
|
||||||
did: "",
|
|
||||||
name: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.firstStep = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
goBackToStep1(step: string) {
|
|
||||||
this.stepType = step;
|
|
||||||
this.firstStep = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadProjects() {
|
|
||||||
try {
|
|
||||||
const response = await fetch(this.apiServer + "/api/v2/report/plans", {
|
|
||||||
method: "GET",
|
|
||||||
headers: await getHeaders(this.activeDid),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status !== 200) {
|
|
||||||
throw new Error("Failed to load projects");
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = await response.json();
|
|
||||||
if (results.data) {
|
|
||||||
this.projects = results.data;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error loading projects:", error);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "Failed to load projects",
|
|
||||||
},
|
|
||||||
3000,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectProject(project: PlanData) {
|
|
||||||
this.giver = {
|
|
||||||
name: project.name,
|
|
||||||
image: project.image,
|
|
||||||
handleId: project.handleId,
|
|
||||||
};
|
|
||||||
this.receiver = {
|
|
||||||
did: this.activeDid,
|
|
||||||
name: "You",
|
|
||||||
};
|
|
||||||
this.firstStep = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
selectRecipient(contact?: Contact) {
|
|
||||||
if (contact) {
|
|
||||||
this.receiver = {
|
|
||||||
did: contact.did,
|
|
||||||
name: contact.name || contact.did,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this.receiver = {
|
|
||||||
did: "",
|
|
||||||
name: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.firstStep = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
selectRecipientProject(project: PlanData) {
|
|
||||||
this.receiver = {
|
|
||||||
// no did, because it's a project
|
|
||||||
name: project.name,
|
|
||||||
image: project.image,
|
|
||||||
handleId: project.handleId,
|
|
||||||
};
|
|
||||||
this.firstStep = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computed property for the query parameters
|
|
||||||
get giftedDetailsQuery() {
|
|
||||||
return {
|
|
||||||
amountInput: this.amountInput,
|
|
||||||
description: this.description,
|
|
||||||
giverDid: this.giverEntityType === "person" ? this.giver?.did : undefined,
|
|
||||||
giverName: this.giver?.name,
|
|
||||||
offerId: this.offerId,
|
|
||||||
fulfillsProjectId: this.giverEntityType === "person" && this.recipientEntityType === "project"
|
|
||||||
? this.toProjectId
|
|
||||||
: undefined,
|
|
||||||
providerProjectId: this.giverEntityType === "project" && this.recipientEntityType === "person"
|
|
||||||
? this.giver?.handleId
|
|
||||||
: this.fromProjectId,
|
|
||||||
recipientDid: this.receiver?.did,
|
|
||||||
recipientName: this.receiver?.name,
|
|
||||||
unitCode: this.unitCode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computed property to get unit options
|
|
||||||
get unitOptions() {
|
|
||||||
return this.libsUtil.UNIT_SHORT;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -27,37 +27,8 @@
|
|||||||
*/
|
*/
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
// Add a union type of all valid route paths
|
|
||||||
export const VALID_DEEP_LINK_ROUTES = [
|
|
||||||
// note that similar lists are below in deepLinkSchemas and in src/services/deepLinks.ts
|
|
||||||
"claim",
|
|
||||||
"claim-add-raw",
|
|
||||||
"claim-cert",
|
|
||||||
"confirm-gift",
|
|
||||||
"contact-import",
|
|
||||||
"did",
|
|
||||||
"invite-one-accept",
|
|
||||||
"onboard-meeting-setup",
|
|
||||||
"project",
|
|
||||||
"user-profile",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
// Create a type from the array
|
|
||||||
export type DeepLinkRoute = (typeof VALID_DEEP_LINK_ROUTES)[number];
|
|
||||||
|
|
||||||
// Update your schema definitions to use this type
|
|
||||||
export const baseUrlSchema = z.object({
|
|
||||||
scheme: z.literal("timesafari"),
|
|
||||||
path: z.string(),
|
|
||||||
queryParams: z.record(z.string()).optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use the type to ensure route validation
|
|
||||||
export const routeSchema = z.enum(VALID_DEEP_LINK_ROUTES);
|
|
||||||
|
|
||||||
// Parameter validation schemas for each route type
|
// Parameter validation schemas for each route type
|
||||||
export const deepLinkSchemas = {
|
export const deepLinkSchemas = {
|
||||||
// note that similar lists are above in VALID_DEEP_LINK_ROUTES and in src/services/deepLinks.ts
|
|
||||||
claim: z.object({
|
claim: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
}),
|
}),
|
||||||
@@ -72,16 +43,24 @@ export const deepLinkSchemas = {
|
|||||||
"confirm-gift": z.object({
|
"confirm-gift": z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
}),
|
}),
|
||||||
|
"contact-edit": z.object({
|
||||||
|
did: z.string(),
|
||||||
|
}),
|
||||||
"contact-import": z.object({
|
"contact-import": z.object({
|
||||||
jwt: z.string(),
|
jwt: z.string(),
|
||||||
}),
|
}),
|
||||||
|
contacts: z.object({
|
||||||
|
contactJwt: z.string().optional(),
|
||||||
|
inviteJwt: z.string().optional(),
|
||||||
|
}),
|
||||||
did: z.object({
|
did: z.object({
|
||||||
did: z.string(),
|
did: z.string(),
|
||||||
}),
|
}),
|
||||||
"invite-one-accept": z.object({
|
"invite-one-accept": z.object({
|
||||||
jwt: z.string(),
|
// optional because A) it could be a query param, and B) the page displays an input if things go wrong
|
||||||
|
jwt: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
"onboard-meeting-setup": z.object({
|
"onboard-meeting-members": z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
}),
|
}),
|
||||||
project: z.object({
|
project: z.object({
|
||||||
@@ -92,6 +71,19 @@ export const deepLinkSchemas = {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create a type from the array
|
||||||
|
export type DeepLinkRoute = (typeof VALID_DEEP_LINK_ROUTES)[number];
|
||||||
|
|
||||||
|
// Update your schema definitions to use this type
|
||||||
|
export const baseUrlSchema = z.object({
|
||||||
|
scheme: z.literal("timesafari"),
|
||||||
|
path: z.string(),
|
||||||
|
queryParams: z.record(z.string()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a union type of all valid route paths
|
||||||
|
export const VALID_DEEP_LINK_ROUTES = Object.keys(deepLinkSchemas) as readonly (keyof typeof deepLinkSchemas)[];
|
||||||
|
|
||||||
export type DeepLinkParams = {
|
export type DeepLinkParams = {
|
||||||
[K in keyof typeof deepLinkSchemas]: z.infer<(typeof deepLinkSchemas)[K]>;
|
[K in keyof typeof deepLinkSchemas]: z.infer<(typeof deepLinkSchemas)[K]>;
|
||||||
};
|
};
|
||||||
@@ -100,3 +92,6 @@ export interface DeepLinkError extends Error {
|
|||||||
code: string;
|
code: string;
|
||||||
details?: unknown;
|
details?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the type to ensure route validation
|
||||||
|
export const routeSchema = z.enum(VALID_DEEP_LINK_ROUTES as [string, ...string[]]);
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import {
|
|||||||
faCircleCheck,
|
faCircleCheck,
|
||||||
faCircleInfo,
|
faCircleInfo,
|
||||||
faCircleQuestion,
|
faCircleQuestion,
|
||||||
faCircleRight,
|
|
||||||
faCircleUser,
|
faCircleUser,
|
||||||
faClock,
|
faClock,
|
||||||
faCoins,
|
faCoins,
|
||||||
@@ -61,7 +60,6 @@ import {
|
|||||||
faLightbulb,
|
faLightbulb,
|
||||||
faLink,
|
faLink,
|
||||||
faLocationDot,
|
faLocationDot,
|
||||||
faLock,
|
|
||||||
faLongArrowAltLeft,
|
faLongArrowAltLeft,
|
||||||
faLongArrowAltRight,
|
faLongArrowAltRight,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
@@ -81,7 +79,6 @@ import {
|
|||||||
faSquareCaretDown,
|
faSquareCaretDown,
|
||||||
faSquareCaretUp,
|
faSquareCaretUp,
|
||||||
faSquarePlus,
|
faSquarePlus,
|
||||||
faThumbtack,
|
|
||||||
faTrashCan,
|
faTrashCan,
|
||||||
faTriangleExclamation,
|
faTriangleExclamation,
|
||||||
faUser,
|
faUser,
|
||||||
@@ -114,7 +111,6 @@ library.add(
|
|||||||
faCircleCheck,
|
faCircleCheck,
|
||||||
faCircleInfo,
|
faCircleInfo,
|
||||||
faCircleQuestion,
|
faCircleQuestion,
|
||||||
faCircleRight,
|
|
||||||
faCircleUser,
|
faCircleUser,
|
||||||
faClock,
|
faClock,
|
||||||
faCoins,
|
faCoins,
|
||||||
@@ -146,7 +142,6 @@ library.add(
|
|||||||
faLightbulb,
|
faLightbulb,
|
||||||
faLink,
|
faLink,
|
||||||
faLocationDot,
|
faLocationDot,
|
||||||
faLock,
|
|
||||||
faLongArrowAltLeft,
|
faLongArrowAltLeft,
|
||||||
faLongArrowAltRight,
|
faLongArrowAltRight,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
@@ -166,7 +161,6 @@ library.add(
|
|||||||
faSquareCaretDown,
|
faSquareCaretDown,
|
||||||
faSquareCaretUp,
|
faSquareCaretUp,
|
||||||
faSquarePlus,
|
faSquarePlus,
|
||||||
faThumbtack,
|
|
||||||
faTrashCan,
|
faTrashCan,
|
||||||
faTriangleExclamation,
|
faTriangleExclamation,
|
||||||
faUser,
|
faUser,
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ import { DEFAULT_ROOT_DERIVATION_PATH } from "./crypto";
|
|||||||
export interface GiverReceiverInputInfo {
|
export interface GiverReceiverInputInfo {
|
||||||
did?: string;
|
did?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
image?: string;
|
|
||||||
handleId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OnboardPage {
|
export enum OnboardPage {
|
||||||
|
|||||||
@@ -72,12 +72,11 @@ const handleDeepLink = async (data: { url: string }) => {
|
|||||||
await deepLinkHandler.handleDeepLink(data.url);
|
await deepLinkHandler.handleDeepLink(data.url);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("[DeepLink] Error handling deep link: ", error);
|
logger.error("[DeepLink] Error handling deep link: ", error);
|
||||||
handleApiError(
|
let message: string = error instanceof Error ? error.message : safeStringify(error);
|
||||||
{
|
if (data.url) {
|
||||||
message: error instanceof Error ? error.message : safeStringify(error),
|
message += `\nURL: ${data.url}`;
|
||||||
} as AxiosError,
|
}
|
||||||
"deep-link",
|
handleApiError({ message } as AxiosError, "deep-link");
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,11 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "contacts",
|
name: "contacts",
|
||||||
component: () => import("../views/ContactsView.vue"),
|
component: () => import("../views/ContactsView.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/database-migration",
|
||||||
|
name: "database-migration",
|
||||||
|
component: () => import("../views/DatabaseMigration.vue"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/did/:did?",
|
path: "/did/:did?",
|
||||||
name: "did",
|
name: "did",
|
||||||
@@ -139,8 +144,9 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
component: () => import("../views/InviteOneView.vue"),
|
component: () => import("../views/InviteOneView.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// optional because A) it could be a query param, and B) the page displays an input if things go wrong
|
||||||
path: "/invite-one-accept/:jwt?",
|
path: "/invite-one-accept/:jwt?",
|
||||||
name: "InviteOneAcceptView",
|
name: "invite-one-accept",
|
||||||
component: () => import("../views/InviteOneAcceptView.vue"),
|
component: () => import("../views/InviteOneAcceptView.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -148,11 +154,6 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "logs",
|
name: "logs",
|
||||||
component: () => import("../views/LogView.vue"),
|
component: () => import("../views/LogView.vue"),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/database-migration",
|
|
||||||
name: "database-migration",
|
|
||||||
component: () => import("../views/DatabaseMigration.vue"),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/new-activity",
|
path: "/new-activity",
|
||||||
name: "new-activity",
|
name: "new-activity",
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Router } from "vue-router";
|
import { Router } from "vue-router";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deepLinkSchemas,
|
deepLinkSchemas,
|
||||||
baseUrlSchema,
|
baseUrlSchema,
|
||||||
@@ -53,6 +55,31 @@ import {
|
|||||||
import { logConsoleAndDb } from "../db/databaseUtil";
|
import { logConsoleAndDb } from "../db/databaseUtil";
|
||||||
import type { DeepLinkError } from "../interfaces/deepLinks";
|
import type { DeepLinkError } from "../interfaces/deepLinks";
|
||||||
|
|
||||||
|
// Helper function to extract the first key from a Zod object schema
|
||||||
|
function getFirstKeyFromZodObject(schema: z.ZodObject<any>): string | undefined {
|
||||||
|
const shape = schema.shape;
|
||||||
|
const keys = Object.keys(shape);
|
||||||
|
return keys.length > 0 ? keys[0] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps deep link routes to their corresponding Vue router names and optional parameter keys.
|
||||||
|
*
|
||||||
|
* It's an object where keys are the deep link routes and values are objects with 'name' and 'paramKey'.
|
||||||
|
*
|
||||||
|
* The paramKey is used to extract the parameter from the route path,
|
||||||
|
* because "router.replace" expects the right parameter name for the route.
|
||||||
|
*/
|
||||||
|
export const ROUTE_MAP: Record<string, { name: string; paramKey?: string }> =
|
||||||
|
Object.entries(deepLinkSchemas).reduce((acc, [routeName, schema]) => {
|
||||||
|
const paramKey = getFirstKeyFromZodObject(schema as z.ZodObject<any>);
|
||||||
|
acc[routeName] = {
|
||||||
|
name: routeName,
|
||||||
|
paramKey
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, { name: string; paramKey?: string }>);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles processing and routing of deep links in the application.
|
* Handles processing and routing of deep links in the application.
|
||||||
* Provides validation, error handling, and routing for deep link URLs.
|
* Provides validation, error handling, and routing for deep link URLs.
|
||||||
@@ -69,30 +96,7 @@ export class DeepLinkHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps deep link routes to their corresponding Vue router names and optional parameter keys.
|
|
||||||
*
|
|
||||||
* The paramKey is used to extract the parameter from the route path,
|
|
||||||
* because "router.replace" expects the right parameter name for the route.
|
|
||||||
* The default is "id".
|
|
||||||
*/
|
|
||||||
private readonly ROUTE_MAP: Record<
|
|
||||||
string,
|
|
||||||
{ name: string; paramKey?: string }
|
|
||||||
> = {
|
|
||||||
// note that similar lists are in src/interfaces/deepLinks.ts
|
|
||||||
claim: { name: "claim" },
|
|
||||||
"claim-add-raw": { name: "claim-add-raw" },
|
|
||||||
"claim-cert": { name: "claim-cert" },
|
|
||||||
"confirm-gift": { name: "confirm-gift" },
|
|
||||||
"contact-import": { name: "contact-import", paramKey: "jwt" },
|
|
||||||
did: { name: "did", paramKey: "did" },
|
|
||||||
"invite-one-accept": { name: "invite-one-accept", paramKey: "jwt" },
|
|
||||||
"onboard-meeting-members": { name: "onboard-meeting-members" },
|
|
||||||
project: { name: "project" },
|
|
||||||
"user-profile": { name: "user-profile" },
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses deep link URL into path, params and query components.
|
* Parses deep link URL into path, params and query components.
|
||||||
* Validates URL structure using Zod schemas.
|
* Validates URL structure using Zod schemas.
|
||||||
*
|
*
|
||||||
@@ -115,18 +119,9 @@ export class DeepLinkHandler {
|
|||||||
|
|
||||||
const [path, queryString] = parts[1].split("?");
|
const [path, queryString] = parts[1].split("?");
|
||||||
const [routePath, ...pathParams] = path.split("/");
|
const [routePath, ...pathParams] = path.split("/");
|
||||||
// logger.info(
|
|
||||||
// "[DeepLink] Debug:",
|
|
||||||
// "Route Path:",
|
|
||||||
// routePath,
|
|
||||||
// "Path Params:",
|
|
||||||
// pathParams,
|
|
||||||
// "Query String:",
|
|
||||||
// queryString,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// Validate route exists before proceeding
|
// Validate route exists before proceeding
|
||||||
if (!this.ROUTE_MAP[routePath]) {
|
if (!ROUTE_MAP[routePath]) {
|
||||||
throw {
|
throw {
|
||||||
code: "INVALID_ROUTE",
|
code: "INVALID_ROUTE",
|
||||||
message: `Invalid route path: ${routePath}`,
|
message: `Invalid route path: ${routePath}`,
|
||||||
@@ -144,9 +139,14 @@ export class DeepLinkHandler {
|
|||||||
const params: Record<string, string> = {};
|
const params: Record<string, string> = {};
|
||||||
if (pathParams) {
|
if (pathParams) {
|
||||||
// Now we know routePath exists in ROUTE_MAP
|
// Now we know routePath exists in ROUTE_MAP
|
||||||
const routeConfig = this.ROUTE_MAP[routePath];
|
const routeConfig = ROUTE_MAP[routePath];
|
||||||
params[routeConfig.paramKey ?? "id"] = pathParams.join("/");
|
params[routeConfig.paramKey ?? "id"] = pathParams.join("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logConsoleAndDb(
|
||||||
|
// `[DeepLink] Debug: Route Path: ${routePath} Path Params: ${JSON.stringify(params)} Query String: ${JSON.stringify(query)}`,
|
||||||
|
// false,
|
||||||
|
// );
|
||||||
return { path: routePath, params, query };
|
return { path: routePath, params, query };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ export class DeepLinkHandler {
|
|||||||
try {
|
try {
|
||||||
// Validate route exists
|
// Validate route exists
|
||||||
const validRoute = routeSchema.parse(path) as DeepLinkRoute;
|
const validRoute = routeSchema.parse(path) as DeepLinkRoute;
|
||||||
routeName = this.ROUTE_MAP[validRoute].name;
|
routeName = ROUTE_MAP[validRoute].name;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log the invalid route attempt
|
// Log the invalid route attempt
|
||||||
logConsoleAndDb(`[DeepLink] Invalid route path: ${path}`, true);
|
logConsoleAndDb(`[DeepLink] Invalid route path: ${path}`, true);
|
||||||
@@ -178,52 +178,65 @@ export class DeepLinkHandler {
|
|||||||
// Redirect to error page with information about the invalid link
|
// Redirect to error page with information about the invalid link
|
||||||
await this.router.replace({
|
await this.router.replace({
|
||||||
name: "deep-link-error",
|
name: "deep-link-error",
|
||||||
|
params,
|
||||||
query: {
|
query: {
|
||||||
originalPath: path,
|
originalPath: path,
|
||||||
errorCode: "INVALID_ROUTE",
|
errorCode: "INVALID_ROUTE",
|
||||||
message: `The link you followed (${path}) is not supported`,
|
errorMessage: `The link you followed (${path}) is not supported`,
|
||||||
|
...query,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
throw {
|
// This previously threw an error but we're redirecting so there's no need.
|
||||||
code: "INVALID_ROUTE",
|
return;
|
||||||
message: `Unsupported route: ${path}`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue with parameter validation as before...
|
// Continue with parameter validation as before...
|
||||||
const schema = deepLinkSchemas[path as keyof typeof deepLinkSchemas];
|
const schema = deepLinkSchemas[path as keyof typeof deepLinkSchemas];
|
||||||
|
|
||||||
|
let validatedParams, validatedQuery;
|
||||||
try {
|
try {
|
||||||
const validatedParams = await schema.parseAsync({
|
validatedParams = await schema.parseAsync(params);
|
||||||
...params,
|
validatedQuery = await schema.parseAsync(query);
|
||||||
...query,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.router.replace({
|
|
||||||
name: routeName,
|
|
||||||
params: validatedParams,
|
|
||||||
query,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// For parameter validation errors, provide specific error feedback
|
// For parameter validation errors, provide specific error feedback
|
||||||
|
logConsoleAndDb(`[DeepLink] Invalid parameters for route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with params: ${JSON.stringify(params)} ... and query: ${JSON.stringify(query)}`, true);
|
||||||
await this.router.replace({
|
await this.router.replace({
|
||||||
name: "deep-link-error",
|
name: "deep-link-error",
|
||||||
|
params,
|
||||||
query: {
|
query: {
|
||||||
originalPath: path,
|
originalPath: path,
|
||||||
errorCode: "INVALID_PARAMETERS",
|
errorCode: "INVALID_PARAMETERS",
|
||||||
message: `The link parameters are invalid: ${(error as Error).message}`,
|
errorMessage: `The link parameters are invalid: ${(error as Error).message}`,
|
||||||
|
...query,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
throw {
|
// This previously threw an error but we're redirecting so there's no need.
|
||||||
code: "INVALID_PARAMETERS",
|
return;
|
||||||
message: (error as Error).message,
|
|
||||||
details: error,
|
|
||||||
params: params,
|
|
||||||
query: query,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.router.replace({
|
||||||
|
name: routeName,
|
||||||
|
params: validatedParams,
|
||||||
|
query: validatedQuery,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logConsoleAndDb(`[DeepLink] Error routing to route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with validated params: ${JSON.stringify(validatedParams)} ... and validated query: ${JSON.stringify(validatedQuery)}`, true);
|
||||||
|
// For parameter validation errors, provide specific error feedback
|
||||||
|
await this.router.replace({
|
||||||
|
name: "deep-link-error",
|
||||||
|
params: validatedParams,
|
||||||
|
query: {
|
||||||
|
originalPath: path,
|
||||||
|
errorCode: "ROUTING_ERROR",
|
||||||
|
errorMessage: `Error routing to ${routeName}: ${(JSON.stringify(error))}`,
|
||||||
|
...validatedQuery,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -235,7 +248,6 @@ export class DeepLinkHandler {
|
|||||||
*/
|
*/
|
||||||
async handleDeepLink(url: string): Promise<void> {
|
async handleDeepLink(url: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
logConsoleAndDb("[DeepLink] Processing URL: " + url, false);
|
|
||||||
const { path, params, query } = this.parseDeepLink(url);
|
const { path, params, query } = this.parseDeepLink(url);
|
||||||
// Ensure params is always a Record<string,string> by converting undefined to empty string
|
// Ensure params is always a Record<string,string> by converting undefined to empty string
|
||||||
const sanitizedParams = Object.fromEntries(
|
const sanitizedParams = Object.fromEntries(
|
||||||
@@ -245,7 +257,7 @@ export class DeepLinkHandler {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
const deepLinkError = error as DeepLinkError;
|
const deepLinkError = error as DeepLinkError;
|
||||||
logConsoleAndDb(
|
logConsoleAndDb(
|
||||||
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.message}`,
|
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.details}`,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
<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">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<div id="ViewBreadcrumb" class="mb-8">
|
<div id="ViewBreadcrumb" class="mb-8">
|
||||||
<h1 class="text-2xl text-center font-semibold relative px-7">
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<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"
|
||||||
><font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
><font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</router-link>
|
</router-link>
|
||||||
{{ stepType === "giver" ? "Given by..." : "Given to..." }}
|
Given by...
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -19,18 +19,19 @@
|
|||||||
<ul class="border-t border-slate-300">
|
<ul class="border-t border-slate-300">
|
||||||
<li class="border-b border-slate-300 py-3">
|
<li class="border-b border-slate-300 py-3">
|
||||||
<h2 class="text-base flex gap-4 items-center">
|
<h2 class="text-base flex gap-4 items-center">
|
||||||
<span class="grow flex gap-2 items-center font-medium">
|
<span class="grow">
|
||||||
<font-awesome
|
<img
|
||||||
icon="circle-question"
|
src="../assets/blank-square.svg"
|
||||||
class="text-slate-400 text-4xl"
|
width="32"
|
||||||
|
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
||||||
/>
|
/>
|
||||||
<span class="italic text-slate-400">(Unnamed/Unknown)</span>
|
Unnamed/Unknown
|
||||||
</span>
|
</span>
|
||||||
<span class="text-right">
|
<span class="text-right">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
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('Unnamed')"
|
@click="openDialog()"
|
||||||
>
|
>
|
||||||
<font-awesome icon="gift" class="fa-fw"></font-awesome>
|
<font-awesome icon="gift" class="fa-fw"></font-awesome>
|
||||||
</button>
|
</button>
|
||||||
@@ -43,14 +44,13 @@
|
|||||||
class="border-b border-slate-300 py-3"
|
class="border-b border-slate-300 py-3"
|
||||||
>
|
>
|
||||||
<h2 class="text-base flex gap-4 items-center">
|
<h2 class="text-base flex gap-4 items-center">
|
||||||
<span class="grow flex gap-2 items-center font-medium">
|
<span class="grow font-semibold">
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:contact="contact"
|
:contact="contact"
|
||||||
:icon-size="34"
|
:icon-size="32"
|
||||||
class="inline-block align-middle border border-slate-300 rounded-full overflow-hidden"
|
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
||||||
/>
|
/>
|
||||||
<span v-if="contact.name">{{ contact.name }}</span>
|
{{ contact.name || "(no name)" }}
|
||||||
<span v-else class="italic text-slate-400">(No name)</span>
|
|
||||||
</span>
|
</span>
|
||||||
<span class="text-right">
|
<span class="text-right">
|
||||||
<button
|
<button
|
||||||
@@ -65,13 +65,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<GiftedDialog
|
<GiftedDialog ref="customDialog" :to-project-id="projectId" />
|
||||||
ref="customDialog"
|
|
||||||
:from-project-id="fromProjectId"
|
|
||||||
:to-project-id="toProjectId"
|
|
||||||
:show-projects="showProjects"
|
|
||||||
:is-from-project-view="isFromProjectView"
|
|
||||||
/>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -103,24 +97,6 @@ export default class ContactGiftingView extends Vue {
|
|||||||
description = "";
|
description = "";
|
||||||
projectId = "";
|
projectId = "";
|
||||||
prompt = "";
|
prompt = "";
|
||||||
recipientProjectName = "";
|
|
||||||
recipientProjectImage = "";
|
|
||||||
recipientProjectHandleId = "";
|
|
||||||
|
|
||||||
// New context parameters
|
|
||||||
stepType = "giver";
|
|
||||||
giverEntityType = "person" as "person" | "project";
|
|
||||||
recipientEntityType = "person" as "person" | "project";
|
|
||||||
giverProjectId = "";
|
|
||||||
giverProjectName = "";
|
|
||||||
giverProjectImage = "";
|
|
||||||
giverProjectHandleId = "";
|
|
||||||
giverDid = "";
|
|
||||||
recipientDid = "";
|
|
||||||
fromProjectId = "";
|
|
||||||
toProjectId = "";
|
|
||||||
showProjects = false;
|
|
||||||
isFromProjectView = false;
|
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
try {
|
try {
|
||||||
@@ -148,41 +124,9 @@ export default class ContactGiftingView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.projectId =
|
this.projectId = (this.$route.query["projectId"] as string) || "";
|
||||||
(this.$route.query["recipientProjectId"] as string) || "";
|
|
||||||
this.recipientProjectName =
|
|
||||||
(this.$route.query["recipientProjectName"] as string) || "";
|
|
||||||
this.recipientProjectImage =
|
|
||||||
(this.$route.query["recipientProjectImage"] as string) || "";
|
|
||||||
this.recipientProjectHandleId =
|
|
||||||
(this.$route.query["recipientProjectHandleId"] as string) || "";
|
|
||||||
this.prompt = (this.$route.query["prompt"] as string) ?? this.prompt;
|
this.prompt = (this.$route.query["prompt"] as string) ?? this.prompt;
|
||||||
|
|
||||||
// Read new context parameters
|
|
||||||
this.stepType = (this.$route.query["stepType"] as string) || "giver";
|
|
||||||
this.giverEntityType =
|
|
||||||
(this.$route.query["giverEntityType"] as "person" | "project") ||
|
|
||||||
"person";
|
|
||||||
this.recipientEntityType =
|
|
||||||
(this.$route.query["recipientEntityType"] as "person" | "project") ||
|
|
||||||
"person";
|
|
||||||
this.giverProjectId =
|
|
||||||
(this.$route.query["giverProjectId"] as string) || "";
|
|
||||||
this.giverProjectName =
|
|
||||||
(this.$route.query["giverProjectName"] as string) || "";
|
|
||||||
this.giverProjectImage =
|
|
||||||
(this.$route.query["giverProjectImage"] as string) || "";
|
|
||||||
this.giverProjectHandleId =
|
|
||||||
(this.$route.query["giverProjectHandleId"] as string) || "";
|
|
||||||
this.giverDid = (this.$route.query["giverDid"] as string) || "";
|
|
||||||
this.recipientDid = (this.$route.query["recipientDid"] as string) || "";
|
|
||||||
this.fromProjectId = (this.$route.query["fromProjectId"] as string) || "";
|
|
||||||
this.toProjectId = (this.$route.query["toProjectId"] as string) || "";
|
|
||||||
this.showProjects =
|
|
||||||
(this.$route.query["showProjects"] as string) === "true";
|
|
||||||
this.isFromProjectView =
|
|
||||||
(this.$route.query["isFromProjectView"] as string) === "true";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
logger.error("Error retrieving settings & contacts:", err);
|
logger.error("Error retrieving settings & contacts:", err);
|
||||||
@@ -200,108 +144,17 @@ export default class ContactGiftingView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openDialog(contact?: GiverReceiverInputInfo | "Unnamed") {
|
openDialog(giver?: GiverReceiverInputInfo) {
|
||||||
if (contact === "Unnamed") {
|
const recipient = this.projectId
|
||||||
// Special case: Pass undefined to trigger Step 1, but with "Unnamed" pre-selected
|
? undefined
|
||||||
let recipient: GiverReceiverInputInfo;
|
: { did: this.activeDid, name: "you" };
|
||||||
let giver: GiverReceiverInputInfo | undefined;
|
(this.$refs.customDialog as GiftedDialog).open(
|
||||||
|
giver,
|
||||||
if (this.stepType === "giver") {
|
recipient,
|
||||||
// We're selecting a giver, so recipient is either a project or the current user
|
undefined,
|
||||||
if (this.recipientEntityType === "project") {
|
"Given by " + (giver?.name || "someone not named"),
|
||||||
recipient = {
|
this.prompt,
|
||||||
did: this.recipientProjectHandleId,
|
);
|
||||||
name: this.recipientProjectName,
|
|
||||||
image: this.recipientProjectImage,
|
|
||||||
handleId: this.recipientProjectHandleId,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
recipient = { did: this.activeDid, name: "You" };
|
|
||||||
}
|
|
||||||
giver = undefined; // Will be set to "Unnamed" in GiftedDialog
|
|
||||||
} else {
|
|
||||||
// We're selecting a recipient, so recipient is "Unnamed" and giver is preserved from context
|
|
||||||
recipient = { did: "", name: "Unnamed" };
|
|
||||||
|
|
||||||
// Preserve the existing giver from the context
|
|
||||||
if (this.giverEntityType === "project") {
|
|
||||||
giver = {
|
|
||||||
// no did, because it's a project
|
|
||||||
name: this.giverProjectName,
|
|
||||||
image: this.giverProjectImage,
|
|
||||||
handleId: this.giverProjectHandleId,
|
|
||||||
};
|
|
||||||
} else if (this.giverDid) {
|
|
||||||
giver = {
|
|
||||||
did: this.giverDid,
|
|
||||||
name: this.giverProjectName || "Someone",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
giver = { did: this.activeDid, name: "You" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(this.$refs.customDialog as GiftedDialog).open(
|
|
||||||
giver,
|
|
||||||
recipient,
|
|
||||||
undefined,
|
|
||||||
this.stepType === "giver" ? "Given by Unnamed" : "Given to Unnamed",
|
|
||||||
this.prompt,
|
|
||||||
);
|
|
||||||
// Immediately select "Unnamed" and move to Step 2
|
|
||||||
(this.$refs.customDialog as GiftedDialog).selectGiver();
|
|
||||||
} else {
|
|
||||||
// Regular case: contact is a GiverReceiverInputInfo
|
|
||||||
let giver: GiverReceiverInputInfo;
|
|
||||||
let recipient: GiverReceiverInputInfo;
|
|
||||||
|
|
||||||
if (this.stepType === "giver") {
|
|
||||||
// We're selecting a giver, so the contact becomes the giver
|
|
||||||
giver = contact as GiverReceiverInputInfo; // Safe because we know contact is not "Unnamed" or undefined
|
|
||||||
|
|
||||||
// Recipient is either a project or the current user
|
|
||||||
if (this.recipientEntityType === "project") {
|
|
||||||
recipient = {
|
|
||||||
did: this.recipientProjectHandleId,
|
|
||||||
name: this.recipientProjectName,
|
|
||||||
image: this.recipientProjectImage,
|
|
||||||
handleId: this.recipientProjectHandleId,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
recipient = { did: this.activeDid, name: "You" };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We're selecting a recipient, so the contact becomes the recipient
|
|
||||||
recipient = contact as GiverReceiverInputInfo; // Safe because we know contact is not "Unnamed" or undefined
|
|
||||||
|
|
||||||
// Preserve the existing giver from the context
|
|
||||||
if (this.giverEntityType === "project") {
|
|
||||||
giver = {
|
|
||||||
did: this.giverProjectHandleId,
|
|
||||||
name: this.giverProjectName,
|
|
||||||
image: this.giverProjectImage,
|
|
||||||
handleId: this.giverProjectHandleId,
|
|
||||||
};
|
|
||||||
} else if (this.giverDid) {
|
|
||||||
giver = {
|
|
||||||
did: this.giverDid,
|
|
||||||
name: this.giverProjectName || "Someone",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
giver = { did: this.activeDid, name: "You" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(this.$refs.customDialog as GiftedDialog).open(
|
|
||||||
giver,
|
|
||||||
recipient,
|
|
||||||
undefined,
|
|
||||||
this.stepType === "giver"
|
|
||||||
? "Given by " + (contact?.name || "someone not named")
|
|
||||||
: "Given to " + (contact?.name || "someone not named"),
|
|
||||||
this.prompt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<h2>Supported Deep Links</h2>
|
<h2>Supported Deep Links</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(routeItem, index) in validRoutes" :key="index">
|
<li v-for="(routeItem, index) in validRoutes" :key="index">
|
||||||
<code>timesafari://{{ routeItem }}/:id</code>
|
<code>timesafari://{{ routeItem }}/:{{ deepLinkSchemaKeys[routeItem] }}</code>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,12 +41,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { VALID_DEEP_LINK_ROUTES } from "../interfaces/deepLinks";
|
import { VALID_DEEP_LINK_ROUTES, deepLinkSchemas } from "../interfaces/deepLinks";
|
||||||
import { logConsoleAndDb } from "../db/databaseUtil";
|
import { logConsoleAndDb } from "../db/databaseUtil";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
// an object with the route as the key and the first param name as the value
|
||||||
|
const deepLinkSchemaKeys = Object.fromEntries(
|
||||||
|
Object.entries(deepLinkSchemas).map(([route, schema]) => {
|
||||||
|
const param = Object.keys(schema.shape)[0];
|
||||||
|
return [route, param];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Extract error information from query params
|
// Extract error information from query params
|
||||||
const errorCode = computed(
|
const errorCode = computed(
|
||||||
@@ -54,7 +61,7 @@ const errorCode = computed(
|
|||||||
);
|
);
|
||||||
const errorMessage = computed(
|
const errorMessage = computed(
|
||||||
() =>
|
() =>
|
||||||
(route.query.message as string) ||
|
(route.query.errorMessage as string) ||
|
||||||
"The deep link you followed is invalid or not supported.",
|
"The deep link you followed is invalid or not supported.",
|
||||||
);
|
);
|
||||||
const originalPath = computed(() => route.query.originalPath as string);
|
const originalPath = computed(() => route.query.originalPath as string);
|
||||||
@@ -93,7 +100,7 @@ const reportIssue = () => {
|
|||||||
// Log the error for analytics
|
// Log the error for analytics
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
logConsoleAndDb(
|
logConsoleAndDb(
|
||||||
`[DeepLink] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}, params: ${JSON.stringify(route.params)}`,
|
`[DeepLinkError] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}, params: ${JSON.stringify(route.params)}, query: ${JSON.stringify(route.query)}`,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,91 +4,84 @@
|
|||||||
|
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<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">
|
||||||
<!-- Breadcrumb -->
|
<!-- Back -->
|
||||||
<div id="ViewBreadcrumb" class="mb-8">
|
<div
|
||||||
<h1 class="text-2xl text-center font-semibold relative px-7 mb-2">
|
v-if="!hideBackButton"
|
||||||
<!-- Back -->
|
class="text-lg text-center font-light relative px-7"
|
||||||
<div
|
>
|
||||||
v-if="!hideBackButton"
|
<h1
|
||||||
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()"
|
||||||
>
|
>
|
||||||
<font-awesome icon="chevron-left" class="fa-fw" />
|
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
|
||||||
</div>
|
|
||||||
What Was Given
|
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<h2 class="text-lg font-normal text-center overflow-hidden">
|
|
||||||
<div class="truncate">
|
|
||||||
From
|
|
||||||
{{
|
|
||||||
providedByProject
|
|
||||||
? providerProjectName
|
|
||||||
: providedByGiver
|
|
||||||
? giverName
|
|
||||||
: "someone not named"
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<div class="truncate">
|
|
||||||
to
|
|
||||||
{{
|
|
||||||
givenToProject
|
|
||||||
? fulfillsProjectName
|
|
||||||
: givenToRecipient
|
|
||||||
? recipientName
|
|
||||||
: "someone not named"
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Heading -->
|
||||||
|
<h1 class="text-4xl text-center font-light px-4 mb-4">What Was Given</h1>
|
||||||
|
|
||||||
|
<h1 class="text-xl font-bold text-center mb-4">
|
||||||
|
<span>
|
||||||
|
From
|
||||||
|
{{
|
||||||
|
providedByProject
|
||||||
|
? providerProjectName
|
||||||
|
: providedByGiver
|
||||||
|
? giverName
|
||||||
|
: "someone not named"
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<span>
|
||||||
|
to
|
||||||
|
{{
|
||||||
|
givenToProject
|
||||||
|
? fulfillsProjectName
|
||||||
|
: givenToRecipient
|
||||||
|
? recipientName
|
||||||
|
: "someone not named"
|
||||||
|
}}</span
|
||||||
|
>
|
||||||
|
</h1>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="description"
|
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"
|
||||||
/>
|
/>
|
||||||
<div class="flex mb-4">
|
<div class="flex flex-row justify-center">
|
||||||
<button
|
<span
|
||||||
class="rounded-s border border-e-0 border-slate-400 bg-slate-200 px-4 py-2"
|
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 text-center text-blue-500 px-2 py-2 w-20"
|
||||||
|
@click="changeUnitCode()"
|
||||||
|
>
|
||||||
|
{{ libsUtil.UNIT_SHORT[unitCode] || unitCode }}
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
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()"
|
||||||
>
|
>
|
||||||
<font-awesome icon="chevron-left" />
|
<font-awesome icon="chevron-left" />
|
||||||
</button>
|
</div>
|
||||||
<input
|
<input
|
||||||
id="inputGivenAmount"
|
|
||||||
v-model="amountInput"
|
v-model="amountInput"
|
||||||
type="number"
|
type="number"
|
||||||
class="flex-1 border border-e-0 border-slate-400 px-2 py-2 text-center w-[1px]"
|
class="border border-r-0 border-slate-400 px-2 py-2 text-center w-20"
|
||||||
/>
|
/>
|
||||||
<button
|
<div
|
||||||
class="rounded-e 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()"
|
||||||
>
|
>
|
||||||
<font-awesome icon="chevron-right" />
|
<font-awesome icon="chevron-right" />
|
||||||
</button>
|
</div>
|
||||||
|
|
||||||
<select
|
|
||||||
v-model="unitCode"
|
|
||||||
class="flex-1 rounded border border-slate-400 ms-2 px-3 py-2"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
v-for="(displayName, code) in unitOptions"
|
|
||||||
:key="code"
|
|
||||||
:value="code"
|
|
||||||
>
|
|
||||||
{{ displayName }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center mt-4" data-testId="imagery">
|
<div class="flex justify-center mt-4" data-testId="imagery">
|
||||||
<span v-if="imageUrl" class="flex items-end gap-3">
|
<span v-if="imageUrl" class="flex justify-between">
|
||||||
<a :href="imageUrl" target="_blank">
|
<a :href="imageUrl" target="_blank">
|
||||||
<img :src="imageUrl" class="h-36 rounded-lg" />
|
<img :src="imageUrl" class="h-24 rounded-xl" />
|
||||||
</a>
|
</a>
|
||||||
<font-awesome
|
<font-awesome
|
||||||
icon="trash-can"
|
icon="trash-can"
|
||||||
class="text-red-500 fa-fw cursor-pointer"
|
class="text-red-500 fa-fw ml-8 mt-10"
|
||||||
@click="confirmDeleteImage"
|
@click="confirmDeleteImage"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -102,22 +95,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<ImageMethodDialog ref="imageDialog" default-camera-mode="environment" />
|
<ImageMethodDialog ref="imageDialog" default-camera-mode="environment" />
|
||||||
|
|
||||||
<div class="mt-4 sm:flex justify-between gap-2">
|
<div class="mt-4 flex justify-between gap-2">
|
||||||
<!-- First Column for Giver -->
|
<!-- First Column for Giver -->
|
||||||
<div class="sm:flex-grow sm:w-1/2 border border-slate-400 p-2 rounded-md overflow-hidden">
|
<div class="flex-grow border border-slate-400 p-2 rounded-md">
|
||||||
<div class="flex items-center">
|
<div class="flex">
|
||||||
<input
|
<input
|
||||||
v-if="giverDid && !providedByProject"
|
v-if="giverDid && !providedByProject"
|
||||||
v-model="providedByGiver"
|
v-model="providedByGiver"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="flex-shrink-0 h-6 w-6 mr-2"
|
class="h-6 w-6 mr-2"
|
||||||
/>
|
/>
|
||||||
<font-awesome
|
<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"
|
||||||
/>
|
/>
|
||||||
<label class="text-sm truncate">
|
<label class="text-sm mt-1">
|
||||||
{{
|
{{
|
||||||
giverDid
|
giverDid
|
||||||
? "This was provided by " + giverName + "."
|
? "This was provided by " + giverName + "."
|
||||||
@@ -127,24 +120,24 @@
|
|||||||
<font-awesome
|
<font-awesome
|
||||||
v-if="!giverDid || providedByProject"
|
v-if="!giverDid || providedByProject"
|
||||||
icon="info-circle"
|
icon="info-circle"
|
||||||
class="text-base cursor-pointer bg-white text-slate-500 ms-1"
|
class="-mt-1 bg-white text-slate-500 h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
||||||
@click="notifyUserOfGiver()"
|
@click="notifyUserOfGiver()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center">
|
<div class="flex">
|
||||||
<input
|
<input
|
||||||
v-if="providerProjectId && !providedByGiver"
|
v-if="providerProjectId && !providedByGiver"
|
||||||
v-model="providedByProject"
|
v-model="providedByProject"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="flex-shrink-0 h-6 w-6 mr-2"
|
class="h-6 w-6 mr-2"
|
||||||
/>
|
/>
|
||||||
<font-awesome
|
<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"
|
||||||
/>
|
/>
|
||||||
<label class="text-sm truncate">
|
<label class="text-sm mt-1">
|
||||||
{{
|
{{
|
||||||
providerProjectId
|
providerProjectId
|
||||||
? "This was provided by " + providerProjectName + "."
|
? "This was provided by " + providerProjectName + "."
|
||||||
@@ -154,31 +147,31 @@
|
|||||||
<font-awesome
|
<font-awesome
|
||||||
v-if="!providerProjectId || providedByGiver"
|
v-if="!providerProjectId || providedByGiver"
|
||||||
icon="info-circle"
|
icon="info-circle"
|
||||||
class="text-base cursor-pointer bg-white text-slate-500 ms-1"
|
class="-mt-1 bg-white text-slate-500 h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
||||||
@click="notifyUserOfProvidingProject()"
|
@click="notifyUserOfProvidingProject()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sm:flex-shrink flex justify-center items-center my-1 sm:my-0">
|
<div class="flex-shrink flex justify-center items-center">
|
||||||
<font-awesome icon="arrow-right" class="fa-fw h-7 rotate-90 sm:rotate-0" />
|
<font-awesome icon="arrow-right" class="fa-fw h-7" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Third Column for Recipient -->
|
<!-- Third Column for Recipient -->
|
||||||
<div class="sm:flex-grow sm:w-1/2 border border-slate-400 p-2 rounded-md overflow-hidden">
|
<div class="flex-grow border border-slate-400 p-2 rounded-md">
|
||||||
<div class="flex items-center">
|
<div class="flex">
|
||||||
<input
|
<input
|
||||||
v-if="recipientDid && !givenToProject"
|
v-if="recipientDid && !givenToProject"
|
||||||
v-model="givenToRecipient"
|
v-model="givenToRecipient"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="flex-shrink-0 h-6 w-6 mr-2"
|
class="h-6 w-6 mr-2"
|
||||||
/>
|
/>
|
||||||
<font-awesome
|
<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"
|
||||||
/>
|
/>
|
||||||
<label class="text-sm truncate">
|
<label class="text-sm mt-1">
|
||||||
{{
|
{{
|
||||||
recipientDid
|
recipientDid
|
||||||
? "This was given to " + recipientName + "."
|
? "This was given to " + recipientName + "."
|
||||||
@@ -188,24 +181,24 @@
|
|||||||
<font-awesome
|
<font-awesome
|
||||||
v-if="!recipientDid || givenToProject"
|
v-if="!recipientDid || givenToProject"
|
||||||
icon="info-circle"
|
icon="info-circle"
|
||||||
class="text-base cursor-pointer bg-white text-slate-500 ms-1"
|
class="-mt-1 bg-white text-slate-500 h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
||||||
@click="notifyUserOfRecipient()"
|
@click="notifyUserOfRecipient()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center">
|
<div class="flex">
|
||||||
<input
|
<input
|
||||||
v-if="fulfillsProjectId && !givenToRecipient"
|
v-if="fulfillsProjectId && !givenToRecipient"
|
||||||
v-model="givenToProject"
|
v-model="givenToProject"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="flex-shrink-0 h-6 w-6 mr-2"
|
class="h-6 w-6 mr-2"
|
||||||
/>
|
/>
|
||||||
<font-awesome
|
<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"
|
||||||
/>
|
/>
|
||||||
<label class="text-sm truncate">
|
<label class="text-sm mt-1">
|
||||||
{{
|
{{
|
||||||
fulfillsProjectId
|
fulfillsProjectId
|
||||||
? "This was given to " + fulfillsProjectName + ". "
|
? "This was given to " + fulfillsProjectName + ". "
|
||||||
@@ -215,7 +208,7 @@
|
|||||||
<font-awesome
|
<font-awesome
|
||||||
v-if="!fulfillsProjectId || givenToRecipient"
|
v-if="!fulfillsProjectId || givenToRecipient"
|
||||||
icon="info-circle"
|
icon="info-circle"
|
||||||
class="text-base cursor-pointer bg-white text-slate-500 ms-1"
|
class="-mt-1 bg-white text-slate-500 h-5 w-5 px-0.5 py-0.5 rounded-sm"
|
||||||
@click="notifyUserFulfillsProject()"
|
@click="notifyUserFulfillsProject()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -236,11 +229,11 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-center text-sm my-4">
|
<p class="text-center mb-2 mt-6 italic">
|
||||||
<b class="font-medium">Sign & Send</b> to publish to the world
|
Sign & Send to publish to the world
|
||||||
<font-awesome
|
<font-awesome
|
||||||
icon="circle-info"
|
icon="circle-info"
|
||||||
class="fa-fw text-blue-500 text-base cursor-pointer"
|
class="pl-2 text-blue-500 cursor-pointer"
|
||||||
@click="explainData()"
|
@click="explainData()"
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
@@ -917,10 +910,5 @@ export default class GiftedDetails extends Vue {
|
|||||||
7000,
|
7000,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computed property to get unit options
|
|
||||||
get unitOptions() {
|
|
||||||
return this.libsUtil.UNIT_SHORT;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -118,73 +118,101 @@ Raymer * @version 1.0.0 */
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else id="sectionRecordSomethingGiven">
|
<div v-else id="sectionRecordSomethingGiven">
|
||||||
<!-- Record Quick-Action -->
|
<!-- !isCreatingIdentifier && isRegistered -->
|
||||||
<div class="mb-6">
|
|
||||||
<div class="flex gap-2 items-center mb-2">
|
|
||||||
<h2 class="text-xl font-bold">Record something given by:</h2>
|
|
||||||
<button
|
|
||||||
class="block ms-auto text-center text-white bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-2 rounded-full"
|
|
||||||
@click="openGiftedPrompts()"
|
|
||||||
>
|
|
||||||
<font-awesome
|
|
||||||
icon="lightbulb"
|
|
||||||
class="block text-center w-[1em]"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<!-- show the actions for recognizing a give -->
|
||||||
<button
|
<div class="flex">
|
||||||
type="button"
|
<h2 class="text-xl font-bold">What have you seen someone do?</h2>
|
||||||
class="text-center text-base 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-2 rounded-lg"
|
<button
|
||||||
@click="openDialogPerson()"
|
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()"
|
||||||
<font-awesome icon="user" />
|
>
|
||||||
Person
|
<font-awesome icon="lightbulb" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="text-center text-base 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-2 rounded-lg"
|
|
||||||
@click="openProjectDialog()"
|
|
||||||
>
|
|
||||||
<font-awesome icon="folder-open" />
|
|
||||||
Project
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ul
|
||||||
|
class="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 gap-x-3 gap-y-5 text-center mt-4"
|
||||||
|
>
|
||||||
|
<li @click="openDialog()">
|
||||||
|
<img
|
||||||
|
src="../assets/blank-square.svg"
|
||||||
|
class="mx-auto border border-blue-500 rounded-md mb-1 cursor-pointer"
|
||||||
|
/>
|
||||||
|
<h3
|
||||||
|
class="text-xs text-blue-500 italic font-medium text-ellipsis whitespace-nowrap overflow-hidden cursor-pointer"
|
||||||
|
>
|
||||||
|
Unnamed/Unknown
|
||||||
|
</h3>
|
||||||
|
</li>
|
||||||
|
<li v-if="allContacts.length === 0" class="text-sm">
|
||||||
|
(Add friends to see more people worthy of recognition.)
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-for="contact in allContacts.slice(0, 6)"
|
||||||
|
:key="contact.did"
|
||||||
|
@click="openDialog(contact)"
|
||||||
|
>
|
||||||
|
<EntityIcon
|
||||||
|
:contact="contact"
|
||||||
|
:icon-size="64"
|
||||||
|
class="mx-auto border border-blue-500 rounded-md mb-1 cursor-pointer"
|
||||||
|
/>
|
||||||
|
<h3
|
||||||
|
class="text-xs text-blue-500 font-medium text-ellipsis whitespace-nowrap overflow-hidden cursor-pointer"
|
||||||
|
>
|
||||||
|
{{ contact.name || contact.did }}
|
||||||
|
</h3>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<router-link
|
||||||
|
v-if="allContacts.length >= 6"
|
||||||
|
:to="{ name: 'contact-gift' }"
|
||||||
|
class="flex align-bottom text-xs text-blue-500 mt-12 cursor-pointer"
|
||||||
|
>
|
||||||
|
... or someone else...
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<GiftedDialog ref="customDialog" :show-projects="showProjectsDialog" />
|
<GiftedDialog ref="customDialog" />
|
||||||
<GiftedPrompts ref="giftedPrompts" />
|
<GiftedPrompts ref="giftedPrompts" />
|
||||||
<FeedFilters ref="feedFilters" />
|
<FeedFilters ref="feedFilters" />
|
||||||
|
|
||||||
|
<div class="relative">
|
||||||
|
<button
|
||||||
|
v-if="isRegistered"
|
||||||
|
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()"
|
||||||
|
>
|
||||||
|
<font-awesome icon="plus" class="fa-fw" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Results List -->
|
<!-- Results List -->
|
||||||
<div class="mt-4 mb-4">
|
<div class="mt-4 mb-4">
|
||||||
<div class="flex gap-2 items-center mb-3">
|
<div class="flex items-center mb-4">
|
||||||
<h2 class="text-xl font-bold">Latest Activity</h2>
|
<h2 class="text-xl font-bold flex items-center gap-4">
|
||||||
<button
|
Latest Activity
|
||||||
v-if="resultsAreFiltered()"
|
<button
|
||||||
class="block ms-auto text-center text-white bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-2 rounded-full"
|
v-if="resultsAreFiltered()"
|
||||||
@click="openFeedFilters()"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md text-xs text-white"
|
||||||
>
|
@click="openFeedFilters()"
|
||||||
<font-awesome
|
>
|
||||||
icon="filter"
|
<font-awesome icon="filter" class="fa-fw" />
|
||||||
class="block text-center w-[1em] translate-y-[0.05em]"
|
</button>
|
||||||
/>
|
<button
|
||||||
</button>
|
v-else
|
||||||
<button
|
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md text-xs text-white"
|
||||||
v-else
|
@click="openFeedFilters()"
|
||||||
class="block ms-auto text-center text-white bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-2 rounded-full"
|
>
|
||||||
@click="openFeedFilters()"
|
<font-awesome icon="filter" class="fa-fw" />
|
||||||
>
|
</button>
|
||||||
<font-awesome
|
</h2>
|
||||||
icon="filter"
|
|
||||||
class="block text-center w-[1em] translate-y-[0.05em]"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -450,7 +478,6 @@ export default class HomeView extends Vue {
|
|||||||
selectedImageData: Blob | null = null;
|
selectedImageData: Blob | null = null;
|
||||||
isImageViewerOpen = false;
|
isImageViewerOpen = false;
|
||||||
imageCache: Map<string, Blob | null> = new Map();
|
imageCache: Map<string, Blob | null> = new Map();
|
||||||
showProjectsDialog = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the component on mount
|
* Initializes the component on mount
|
||||||
@@ -1599,33 +1626,17 @@ export default class HomeView extends Vue {
|
|||||||
* @param giver Optional contact info for giver
|
* @param giver Optional contact info for giver
|
||||||
* @param description Optional gift description
|
* @param description Optional gift description
|
||||||
*/
|
*/
|
||||||
openDialog(giver?: GiverReceiverInputInfo | "Unnamed", description?: string) {
|
openDialog(giver?: GiverReceiverInputInfo, description?: string) {
|
||||||
if (giver === "Unnamed") {
|
(this.$refs.customDialog as GiftedDialog).open(
|
||||||
// Special case: Pass undefined to trigger Step 1, but with "Unnamed" pre-selected
|
giver,
|
||||||
(this.$refs.customDialog as GiftedDialog).open(
|
{
|
||||||
undefined,
|
did: this.activeDid,
|
||||||
{
|
name: "you",
|
||||||
did: this.activeDid,
|
} as GiverReceiverInputInfo,
|
||||||
name: "You",
|
undefined,
|
||||||
} as GiverReceiverInputInfo,
|
"Given by " + (giver?.name || "someone not named"),
|
||||||
undefined,
|
description,
|
||||||
"Given by Unnamed",
|
);
|
||||||
description,
|
|
||||||
);
|
|
||||||
// Immediately select "Unnamed" and move to Step 2
|
|
||||||
(this.$refs.customDialog as GiftedDialog).selectGiver();
|
|
||||||
} else {
|
|
||||||
(this.$refs.customDialog as GiftedDialog).open(
|
|
||||||
giver,
|
|
||||||
{
|
|
||||||
did: this.activeDid,
|
|
||||||
name: "You",
|
|
||||||
} as GiverReceiverInputInfo,
|
|
||||||
undefined,
|
|
||||||
"Given by " + (giver?.name || "someone not named"),
|
|
||||||
description,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1859,18 +1870,5 @@ export default class HomeView extends Vue {
|
|||||||
this.$router.push({ name: "contact-qr" });
|
this.$router.push({ name: "contact-qr" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openDialogPerson(
|
|
||||||
giver?: GiverReceiverInputInfo | "Unnamed",
|
|
||||||
description?: string,
|
|
||||||
) {
|
|
||||||
this.showProjectsDialog = false;
|
|
||||||
this.openDialog(giver, description);
|
|
||||||
}
|
|
||||||
|
|
||||||
openProjectDialog() {
|
|
||||||
this.showProjectsDialog = true;
|
|
||||||
(this.$refs.customDialog as any).open();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export default class InviteOneAcceptView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract JWT from route path
|
// Extract JWT from route path
|
||||||
const jwt = (this.$route.params.jwt as string) || "";
|
const jwt = (this.$route.params.jwt as string) || this.$route.query.jwt as string || "";
|
||||||
await this.processInvite(jwt, false);
|
await this.processInvite(jwt, false);
|
||||||
|
|
||||||
this.checkingInvite = false;
|
this.checkingInvite = false;
|
||||||
|
|||||||
@@ -214,11 +214,63 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<GiftedDialog
|
<div v-if="activeDid && isRegistered">
|
||||||
ref="giveDialogToThis"
|
<div class="text-center">
|
||||||
:to-project-id="projectId"
|
<p class="mt-2 mt-4 text-center">Record a contribution from:</p>
|
||||||
:is-from-project-view="true"
|
</div>
|
||||||
/>
|
<ul
|
||||||
|
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 })">
|
||||||
|
<font-awesome
|
||||||
|
icon="hand"
|
||||||
|
class="fa-fw text-blue-500 text-5xl cursor-pointer"
|
||||||
|
/>
|
||||||
|
<h3
|
||||||
|
class="mt-5 text-xs text-blue-500 font-medium text-ellipsis whitespace-nowrap overflow-hidden cursor-pointer"
|
||||||
|
>
|
||||||
|
You
|
||||||
|
</h3>
|
||||||
|
</li>
|
||||||
|
<li @click="openGiftDialogToProject()">
|
||||||
|
<img
|
||||||
|
src="../assets/blank-square.svg"
|
||||||
|
class="mx-auto border border-blue-300 rounded-md mb-1 cursor-pointer"
|
||||||
|
/>
|
||||||
|
<h3
|
||||||
|
class="text-xs text-blue-500 italic font-medium text-ellipsis whitespace-nowrap overflow-hidden cursor-pointer"
|
||||||
|
>
|
||||||
|
Unnamed/Unknown
|
||||||
|
</h3>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-for="contact in allContacts.slice(0, 5)"
|
||||||
|
:key="contact.did"
|
||||||
|
@click="openGiftDialogToProject(contact)"
|
||||||
|
>
|
||||||
|
<EntityIcon
|
||||||
|
:contact="contact"
|
||||||
|
:icon-size="64"
|
||||||
|
class="mx-auto border border-blue-300 rounded-md mb-1 cursor-pointer"
|
||||||
|
/>
|
||||||
|
<h3
|
||||||
|
class="text-xs text-blue-500 font-medium text-ellipsis whitespace-nowrap overflow-hidden cursor-pointer"
|
||||||
|
>
|
||||||
|
{{ contact.name || "(no name)" }}
|
||||||
|
</h3>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span
|
||||||
|
v-if="allContacts.length >= 5"
|
||||||
|
class="flex align-bottom text-xs text-blue-500 mt-12 cursor-pointer"
|
||||||
|
@click="onClickAllContactsGifting()"
|
||||||
|
>
|
||||||
|
... or someone else...
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<GiftedDialog ref="giveDialogToThis" :to-project-id="projectId" />
|
||||||
|
|
||||||
<!-- Offers & Gifts to & from this -->
|
<!-- Offers & Gifts to & from this -->
|
||||||
<div class="grid items-start grid-cols-1 sm:grid-cols-3 gap-4 mt-4">
|
<div class="grid items-start grid-cols-1 sm:grid-cols-3 gap-4 mt-4">
|
||||||
@@ -484,12 +536,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<GiftedDialog
|
<GiftedDialog ref="giveDialogFromThis" :from-project-id="projectId" />
|
||||||
ref="giveDialogFromThis"
|
|
||||||
:from-project-id="projectId"
|
|
||||||
:show-projects="true"
|
|
||||||
:is-from-project-view="true"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<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
|
||||||
@@ -1223,52 +1270,21 @@ export default class ProjectViewView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
openGiftDialogToProject(
|
openGiftDialogToProject(contact?: libsUtil.GiverReceiverInputInfo) {
|
||||||
contact?: libsUtil.GiverReceiverInputInfo | "Unnamed",
|
(this.$refs.giveDialogToThis as GiftedDialog).open(
|
||||||
) {
|
contact,
|
||||||
if (contact === "Unnamed") {
|
undefined,
|
||||||
// Special case: Pass undefined to trigger Step 1, but with "Unnamed" pre-selected
|
undefined,
|
||||||
(this.$refs.giveDialogToThis as GiftedDialog).open(
|
(contact?.name || "Someone not named") + ` gave to this project`,
|
||||||
undefined,
|
);
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
"Given by Unnamed to this project",
|
|
||||||
);
|
|
||||||
// Immediately select "Unnamed" and move to Step 2
|
|
||||||
(this.$refs.giveDialogToThis as GiftedDialog).selectGiver();
|
|
||||||
} else {
|
|
||||||
// Open straight to Step 2 with current user as giver and current project as recipient
|
|
||||||
(this.$refs.giveDialogToThis as GiftedDialog).open(
|
|
||||||
{
|
|
||||||
did: this.activeDid,
|
|
||||||
name: "You",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: this.issuer,
|
|
||||||
name: this.name,
|
|
||||||
handleId: this.projectId,
|
|
||||||
image: this.imageUrl,
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
`Given to ${this.name}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openGiftDialogFromProject() {
|
openGiftDialogFromProject() {
|
||||||
// Set the project as giver and the current user as recipient
|
|
||||||
(this.$refs.giveDialogFromThis as GiftedDialog).open(
|
(this.$refs.giveDialogFromThis as GiftedDialog).open(
|
||||||
{
|
undefined,
|
||||||
did: undefined,
|
|
||||||
name: this.name,
|
|
||||||
handleId: this.projectId,
|
|
||||||
image: this.imageUrl,
|
|
||||||
},
|
|
||||||
{ did: this.activeDid, name: "You" },
|
{ did: this.activeDid, name: "You" },
|
||||||
undefined,
|
undefined,
|
||||||
`${this.name} gave to you`,
|
`This project gave to you`,
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
|||||||
"HUR",
|
"HUR",
|
||||||
BVC_MEETUPS_PROJECT_CLAIM_ID,
|
BVC_MEETUPS_PROJECT_CLAIM_ID,
|
||||||
);
|
);
|
||||||
if (timeResult.type === "success") {
|
if (timeResult.success) {
|
||||||
timeSuccess = true;
|
timeSuccess = true;
|
||||||
} else {
|
} else {
|
||||||
logger.error("Error sending time:", timeResult);
|
logger.error("Error sending time:", timeResult);
|
||||||
@@ -154,7 +154,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
|||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text:
|
text:
|
||||||
timeResult?.error?.userMessage ||
|
timeResult?.error ||
|
||||||
"There was an error sending the time.",
|
"There was an error sending the time.",
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
@@ -171,7 +171,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
|||||||
apiServer,
|
apiServer,
|
||||||
axios,
|
axios,
|
||||||
);
|
);
|
||||||
if (attendResult.type === "success") {
|
if (attendResult.success) {
|
||||||
attendedSuccess = true;
|
attendedSuccess = true;
|
||||||
} else {
|
} else {
|
||||||
logger.error("Error sending attendance:", attendResult);
|
logger.error("Error sending attendance:", attendResult);
|
||||||
@@ -181,7 +181,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
|||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text:
|
text:
|
||||||
attendResult?.error?.userMessage ||
|
attendResult?.error ||
|
||||||
"There was an error sending the attendance.",
|
"There was an error sending the attendance.",
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import path from "path";
|
|||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
// Load environment variables
|
// Load environment variables
|
||||||
dotenv.config();
|
console.log('NODE_ENV:', process.env.NODE_ENV)
|
||||||
|
dotenv.config({ path: `.env.${process.env.NODE_ENV}` })
|
||||||
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|||||||
Reference in New Issue
Block a user