Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de47829dc2 | ||
|
|
91e46f435e | ||
|
|
d086ab2f46 | ||
|
|
ff61a0bdf3 | ||
|
|
e0b9481be5 | ||
|
|
a11ff04afa | ||
|
|
e8bf8014b4 | ||
|
|
c1713e1b0b | ||
|
|
0277b05fef | ||
|
|
d5db39878c | ||
|
|
778d00c2a4 | ||
|
|
4f9fb068c8 | ||
|
|
0eb8d3d50e | ||
|
|
f98d6c7020 | ||
|
|
bcbb80e034 | ||
|
|
64f24dc473 | ||
|
|
6ddde21a86 | ||
|
|
fd0026ac2d | ||
|
|
3fce10ae98 | ||
|
|
002f240720 | ||
|
|
ffe8d90161 | ||
|
|
6d6816d1a8 | ||
|
|
c1477d0266 | ||
|
|
33ce6bdb72 | ||
|
|
dc21e8dac3 | ||
|
|
a9a8ba217c | ||
|
|
b0d99e7c1e | ||
|
|
861408c7bc |
@@ -1,34 +1,122 @@
|
|||||||
---
|
---
|
||||||
alwaysApply: true
|
alwaysApply: true
|
||||||
---
|
---
|
||||||
# Rules for peaceful co-existence with developers
|
# Directive: Peaceful Co-Existence with Developers
|
||||||
|
|
||||||
do not add or commit for the user; let him control that process
|
## 1) Version-Control Ownership
|
||||||
|
|
||||||
the content of commit messages should be from the files awaiting staging
|
* **MUST NOT** run `git add`, `git commit`, or any write action.
|
||||||
and those which have been staged. use the differences in those files
|
* **MUST** leave staging/committing to the developer.
|
||||||
to inform the content of the commit message
|
|
||||||
|
|
||||||
always preview changes and commit message to use and allow me to copy and paste
|
## 2) Source of Truth for Commit Text
|
||||||
✅ Preferred Commit Message Format
|
|
||||||
|
|
||||||
Short summary in the first line (concise and high-level).
|
* **MUST** derive messages **only** from:
|
||||||
Avoid long commit bodies unless truly necessary.
|
|
||||||
|
|
||||||
✅ Valued Content in Commit Messages
|
* files **staged** for commit (primary), and
|
||||||
|
* files **awaiting staging** (context).
|
||||||
|
* **MUST** use the **diffs** to inform content.
|
||||||
|
* **MUST NOT** invent changes or imply work not present in diffs.
|
||||||
|
|
||||||
Specific fixes or features.
|
## 3) Mandatory Preview Flow
|
||||||
Symptoms or problems that were fixed.
|
|
||||||
Notes about tests passing or TS/linting errors being resolved (briefly).
|
|
||||||
|
|
||||||
❌ Avoid in Commit Messages
|
* **ALWAYS** present, before any real commit:
|
||||||
|
|
||||||
Vague terms: “improved”, “enhanced”, “better” — especially from AI.
|
* file list + brief per-file notes,
|
||||||
Minor changes: small doc tweaks, one-liners, cleanup, or lint fixes.
|
* a **draft commit message** (copy-paste ready),
|
||||||
Redundant blurbs: repeated across files or too generic.
|
* nothing auto-applied.
|
||||||
Multiple overlapping purposes in a single commit — prefer narrow, focused commits.
|
|
||||||
Long explanations of what can be deduced from good in-line code comments.
|
|
||||||
|
|
||||||
Guiding Principle
|
---
|
||||||
|
|
||||||
Let code and inline documentation speak for themselves. Use commits to highlight what isn't obvious from reading the code.
|
# Commit Message Format (Normative)
|
||||||
|
|
||||||
|
## A. Subject Line (required)
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>(<scope>)<!>: <summary>
|
||||||
|
```
|
||||||
|
|
||||||
|
* **type** (lowercase, Conventional Commits): `feat|fix|refactor|perf|docs|test|build|chore|ci|revert`
|
||||||
|
* **scope**: optional module/package/area (e.g., `api`, `ui/login`, `db`)
|
||||||
|
* **!**: include when a breaking change is introduced
|
||||||
|
* **summary**: imperative mood, ≤ 72 chars, no trailing period
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
* `fix(api): handle null token in refresh path`
|
||||||
|
* `feat(ui/login)!: require OTP after 3 failed attempts`
|
||||||
|
|
||||||
|
## B. Body (optional, when it adds non-obvious value)
|
||||||
|
|
||||||
|
* One blank line after subject.
|
||||||
|
* Wrap at \~72 chars.
|
||||||
|
* Explain **what** and **why**, not line-by-line “how”.
|
||||||
|
* Include brief notes like tests passing or TS/lint issues resolved **only if material**.
|
||||||
|
|
||||||
|
**Body checklist**
|
||||||
|
|
||||||
|
* [ ] Problem/symptom being addressed
|
||||||
|
* [ ] High-level approach or rationale
|
||||||
|
* [ ] Risks, tradeoffs, or follow-ups (if any)
|
||||||
|
|
||||||
|
## C. Footer (optional)
|
||||||
|
|
||||||
|
* Issue refs: `Closes #123`, `Refs #456`
|
||||||
|
* Breaking change (alternative to `!`):
|
||||||
|
`BREAKING CHANGE: <impact + migration note>`
|
||||||
|
* Authors: `Co-authored-by: Name <email>`
|
||||||
|
* Security: `CVE-XXXX-YYYY: <short note>` (if applicable)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Content Guidance
|
||||||
|
|
||||||
|
### Include (when relevant)
|
||||||
|
|
||||||
|
* Specific fixes/features delivered
|
||||||
|
* Symptoms/problems fixed
|
||||||
|
* Brief note that tests passed or TS/lint errors resolved
|
||||||
|
|
||||||
|
### Avoid
|
||||||
|
|
||||||
|
* Vague: *improved, enhanced, better*
|
||||||
|
* Trivialities: tiny docs, one-liners, pure lint cleanups (separate, focused commits if needed)
|
||||||
|
* Redundancy: generic blurbs repeated across files
|
||||||
|
* Multi-purpose dumps: keep commits **narrow and focused**
|
||||||
|
* Long explanations that good inline code comments already cover
|
||||||
|
|
||||||
|
**Guiding Principle:** Let code and inline docs speak. Use commits to highlight what isn’t obvious.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Copy-Paste Templates
|
||||||
|
|
||||||
|
## Minimal (no body)
|
||||||
|
|
||||||
|
```text
|
||||||
|
<type>(<scope>): <summary>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Standard (with body & footer)
|
||||||
|
|
||||||
|
```text
|
||||||
|
<type>(<scope>)<!>: <summary>
|
||||||
|
|
||||||
|
<why-this-change?>
|
||||||
|
<what-it-does?>
|
||||||
|
<risks-or-follow-ups?>
|
||||||
|
|
||||||
|
Closes #<id>
|
||||||
|
BREAKING CHANGE: <impact + migration>
|
||||||
|
Co-authored-by: <Name> <email>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Assistant Output Checklist (before showing the draft)
|
||||||
|
|
||||||
|
* [ ] List changed files + 1–2 line notes per file
|
||||||
|
* [ ] Provide **one** focused draft message (subject/body/footer)
|
||||||
|
* [ ] Subject ≤ 72 chars, imperative mood, correct `type(scope)!` syntax
|
||||||
|
* [ ] Body only if it adds non-obvious value
|
||||||
|
* [ ] No invented changes; aligns strictly with diffs
|
||||||
|
* [ ] Render as a single copy-paste block for the developer
|
||||||
|
|||||||
+3
-2
@@ -1040,7 +1040,7 @@ If you need to build manually or want to understand the individual steps:
|
|||||||
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 39 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.0.6;/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
|
||||||
```
|
```
|
||||||
@@ -1063,11 +1063,12 @@ If you need to build manually or want to understand the individual steps:
|
|||||||
* 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
|
||||||
|
|
||||||
|
|||||||
+19
-1
@@ -12,9 +12,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Deep link URLs (and other prod settings)
|
- Deep link URLs (and other prod settings)
|
||||||
- Error in BVC begin view
|
- Error in BVC begin view
|
||||||
|
|
||||||
## [Unreleased]
|
## [1.0.6] - 2025.08.09
|
||||||
|
### Fixed
|
||||||
|
- Deep link errors where none would validate
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.5] - 2025.07.24
|
||||||
|
### Fixed
|
||||||
|
- Export & import of contacts corrupted contact methods
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.4] - 2025.07.20 - 002f2407208d56cc59c0aa7c880535ae4cbace8b
|
||||||
|
### Fixed
|
||||||
|
- Deep link for invite-one-accept
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.3] - 2025.07.12 - a9a8ba217cd6015321911e98e6843e988dc2c4ae
|
||||||
### Changed
|
### Changed
|
||||||
- Photo is pinned to profile mode
|
- 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
|
||||||
|
|||||||
@@ -137,7 +137,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 39
|
||||||
versionName "1.0.2"
|
versionName "1.0.6"
|
||||||
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.
|
||||||
|
|||||||
@@ -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 = 37;
|
CURRENT_PROJECT_VERSION = 39;
|
||||||
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.4;
|
MARKETING_VERSION = 1.0.6;
|
||||||
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 = 37;
|
CURRENT_PROJECT_VERSION = 39;
|
||||||
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.4;
|
MARKETING_VERSION = 1.0.6;
|
||||||
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 = "";
|
||||||
|
|||||||
Generated
+2787
-529
File diff suppressed because it is too large
Load Diff
@@ -251,8 +251,7 @@
|
|||||||
import { Component, Prop, Vue, Emit } from "vue-facing-decorator";
|
import { Component, Prop, Vue, Emit } from "vue-facing-decorator";
|
||||||
import { GiveRecordWithContactInfo } from "@/interfaces/give";
|
import { GiveRecordWithContactInfo } from "@/interfaces/give";
|
||||||
import EntityIcon from "./EntityIcon.vue";
|
import EntityIcon from "./EntityIcon.vue";
|
||||||
import { isGiveClaimType, notifyWhyCannotConfirm } from "../libs/util";
|
import { isHiddenDid } from "../libs/endorserServer";
|
||||||
import { containsHiddenDid, isHiddenDid } from "../libs/endorserServer";
|
|
||||||
import ProjectIcon from "./ProjectIcon.vue";
|
import ProjectIcon from "./ProjectIcon.vue";
|
||||||
import { createNotifyHelpers } from "@/utils/notify";
|
import { createNotifyHelpers } from "@/utils/notify";
|
||||||
import {
|
import {
|
||||||
@@ -272,7 +271,6 @@ export default class ActivityListItem extends Vue {
|
|||||||
@Prop() lastViewedClaimId?: string;
|
@Prop() lastViewedClaimId?: string;
|
||||||
@Prop() isRegistered!: boolean;
|
@Prop() isRegistered!: boolean;
|
||||||
@Prop() activeDid!: string;
|
@Prop() activeDid!: string;
|
||||||
@Prop() confirmerIdList?: string[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function prop for handling image caching
|
* Function prop for handling image caching
|
||||||
@@ -331,15 +329,6 @@ export default class ActivityListItem extends Vue {
|
|||||||
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
|
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
get canConfirm(): boolean {
|
|
||||||
if (!this.isRegistered) return false;
|
|
||||||
if (!isGiveClaimType(this.record.fullClaim?.["@type"])) return false;
|
|
||||||
if (this.confirmerIdList?.includes(this.activeDid)) return false;
|
|
||||||
if (this.record.issuerDid === this.activeDid) return false;
|
|
||||||
if (containsHiddenDid(this.record.fullClaim)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit methods using @Emit decorator
|
// Emit methods using @Emit decorator
|
||||||
@Emit("viewImage")
|
@Emit("viewImage")
|
||||||
emitViewImage(imageUrl: string) {
|
emitViewImage(imageUrl: string) {
|
||||||
@@ -351,26 +340,6 @@ export default class ActivityListItem extends Vue {
|
|||||||
return jwtId;
|
return jwtId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Emit("confirmClaim")
|
|
||||||
emitConfirmClaim() {
|
|
||||||
if (!this.canConfirm) {
|
|
||||||
notifyWhyCannotConfirm(
|
|
||||||
(msg, timeout) => this.notify.info(msg.text ?? "", timeout),
|
|
||||||
this.isRegistered,
|
|
||||||
this.record.fullClaim?.["@type"],
|
|
||||||
this.record,
|
|
||||||
this.activeDid,
|
|
||||||
this.confirmerIdList,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return this.record;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleConfirmClick() {
|
|
||||||
this.emitConfirmClaim();
|
|
||||||
}
|
|
||||||
|
|
||||||
get friendlyDate(): string {
|
get friendlyDate(): string {
|
||||||
const date = new Date(this.record.issuedAt);
|
const date = new Date(this.record.issuedAt);
|
||||||
return date.toLocaleDateString(undefined, {
|
return date.toLocaleDateString(undefined, {
|
||||||
|
|||||||
@@ -54,7 +54,10 @@ messages * - Conditional UI based on platform capabilities * * @component *
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Vue } from "vue-facing-decorator";
|
import { Component, Prop, Vue } from "vue-facing-decorator";
|
||||||
|
import * as R from "ramda";
|
||||||
|
|
||||||
import { AppString, NotificationIface } from "../constants/app";
|
import { AppString, NotificationIface } from "../constants/app";
|
||||||
|
import { Contact } from "../db/tables/contacts";
|
||||||
|
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { contactsToExportJson } from "../libs/util";
|
import { contactsToExportJson } from "../libs/util";
|
||||||
@@ -179,7 +182,19 @@ export default class DataExportSection extends Vue {
|
|||||||
const allContacts = await this.$contacts();
|
const allContacts = await this.$contacts();
|
||||||
|
|
||||||
// Convert contacts to export format
|
// Convert contacts to export format
|
||||||
const exportData = contactsToExportJson(allContacts);
|
const processedContacts: Contact[] = allContacts.map((contact) => {
|
||||||
|
// first remove the contactMethods field, mostly to cast to a clear type (that will end up with JSON objects)
|
||||||
|
const exContact: Contact = R.omit(["contactMethods"], contact);
|
||||||
|
// now add contactMethods as a true array of ContactMethod objects
|
||||||
|
exContact.contactMethods = contact.contactMethods
|
||||||
|
? (typeof contact.contactMethods === 'string' && contact.contactMethods.trim() !== ''
|
||||||
|
? JSON.parse(contact.contactMethods)
|
||||||
|
: [])
|
||||||
|
: [];
|
||||||
|
return exContact;
|
||||||
|
});
|
||||||
|
|
||||||
|
const exportData = contactsToExportJson(processedContacts);
|
||||||
const jsonStr = JSON.stringify(exportData, null, 2);
|
const jsonStr = JSON.stringify(exportData, null, 2);
|
||||||
|
|
||||||
// Use platform service to handle export (no platform-specific logic here!)
|
// Use platform service to handle export (no platform-specific logic here!)
|
||||||
|
|||||||
@@ -15,26 +15,25 @@ Raymer */
|
|||||||
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||||
placeholder="Description of what is offered"
|
placeholder="Description of what is offered"
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-row mt-2">
|
<div class="flex mb-4">
|
||||||
<span :class="unitCodeDisplayClasses" @click="changeUnitCode()">
|
<AmountInput
|
||||||
{{ libsUtil.UNIT_SHORT[amountUnitCode] }}
|
:value="parseFloat(amountInput) || 0"
|
||||||
</span>
|
:onUpdateValue="handleAmountUpdate"
|
||||||
<div
|
|
||||||
v-if="showDecrementButton"
|
|
||||||
:class="controlButtonClasses"
|
|
||||||
@click="decrement()"
|
|
||||||
>
|
|
||||||
<font-awesome icon="chevron-left" />
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
v-model="amountInput"
|
|
||||||
data-testId="inputOfferAmount"
|
data-testId="inputOfferAmount"
|
||||||
type="number"
|
|
||||||
:class="amountInputClasses"
|
|
||||||
/>
|
/>
|
||||||
<div :class="incrementButtonClasses" @click="increment()">
|
|
||||||
<font-awesome icon="chevron-right" />
|
<select
|
||||||
</div>
|
v-model="amountUnitCode"
|
||||||
|
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="mt-4 flex justify-center">
|
<div class="mt-4 flex justify-center">
|
||||||
<span>
|
<span>
|
||||||
@@ -73,10 +72,15 @@ import {
|
|||||||
NOTIFY_OFFER_CREATION_ERROR,
|
NOTIFY_OFFER_CREATION_ERROR,
|
||||||
NOTIFY_OFFER_SUCCESS,
|
NOTIFY_OFFER_SUCCESS,
|
||||||
NOTIFY_OFFER_SUBMISSION_ERROR,
|
NOTIFY_OFFER_SUBMISSION_ERROR,
|
||||||
|
NOTIFY_OFFER_ERROR_NEGATIVE_AMOUNT,
|
||||||
} from "@/constants/notifications";
|
} from "@/constants/notifications";
|
||||||
|
import AmountInput from "./AmountInput.vue";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
mixins: [PlatformServiceMixin],
|
mixins: [PlatformServiceMixin],
|
||||||
|
components: {
|
||||||
|
AmountInput,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export default class OfferDialog extends Vue {
|
export default class OfferDialog extends Vue {
|
||||||
@Prop projectId?: string;
|
@Prop projectId?: string;
|
||||||
@@ -122,35 +126,10 @@ export default class OfferDialog extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CSS classes for unit code selector and increment/decrement buttons
|
* Computed property to get unit options for the select dropdown
|
||||||
* Reduces template complexity for repeated border and styling patterns
|
|
||||||
*/
|
*/
|
||||||
get controlButtonClasses(): string {
|
get unitOptions() {
|
||||||
return "border border-r-0 border-slate-400 bg-slate-200 px-4 py-2";
|
return this.libsUtil.UNIT_SHORT;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSS classes for unit code display span
|
|
||||||
* Reduces template complexity for unit code button styling
|
|
||||||
*/
|
|
||||||
get unitCodeDisplayClasses(): string {
|
|
||||||
return "rounded-l border border-r-0 border-slate-400 bg-slate-200 w-1/3 text-center text-blue-500 px-2 py-2";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSS classes for amount input field
|
|
||||||
* Reduces template complexity for input styling
|
|
||||||
*/
|
|
||||||
get amountInputClasses(): string {
|
|
||||||
return "w-full border border-r-0 border-slate-400 px-2 py-2 text-center";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSS classes for the right-most increment button
|
|
||||||
* Reduces template complexity for border styling
|
|
||||||
*/
|
|
||||||
get incrementButtonClasses(): string {
|
|
||||||
return "rounded-r border border-slate-400 bg-slate-200 px-4 py-2";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -173,13 +152,7 @@ export default class OfferDialog extends Vue {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the decrement button should be visible
|
|
||||||
* Encapsulates conditional logic from template
|
|
||||||
*/
|
|
||||||
get showDecrementButton(): boolean {
|
|
||||||
return this.amountInput !== "0";
|
|
||||||
}
|
|
||||||
|
|
||||||
// =================================================
|
// =================================================
|
||||||
// COMPONENT METHODS
|
// COMPONENT METHODS
|
||||||
@@ -226,30 +199,14 @@ export default class OfferDialog extends Vue {
|
|||||||
this.visible = false;
|
this.visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Cycle through available unit codes
|
|
||||||
*/
|
|
||||||
changeUnitCode() {
|
|
||||||
const units = Object.keys(this.libsUtil.UNIT_SHORT);
|
|
||||||
const index = units.indexOf(this.amountUnitCode);
|
|
||||||
this.amountUnitCode = units[(index + 1) % units.length];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increment the amount input
|
* Handle amount updates from AmountInput component
|
||||||
|
* @param value - New amount value
|
||||||
*/
|
*/
|
||||||
increment() {
|
handleAmountUpdate(value: number) {
|
||||||
this.amountInput = `${(parseFloat(this.amountInput) || 0) + 1}`;
|
this.amountInput = value.toString();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrement the amount input
|
|
||||||
*/
|
|
||||||
decrement() {
|
|
||||||
this.amountInput = `${Math.max(
|
|
||||||
0,
|
|
||||||
(parseFloat(this.amountInput) || 1) - 1,
|
|
||||||
)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -273,6 +230,28 @@ export default class OfferDialog extends Vue {
|
|||||||
* Confirm and submit the offer
|
* Confirm and submit the offer
|
||||||
*/
|
*/
|
||||||
async confirm() {
|
async confirm() {
|
||||||
|
if (!this.activeDid) {
|
||||||
|
this.notify.error(NOTIFY_OFFER_IDENTITY_REQUIRED.message, TIMEOUTS.LONG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseFloat(this.amountInput) < 0) {
|
||||||
|
this.notify.error(
|
||||||
|
NOTIFY_OFFER_ERROR_NEGATIVE_AMOUNT.message,
|
||||||
|
TIMEOUTS.SHORT,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.description && !parseFloat(this.amountInput)) {
|
||||||
|
const message = NOTIFY_OFFER_DESCRIPTION_REQUIRED.message.replace(
|
||||||
|
"{unit}",
|
||||||
|
this.libsUtil.UNIT_LONG[this.amountUnitCode],
|
||||||
|
);
|
||||||
|
this.notify.error(message, TIMEOUTS.SHORT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
this.notify.toast(NOTIFY_OFFER_RECORDING.text, undefined, TIMEOUTS.BRIEF);
|
this.notify.toast(NOTIFY_OFFER_RECORDING.text, undefined, TIMEOUTS.BRIEF);
|
||||||
|
|
||||||
@@ -301,20 +280,6 @@ export default class OfferDialog extends Vue {
|
|||||||
unitCode: string = "HUR",
|
unitCode: string = "HUR",
|
||||||
expirationDateInput?: string,
|
expirationDateInput?: string,
|
||||||
) {
|
) {
|
||||||
if (!this.activeDid) {
|
|
||||||
this.notify.error(NOTIFY_OFFER_IDENTITY_REQUIRED.message, TIMEOUTS.LONG);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!description && !amount) {
|
|
||||||
const message = NOTIFY_OFFER_DESCRIPTION_REQUIRED.message.replace(
|
|
||||||
"{unit}",
|
|
||||||
this.libsUtil.UNIT_LONG[unitCode],
|
|
||||||
);
|
|
||||||
this.notify.error(message, TIMEOUTS.MODAL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await createAndSubmitOffer(
|
const result = await createAndSubmitOffer(
|
||||||
this.axios,
|
this.axios,
|
||||||
@@ -363,7 +328,7 @@ export default class OfferDialog extends Vue {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 1000;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog {
|
.dialog {
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export const deepLinkSchemas = {
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
}),
|
}),
|
||||||
"claim-add-raw": z.object({
|
"claim-add-raw": z.object({
|
||||||
id: z.string(),
|
|
||||||
claim: z.string().optional(),
|
claim: z.string().optional(),
|
||||||
claimJwtId: z.string().optional(),
|
claimJwtId: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
+1
-1
@@ -893,7 +893,7 @@ export interface DatabaseExport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an array of contacts to the standardized database export JSON format.
|
* Converts an array of contacts to the export JSON format.
|
||||||
* This format is used for data migration and backup purposes.
|
* This format is used for data migration and backup purposes.
|
||||||
*
|
*
|
||||||
* @param contacts - Array of Contact objects to convert
|
* @param contacts - Array of Contact objects to convert
|
||||||
|
|||||||
@@ -103,9 +103,10 @@ export class ProfileService {
|
|||||||
{ headers },
|
{ headers },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 201) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
logger.error("Error saving profile:", response);
|
||||||
throw new Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_SAVED);
|
throw new Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_SAVED);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -149,6 +149,10 @@ export class DeepLinkHandler {
|
|||||||
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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,14 +199,14 @@ export class DeepLinkHandler {
|
|||||||
// 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;
|
let validatedParams;
|
||||||
try {
|
try {
|
||||||
validatedParams = await schema.parseAsync(params);
|
validatedParams = await schema.parseAsync(params);
|
||||||
validatedQuery = await schema.parseAsync(query);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// For parameter validation errors, provide specific error feedback
|
// For parameter validation errors, provide specific error feedback
|
||||||
console.error(
|
logConsoleAndDb(
|
||||||
`[DeepLink] Invalid parameters for route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with params: ${JSON.stringify(params)} ... and query: ${JSON.stringify(query)}`,
|
`[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",
|
||||||
@@ -223,11 +227,11 @@ export class DeepLinkHandler {
|
|||||||
await this.router.replace({
|
await this.router.replace({
|
||||||
name: routeName,
|
name: routeName,
|
||||||
params: validatedParams,
|
params: validatedParams,
|
||||||
query: validatedQuery,
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.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)}`,
|
`[DeepLink] Error routing to route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with validated params: ${JSON.stringify(validatedParams)}`,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
// For parameter validation errors, provide specific error feedback
|
// For parameter validation errors, provide specific error feedback
|
||||||
await this.router.replace({
|
await this.router.replace({
|
||||||
@@ -237,7 +241,6 @@ export class DeepLinkHandler {
|
|||||||
originalPath: path,
|
originalPath: path,
|
||||||
errorCode: "ROUTING_ERROR",
|
errorCode: "ROUTING_ERROR",
|
||||||
errorMessage: `Error routing to ${routeName}: ${JSON.stringify(error)}`,
|
errorMessage: `Error routing to ${routeName}: ${JSON.stringify(error)}`,
|
||||||
...validatedQuery,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -260,8 +263,9 @@ export class DeepLinkHandler {
|
|||||||
await this.validateAndRoute(path, sanitizedParams, query);
|
await this.validateAndRoute(path, sanitizedParams, query);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const deepLinkError = error as DeepLinkError;
|
const deepLinkError = error as DeepLinkError;
|
||||||
console.error(
|
logConsoleAndDb(
|
||||||
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.details}`,
|
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.details}`,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
throw {
|
throw {
|
||||||
|
|||||||
@@ -60,8 +60,10 @@
|
|||||||
@share-info="onShareInfo"
|
@share-info="onShareInfo"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Notifications -->
|
||||||
|
<!-- Currently disabled because it doesn't work, even on Chrome. If restored, make sure it works or doesn't show on mobile/electron. -->
|
||||||
<section
|
<section
|
||||||
v-if="isRegistered"
|
v-if="false"
|
||||||
id="sectionNotifications"
|
id="sectionNotifications"
|
||||||
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
||||||
aria-labelledby="notificationsHeading"
|
aria-labelledby="notificationsHeading"
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ function isApiResponse(response: unknown): response is AxiosResponse {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Testing Required - Database Operations + Logging Migration to PlatformServiceMixin
|
|
||||||
// Priority: High | Migrated: 2025-07-06 | Author: Matthew Raymer
|
// Priority: High | Migrated: 2025-07-06 | Author: Matthew Raymer
|
||||||
//
|
//
|
||||||
// MIGRATION DETAILS: Migrated from legacy database utilities + logging to PlatformServiceMixin
|
// MIGRATION DETAILS: Migrated from legacy database utilities + logging to PlatformServiceMixin
|
||||||
|
|||||||
@@ -553,11 +553,6 @@ export default class ContactQRScanFull extends Vue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new contact
|
|
||||||
// @ts-expect-error because we're just using the value to store to the DB
|
|
||||||
contact.contactMethods = JSON.stringify(
|
|
||||||
(this as any)._parseJsonField(contact.contactMethods, []),
|
|
||||||
);
|
|
||||||
await this.$insertContact(contact);
|
await this.$insertContact(contact);
|
||||||
|
|
||||||
if (this.activeDid) {
|
if (this.activeDid) {
|
||||||
|
|||||||
@@ -213,15 +213,11 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
$router!: Router;
|
$router!: Router;
|
||||||
|
|
||||||
// Notification helper system
|
// Notification helper system
|
||||||
private notify = createNotifyHelpers(this.$notify);
|
notify!: ReturnType<typeof createNotifyHelpers>;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
|
|
||||||
// Axios instance for API calls
|
|
||||||
get axios() {
|
|
||||||
return (this as any).$platformService.axios;
|
|
||||||
}
|
|
||||||
givenName = "";
|
givenName = "";
|
||||||
hideRegisterPromptOnNewContact = false;
|
hideRegisterPromptOnNewContact = false;
|
||||||
isRegistered = false;
|
isRegistered = false;
|
||||||
@@ -288,6 +284,8 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
|
|||||||
+24
-9
@@ -273,6 +273,7 @@ import {
|
|||||||
displayAmount,
|
displayAmount,
|
||||||
getHeaders,
|
getHeaders,
|
||||||
register,
|
register,
|
||||||
|
setVisibilityUtil,
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import * as libsUtil from "../libs/util";
|
import * as libsUtil from "../libs/util";
|
||||||
import EntityIcon from "../components/EntityIcon.vue";
|
import EntityIcon from "../components/EntityIcon.vue";
|
||||||
@@ -324,6 +325,7 @@ export default class DIDView extends Vue {
|
|||||||
apiServer = "";
|
apiServer = "";
|
||||||
claims: Array<GenericCredWrapper<GenericVerifiableCredential>> = [];
|
claims: Array<GenericCredWrapper<GenericVerifiableCredential>> = [];
|
||||||
contactFromDid?: Contact;
|
contactFromDid?: Contact;
|
||||||
|
|
||||||
contactYaml = "";
|
contactYaml = "";
|
||||||
hitEnd = false;
|
hitEnd = false;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
@@ -722,18 +724,31 @@ export default class DIDView extends Vue {
|
|||||||
visibility: boolean,
|
visibility: boolean,
|
||||||
showSuccessAlert: boolean,
|
showSuccessAlert: boolean,
|
||||||
) {
|
) {
|
||||||
// Update contact visibility using mixin method
|
const result = await setVisibilityUtil(
|
||||||
await this.$updateContact(contact.did, { seesMe: visibility });
|
this.activeDid,
|
||||||
|
this.apiServer,
|
||||||
|
this.axios,
|
||||||
|
contact,
|
||||||
|
visibility,
|
||||||
|
);
|
||||||
|
|
||||||
if (showSuccessAlert) {
|
if (result.success) {
|
||||||
|
if (showSuccessAlert) {
|
||||||
|
const message =
|
||||||
|
(contact.name || "That user") +
|
||||||
|
" can " +
|
||||||
|
(visibility ? "" : "not ") +
|
||||||
|
"see your activity.";
|
||||||
|
this.notify.success(message, TIMEOUTS.SHORT);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
logger.error("Got strange result from setting visibility:", result);
|
||||||
const message =
|
const message =
|
||||||
(contact.name || "That user") +
|
(result.error as string) || "Could not set visibility on the server.";
|
||||||
" can " +
|
this.notify.error(message, TIMEOUTS.LONG);
|
||||||
(visibility ? "" : "not ") +
|
return false;
|
||||||
"see your activity.";
|
|
||||||
this.notify.success(message, TIMEOUTS.SHORT);
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -588,10 +588,20 @@ export default class GiftedDetails extends Vue {
|
|||||||
this.imageUrl = "";
|
this.imageUrl = "";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error deleting image:", error);
|
logger.error("Error deleting image:", error);
|
||||||
this.notify.error(
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_ERROR.message,
|
if ((error as any)?.response?.status === 404) {
|
||||||
TIMEOUTS.LONG,
|
logger.log("Weird: the image was already deleted.", error);
|
||||||
);
|
|
||||||
|
localStorage.removeItem("imageUrl");
|
||||||
|
this.imageUrl = "";
|
||||||
|
|
||||||
|
// it already doesn't exist so we won't say anything to the user
|
||||||
|
} else {
|
||||||
|
this.notify.error(
|
||||||
|
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_ERROR.message,
|
||||||
|
TIMEOUTS.LONG,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+53
-53
@@ -62,59 +62,6 @@
|
|||||||
|
|
||||||
<h2 class="text-xl font-semibold">I want to know more because...</h2>
|
<h2 class="text-xl font-semibold">I want to know more because...</h2>
|
||||||
<ul class="list-disc list-outside ml-4">
|
<ul class="list-disc list-outside ml-4">
|
||||||
<li class="p-2">
|
|
||||||
<div class="text-blue-500" @click="toggleAlpha">... I'm a member of Alpha chat.</div>
|
|
||||||
<div v-if="showAlpha">
|
|
||||||
<p>
|
|
||||||
This is a project for public benefit. You are invited to add your gratitude
|
|
||||||
and propose projects on a distributable ledger.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The underlying data is on a merkle tree with each verifiable claim, signature and all.
|
|
||||||
The chain includes individual IDs for discovery & visibility, so not all data is distributed -- yet.
|
|
||||||
The goal is to eventually distribute the data on people's devices with their chosen network,
|
|
||||||
where anyone could host their own chain of provenance if they choose.
|
|
||||||
The formats follow standard schemas (eg. schema.org) to encourage interoperability.
|
|
||||||
We're currently at the beginning phase where we're trusting the server to keep IDs private.
|
|
||||||
It's all open-source, and we expect to have a professional audit someday.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
A person's network of contacts is similar: the server currently knows some of the links between people
|
|
||||||
to allow discovery and visibility. However, even that will be manageable on personal devices someday.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
There are no tokens to maintain the chain: the purpose is to create software that communities
|
|
||||||
and activists can easily join and use. We're betting that this is a case where network
|
|
||||||
participants have the motivation to run the software. The protocol is meant to be lightweight enough that
|
|
||||||
non-technical people can run it on inexpensive devices they already own. There may be cases for
|
|
||||||
MPC or ZKP in the future when they are more widespread and standard,
|
|
||||||
but our preference is to engineer as simply as possible with "white-magic" cryptography
|
|
||||||
over those "black-magic" functions.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Let's make real distributed computing and shared data happen, starting with our own small networks.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
... and exemplify the fun along the way.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="p-2">
|
|
||||||
<div class="text-blue-500" @click="toggleGroup">... I want to find a group I'll enjoy working with.</div>
|
|
||||||
<div v-if="showGroup">
|
|
||||||
<p>
|
|
||||||
This app encourages people to offer small bits of time to one another. It's a way to
|
|
||||||
run experiments with other people... tests of working together, which can start small
|
|
||||||
and easy but build into cooperation with people who are like-minded and who work well together.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Search the projects and place an offer on an interesting one
|
|
||||||
-- or create your own project and see who offers to help.
|
|
||||||
After your first experiment, you can give and get confirmation about the work, which you might choose
|
|
||||||
to show to future contacts.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="p-2">
|
<li class="p-2">
|
||||||
<div class="text-blue-500" @click="toggleCommunity">... I want to participate in community projects.</div>
|
<div class="text-blue-500" @click="toggleCommunity">... I want to participate in community projects.</div>
|
||||||
<div v-if="showCommunity">
|
<div v-if="showCommunity">
|
||||||
@@ -188,6 +135,59 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="p-2">
|
||||||
|
<div class="text-blue-500" @click="toggleGroup">... I want to find a group I'll enjoy working with.</div>
|
||||||
|
<div v-if="showGroup">
|
||||||
|
<p>
|
||||||
|
This app encourages people to offer small bits of time to one another. It's a way to
|
||||||
|
run experiments with other people... tests of working together, which can start small
|
||||||
|
and easy but build into cooperation with people who are like-minded and who work well together.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Search the projects and place an offer on an interesting one
|
||||||
|
-- or create your own project and see who offers to help.
|
||||||
|
After your first experiment, you can give and get confirmation about the work, which you might choose
|
||||||
|
to show to future contacts.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="p-2">
|
||||||
|
<div class="text-blue-500" @click="toggleAlpha">... I'm a member of Alpha chat.</div>
|
||||||
|
<div v-if="showAlpha">
|
||||||
|
<p>
|
||||||
|
This is a project for public benefit. You are invited to add your gratitude
|
||||||
|
and propose projects on a distributable ledger.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The underlying data is on a merkle tree with each verifiable claim, signature and all.
|
||||||
|
The chain includes individual IDs for discovery & visibility, so not all data is distributed -- yet.
|
||||||
|
The goal is to eventually distribute the data on people's devices with their chosen network,
|
||||||
|
where anyone could host their own chain of provenance if they choose.
|
||||||
|
The formats follow standard schemas (eg. schema.org) to encourage interoperability.
|
||||||
|
We're currently at the beginning phase where we're trusting the server to keep IDs private.
|
||||||
|
It's all open-source, and we expect to have a professional audit someday.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
A person's network of contacts is similar: the server currently knows some of the links between people
|
||||||
|
to allow discovery and visibility. However, even that will be manageable on personal devices someday.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
There are no tokens to maintain the chain: the purpose is to create software that communities
|
||||||
|
and activists can easily join and use. We're betting that this is a case where network
|
||||||
|
participants have the motivation to run the software. The protocol is meant to be lightweight enough that
|
||||||
|
non-technical people can run it on inexpensive devices they already own. There may be cases for
|
||||||
|
MPC or ZKP in the future when they are more widespread and standard,
|
||||||
|
but our preference is to engineer as simply as possible with "white-magic" cryptography
|
||||||
|
over those "black-magic" functions.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Let's make real distributed computing and shared data happen, starting with our own small networks.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
... and exemplify the fun along the way.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 class="text-xl font-semibold">How do I get started?</h2>
|
<h2 class="text-xl font-semibold">How do I get started?</h2>
|
||||||
|
|||||||
+1
-120
@@ -234,11 +234,9 @@ Raymer * @version 1.0.0 */
|
|||||||
:last-viewed-claim-id="feedLastViewedClaimId"
|
:last-viewed-claim-id="feedLastViewedClaimId"
|
||||||
:is-registered="isRegistered"
|
:is-registered="isRegistered"
|
||||||
:active-did="activeDid"
|
:active-did="activeDid"
|
||||||
:confirmer-id-list="record.confirmerIdList"
|
|
||||||
:on-image-cache="cacheImageData"
|
:on-image-cache="cacheImageData"
|
||||||
@load-claim="onClickLoadClaim"
|
@load-claim="onClickLoadClaim"
|
||||||
@view-image="openImageViewer"
|
@view-image="openImageViewer"
|
||||||
@confirm-claim="confirmClaim"
|
|
||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
@@ -306,15 +304,11 @@ import {
|
|||||||
OnboardPage,
|
OnboardPage,
|
||||||
} from "../libs/util";
|
} from "../libs/util";
|
||||||
import { GiveSummaryRecord } from "../interfaces/records";
|
import { GiveSummaryRecord } from "../interfaces/records";
|
||||||
import * as serverUtil from "../libs/endorserServer";
|
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { GiveRecordWithContactInfo } from "../interfaces/give";
|
import { GiveRecordWithContactInfo } from "../interfaces/give";
|
||||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||||
import {
|
import { NOTIFY_CONTACT_LOADING_ISSUE } from "@/constants/notifications";
|
||||||
NOTIFY_CONTACT_LOADING_ISSUE,
|
|
||||||
NOTIFY_CONFIRMATION_ERROR,
|
|
||||||
} from "@/constants/notifications";
|
|
||||||
import * as Package from "../../package.json";
|
import * as Package from "../../package.json";
|
||||||
|
|
||||||
// consolidate this with GiveActionClaim in src/interfaces/claims.ts
|
// consolidate this with GiveActionClaim in src/interfaces/claims.ts
|
||||||
@@ -640,42 +634,6 @@ export default class HomeView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads user settings from database using ultra-concise mixin
|
|
||||||
* Used for displaying settings in feed and actions
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
* Called by mounted() and reloadFeedOnChange()
|
|
||||||
*/
|
|
||||||
private async loadSettings() {
|
|
||||||
// Use the current activeDid (set in initializeIdentity) to get user-specific settings
|
|
||||||
const settings = await this.$accountSettings(this.activeDid, {
|
|
||||||
apiServer: "",
|
|
||||||
activeDid: "",
|
|
||||||
filterFeedByVisible: false,
|
|
||||||
filterFeedByNearby: false,
|
|
||||||
isRegistered: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.apiServer = settings.apiServer || "";
|
|
||||||
|
|
||||||
// **CRITICAL**: Ensure correct API server for platform
|
|
||||||
await this.ensureCorrectApiServer();
|
|
||||||
|
|
||||||
this.activeDid = settings.activeDid || "";
|
|
||||||
this.feedLastViewedClaimId = settings.lastViewedClaimId;
|
|
||||||
this.givenName = settings.firstName || "";
|
|
||||||
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
|
|
||||||
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
|
|
||||||
this.isRegistered = !!settings.isRegistered;
|
|
||||||
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId;
|
|
||||||
this.lastAckedOfferToUserProjectsJwtId =
|
|
||||||
settings.lastAckedOfferToUserProjectsJwtId;
|
|
||||||
this.searchBoxes = settings.searchBoxes || [];
|
|
||||||
this.showShortcutBvc = !!settings.showShortcutBvc;
|
|
||||||
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads user contacts from database using ultra-concise mixin
|
* Loads user contacts from database using ultra-concise mixin
|
||||||
* Used for displaying contact info in feed and actions
|
* Used for displaying contact info in feed and actions
|
||||||
@@ -690,36 +648,6 @@ export default class HomeView extends Vue {
|
|||||||
.map((c) => c.did);
|
.map((c) => c.did);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies user registration status with endorser service
|
|
||||||
* - Checks if unregistered user can access API
|
|
||||||
* - Updates registration status if successful
|
|
||||||
* - Preserves unregistered state on failure
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
* Called by mounted() and initializeIdentity()
|
|
||||||
*/
|
|
||||||
private async checkRegistrationStatus() {
|
|
||||||
if (!this.isRegistered && this.activeDid) {
|
|
||||||
try {
|
|
||||||
const resp = await fetchEndorserRateLimits(
|
|
||||||
this.apiServer,
|
|
||||||
this.axios,
|
|
||||||
this.activeDid,
|
|
||||||
);
|
|
||||||
if (resp.status === 200) {
|
|
||||||
// Ultra-concise settings update with automatic cache invalidation!
|
|
||||||
await this.$saveMySettings({ isRegistered: true });
|
|
||||||
this.isRegistered = true;
|
|
||||||
// Force Vue to re-render the template
|
|
||||||
await this.$nextTick();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// ignore the error... just keep us unregistered
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes feed data
|
* Initializes feed data
|
||||||
* Triggers updateAllFeed() to populate activity feed
|
* Triggers updateAllFeed() to populate activity feed
|
||||||
@@ -1716,53 +1644,6 @@ export default class HomeView extends Vue {
|
|||||||
this.isImageViewerOpen = true;
|
this.isImageViewerOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles claim confirmation
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
* Called by ActivityListItem component
|
|
||||||
* @param record Record to confirm
|
|
||||||
*/
|
|
||||||
async confirmClaim(record: GiveRecordWithContactInfo) {
|
|
||||||
this.notify.confirm(
|
|
||||||
"Do you personally confirm that this is true?",
|
|
||||||
async () => {
|
|
||||||
const goodClaim = serverUtil.removeSchemaContext(
|
|
||||||
serverUtil.removeVisibleToDids(
|
|
||||||
serverUtil.addLastClaimOrHandleAsIdIfMissing(
|
|
||||||
record.fullClaim,
|
|
||||||
record.jwtId,
|
|
||||||
record.handleId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const confirmationClaim = {
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "AgreeAction",
|
|
||||||
object: goodClaim,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await serverUtil.createAndSubmitClaim(
|
|
||||||
confirmationClaim,
|
|
||||||
this.activeDid,
|
|
||||||
this.apiServer,
|
|
||||||
this.axios,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
this.notify.confirmationSubmitted();
|
|
||||||
|
|
||||||
// Refresh the feed to show updated confirmation status
|
|
||||||
await this.updateAllFeed();
|
|
||||||
} else {
|
|
||||||
logger.error("Error submitting confirmation:", result);
|
|
||||||
this.notify.error(NOTIFY_CONFIRMATION_ERROR.message, TIMEOUTS.LONG);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleQRCodeClick() {
|
private handleQRCodeClick() {
|
||||||
if (Capacitor.isNativePlatform()) {
|
if (Capacitor.isNativePlatform()) {
|
||||||
this.$router.push({ name: "contact-qr-scan-full" });
|
this.$router.push({ name: "contact-qr-scan-full" });
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ import {
|
|||||||
serverMessageForUser,
|
serverMessageForUser,
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||||
|
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
|
|
||||||
interface Meeting {
|
interface Meeting {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -129,19 +131,11 @@ interface Meeting {
|
|||||||
mixins: [PlatformServiceMixin],
|
mixins: [PlatformServiceMixin],
|
||||||
})
|
})
|
||||||
export default class OnboardMeetingListView extends Vue {
|
export default class OnboardMeetingListView extends Vue {
|
||||||
$notify!: (
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
notification: {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
onYes?: () => void;
|
|
||||||
yesText?: string;
|
|
||||||
},
|
|
||||||
timeout?: number,
|
|
||||||
) => void;
|
|
||||||
$router!: Router;
|
$router!: Router;
|
||||||
|
|
||||||
|
notify!: ReturnType<typeof createNotifyHelpers>;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
attendingMeeting: Meeting | null = null;
|
attendingMeeting: Meeting | null = null;
|
||||||
@@ -153,30 +147,66 @@ export default class OnboardMeetingListView extends Vue {
|
|||||||
selectedMeeting: Meeting | null = null;
|
selectedMeeting: Meeting | null = null;
|
||||||
showPasswordDialog = false;
|
showPasswordDialog = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vue lifecycle hook - component initialization
|
||||||
|
*
|
||||||
|
* Initializes the component by loading user settings and fetching available
|
||||||
|
* onboarding meetings. This method is called when the component is created
|
||||||
|
* and sets up all necessary data for the meeting list interface.
|
||||||
|
*
|
||||||
|
* Workflow:
|
||||||
|
* 1. Initialize notification system using createNotifyHelpers
|
||||||
|
* 2. Load user account settings (DID, API server, registration status)
|
||||||
|
* 3. Fetch available onboarding meetings from the server
|
||||||
|
*
|
||||||
|
* Dependencies:
|
||||||
|
* - PlatformServiceMixin for settings access ($accountSettings)
|
||||||
|
* - Server API for meeting data (fetchMeetings)
|
||||||
|
*
|
||||||
|
* Error Handling:
|
||||||
|
* - Server errors during meeting fetch are handled in fetchMeetings()
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
async created() {
|
async created() {
|
||||||
const settings = await this.$accountSettings();
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
if (settings?.activeDid) {
|
// Load user account settings
|
||||||
try {
|
const settings = await this.$accountSettings();
|
||||||
// Verify database settings are accessible
|
|
||||||
await this.$query("SELECT * FROM settings WHERE accountDid = ?", [
|
|
||||||
settings.activeDid,
|
|
||||||
]);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error checking database settings:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.activeDid = settings?.activeDid || "";
|
this.activeDid = settings?.activeDid || "";
|
||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
this.firstName = settings?.firstName || "";
|
this.firstName = settings?.firstName || "";
|
||||||
this.isRegistered = !!settings?.isRegistered;
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
|
|
||||||
if (this.isRegistered) {
|
await this.fetchMeetings();
|
||||||
await this.fetchMeetings();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches available onboarding meetings from the server
|
||||||
|
*
|
||||||
|
* This method retrieves the list of onboarding meetings that the user can join.
|
||||||
|
* It first checks if the user is already attending a meeting, and if so,
|
||||||
|
* displays that meeting instead of the full list.
|
||||||
|
*
|
||||||
|
* Workflow:
|
||||||
|
* 1. Check if user is already attending a meeting (groupOnboardMember endpoint)
|
||||||
|
* 2. If attending: Fetch meeting details and display single meeting view
|
||||||
|
* 3. If not attending: Fetch all available meetings (groupsOnboarding endpoint)
|
||||||
|
* 4. Handle loading states and error conditions
|
||||||
|
*
|
||||||
|
* API Endpoints Used:
|
||||||
|
* - GET /api/partner/groupOnboardMember - Check current attendance
|
||||||
|
* - GET /api/partner/groupOnboard/{id} - Get meeting details
|
||||||
|
* - GET /api/partner/groupsOnboarding - Get all available meetings
|
||||||
|
*
|
||||||
|
* State Management:
|
||||||
|
* - Sets isLoading flag during API calls
|
||||||
|
* - Updates attendingMeeting or meetings array
|
||||||
|
* - Handles error states with user notifications
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
async fetchMeetings() {
|
async fetchMeetings() {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
try {
|
try {
|
||||||
@@ -226,20 +256,36 @@ export default class OnboardMeetingListView extends Vue {
|
|||||||
"Error fetching meetings: " + errorStringForLog(error),
|
"Error fetching meetings: " + errorStringForLog(error),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
this.$notify(
|
this.notify.error(
|
||||||
{
|
serverMessageForUser(error) || "There was a problem fetching meetings.",
|
||||||
group: "alert",
|
TIMEOUTS.LONG,
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: serverMessageForUser(error) || "Failed to fetch meetings.",
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the password dialog for joining a meeting
|
||||||
|
*
|
||||||
|
* This method initiates the process of joining an onboarding meeting by
|
||||||
|
* opening a modal dialog that prompts the user for the meeting password.
|
||||||
|
* The dialog is focused and ready for input when displayed.
|
||||||
|
*
|
||||||
|
* Workflow:
|
||||||
|
* 1. Clear any previous password input
|
||||||
|
* 2. Store the selected meeting for later use
|
||||||
|
* 3. Show the password dialog modal
|
||||||
|
* 4. Focus the password input field for immediate typing
|
||||||
|
*
|
||||||
|
* UI State Changes:
|
||||||
|
* - Sets showPasswordDialog to true (shows modal)
|
||||||
|
* - Clears password field for fresh input
|
||||||
|
* - Stores selectedMeeting for password submission
|
||||||
|
*
|
||||||
|
* @param meeting - The meeting object the user wants to join
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
promptPassword(meeting: Meeting) {
|
promptPassword(meeting: Meeting) {
|
||||||
this.password = "";
|
this.password = "";
|
||||||
this.selectedMeeting = meeting;
|
this.selectedMeeting = meeting;
|
||||||
@@ -252,12 +298,61 @@ export default class OnboardMeetingListView extends Vue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the password dialog and resets state
|
||||||
|
*
|
||||||
|
* This method handles the cancellation of the meeting password dialog.
|
||||||
|
* It cleans up the dialog state and resets all related variables to
|
||||||
|
* their initial state, ensuring a clean slate for future dialog interactions.
|
||||||
|
*
|
||||||
|
* State Cleanup:
|
||||||
|
* - Clears password input field
|
||||||
|
* - Removes selected meeting reference
|
||||||
|
* - Hides password dialog modal
|
||||||
|
*
|
||||||
|
* This ensures that if the user reopens the dialog, they start with
|
||||||
|
* a fresh state without any leftover data from previous attempts.
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
cancelPasswordDialog() {
|
cancelPasswordDialog() {
|
||||||
this.password = "";
|
this.password = "";
|
||||||
this.selectedMeeting = null;
|
this.selectedMeeting = null;
|
||||||
this.showPasswordDialog = false;
|
this.showPasswordDialog = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the password and joins the selected meeting
|
||||||
|
*
|
||||||
|
* This method handles the complete workflow of joining an onboarding meeting.
|
||||||
|
* It encrypts the user's member data with the provided password and sends
|
||||||
|
* it to the server to register the user as a meeting participant.
|
||||||
|
*
|
||||||
|
* Workflow:
|
||||||
|
* 1. Validate that a meeting is selected (safety check)
|
||||||
|
* 2. Create member data object with user information
|
||||||
|
* 3. Encrypt member data using the meeting password
|
||||||
|
* 4. Send encrypted data to server via groupOnboardMember endpoint
|
||||||
|
* 5. On success: Navigate to meeting members view with credentials
|
||||||
|
* 6. On failure: Show error notification to user
|
||||||
|
*
|
||||||
|
* Data Encryption:
|
||||||
|
* - Member data includes: name, DID, registration status
|
||||||
|
* - Data is encrypted using the meeting password for security
|
||||||
|
* - Encrypted data is sent to server for verification
|
||||||
|
*
|
||||||
|
* Navigation:
|
||||||
|
* - On successful join: Redirects to onboard-meeting-members view
|
||||||
|
* - Passes groupId, password, and memberId as route parameters
|
||||||
|
* - Allows user to see other meeting participants
|
||||||
|
*
|
||||||
|
* Error Handling:
|
||||||
|
* - Invalid passwords result in server rejection
|
||||||
|
* - Network errors are caught and displayed to user
|
||||||
|
* - All errors are logged for debugging purposes
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
async submitPassword() {
|
async submitPassword() {
|
||||||
if (!this.selectedMeeting) {
|
if (!this.selectedMeeting) {
|
||||||
// this should never happen
|
// this should never happen
|
||||||
@@ -316,69 +411,95 @@ export default class OnboardMeetingListView extends Vue {
|
|||||||
"Error joining meeting: " + errorStringForLog(error),
|
"Error joining meeting: " + errorStringForLog(error),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
this.$notify(
|
this.notify.error(
|
||||||
{
|
serverMessageForUser(error) ||
|
||||||
group: "alert",
|
"There was a problem joining the meeting.",
|
||||||
type: "danger",
|
TIMEOUTS.LONG,
|
||||||
title: "Error",
|
|
||||||
text:
|
|
||||||
serverMessageForUser(error) || "You failed to join the meeting.",
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompts user to confirm leaving the current meeting
|
||||||
|
*
|
||||||
|
* This method initiates the process of leaving an onboarding meeting.
|
||||||
|
* It shows a confirmation dialog to prevent accidental departures,
|
||||||
|
* then handles the server-side removal and UI updates.
|
||||||
|
*
|
||||||
|
* Workflow:
|
||||||
|
* 1. Display confirmation dialog asking user to confirm departure
|
||||||
|
* 2. On confirmation: Send DELETE request to groupOnboardMember endpoint
|
||||||
|
* 3. On success: Clear attending meeting state and refresh meeting list
|
||||||
|
* 4. Show success notification to user
|
||||||
|
* 5. On failure: Show error notification with details
|
||||||
|
*
|
||||||
|
* Server Interaction:
|
||||||
|
* - DELETE /api/partner/groupOnboardMember - Removes user from meeting
|
||||||
|
* - Requires authentication headers for user verification
|
||||||
|
* - Server handles the actual removal from meeting database
|
||||||
|
*
|
||||||
|
* State Management:
|
||||||
|
* - Clears attendingMeeting when successfully left
|
||||||
|
* - Refreshes meetings list to show updated availability
|
||||||
|
* - Updates UI to show meeting list instead of single meeting
|
||||||
|
*
|
||||||
|
* User Experience:
|
||||||
|
* - Confirmation prevents accidental departures
|
||||||
|
* - Clear feedback on success/failure
|
||||||
|
* - Seamless transition back to meeting list
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
async leaveMeeting() {
|
async leaveMeeting() {
|
||||||
this.$notify(
|
this.notify.confirm(
|
||||||
{
|
"Are you sure you want to leave this meeting?",
|
||||||
group: "modal",
|
async () => {
|
||||||
type: "confirm",
|
try {
|
||||||
title: "Leave Meeting",
|
const headers = await getHeaders(this.activeDid);
|
||||||
text: "Are you sure you want to leave this meeting?",
|
await this.axios.delete(
|
||||||
onYes: async () => {
|
this.apiServer + "/api/partner/groupOnboardMember",
|
||||||
try {
|
{ headers },
|
||||||
const headers = await getHeaders(this.activeDid);
|
);
|
||||||
await this.axios.delete(
|
|
||||||
this.apiServer + "/api/partner/groupOnboardMember",
|
|
||||||
{ headers },
|
|
||||||
);
|
|
||||||
|
|
||||||
this.attendingMeeting = null;
|
this.attendingMeeting = null;
|
||||||
await this.fetchMeetings();
|
await this.fetchMeetings();
|
||||||
|
|
||||||
this.$notify(
|
this.notify.success("You left the meeting.", TIMEOUTS.LONG);
|
||||||
{
|
} catch (error) {
|
||||||
group: "alert",
|
this.$logAndConsole(
|
||||||
type: "success",
|
"Error leaving meeting: " + errorStringForLog(error),
|
||||||
title: "Success",
|
true,
|
||||||
text: "You left the meeting.",
|
);
|
||||||
},
|
this.notify.error(
|
||||||
5000,
|
serverMessageForUser(error) ||
|
||||||
);
|
"There was a problem leaving the meeting.",
|
||||||
} catch (error) {
|
TIMEOUTS.LONG,
|
||||||
this.$logAndConsole(
|
);
|
||||||
"Error leaving meeting: " + errorStringForLog(error),
|
}
|
||||||
true,
|
|
||||||
);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text:
|
|
||||||
serverMessageForUser(error) ||
|
|
||||||
"You failed to leave the meeting.",
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
-1,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the meeting creation page
|
||||||
|
*
|
||||||
|
* This method handles the navigation to the meeting setup page where
|
||||||
|
* registered users can create new onboarding meetings. It's only
|
||||||
|
* available to users who are registered in the system.
|
||||||
|
*
|
||||||
|
* Navigation:
|
||||||
|
* - Routes to onboard-meeting-setup view
|
||||||
|
* - Allows user to configure new meeting settings
|
||||||
|
* - Only accessible to registered users (controlled by template)
|
||||||
|
*
|
||||||
|
* User Flow:
|
||||||
|
* - User clicks "Create Meeting" button
|
||||||
|
* - System navigates to setup page
|
||||||
|
* - User can configure meeting name, password, etc.
|
||||||
|
* - New meeting becomes available to other users
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
createMeeting() {
|
createMeeting() {
|
||||||
this.$router.push({ name: "onboard-meeting-setup" });
|
this.$router.push({ name: "onboard-meeting-setup" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,15 +75,9 @@ export default class ShareMyContactInfoView extends Vue {
|
|||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
// Debug logging for test diagnosis
|
|
||||||
const settings = await this.$settings();
|
const settings = await this.$settings();
|
||||||
|
|
||||||
const activeDid = settings?.activeDid;
|
const activeDid = settings?.activeDid;
|
||||||
// @ts-expect-error - Debug property for testing contact sharing functionality
|
|
||||||
window.__SHARE_CONTACT_DEBUG__ = { settings, activeDid };
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
if (!activeDid) {
|
if (!activeDid) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
this.$router.push({ name: "home" });
|
this.$router.push({ name: "home" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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