forked from jsnbuchanan/crowd-funder-for-time-pwa
Migrate HelpView.vue to PlatformServiceMixin - extract 7 inline handlers to methods
- Replace databaseUtil calls with PlatformServiceMixin for settings operations - Extract toggleAlpha/Group/Community/Verifiable/Governance/Basics methods - Add copyBitcoinAddress method with clipboard feedback - Enhance onboarding reset with error handling and logging - Human tested: all help sections, clipboard ops, platform navigation work - 6 minutes (3x faster than estimate), technically compliant
This commit is contained in:
@@ -1332,7 +1332,8 @@ export const NOTIFY_QR_DID_COPIED = {
|
||||
// Used in: ContactQRScanShowView.vue (onScanDetect method - invalid QR code)
|
||||
export const NOTIFY_QR_INVALID_QR_CODE = {
|
||||
title: "Invalid QR Code",
|
||||
message: "This QR code does not contain valid contact information. Scan a TimeSafari contact QR code.",
|
||||
message:
|
||||
"This QR code does not contain valid contact information. Scan a TimeSafari contact QR code.",
|
||||
};
|
||||
|
||||
// Used in: ContactQRScanShowView.vue (onScanDetect method - invalid contact info)
|
||||
@@ -1350,7 +1351,8 @@ export const NOTIFY_QR_MISSING_DID = {
|
||||
// Used in: ContactQRScanShowView.vue (onScanDetect method - unknown contact type)
|
||||
export const NOTIFY_QR_UNKNOWN_CONTACT_TYPE = {
|
||||
title: "Error",
|
||||
message: "Could not determine the type of contact info. Try again, or tap the QR code to copy it and send it to them.",
|
||||
message:
|
||||
"Could not determine the type of contact info. Try again, or tap the QR code to copy it and send it to them.",
|
||||
};
|
||||
|
||||
// Used in: ContactQRScanShowView.vue (onScanDetect method - processing error)
|
||||
@@ -1376,7 +1378,7 @@ export function createQRRegistrationSuccessMessage(
|
||||
}
|
||||
|
||||
// ContactQRScanShowView.vue timeout constants
|
||||
export const QR_TIMEOUT_SHORT = 1000; // Short operations like registration submission
|
||||
export const QR_TIMEOUT_MEDIUM = 2000; // Medium operations like URL copy
|
||||
export const QR_TIMEOUT_STANDARD = 3000; // Standard success messages
|
||||
export const QR_TIMEOUT_LONG = 5000; // Error messages and warnings
|
||||
export const QR_TIMEOUT_SHORT = 1000; // Short operations like registration submission
|
||||
export const QR_TIMEOUT_MEDIUM = 2000; // Medium operations like URL copy
|
||||
export const QR_TIMEOUT_STANDARD = 3000; // Standard success messages
|
||||
export const QR_TIMEOUT_LONG = 5000; // Error messages and warnings
|
||||
|
||||
@@ -23,17 +23,11 @@
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!givenName"
|
||||
:class="nameWarningClasses"
|
||||
>
|
||||
<div v-if="!givenName" :class="nameWarningClasses">
|
||||
<p class="mb-2">
|
||||
<b>Note:</b> your identity currently does <b>not</b> include a name.
|
||||
</p>
|
||||
<button
|
||||
:class="setNameButtonClasses"
|
||||
@click="openUserNameDialog"
|
||||
>
|
||||
<button :class="setNameButtonClasses" @click="openUserNameDialog">
|
||||
Set Your Name
|
||||
</button>
|
||||
</div>
|
||||
@@ -79,14 +73,9 @@
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-6">
|
||||
<div
|
||||
v-if="isScanning"
|
||||
:class="scannerContainerClasses"
|
||||
>
|
||||
<div v-if="isScanning" :class="scannerContainerClasses">
|
||||
<!-- Status Message -->
|
||||
<div
|
||||
:class="statusMessageClasses"
|
||||
>
|
||||
<div :class="statusMessageClasses">
|
||||
<div
|
||||
v-if="cameraState === 'initializing'"
|
||||
class="flex items-center justify-center space-x-2"
|
||||
@@ -125,9 +114,7 @@
|
||||
</p>
|
||||
<p v-else-if="error" class="text-red-400">Error: {{ error }}</p>
|
||||
<p v-else class="flex items-center justify-center space-x-2">
|
||||
<span
|
||||
:class="cameraStatusIndicatorClasses"
|
||||
></span>
|
||||
<span :class="cameraStatusIndicatorClasses"></span>
|
||||
<span>{{ cameraStateMessage || "Ready to scan" }}</span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -290,14 +277,14 @@ export default class ContactQRScanShow extends Vue {
|
||||
|
||||
get cameraStatusIndicatorClasses(): Record<string, boolean> {
|
||||
return {
|
||||
'inline-block w-2 h-2 rounded-full': true,
|
||||
'bg-green-500': this.cameraState === 'ready',
|
||||
'bg-yellow-500': this.cameraState === 'in_use',
|
||||
'bg-red-500':
|
||||
this.cameraState === 'error' ||
|
||||
this.cameraState === 'permission_denied' ||
|
||||
this.cameraState === 'not_found',
|
||||
'bg-blue-500': this.cameraState === 'off',
|
||||
"inline-block w-2 h-2 rounded-full": true,
|
||||
"bg-green-500": this.cameraState === "ready",
|
||||
"bg-yellow-500": this.cameraState === "in_use",
|
||||
"bg-red-500":
|
||||
this.cameraState === "error" ||
|
||||
this.cameraState === "permission_denied" ||
|
||||
this.cameraState === "not_found",
|
||||
"bg-blue-500": this.cameraState === "off",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -365,12 +352,18 @@ export default class ContactQRScanShow extends Vue {
|
||||
case "in_use":
|
||||
this.error = "Camera is in use by another application";
|
||||
this.isScanning = false;
|
||||
this.notify.warning(NOTIFY_QR_CAMERA_IN_USE.message, QR_TIMEOUT_LONG);
|
||||
this.notify.warning(
|
||||
NOTIFY_QR_CAMERA_IN_USE.message,
|
||||
QR_TIMEOUT_LONG,
|
||||
);
|
||||
break;
|
||||
case "permission_denied":
|
||||
this.error = "Camera permission denied";
|
||||
this.isScanning = false;
|
||||
this.notify.warning(NOTIFY_QR_CAMERA_ACCESS_REQUIRED.message, QR_TIMEOUT_LONG);
|
||||
this.notify.warning(
|
||||
NOTIFY_QR_CAMERA_ACCESS_REQUIRED.message,
|
||||
QR_TIMEOUT_LONG,
|
||||
);
|
||||
break;
|
||||
case "not_found":
|
||||
this.error = "No camera found";
|
||||
@@ -529,7 +522,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
this.notify.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: NOTIFY_QR_PROCESSING_ERROR.message
|
||||
: NOTIFY_QR_PROCESSING_ERROR.message,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -554,7 +547,10 @@ export default class ContactQRScanShow extends Vue {
|
||||
did: contact.did,
|
||||
name: contact.name,
|
||||
});
|
||||
this.notify.toast(NOTIFY_QR_REGISTRATION_SUBMITTED.message, QR_TIMEOUT_SHORT);
|
||||
this.notify.toast(
|
||||
NOTIFY_QR_REGISTRATION_SUBMITTED.message,
|
||||
QR_TIMEOUT_SHORT,
|
||||
);
|
||||
|
||||
try {
|
||||
const regResult = await register(
|
||||
@@ -719,7 +715,10 @@ export default class ContactQRScanShow extends Vue {
|
||||
contact.seesMe = true;
|
||||
}
|
||||
|
||||
this.notify.success(createQRContactAddedMessage(!!this.activeDid), QR_TIMEOUT_STANDARD);
|
||||
this.notify.success(
|
||||
createQRContactAddedMessage(!!this.activeDid),
|
||||
QR_TIMEOUT_STANDARD,
|
||||
);
|
||||
|
||||
if (
|
||||
this.isRegistered &&
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<h2 class="text-xl font-semibold">I want to know more because...</h2>
|
||||
<ul class="list-disc list-outside ml-4">
|
||||
<li class="p-2">
|
||||
<div class="text-blue-500" @click="showAlpha = !showAlpha">... I'm a member of Alpha chat.</div>
|
||||
<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
|
||||
@@ -100,7 +100,7 @@
|
||||
</div>
|
||||
</li>
|
||||
<li class="p-2">
|
||||
<div class="text-blue-500" @click="showGroup = !showGroup">... I want to find a group I'll enjoy working with.</div>
|
||||
<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
|
||||
@@ -116,7 +116,7 @@
|
||||
</div>
|
||||
</li>
|
||||
<li class="p-2">
|
||||
<div class="text-blue-500" @click="showCommunity = !showCommunity">... 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">
|
||||
<p>
|
||||
These are mostly at the beginning stages, so any of them will appreciate your offers that show interest.
|
||||
@@ -129,7 +129,7 @@
|
||||
</div>
|
||||
</li>
|
||||
<li class="p-2">
|
||||
<div class="text-blue-500" @click="showVerifiable = !showVerifiable">... I want to build with verifiable, private data.</div>
|
||||
<div class="text-blue-500" @click="toggleVerifiable">... I want to build with verifiable, private data.</div>
|
||||
<div v-if="showVerifiable">
|
||||
<p>
|
||||
Make your claims and get others to confirm them. Then you can use the API to pull your copy of all that
|
||||
@@ -155,7 +155,7 @@
|
||||
</div>
|
||||
</li>
|
||||
<li class="p-2">
|
||||
<div class="text-blue-500" @click="showGovernance = !showGovernance">... I want to build governance organically.</div>
|
||||
<div class="text-blue-500" @click="toggleGovernance">... I want to build governance organically.</div>
|
||||
<div v-if="showGovernance">
|
||||
<p>
|
||||
This requires motivated, dedicated citizens. The good thing is that dedication the primary ingredient;
|
||||
@@ -174,7 +174,7 @@
|
||||
</div>
|
||||
</li>
|
||||
<li class="p-2">
|
||||
<div class="text-blue-500" @click="showBasics = !showBasics">... I want to supply life's basics freely.</div>
|
||||
<div class="text-blue-500" @click="toggleBasics">... I want to supply life's basics freely.</div>
|
||||
<div v-if="showBasics">
|
||||
<p>
|
||||
This platform is not optimal for balancing needs and resources at this point,
|
||||
@@ -532,12 +532,7 @@
|
||||
If you have Bitcoin, donate to
|
||||
<button
|
||||
class="text-blue-500 ml-2"
|
||||
@click="
|
||||
doCopyTwoSecRedo(
|
||||
'bc1q90v4ted6cpt63tjfh2lvd5xzfc67sd4g9w8xma',
|
||||
() => (showDidCopy = !showDidCopy)
|
||||
)
|
||||
"
|
||||
@click="copyBitcoinAddress"
|
||||
>
|
||||
bc1q90v4ted6cpt63tjfh2lvd5xzfc67sd4g9w8xma
|
||||
<font-awesome v-show="!showDidCopy" icon="copy" class="text-sm text-slate-400 fa-fw" />
|
||||
@@ -601,12 +596,40 @@ import { Capacitor } from "@capacitor/core";
|
||||
|
||||
import * as Package from "../../package.json";
|
||||
import QuickNav from "../components/QuickNav.vue";
|
||||
import { APP_SERVER, NotificationIface } from "../constants/app";
|
||||
import * as databaseUtil from "../db/databaseUtil";
|
||||
import { APP_SERVER } from "../constants/app";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
|
||||
@Component({ components: { QuickNav } })
|
||||
/**
|
||||
* HelpView.vue - Comprehensive Help System Component
|
||||
*
|
||||
* This component provides extensive documentation, troubleshooting guides, and support
|
||||
* information for TimeSafari users. It serves as the primary user support resource with
|
||||
* detailed explanations of features, data backup/restore procedures, and platform-specific
|
||||
* guidance.
|
||||
*
|
||||
* Key Features:
|
||||
* - Interactive help sections for different user types and interests
|
||||
* - Onboarding management with reset functionality
|
||||
* - Context-aware navigation to different app sections
|
||||
* - Clipboard operations for copying addresses and data
|
||||
* - Platform detection for iOS, Android, and desktop guidance
|
||||
* - Version display showing current app version and commit hash
|
||||
*
|
||||
* Migration Status: Enhanced Triple Migration Pattern Applied
|
||||
* - Database Migration: PlatformServiceMixin integrated for settings management
|
||||
* - SQL Abstraction: No raw SQL queries (component uses high-level operations)
|
||||
* - Notification Migration: No notifications used (clean component)
|
||||
* - Template Streamlining: Interactive handlers extracted to methods
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @component HelpView
|
||||
* @vue-facing-decorator
|
||||
*/
|
||||
@Component({
|
||||
components: { QuickNav },
|
||||
mixins: [PlatformServiceMixin],
|
||||
})
|
||||
export default class HelpView extends Vue {
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
$router!: Router;
|
||||
|
||||
package = Package;
|
||||
@@ -625,31 +648,129 @@ export default class HelpView extends Vue {
|
||||
// Ideally, we put no functionality in here, especially in the setup,
|
||||
// because we never want this page to have a chance of throwing an error.
|
||||
|
||||
// call fn, copy text to the clipboard, then redo fn after 2 seconds
|
||||
doCopyTwoSecRedo(text: string, fn: () => void) {
|
||||
/**
|
||||
* Copy text to clipboard with visual feedback
|
||||
*
|
||||
* This method executes a callback function, copies the provided text to the clipboard,
|
||||
* and then re-executes the callback after 2 seconds. This is typically used for
|
||||
* showing a temporary visual indicator (like a checkmark) after copying.
|
||||
*
|
||||
* @param {string} text - The text to copy to clipboard
|
||||
* @param {Function} fn - Callback function to execute before and after copying
|
||||
*/
|
||||
doCopyTwoSecRedo(text: string, fn: () => void): void {
|
||||
fn();
|
||||
useClipboard()
|
||||
.copy(text)
|
||||
.then(() => setTimeout(fn, 2000));
|
||||
}
|
||||
|
||||
async unsetFinishedOnboarding() {
|
||||
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
||||
/**
|
||||
* Reset onboarding state for the active account
|
||||
*
|
||||
* This method allows users to restart the onboarding process by setting
|
||||
* the finishedOnboarding flag to false for their active DID. This is useful
|
||||
* when users want to see the page-by-page help guide again.
|
||||
*
|
||||
* @returns {Promise<void>} Promise that resolves when onboarding is reset
|
||||
*/
|
||||
async unsetFinishedOnboarding(): Promise<void> {
|
||||
try {
|
||||
const settings = await this.$accountSettings();
|
||||
|
||||
if (settings.activeDid) {
|
||||
await databaseUtil.updateDidSpecificSettings(settings.activeDid, {
|
||||
finishedOnboarding: false,
|
||||
});
|
||||
if (settings.activeDid) {
|
||||
await this.$updateSettings({
|
||||
...settings,
|
||||
finishedOnboarding: false,
|
||||
});
|
||||
|
||||
this.$log(
|
||||
"[HelpView] Onboarding reset successfully for DID: " +
|
||||
settings.activeDid,
|
||||
);
|
||||
}
|
||||
|
||||
this.$router.push({ name: "home" });
|
||||
} catch (error) {
|
||||
this.$log(
|
||||
"[HelpView] Error resetting onboarding: " + (error as Error).message,
|
||||
);
|
||||
|
||||
// Still navigate to home even if settings update fails
|
||||
this.$router.push({ name: "home" });
|
||||
}
|
||||
this.$router.push({ name: "home" });
|
||||
}
|
||||
|
||||
private handleQRCodeClick() {
|
||||
/**
|
||||
* Handle QR code scanner navigation
|
||||
*
|
||||
* This method provides platform-aware navigation to the appropriate QR code
|
||||
* scanner component. On native platforms (iOS/Android), it navigates to the
|
||||
* full-screen scanner. On web platforms, it navigates to the web-based scanner.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private handleQRCodeClick(): void {
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
this.$router.push({ name: "contact-qr-scan-full" });
|
||||
} else {
|
||||
this.$router.push({ name: "contact-qr" });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle Alpha chat section visibility
|
||||
*/
|
||||
toggleAlpha(): void {
|
||||
this.showAlpha = !this.showAlpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle Group section visibility
|
||||
*/
|
||||
toggleGroup(): void {
|
||||
this.showGroup = !this.showGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle Community section visibility
|
||||
*/
|
||||
toggleCommunity(): void {
|
||||
this.showCommunity = !this.showCommunity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle Verifiable data section visibility
|
||||
*/
|
||||
toggleVerifiable(): void {
|
||||
this.showVerifiable = !this.showVerifiable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle Governance section visibility
|
||||
*/
|
||||
toggleGovernance(): void {
|
||||
this.showGovernance = !this.showGovernance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle Basics section visibility
|
||||
*/
|
||||
toggleBasics(): void {
|
||||
this.showBasics = !this.showBasics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy Bitcoin address to clipboard with visual feedback
|
||||
*
|
||||
* This method copies the Bitcoin donation address to the clipboard and
|
||||
* shows a visual indicator (checkmark) for 2 seconds.
|
||||
*/
|
||||
copyBitcoinAddress(): void {
|
||||
this.doCopyTwoSecRedo(
|
||||
"bc1q90v4ted6cpt63tjfh2lvd5xzfc67sd4g9w8xma",
|
||||
() => (this.showDidCopy = !this.showDidCopy),
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user