Compare commits
8 Commits
remove-can
...
notificati
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9d9df32e1 | ||
|
|
e655082af6 | ||
|
|
ea2fa30903 | ||
|
|
39dbbb08f7 | ||
|
|
eb21d3c247 | ||
|
|
213f5f0555 | ||
|
|
2db2c39830 | ||
|
|
106cefab51 |
@@ -4,6 +4,7 @@ alwaysApply: false
|
|||||||
---
|
---
|
||||||
✅ use system date command to timestamp all interactions with accurate date and
|
✅ use system date command to timestamp all interactions with accurate date and
|
||||||
time
|
time
|
||||||
|
✅ python script files must always have a blank line at their end
|
||||||
✅ remove whitespace at the end of lines
|
✅ remove whitespace at the end of lines
|
||||||
✅ use npm run lint-fix to check for warnings
|
✅ use npm run lint-fix to check for warnings
|
||||||
✅ do not use npm run dev let me handle running and supplying feedback
|
✅ do not use npm run dev let me handle running and supplying feedback
|
||||||
@@ -21,10 +22,12 @@ alwaysApply: false
|
|||||||
|
|
||||||
- [ ] **Timestamp Usage**: Include accurate timestamps in all interactions
|
- [ ] **Timestamp Usage**: Include accurate timestamps in all interactions
|
||||||
- [ ] **Code Quality**: Use npm run lint-fix to check for warnings
|
- [ ] **Code Quality**: Use npm run lint-fix to check for warnings
|
||||||
|
- [ ] **File Standards**: Ensure Python files have blank line at end
|
||||||
- [ ] **Whitespace**: Remove trailing whitespace from all lines
|
- [ ] **Whitespace**: Remove trailing whitespace from all lines
|
||||||
|
|
||||||
### After Development
|
### After Development
|
||||||
|
|
||||||
- [ ] **Linting Check**: Run npm run lint-fix to verify code quality
|
- [ ] **Linting Check**: Run npm run lint-fix to verify code quality
|
||||||
|
- [ ] **File Validation**: Confirm Python files end with blank line
|
||||||
- [ ] **Whitespace Review**: Verify no trailing whitespace remains
|
- [ ] **Whitespace Review**: Verify no trailing whitespace remains
|
||||||
- [ ] **Documentation**: Update relevant documentation with changes
|
- [ ] **Documentation**: Update relevant documentation with changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "timesafari",
|
"name": "timesafari",
|
||||||
"version": "1.1.1-beta",
|
"version": "1.1.0-beta",
|
||||||
"description": "Time Safari Application",
|
"description": "Time Safari Application",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Time Safari Team"
|
"name": "Time Safari Team"
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
"guard": "bash ./scripts/build-arch-guard.sh",
|
"guard": "bash ./scripts/build-arch-guard.sh",
|
||||||
"guard:test": "bash ./scripts/build-arch-guard.sh --staged",
|
"guard:test": "bash ./scripts/build-arch-guard.sh --staged",
|
||||||
"guard:setup": "npm run prepare && echo '✅ Build Architecture Guard is now active!'",
|
"guard:setup": "npm run prepare && echo '✅ Build Architecture Guard is now active!'",
|
||||||
"clean:android": "./scripts/uninstall-android.sh",
|
"clean:android": "./scripts/clean-android.sh",
|
||||||
"clean:ios": "rm -rf ios/App/build ios/App/Pods ios/App/output ios/App/App/public ios/DerivedData ios/capacitor-cordova-ios-plugins ios/App/App/capacitor.config.json ios/App/App/config.xml || true",
|
"clean:ios": "rm -rf ios/App/build ios/App/Pods ios/App/output ios/App/App/public ios/DerivedData ios/capacitor-cordova-ios-plugins ios/App/App/capacitor.config.json ios/App/App/config.xml || true",
|
||||||
"clean:electron": "./scripts/build-electron.sh --clean",
|
"clean:electron": "./scripts/build-electron.sh --clean",
|
||||||
"clean:all": "npm run clean:ios && npm run clean:android && npm run clean:electron",
|
"clean:all": "npm run clean:ios && npm run clean:android && npm run clean:electron",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
# --sync Sync Capacitor only
|
# --sync Sync Capacitor only
|
||||||
# --assets Generate assets only
|
# --assets Generate assets only
|
||||||
# --deploy Deploy APK to connected device
|
# --deploy Deploy APK to connected device
|
||||||
# --uninstall Uninstall app from connected device
|
|
||||||
# -h, --help Show this help message
|
# -h, --help Show this help message
|
||||||
# -v, --verbose Enable verbose logging
|
# -v, --verbose Enable verbose logging
|
||||||
#
|
#
|
||||||
@@ -197,7 +196,6 @@ SYNC_ONLY=false
|
|||||||
ASSETS_ONLY=false
|
ASSETS_ONLY=false
|
||||||
DEPLOY_APP=false
|
DEPLOY_APP=false
|
||||||
AUTO_RUN=false
|
AUTO_RUN=false
|
||||||
UNINSTALL=false
|
|
||||||
CUSTOM_API_IP=""
|
CUSTOM_API_IP=""
|
||||||
|
|
||||||
# Function to parse Android-specific arguments
|
# Function to parse Android-specific arguments
|
||||||
@@ -248,9 +246,6 @@ parse_android_args() {
|
|||||||
--auto-run)
|
--auto-run)
|
||||||
AUTO_RUN=true
|
AUTO_RUN=true
|
||||||
;;
|
;;
|
||||||
--uninstall)
|
|
||||||
UNINSTALL=true
|
|
||||||
;;
|
|
||||||
--api-ip)
|
--api-ip)
|
||||||
if [ $((i + 1)) -lt ${#args[@]} ]; then
|
if [ $((i + 1)) -lt ${#args[@]} ]; then
|
||||||
CUSTOM_API_IP="${args[$((i + 1))]}"
|
CUSTOM_API_IP="${args[$((i + 1))]}"
|
||||||
@@ -296,7 +291,6 @@ print_android_usage() {
|
|||||||
echo " --assets Generate assets only"
|
echo " --assets Generate assets only"
|
||||||
echo " --deploy Deploy APK to connected device"
|
echo " --deploy Deploy APK to connected device"
|
||||||
echo " --auto-run Auto-run app after build"
|
echo " --auto-run Auto-run app after build"
|
||||||
echo " --uninstall Uninstall app from connected device"
|
|
||||||
echo " --api-ip <ip> Custom IP address for claim API (defaults to 10.0.2.2)"
|
echo " --api-ip <ip> Custom IP address for claim API (defaults to 10.0.2.2)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Common Options:"
|
echo "Common Options:"
|
||||||
@@ -311,7 +305,6 @@ print_android_usage() {
|
|||||||
echo " $0 --clean # Clean only"
|
echo " $0 --clean # Clean only"
|
||||||
echo " $0 --sync # Sync only"
|
echo " $0 --sync # Sync only"
|
||||||
echo " $0 --deploy # Build and deploy to device"
|
echo " $0 --deploy # Build and deploy to device"
|
||||||
echo " $0 --uninstall # Uninstall app from device"
|
|
||||||
echo " $0 --dev # Dev build with default 10.0.2.2"
|
echo " $0 --dev # Dev build with default 10.0.2.2"
|
||||||
echo " $0 --dev --api-ip 192.168.1.100 # Dev build with custom API IP"
|
echo " $0 --dev --api-ip 192.168.1.100 # Dev build with custom API IP"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -358,18 +351,8 @@ fi
|
|||||||
# Setup application directories
|
# Setup application directories
|
||||||
setup_app_directories
|
setup_app_directories
|
||||||
|
|
||||||
# Load environment-specific .env file if it exists
|
# Load environment from .env file if it exists
|
||||||
env_file=".env.$BUILD_MODE"
|
load_env_file ".env"
|
||||||
if [ -f "$env_file" ]; then
|
|
||||||
load_env_file "$env_file"
|
|
||||||
else
|
|
||||||
log_debug "No $env_file file found, using default environment"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Load .env file if it exists (fallback)
|
|
||||||
if [ -f ".env" ]; then
|
|
||||||
load_env_file ".env"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Handle clean-only mode
|
# Handle clean-only mode
|
||||||
if [ "$CLEAN_ONLY" = true ]; then
|
if [ "$CLEAN_ONLY" = true ]; then
|
||||||
@@ -424,13 +407,8 @@ safe_execute "Validating asset configuration" "npm run assets:validate" || {
|
|||||||
log_info "If you encounter build failures, please run 'npm install' first to ensure all dependencies are available."
|
log_info "If you encounter build failures, please run 'npm install' first to ensure all dependencies are available."
|
||||||
}
|
}
|
||||||
|
|
||||||
# Step 2: Uninstall Android app
|
# Step 2: Clean Android app
|
||||||
if [ "$UNINSTALL" = true ]; then
|
safe_execute "Cleaning Android app" "npm run clean:android" || exit 1
|
||||||
log_info "Uninstall: uninstalling app from device"
|
|
||||||
safe_execute "Uninstalling Android app" "./scripts/uninstall-android.sh" || exit 1
|
|
||||||
log_success "Uninstall completed successfully!"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Step 3: Clean dist directory
|
# Step 3: Clean dist directory
|
||||||
log_info "Cleaning dist directory..."
|
log_info "Cleaning dist directory..."
|
||||||
|
|||||||
@@ -341,19 +341,7 @@ main_electron_build() {
|
|||||||
# Setup environment
|
# Setup environment
|
||||||
setup_build_env "electron" "$BUILD_MODE"
|
setup_build_env "electron" "$BUILD_MODE"
|
||||||
setup_app_directories
|
setup_app_directories
|
||||||
|
|
||||||
# Load environment-specific .env file if it exists
|
|
||||||
env_file=".env.$BUILD_MODE"
|
|
||||||
if [ -f "$env_file" ]; then
|
|
||||||
load_env_file "$env_file"
|
|
||||||
else
|
|
||||||
log_debug "No $env_file file found, using default environment"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Load .env file if it exists (fallback)
|
|
||||||
if [ -f ".env" ]; then
|
|
||||||
load_env_file ".env"
|
load_env_file ".env"
|
||||||
fi
|
|
||||||
|
|
||||||
# Step 1: Clean Electron build artifacts
|
# Step 1: Clean Electron build artifacts
|
||||||
clean_electron_artifacts
|
clean_electron_artifacts
|
||||||
|
|||||||
@@ -324,18 +324,8 @@ fi
|
|||||||
# Setup application directories
|
# Setup application directories
|
||||||
setup_app_directories
|
setup_app_directories
|
||||||
|
|
||||||
# Load environment-specific .env file if it exists
|
# Load environment from .env file if it exists
|
||||||
env_file=".env.$BUILD_MODE"
|
load_env_file ".env"
|
||||||
if [ -f "$env_file" ]; then
|
|
||||||
load_env_file "$env_file"
|
|
||||||
else
|
|
||||||
log_debug "No $env_file file found, using default environment"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Load .env file if it exists (fallback)
|
|
||||||
if [ -f ".env" ]; then
|
|
||||||
load_env_file ".env"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Validate iOS environment
|
# Validate iOS environment
|
||||||
validate_ios_environment
|
validate_ios_environment
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# uninstall-android.sh
|
# clean-android.sh
|
||||||
# Author: Matthew Raymer
|
# Author: Matthew Raymer
|
||||||
# Date: 2025-08-19
|
# Date: 2025-08-19
|
||||||
# Description: Uninstall Android app with timeout protection to prevent hanging
|
# Description: Clean Android app with timeout protection to prevent hanging
|
||||||
# This script safely uninstalls the TimeSafari app from connected Android devices
|
# This script safely uninstalls the TimeSafari app from connected Android devices
|
||||||
# with a 30-second timeout to prevent indefinite hanging.
|
# with a 30-second timeout to prevent indefinite hanging.
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ const inputImageFileNameRef = ref<Blob>();
|
|||||||
export default class ImageMethodDialog extends Vue {
|
export default class ImageMethodDialog extends Vue {
|
||||||
$notify!: NotifyFunction;
|
$notify!: NotifyFunction;
|
||||||
$router!: Router;
|
$router!: Router;
|
||||||
notify!: ReturnType<typeof createNotifyHelpers>;
|
notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
/** Active DID for user authentication */
|
/** Active DID for user authentication */
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
@@ -498,9 +498,6 @@ export default class ImageMethodDialog extends Vue {
|
|||||||
* @throws {Error} When settings retrieval fails
|
* @throws {Error} When settings retrieval fails
|
||||||
*/
|
*/
|
||||||
async mounted() {
|
async mounted() {
|
||||||
// Initialize notification helpers
|
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get activeDid from active_identity table (single source of truth)
|
// Get activeDid from active_identity table (single source of truth)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|||||||
@@ -14,11 +14,20 @@
|
|||||||
'text-slate-500': selected !== 'Home',
|
'text-slate-500': selected !== 'Home',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<router-link :to="{ name: 'home' }" class="block text-center py-2 px-1">
|
<router-link
|
||||||
|
:to="{ name: 'home' }"
|
||||||
|
class="relative block text-center py-2 px-1"
|
||||||
|
>
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<font-awesome icon="house-chimney" class="fa-fw" />
|
<font-awesome icon="house-chimney" class="fa-fw" />
|
||||||
<span class="text-xs mt-1">feed</span>
|
<span class="text-xs mt-1">feed</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Notification dot - show while the user has unread notifications -->
|
||||||
|
<font-awesome
|
||||||
|
icon="circle"
|
||||||
|
class="absolute left-1/2 top-1 translate-x-2 text-rose-500 text-[10px] border border-white rounded-full"
|
||||||
|
></font-awesome>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
@@ -89,7 +98,7 @@
|
|||||||
>
|
>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'account' }"
|
:to="{ name: 'account' }"
|
||||||
class="block text-center py-2 px-1"
|
class="relative block text-center py-2 px-1"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<font-awesome icon="circle-user" class="fa-fw" />
|
<font-awesome icon="circle-user" class="fa-fw" />
|
||||||
@@ -102,6 +111,12 @@
|
|||||||
-->
|
-->
|
||||||
<span class="text-xs mt-1">profile</span>
|
<span class="text-xs mt-1">profile</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Notification dot - show while the user has not yet backed up their seed phrase -->
|
||||||
|
<font-awesome
|
||||||
|
icon="circle"
|
||||||
|
class="absolute left-1/2 top-1 translate-x-2 text-rose-500 text-[10px] border border-white rounded-full"
|
||||||
|
></font-awesome>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { logger } from "../utils/logger";
|
|||||||
export default class TopMessage extends Vue {
|
export default class TopMessage extends Vue {
|
||||||
// Enhanced PlatformServiceMixin v4.0 provides:
|
// Enhanced PlatformServiceMixin v4.0 provides:
|
||||||
// - Cached database operations: this.$contacts(), this.$settings(), this.$accountSettings()
|
// - Cached database operations: this.$contacts(), this.$settings(), this.$accountSettings()
|
||||||
// - Settings shortcuts: this.$saveSettings()
|
// - Settings shortcuts: this.$saveSettings(), this.$saveMySettings()
|
||||||
// - Cache management: this.$refreshSettings(), this.$clearAllCaches()
|
// - Cache management: this.$refreshSettings(), this.$clearAllCaches()
|
||||||
// - Ultra-concise database methods: this.$db(), this.$exec(), this.$query()
|
// - Ultra-concise database methods: this.$db(), this.$exec(), this.$query()
|
||||||
// - All methods use smart caching with TTL for massive performance gains
|
// - All methods use smart caching with TTL for massive performance gains
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<!-- show spinner if loading limits -->
|
<!-- show spinner if loading limits -->
|
||||||
<div
|
<div
|
||||||
v-if="loadingLimits"
|
v-if="loadingLimits"
|
||||||
class="text-slate-500 text-center italic mb-4"
|
class="text-center"
|
||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
>
|
>
|
||||||
@@ -19,10 +19,7 @@
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></font-awesome>
|
></font-awesome>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="mb-4 text-center">
|
||||||
v-if="limitsMessage"
|
|
||||||
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
|
|
||||||
>
|
|
||||||
{{ limitsMessage }}
|
{{ limitsMessage }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="endorserLimits">
|
<div v-if="endorserLimits">
|
||||||
|
|||||||
@@ -68,21 +68,13 @@ const MIG_004_SQL = `
|
|||||||
WHERE id = 1
|
WHERE id = 1
|
||||||
AND EXISTS (SELECT 1 FROM settings WHERE id = 1 AND activeDid IS NOT NULL AND activeDid != '');
|
AND EXISTS (SELECT 1 FROM settings WHERE id = 1 AND activeDid IS NOT NULL AND activeDid != '');
|
||||||
|
|
||||||
-- Copy important settings that were set in the MASTER_SETTINGS_KEY to the main identity.
|
|
||||||
-- (We're not doing them all because some were already identity-specific and others aren't as critical.)
|
|
||||||
UPDATE settings
|
|
||||||
SET lastViewedClaimId = (SELECT lastViewedClaimId FROM settings WHERE id = 1),
|
|
||||||
profileImageUrl = (SELECT profileImageUrl FROM settings WHERE id = 1),
|
|
||||||
showShortcutBvc = (SELECT showShortcutBvc FROM settings WHERE id = 1),
|
|
||||||
warnIfProdServer = (SELECT warnIfProdServer FROM settings WHERE id = 1),
|
|
||||||
warnIfTestServer = (SELECT warnIfTestServer FROM settings WHERE id = 1)
|
|
||||||
WHERE id = 2;
|
|
||||||
|
|
||||||
-- CLEANUP: Remove orphaned settings records and clear legacy activeDid values
|
-- CLEANUP: Remove orphaned settings records and clear legacy activeDid values
|
||||||
-- which usually simply deletes the MASTER_SETTINGS_KEY record.
|
|
||||||
-- This completes the migration from settings-based to table-based active identity
|
-- This completes the migration from settings-based to table-based active identity
|
||||||
DELETE FROM settings WHERE accountDid IS NULL;
|
-- Use guarded operations to prevent accidental data loss
|
||||||
UPDATE settings SET activeDid = NULL;
|
DELETE FROM settings WHERE accountDid IS NULL AND id != 1;
|
||||||
|
UPDATE settings SET activeDid = NULL WHERE id = 1 AND EXISTS (
|
||||||
|
SELECT 1 FROM active_identity WHERE id = 1 AND activeDid IS NOT NULL
|
||||||
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Each migration can include multiple SQL statements (with semicolons)
|
// Each migration can include multiple SQL statements (with semicolons)
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ import {
|
|||||||
faQuestion,
|
faQuestion,
|
||||||
faRightFromBracket,
|
faRightFromBracket,
|
||||||
faRotate,
|
faRotate,
|
||||||
|
faScroll,
|
||||||
faShareNodes,
|
faShareNodes,
|
||||||
faSpinner,
|
faSpinner,
|
||||||
faSquare,
|
faSquare,
|
||||||
@@ -169,6 +170,7 @@ library.add(
|
|||||||
faQrcode,
|
faQrcode,
|
||||||
faQuestion,
|
faQuestion,
|
||||||
faRotate,
|
faRotate,
|
||||||
|
faScroll,
|
||||||
faRightFromBracket,
|
faRightFromBracket,
|
||||||
faShareNodes,
|
faShareNodes,
|
||||||
faSpinner,
|
faSpinner,
|
||||||
|
|||||||
@@ -799,7 +799,7 @@ export async function runMigrations<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only show completion message in development
|
// Only show completion message in development
|
||||||
logger.log(
|
logger.debug(
|
||||||
`🎉 [Migration] Migration process complete! Summary: ${appliedCount} applied, ${skippedCount} skipped`,
|
`🎉 [Migration] Migration process complete! Summary: ${appliedCount} applied, ${skippedCount} skipped`,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ export const PlatformServiceMixin = {
|
|||||||
* Utility method for retrieving master settings
|
* Utility method for retrieving master settings
|
||||||
* Common pattern used across many components
|
* Common pattern used across many components
|
||||||
*/
|
*/
|
||||||
async _getMasterSettings(
|
async $getMasterSettings(
|
||||||
fallback: Settings | null = null,
|
fallback: Settings | null = null,
|
||||||
): Promise<Settings | null> {
|
): Promise<Settings | null> {
|
||||||
try {
|
try {
|
||||||
@@ -571,7 +571,7 @@ export const PlatformServiceMixin = {
|
|||||||
): Promise<Settings> {
|
): Promise<Settings> {
|
||||||
try {
|
try {
|
||||||
// Get default settings
|
// Get default settings
|
||||||
const defaultSettings = await this._getMasterSettings(defaultFallback);
|
const defaultSettings = await this.$getMasterSettings(defaultFallback);
|
||||||
|
|
||||||
// If no account DID, return defaults
|
// If no account DID, return defaults
|
||||||
if (!accountDid) {
|
if (!accountDid) {
|
||||||
@@ -970,7 +970,7 @@ export const PlatformServiceMixin = {
|
|||||||
* @returns Fresh settings object from database
|
* @returns Fresh settings object from database
|
||||||
*/
|
*/
|
||||||
async $settings(defaults: Settings = {}): Promise<Settings> {
|
async $settings(defaults: Settings = {}): Promise<Settings> {
|
||||||
const settings = await this._getMasterSettings(defaults);
|
const settings = await this.$getMasterSettings(defaults);
|
||||||
|
|
||||||
if (!settings) {
|
if (!settings) {
|
||||||
return defaults;
|
return defaults;
|
||||||
@@ -1003,7 +1003,7 @@ export const PlatformServiceMixin = {
|
|||||||
): Promise<Settings> {
|
): Promise<Settings> {
|
||||||
try {
|
try {
|
||||||
// Get default settings first
|
// Get default settings first
|
||||||
const defaultSettings = await this._getMasterSettings(defaults);
|
const defaultSettings = await this.$getMasterSettings(defaults);
|
||||||
|
|
||||||
if (!defaultSettings) {
|
if (!defaultSettings) {
|
||||||
return defaults;
|
return defaults;
|
||||||
@@ -1212,11 +1212,6 @@ export const PlatformServiceMixin = {
|
|||||||
* @param changes Settings changes to save
|
* @param changes Settings changes to save
|
||||||
* @returns Promise<boolean> Success status
|
* @returns Promise<boolean> Success status
|
||||||
*/
|
*/
|
||||||
/**
|
|
||||||
* Since this is unused, and since it relies on this.activeDid which isn't guaranteed to exist,
|
|
||||||
* let's take this out for the sake of safety.
|
|
||||||
* Totally remove after start of 2026 (since it would be obvious by then that it's not used).
|
|
||||||
*
|
|
||||||
async $saveMySettings(changes: Partial<Settings>): Promise<boolean> {
|
async $saveMySettings(changes: Partial<Settings>): Promise<boolean> {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const currentDid = (this as any).activeDid;
|
const currentDid = (this as any).activeDid;
|
||||||
@@ -1226,7 +1221,6 @@ export const PlatformServiceMixin = {
|
|||||||
}
|
}
|
||||||
return await this.$saveUserSettings(currentDid, changes);
|
return await this.$saveUserSettings(currentDid, changes);
|
||||||
},
|
},
|
||||||
**/
|
|
||||||
|
|
||||||
// =================================================
|
// =================================================
|
||||||
// CACHE MANAGEMENT METHODS
|
// CACHE MANAGEMENT METHODS
|
||||||
@@ -1848,7 +1842,7 @@ export const PlatformServiceMixin = {
|
|||||||
async $debugMergedSettings(did: string): Promise<void> {
|
async $debugMergedSettings(did: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Get default settings
|
// Get default settings
|
||||||
const defaultSettings = await this._getMasterSettings({});
|
const defaultSettings = await this.$getMasterSettings({});
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`[PlatformServiceMixin] Default settings:`,
|
`[PlatformServiceMixin] Default settings:`,
|
||||||
defaultSettings,
|
defaultSettings,
|
||||||
@@ -1898,6 +1892,7 @@ export interface IPlatformServiceMixin {
|
|||||||
params?: unknown[],
|
params?: unknown[],
|
||||||
): Promise<SqlValue[] | undefined>;
|
): Promise<SqlValue[] | undefined>;
|
||||||
$dbRawQuery(sql: string, params?: unknown[]): Promise<unknown | undefined>;
|
$dbRawQuery(sql: string, params?: unknown[]): Promise<unknown | undefined>;
|
||||||
|
$getMasterSettings(fallback?: Settings | null): Promise<Settings | null>;
|
||||||
$getMergedSettings(
|
$getMergedSettings(
|
||||||
defaultKey: string,
|
defaultKey: string,
|
||||||
accountDid?: string,
|
accountDid?: string,
|
||||||
@@ -2022,6 +2017,7 @@ declare module "@vue/runtime-core" {
|
|||||||
params?: unknown[],
|
params?: unknown[],
|
||||||
): Promise<unknown[] | undefined>;
|
): Promise<unknown[] | undefined>;
|
||||||
$dbRawQuery(sql: string, params?: unknown[]): Promise<unknown | undefined>;
|
$dbRawQuery(sql: string, params?: unknown[]): Promise<unknown | undefined>;
|
||||||
|
$getMasterSettings(defaults?: Settings | null): Promise<Settings | null>;
|
||||||
$getMergedSettings(
|
$getMergedSettings(
|
||||||
key: string,
|
key: string,
|
||||||
did?: string,
|
did?: string,
|
||||||
@@ -2044,8 +2040,7 @@ declare module "@vue/runtime-core" {
|
|||||||
did: string,
|
did: string,
|
||||||
changes: Partial<Settings>,
|
changes: Partial<Settings>,
|
||||||
): Promise<boolean>;
|
): Promise<boolean>;
|
||||||
// @deprecated; see implementation note above
|
$saveMySettings(changes: Partial<Settings>): Promise<boolean>;
|
||||||
// $saveMySettings(changes: Partial<Settings>): Promise<boolean>;
|
|
||||||
|
|
||||||
// Cache management methods
|
// Cache management methods
|
||||||
$refreshSettings(): Promise<Settings>;
|
$refreshSettings(): Promise<Settings>;
|
||||||
|
|||||||
@@ -1454,6 +1454,7 @@ export default class AccountViewView extends Vue {
|
|||||||
this.imageLimits = imageResp.data;
|
this.imageLimits = imageResp.data;
|
||||||
} else {
|
} else {
|
||||||
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IMAGE_ACCESS;
|
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IMAGE_ACCESS;
|
||||||
|
this.notify.warning(ACCOUNT_VIEW_CONSTANTS.LIMITS.CANNOT_UPLOAD_IMAGES);
|
||||||
}
|
}
|
||||||
|
|
||||||
const endorserResp = await fetchEndorserRateLimits(
|
const endorserResp = await fetchEndorserRateLimits(
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ export default class ContactAmountssView extends Vue {
|
|||||||
const contact = await this.$getContact(contactDid);
|
const contact = await this.$getContact(contactDid);
|
||||||
this.contact = contact;
|
this.contact = contact;
|
||||||
|
|
||||||
const settings = await this.$settings();
|
const settings = await this.$getMasterSettings();
|
||||||
|
|
||||||
// Get activeDid from active_identity table (single source of truth)
|
// Get activeDid from active_identity table (single source of truth)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ export default class HelpNotificationsView extends Vue {
|
|||||||
notifyingReminderTime = "";
|
notifyingReminderTime = "";
|
||||||
|
|
||||||
// Notification helper system
|
// Notification helper system
|
||||||
notify!: ReturnType<typeof createNotifyHelpers>;
|
notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computed property for consistent button styling
|
* Computed property for consistent button styling
|
||||||
@@ -430,9 +430,6 @@ export default class HelpNotificationsView extends Vue {
|
|||||||
* Handles errors gracefully with proper logging without exposing sensitive data.
|
* Handles errors gracefully with proper logging without exposing sensitive data.
|
||||||
*/
|
*/
|
||||||
async mounted() {
|
async mounted() {
|
||||||
// Initialize notification helpers
|
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const registration = await navigator.serviceWorker?.ready;
|
const registration = await navigator.serviceWorker?.ready;
|
||||||
const fullSub = await registration?.pushManager.getSubscription();
|
const fullSub = await registration?.pushManager.getSubscription();
|
||||||
|
|||||||
@@ -80,12 +80,11 @@ Raymer * @version 1.0.0 */
|
|||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-8">
|
|
||||||
<!--
|
<!--
|
||||||
They should have an identifier, even if it's an auto-generated one that they'll never use.
|
They should have an identifier, even if it's an auto-generated one that they'll never use.
|
||||||
Identity creation is now handled by router navigation guard.
|
Identity creation is now handled by router navigation guard.
|
||||||
-->
|
-->
|
||||||
<div class="mb-4">
|
<div class="mb-6">
|
||||||
<RegistrationNotice
|
<RegistrationNotice
|
||||||
v-if="!isUserRegistered"
|
v-if="!isUserRegistered"
|
||||||
:passkeys-enabled="PASSKEYS_ENABLED"
|
:passkeys-enabled="PASSKEYS_ENABLED"
|
||||||
@@ -95,16 +94,16 @@ Raymer * @version 1.0.0 */
|
|||||||
|
|
||||||
<div v-if="isUserRegistered" id="sectionRecordSomethingGiven">
|
<div v-if="isUserRegistered" id="sectionRecordSomethingGiven">
|
||||||
<!-- Record Quick-Action -->
|
<!-- Record Quick-Action -->
|
||||||
<div class="mb-6">
|
<div class="bg-slate-200 rounded-lg overflow-hidden p-3 pt-2.5">
|
||||||
<div class="flex gap-2 items-center mb-2">
|
<div class="flex gap-2 items-center mb-2">
|
||||||
<h2 class="text-xl font-bold">Record something given by:</h2>
|
<h2 class="font-bold">Record something given by:</h2>
|
||||||
<button
|
<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"
|
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-1.5 rounded-full"
|
||||||
@click="openGiftedPrompts()"
|
@click="openGiftedPrompts()"
|
||||||
>
|
>
|
||||||
<font-awesome
|
<font-awesome
|
||||||
icon="lightbulb"
|
icon="lightbulb"
|
||||||
class="block text-center w-[1em]"
|
class="block text-center text-sm w-[1em]"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,7 +111,7 @@ Raymer * @version 1.0.0 */
|
|||||||
<div class="grid grid-cols-2 gap-2">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<button
|
<button
|
||||||
type="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"
|
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-md"
|
||||||
@click="openPersonDialog()"
|
@click="openPersonDialog()"
|
||||||
>
|
>
|
||||||
<font-awesome icon="user" />
|
<font-awesome icon="user" />
|
||||||
@@ -120,7 +119,7 @@ Raymer * @version 1.0.0 */
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="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"
|
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-md"
|
||||||
@click="openProjectDialog()"
|
@click="openProjectDialog()"
|
||||||
>
|
>
|
||||||
<font-awesome icon="folder-open" />
|
<font-awesome icon="folder-open" />
|
||||||
@@ -130,7 +129,6 @@ Raymer * @version 1.0.0 */
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<GiftedDialog
|
<GiftedDialog
|
||||||
ref="giftedDialog"
|
ref="giftedDialog"
|
||||||
@@ -138,74 +136,90 @@ Raymer * @version 1.0.0 */
|
|||||||
:recipient-entity-type="'person'"
|
:recipient-entity-type="'person'"
|
||||||
/>
|
/>
|
||||||
<GiftedPrompts ref="giftedPrompts" />
|
<GiftedPrompts ref="giftedPrompts" />
|
||||||
<FeedFilters ref="feedFilters" />
|
|
||||||
|
|
||||||
<!-- 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">
|
<!-- ALTERNATIVE UI: Feed + Notification Tabs -->
|
||||||
<h2 class="text-xl font-bold">Latest Activity</h2>
|
<div
|
||||||
<button
|
class="sticky top-0 z-50 grid grid-cols-5 text-xl sm:text-2xl pt-4 pb-1 px-1 -mt-3 -mx-1 mb-4 bg-white rounded-b-[10px]"
|
||||||
v-if="resultsAreFiltered()"
|
|
||||||
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"
|
|
||||||
@click="openFeedFilters()"
|
|
||||||
>
|
>
|
||||||
<font-awesome
|
<button
|
||||||
icon="filter"
|
class="relative text-center bg-slate-400 text-white px-1 pt-3 pb-2 first:rounded-s-md last:rounded-e-md border-r border-slate-300 last:border-r-0 leading-none"
|
||||||
class="block text-center w-[1em] translate-y-[0.05em]"
|
>
|
||||||
/>
|
<font-awesome icon="scroll" />
|
||||||
|
<div class="text-xs sm:text-sm mt-1">activity</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
class="relative text-center bg-slate-200 text-slate-500 px-1 pt-3 pb-2 first:rounded-s-md last:rounded-e-md border-r border-slate-300 last:border-r-0 leading-none"
|
||||||
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="hand-holding-heart" />
|
||||||
|
<div class="text-xs sm:text-sm mt-1">offers</div>
|
||||||
|
|
||||||
|
<!-- Unread count -->
|
||||||
|
<span
|
||||||
|
class="absolute -top-2 -translate-x-1/2 bg-rose-500 text-white border border-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em]"
|
||||||
|
>2</span
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="relative text-center bg-slate-200 text-slate-500 px-1 pt-3 pb-2 first:rounded-s-md last:rounded-e-md border-r border-slate-300 last:border-r-0 leading-none"
|
||||||
|
>
|
||||||
|
<font-awesome icon="folder-open" />
|
||||||
|
<div class="text-xs sm:text-sm mt-1">projects</div>
|
||||||
|
|
||||||
|
<!-- Unread count -->
|
||||||
|
<span
|
||||||
|
class="absolute -top-2 -translate-x-1/2 bg-rose-500 text-white border border-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em]"
|
||||||
|
>50+</span
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="relative text-center bg-slate-200 text-slate-500 px-1 pt-3 pb-2 first:rounded-s-md last:rounded-e-md border-r border-slate-300 last:border-r-0 leading-none"
|
||||||
|
>
|
||||||
|
<font-awesome icon="users" />
|
||||||
|
<div class="text-xs sm:text-sm mt-1">people</div>
|
||||||
|
|
||||||
|
<!-- Unread count -->
|
||||||
|
<span
|
||||||
|
class="absolute -top-2 -translate-x-1/2 bg-rose-500 text-white border border-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em]"
|
||||||
|
>4</span
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="relative text-center bg-slate-200 text-slate-500 px-1 pt-3 pb-2 first:rounded-s-md last:rounded-e-md border-r border-slate-300 last:border-r-0 leading-none"
|
||||||
|
>
|
||||||
|
<font-awesome icon="image" />
|
||||||
|
<div class="text-xs sm:text-sm mt-1">items</div>
|
||||||
|
|
||||||
|
<!-- Unread count -->
|
||||||
|
<span
|
||||||
|
class="absolute -top-2 -translate-x-1/2 bg-rose-500 text-white border border-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em]"
|
||||||
|
>7</span
|
||||||
>
|
>
|
||||||
<font-awesome
|
|
||||||
icon="filter"
|
|
||||||
class="block text-center w-[1em] translate-y-[0.05em]"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="flex gap-2 items-center justify-between mb-2 text-sm">
|
||||||
class="border-t p-2 border-slate-300"
|
<h2 class="text-base font-bold">Latest Activity</h2>
|
||||||
@click="goToActivityToUserPage()"
|
<button
|
||||||
|
v-if="resultsAreFiltered()"
|
||||||
|
class="flex items-center justify-end gap-2 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1 rounded"
|
||||||
|
@click="openFeedFilters()"
|
||||||
>
|
>
|
||||||
<div class="flex justify-center">
|
Filter
|
||||||
<div
|
<font-awesome icon="filter"></font-awesome>
|
||||||
v-if="numNewOffersToUser"
|
</button>
|
||||||
class="bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] m-1 px-4 py-4 rounded-md text-white"
|
<button
|
||||||
|
v-else
|
||||||
|
class="flex items-center justify-end gap-2 bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1 rounded"
|
||||||
|
@click="openFeedFilters()"
|
||||||
>
|
>
|
||||||
<span
|
Filter
|
||||||
class="block text-center text-6xl"
|
<font-awesome icon="filter"></font-awesome>
|
||||||
data-testId="newDirectOffersActivityNumber"
|
</button>
|
||||||
>
|
|
||||||
{{ numNewOffersToUser }}{{ newOffersToUserHitLimit ? "+" : "" }}
|
|
||||||
</span>
|
|
||||||
<p class="text-center">
|
|
||||||
new offer{{ numNewOffersToUser === 1 ? "" : "s" }} to you
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="numNewOffersToUserProjects"
|
|
||||||
class="bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] m-1 px-4 py-4 rounded-md text-white"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="block text-center text-6xl"
|
|
||||||
data-testId="newOffersToUserProjectsActivityNumber"
|
|
||||||
>
|
|
||||||
{{ numNewOffersToUserProjects
|
|
||||||
}}{{ newOffersToUserProjectsHitLimit ? "+" : "" }}
|
|
||||||
</span>
|
|
||||||
<p class="text-center">
|
|
||||||
new offer{{ numNewOffersToUserProjects === 1 ? "" : "s" }} to your
|
|
||||||
projects
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-end mt-2">
|
|
||||||
<button class="text-blue-500">View All New Activity For You</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<FeedFilters ref="feedFilters" />
|
||||||
|
|
||||||
<InfiniteScroll @reached-bottom="loadMoreGives">
|
<InfiniteScroll @reached-bottom="loadMoreGives">
|
||||||
<ul id="listLatestActivity" class="space-y-4">
|
<ul id="listLatestActivity" class="space-y-4">
|
||||||
<ActivityListItem
|
<ActivityListItem
|
||||||
@@ -645,9 +659,7 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
// Ultra-concise settings update with automatic cache invalidation!
|
// Ultra-concise settings update with automatic cache invalidation!
|
||||||
await this.$saveUserSettings(this.activeDid, {
|
await this.$saveMySettings({ isRegistered: true });
|
||||||
isRegistered: true,
|
|
||||||
});
|
|
||||||
this.isRegistered = true;
|
this.isRegistered = true;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -306,9 +306,6 @@ export default class IdentitySwitcherView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.$exec("DELETE FROM accounts WHERE id = ?", [id]);
|
await this.$exec("DELETE FROM accounts WHERE id = ?", [id]);
|
||||||
await this.$exec("DELETE FROM settings WHERE accountDid = ?", [
|
|
||||||
accountDid,
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
|
|||||||
@@ -3,108 +3,198 @@
|
|||||||
<!-- 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 -->
|
<!-- Breadcrumb -->
|
||||||
<div id="ViewBreadcrumb" class="mb-8">
|
<div class="mb-2">
|
||||||
<h1 class="text-lg text-center font-light relative px-7">
|
<h1 class="text-2xl text-center font-semibold relative px-7">
|
||||||
<!-- Back -->
|
<!-- Back -->
|
||||||
<font-awesome
|
<font-awesome
|
||||||
icon="chevron-left"
|
icon="chevron-left"
|
||||||
class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
class="fa-fw text-lg text-center px-2 py-1 absolute -left-2 top-[0.2em]"
|
||||||
@click="$router.back()"
|
@click="$router.back()"
|
||||||
/>
|
/>
|
||||||
New Activity For You
|
|
||||||
|
Notifications
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Display a single row with the name of "New Offers To You" with a count. -->
|
<!-- Main Tabs -->
|
||||||
<div class="flex justify-between" data-testId="showOffersToUser">
|
<div class="text-center text-slate-500 border-b border-slate-300 mt-4 mb-2">
|
||||||
<div>
|
<ul class="flex flex-wrap justify-center gap-4 -mb-px">
|
||||||
<span class="text-lg font-medium"
|
<li class="flex items-center gap-[0.175em]">
|
||||||
>{{ newOffersToUser.length
|
<a
|
||||||
}}{{ newOffersToUserHitLimit ? "+" : "" }}</span
|
href="#"
|
||||||
|
class="inline-block py-2 rounded-t-lg border-b-2 active text-black border-black font-semibold"
|
||||||
>
|
>
|
||||||
<span class="text-lg font-medium ml-4"
|
Offers
|
||||||
>New Offer{{ newOffersToUser.length === 1 ? "" : "s" }} To You</span
|
|
||||||
>
|
|
||||||
<font-awesome
|
|
||||||
v-if="newOffersToUser.length > 0"
|
|
||||||
:icon="showOffersDetails ? 'chevron-down' : 'chevron-right'"
|
|
||||||
class="cursor-pointer ml-4 mr-4 text-lg"
|
|
||||||
@click="expandOffersToUserAndMarkRead()"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<a class="text-blue-500 cursor-pointer" @click="handleSeeAllOffersToUser">
|
|
||||||
See all
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="showOffersDetails" class="ml-4 mt-4">
|
<!-- Unread count -->
|
||||||
<ul class="list-disc ml-4">
|
<span
|
||||||
<li
|
class="inline-block bg-rose-500 text-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em] -me-1.5 -mt-[2px]"
|
||||||
v-for="offer in newOffersToUser"
|
>3</span
|
||||||
:key="offer.jwtId"
|
|
||||||
class="mt-4 relative group"
|
|
||||||
>
|
>
|
||||||
<span>{{
|
</li>
|
||||||
didInfo(offer.offeredByDid, activeDid, allMyDids, allContacts)
|
<li class="flex items-center gap-[0.175em]">
|
||||||
}}</span>
|
<a
|
||||||
offered
|
href="#"
|
||||||
<span v-if="offer.objectDescription">{{
|
class="inline-block py-2 rounded-t-lg border-b-2 text-blue-600 border-transparent hover:border-slate-400"
|
||||||
offer.objectDescription
|
|
||||||
}}</span
|
|
||||||
>{{ offer.objectDescription && offer.amount ? ", and " : "" }}
|
|
||||||
<span v-if="offer.amount">{{
|
|
||||||
displayAmount(offer.unit, offer.amount)
|
|
||||||
}}</span>
|
|
||||||
<router-link
|
|
||||||
:to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }"
|
|
||||||
class="text-blue-500"
|
|
||||||
>
|
>
|
||||||
<font-awesome
|
Projects
|
||||||
icon="file-lines"
|
</a>
|
||||||
class="pl-2 text-blue-500 cursor-pointer"
|
|
||||||
/>
|
<!-- Unread count -->
|
||||||
</router-link>
|
<span
|
||||||
<!-- New line that appears on hover or when the offer is clicked -->
|
class="inline-block bg-rose-500 text-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em] -me-1.5 -mt-[2px]"
|
||||||
<div
|
>9+</span
|
||||||
class="absolute left-0 w-full text-left text-gray-500 text-sm hidden group-hover:flex cursor-pointer items-center"
|
|
||||||
@click="markOffersAsReadStartingWith(offer.jwtId)"
|
|
||||||
>
|
>
|
||||||
<span class="inline-block w-8 h-px bg-gray-500 mr-2" />
|
</li>
|
||||||
Click to keep all above as new offers
|
<li class="flex items-center gap-[0.175em]">
|
||||||
</div>
|
<a
|
||||||
|
href="#"
|
||||||
|
class="inline-block py-2 rounded-t-lg border-b-2 text-blue-600 border-transparent hover:border-slate-400"
|
||||||
|
>
|
||||||
|
People
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-[0.175em]">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="inline-block py-2 rounded-t-lg border-b-2 text-blue-600 border-transparent hover:border-slate-400"
|
||||||
|
>
|
||||||
|
Items
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Display a single row with the name of "New Offers To Your Projects" with a count. -->
|
<!-- Sub Tabs - Offers -->
|
||||||
<div
|
<div class="text-center text-slate-500 border-b border-slate-300 mb-2">
|
||||||
class="mt-4 flex justify-between"
|
<ul class="flex flex-wrap justify-center gap-4 text-sm -mb-px">
|
||||||
data-testId="showOffersToUserProjects"
|
<li class="flex items-center gap-[0.175em]">
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<span class="text-lg font-medium"
|
|
||||||
>{{ newOffersToUserProjects.length
|
|
||||||
}}{{ newOffersToUserProjectsHitLimit ? "+" : "" }}</span
|
|
||||||
>
|
|
||||||
<span class="text-lg font-medium ml-4"
|
|
||||||
>New Offer{{ newOffersToUserProjects.length === 1 ? "" : "s" }} To
|
|
||||||
Your Projects</span
|
|
||||||
>
|
|
||||||
<font-awesome
|
|
||||||
v-if="newOffersToUserProjects.length > 0"
|
|
||||||
:icon="
|
|
||||||
showOffersToUserProjectsDetails ? 'chevron-down' : 'chevron-right'
|
|
||||||
"
|
|
||||||
class="cursor-pointer ml-4 mr-4 text-lg"
|
|
||||||
@click="expandOffersToUserProjectsAndMarkRead()"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<a
|
<a
|
||||||
class="text-blue-500 cursor-pointer"
|
href="#"
|
||||||
@click="handleSeeAllOffersToUserProjects"
|
class="inline-block py-2 rounded-t-lg border-b-2 active text-black border-black font-semibold"
|
||||||
>
|
>
|
||||||
See all
|
To You
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<!-- Unread count -->
|
||||||
|
<span
|
||||||
|
class="inline-block bg-rose-500 text-white text-xs font-semibold leading-none rounded-full px-[0.4em] py-[0.15em] -me-1.5 -mt-[2px]"
|
||||||
|
>2</span
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-[0.175em]">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="inline-block py-2 rounded-t-lg border-b-2 text-blue-600 border-transparent hover:border-slate-400"
|
||||||
|
>
|
||||||
|
Your Projects
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-[0.175em]">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="inline-block py-2 rounded-t-lg border-b-2 text-blue-600 border-transparent hover:border-slate-400"
|
||||||
|
>
|
||||||
|
Favorites
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Offers to You -->
|
||||||
|
<div v-if="showOffersDetails" class="mt-4">
|
||||||
|
<div class="flex justify-end items-center text-sm mb-2">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="flex items-center justify-end gap-2 bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1 rounded"
|
||||||
|
>
|
||||||
|
Mark all as read
|
||||||
|
<font-awesome icon="check"></font-awesome>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<ul class="text-sm border-t border-slate-300">
|
||||||
|
<li
|
||||||
|
v-for="offer in newOffersToUser"
|
||||||
|
:key="offer.jwtId"
|
||||||
|
class="flex justify-between items-center gap-4 border-b border-slate-300 py-2"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
:to="{ path: '/claim/' + encodeURIComponent(offer.jwtId) }"
|
||||||
|
class="block"
|
||||||
|
>
|
||||||
|
<span class="font-semibold">{{
|
||||||
|
didInfo(offer.offeredByDid, activeDid, allMyDids, allContacts)
|
||||||
|
}}</span>
|
||||||
|
offered
|
||||||
|
<span
|
||||||
|
v-if="offer.objectDescription"
|
||||||
|
class="font-semibold text-blue-600"
|
||||||
|
>{{ offer.objectDescription }}</span
|
||||||
|
>{{ offer.objectDescription && offer.amount ? " and " : "" }}
|
||||||
|
<span v-if="offer.amount" class="font-semibold text-blue-600">{{
|
||||||
|
displayAmount(offer.unit, offer.amount)
|
||||||
|
}}</span>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<!-- Unread indicator -->
|
||||||
|
<font-awesome
|
||||||
|
icon="circle"
|
||||||
|
class="text-rose-500 text-[8px] border border-rose-500 rounded-full"
|
||||||
|
></font-awesome>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Sample read item -->
|
||||||
|
<li class="border-b border-slate-300 py-2">
|
||||||
|
<!-- Last viewed separator -->
|
||||||
|
<div
|
||||||
|
class="border-t border-dashed border-slate-300 text-orange-400 mt-4 mb-2 font-bold text-sm"
|
||||||
|
>
|
||||||
|
<span class="block w-fit mx-auto -mt-2.5 bg-white px-2">
|
||||||
|
You've already seen all the following
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<hr class="border-slate-300 mt-4" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center gap-4">
|
||||||
|
<!-- Notification details -->
|
||||||
|
<a href="#" class="block text-slate-400">
|
||||||
|
<span class="font-semibold">User One</span>
|
||||||
|
offered
|
||||||
|
<span class="font-semibold">Sample read notification item</span>
|
||||||
|
and
|
||||||
|
<span class="font-semibold">50 USD</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Read indicator -->
|
||||||
|
<font-awesome
|
||||||
|
icon="circle"
|
||||||
|
class="text-transparent text-[8px] border border-slate-300 rounded-full"
|
||||||
|
></font-awesome>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Sample read item -->
|
||||||
|
<li class="border-b border-slate-300 py-2">
|
||||||
|
<div class="flex justify-between items-center gap-4">
|
||||||
|
<!-- Notification details -->
|
||||||
|
<a href="#" class="block text-slate-400">
|
||||||
|
<span class="font-semibold">User One</span>
|
||||||
|
offered
|
||||||
|
<span class="font-semibold">Sample read notification item</span>
|
||||||
|
and
|
||||||
|
<span class="font-semibold">50 USD</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Read indicator -->
|
||||||
|
<font-awesome
|
||||||
|
icon="circle"
|
||||||
|
class="text-transparent text-[8px] border border-slate-300 rounded-full"
|
||||||
|
></font-awesome>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="showOffersToUserProjectsDetails" class="ml-4 mt-4">
|
<div v-if="showOffersToUserProjectsDetails" class="ml-4 mt-4">
|
||||||
@@ -194,7 +284,7 @@ export default class NewActivityView extends Vue {
|
|||||||
newOffersToUserProjects: Array<OfferToPlanSummaryRecord> = [];
|
newOffersToUserProjects: Array<OfferToPlanSummaryRecord> = [];
|
||||||
newOffersToUserProjectsHitLimit = false;
|
newOffersToUserProjectsHitLimit = false;
|
||||||
|
|
||||||
showOffersDetails = false;
|
showOffersDetails = true;
|
||||||
showOffersToUserProjectsDetails = false;
|
showOffersToUserProjectsDetails = false;
|
||||||
didInfo = didInfo;
|
didInfo = didInfo;
|
||||||
displayAmount = displayAmount;
|
displayAmount = displayAmount;
|
||||||
@@ -249,7 +339,7 @@ export default class NewActivityView extends Vue {
|
|||||||
|
|
||||||
async expandOffersToUserAndMarkRead() {
|
async expandOffersToUserAndMarkRead() {
|
||||||
this.showOffersDetails = !this.showOffersDetails;
|
this.showOffersDetails = !this.showOffersDetails;
|
||||||
if (this.showOffersDetails && this.newOffersToUser.length > 0) {
|
if (this.showOffersDetails) {
|
||||||
await this.$updateSettings({
|
await this.$updateSettings({
|
||||||
lastAckedOfferToUserJwtId: this.newOffersToUser[0].jwtId,
|
lastAckedOfferToUserJwtId: this.newOffersToUser[0].jwtId,
|
||||||
});
|
});
|
||||||
@@ -286,10 +376,7 @@ export default class NewActivityView extends Vue {
|
|||||||
async expandOffersToUserProjectsAndMarkRead() {
|
async expandOffersToUserProjectsAndMarkRead() {
|
||||||
this.showOffersToUserProjectsDetails =
|
this.showOffersToUserProjectsDetails =
|
||||||
!this.showOffersToUserProjectsDetails;
|
!this.showOffersToUserProjectsDetails;
|
||||||
if (
|
if (this.showOffersToUserProjectsDetails) {
|
||||||
this.showOffersToUserProjectsDetails &&
|
|
||||||
this.newOffersToUserProjects.length > 0
|
|
||||||
) {
|
|
||||||
await this.$updateSettings({
|
await this.$updateSettings({
|
||||||
lastAckedOfferToUserProjectsJwtId:
|
lastAckedOfferToUserProjectsJwtId:
|
||||||
this.newOffersToUserProjects[0].jwtId,
|
this.newOffersToUserProjects[0].jwtId,
|
||||||
@@ -297,7 +384,7 @@ export default class NewActivityView extends Vue {
|
|||||||
// note that we don't update this.lastAckedOfferToUserProjectsJwtId in case
|
// note that we don't update this.lastAckedOfferToUserProjectsJwtId in case
|
||||||
// they later choose the last one to keep the offers as new
|
// they later choose the last one to keep the offers as new
|
||||||
this.notify.info(
|
this.notify.info(
|
||||||
"The offers are marked as viewed. Click in the list to keep them as new.",
|
"The offers are now marked as viewed. Click in the list to keep them as new.",
|
||||||
TIMEOUTS.LONG,
|
TIMEOUTS.LONG,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -325,13 +412,5 @@ export default class NewActivityView extends Vue {
|
|||||||
TIMEOUTS.STANDARD,
|
TIMEOUTS.STANDARD,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSeeAllOffersToUser() {
|
|
||||||
this.$router.push("/recent-offers-to-user");
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleSeeAllOffersToUserProjects() {
|
|
||||||
this.$router.push("/recent-offers-to-user-projects");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
|||||||
$router!: Router;
|
$router!: Router;
|
||||||
|
|
||||||
// Notification helper system
|
// Notification helper system
|
||||||
private notify!: ReturnType<typeof createNotifyHelpers>;
|
private notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
attended = true;
|
attended = true;
|
||||||
gaveTime = true;
|
gaveTime = true;
|
||||||
@@ -111,9 +111,6 @@ export default class QuickActionBvcBeginView extends Vue {
|
|||||||
* Uses America/Denver timezone for Bountiful location
|
* Uses America/Denver timezone for Bountiful location
|
||||||
*/
|
*/
|
||||||
async mounted() {
|
async mounted() {
|
||||||
// Initialize notification helpers
|
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"[QuickActionBvcBeginView] Mounted - calculating meeting date",
|
"[QuickActionBvcBeginView] Mounted - calculating meeting date",
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,20 +32,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InfiniteScroll @reached-bottom="loadMoreOffersToUserProjects">
|
<InfiniteScroll @reached-bottom="loadMoreOffersToUserProjects">
|
||||||
<ul data-testId="listRecentOffersToUserProjects">
|
<ul
|
||||||
|
data-testId="listRecentOffersToUserProjects"
|
||||||
|
class="border-t border-slate-300"
|
||||||
|
>
|
||||||
<li
|
<li
|
||||||
v-for="offer in newOffersToUserProjects"
|
v-for="offer in newOffersToUserProjects"
|
||||||
:key="offer.jwtId"
|
:key="offer.jwtId"
|
||||||
class="mt-4 relative group"
|
class="mt-4 relative group"
|
||||||
>
|
>
|
||||||
<!-- Last viewed separator -->
|
|
||||||
<div
|
<div
|
||||||
v-if="offer.jwtId == lastAckedOfferToUserProjectsJwtId"
|
v-if="offer.jwtId == lastAckedOfferToUserProjectsJwtId"
|
||||||
class="border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-6 font-bold text-sm"
|
class="border-b border-slate-300 text-orange-400 pb-2 mb-2 font-bold text-sm"
|
||||||
>
|
>
|
||||||
<span class="block w-fit mx-auto -mb-2.5 bg-white px-2">
|
|
||||||
You've already seen all the following
|
You've already seen all the following
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span>{{
|
<span>{{
|
||||||
@@ -147,14 +147,6 @@ export default class RecentOffersToUserView extends Vue {
|
|||||||
this.newOffersToUserProjects = offersToUserProjectsData.data;
|
this.newOffersToUserProjects = offersToUserProjectsData.data;
|
||||||
this.newOffersToUserProjectsAtEnd = !offersToUserProjectsData.hitLimit;
|
this.newOffersToUserProjectsAtEnd = !offersToUserProjectsData.hitLimit;
|
||||||
|
|
||||||
// Mark offers as read after data is loaded
|
|
||||||
if (this.newOffersToUserProjects.length > 0) {
|
|
||||||
await this.$updateSettings({
|
|
||||||
lastAckedOfferToUserProjectsJwtId:
|
|
||||||
this.newOffersToUserProjects[0].jwtId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
||||||
|
|||||||
@@ -27,20 +27,20 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<InfiniteScroll @reached-bottom="loadMoreOffersToUser">
|
<InfiniteScroll @reached-bottom="loadMoreOffersToUser">
|
||||||
<ul data-testId="listRecentOffersToUser">
|
<ul
|
||||||
|
data-testId="listRecentOffersToUser"
|
||||||
|
class="border-t border-slate-300"
|
||||||
|
>
|
||||||
<li
|
<li
|
||||||
v-for="offer in newOffersToUser"
|
v-for="offer in newOffersToUser"
|
||||||
:key="offer.jwtId"
|
:key="offer.jwtId"
|
||||||
class="mt-4 relative group"
|
class="mt-4 relative group"
|
||||||
>
|
>
|
||||||
<!-- Last viewed separator -->
|
|
||||||
<div
|
<div
|
||||||
v-if="offer.jwtId == lastAckedOfferToUserJwtId"
|
v-if="offer.jwtId == lastAckedOfferToUserJwtId"
|
||||||
class="border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-6 font-bold text-sm"
|
class="border-b border-slate-300 text-orange-400 pb-2 mb-2 font-bold text-sm"
|
||||||
>
|
>
|
||||||
<span class="block w-fit mx-auto -mb-2.5 bg-white px-2">
|
|
||||||
You've already seen all the following
|
You've already seen all the following
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span>{{
|
<span>{{
|
||||||
@@ -138,13 +138,6 @@ export default class RecentOffersToUserView extends Vue {
|
|||||||
this.newOffersToUser = offersToUserData.data;
|
this.newOffersToUser = offersToUserData.data;
|
||||||
this.newOffersToUserAtEnd = !offersToUserData.hitLimit;
|
this.newOffersToUserAtEnd = !offersToUserData.hitLimit;
|
||||||
|
|
||||||
// Mark offers as read after data is loaded
|
|
||||||
if (this.newOffersToUser.length > 0) {
|
|
||||||
await this.$updateSettings({
|
|
||||||
lastAckedOfferToUserJwtId: this.newOffersToUser[0].jwtId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export default class SeedBackupView extends Vue {
|
|||||||
showSeed = false;
|
showSeed = false;
|
||||||
|
|
||||||
// Notification helper system
|
// Notification helper system
|
||||||
notify!: ReturnType<typeof createNotifyHelpers>;
|
notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computed property for consistent copy feedback styling
|
* Computed property for consistent copy feedback styling
|
||||||
@@ -204,9 +204,6 @@ export default class SeedBackupView extends Vue {
|
|||||||
* Handles errors gracefully with user notifications.
|
* Handles errors gracefully with user notifications.
|
||||||
*/
|
*/
|
||||||
async created() {
|
async created() {
|
||||||
// Initialize notification helpers
|
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let activeDid = "";
|
let activeDid = "";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user