Browse Source

Remove ContactScanView and update QR scan view naming for consistency

- Deleted ContactScanView.vue and removed its route from the router
- Renamed ContactQRScanView.vue to ContactQRScanFullView.vue
- Updated all router paths, names, and references to use 'contact-qr-scan-full'
- Updated related router links in ContactQRScanShowView.vue for consistency
- Ensured all naming and routing is consolidated and matches the new view/component name
sql-wa-sqlite
Matt Raymer 3 weeks ago
parent
commit
55176ed5db
  1. 6
      src/router/index.ts
  2. 2
      src/views/ContactQRScanShowView.vue
  3. 430
      src/views/ContactQRScanView.vue
  4. 92
      src/views/ContactScanView.vue

6
src/router/index.ts

@ -88,9 +88,9 @@ const routes: Array<RouteRecordRaw> = [
component: () => import("../views/ContactQRScanShowView.vue"),
},
{
path: "/contact-qr-scan",
name: "contact-qr-scan",
component: () => import("../views/ContactQRScanView.vue"),
path: "/contact-qr-scan-full",
name: "contact-qr-scan-full",
component: () => import("../views/ContactQRScanFullView.vue"),
},
{
path: "/contacts",

2
src/views/ContactQRScanShowView.vue

@ -172,7 +172,7 @@
<button
v-if="isNativePlatform"
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white text-lg px-3 py-2 rounded-lg"
@click="$router.push({ name: 'contact-qr-scan' })"
@click="$router.push({ name: 'contact-qr-scan-full' })"
>
Scan QR Code
</button>

430
src/views/ContactQRScanView.vue

@ -1,430 +0,0 @@
<template>
<!-- CONTENT -->
<section id="Content" class="relativew-[100vw] h-[100vh]">
<div
class="absolute inset-x-0 bottom-0 bg-black/50 p-6 pb-[calc(env(safe-area-inset-bottom)+1.5rem)]"
>
<p class="text-center text-white mb-3">
Point your camera at a TimeSafari contact QR code to scan it
automatically.
</p>
<p v-if="error" class="text-center text-rose-300 mb-3">{{ error }}</p>
<div class="flex justify-center items-center">
<button
class="text-center text-slate-600 leading-none bg-white p-2 rounded-full drop-shadow-lg"
@click="handleBack"
>
<font-awesome icon="xmark" class="size-6"></font-awesome>
</button>
</div>
</div>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import { logger } from "../utils/logger";
import { QRScannerFactory } from "../services/QRScanner/QRScannerFactory";
import QuickNav from "../components/QuickNav.vue";
import { NotificationIface } from "../constants/app";
import { db } from "../db/index";
import { Contact } from "../db/tables/contacts";
import { getContactJwtFromJwtUrl } from "../libs/crypto";
import { decodeEndorserJwt } from "../libs/crypto/vc";
import { retrieveSettingsForActiveAccount } from "../db/index";
import { setVisibilityUtil } from "../libs/endorserServer";
interface QRScanResult {
rawValue?: string;
barcode?: string;
}
@Component({
components: {
QuickNav,
},
})
export default class ContactQRScan extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
$router!: Router;
isScanning = false;
error: string | null = null;
activeDid = "";
apiServer = "";
// Add new properties to track scanning state
private lastScannedValue: string = "";
private lastScanTime: number = 0;
private readonly SCAN_DEBOUNCE_MS = 2000; // Prevent duplicate scans within 2 seconds
// Add cleanup tracking
private isCleaningUp = false;
private isMounted = false;
async created() {
try {
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
} catch (error) {
logger.error("Error initializing component:", {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
this.$notify({
group: "alert",
type: "danger",
title: "Initialization Error",
text: "Failed to initialize QR scanner. Please try again.",
});
}
}
async startScanning() {
if (this.isCleaningUp) {
logger.debug("Cannot start scanning during cleanup");
return;
}
try {
this.error = null;
this.isScanning = true;
this.lastScannedValue = "";
this.lastScanTime = 0;
const scanner = QRScannerFactory.getInstance();
// Check if scanning is supported first
if (!(await scanner.isSupported())) {
this.error =
"Camera access requires HTTPS. Please use a secure connection.";
this.isScanning = false;
this.$notify(
{
group: "alert",
type: "warning",
title: "HTTPS Required",
text: "Camera access requires a secure (HTTPS) connection",
},
5000,
);
return;
}
// Check permissions first
if (!(await scanner.checkPermissions())) {
const granted = await scanner.requestPermissions();
if (!granted) {
this.error = "Camera permission denied";
this.isScanning = false;
// Show notification for better visibility
this.$notify(
{
group: "alert",
type: "warning",
title: "Camera Access Required",
text: "Camera permission denied",
},
5000,
);
return;
}
}
// Add scan listener
scanner.addListener({
onScan: this.onScanDetect,
onError: this.onScanError,
});
// Start scanning
await scanner.startScan();
} catch (error) {
this.error = error instanceof Error ? error.message : String(error);
this.isScanning = false;
logger.error("Error starting scan:", {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
}
}
async stopScanning() {
try {
const scanner = QRScannerFactory.getInstance();
await scanner.stopScan();
} catch (error) {
logger.error("Error stopping scan:", {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
} finally {
this.isScanning = false;
this.lastScannedValue = "";
this.lastScanTime = 0;
}
}
async cleanupScanner() {
if (this.isCleaningUp) {
return;
}
this.isCleaningUp = true;
try {
logger.info("Cleaning up QR scanner resources");
await this.stopScanning();
await QRScannerFactory.cleanup();
} catch (error) {
logger.error("Error during scanner cleanup:", {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
} finally {
this.isCleaningUp = false;
}
}
/**
* Handle QR code scan result with debouncing to prevent duplicate scans
*/
async onScanDetect(result: string | QRScanResult) {
try {
// Extract raw value from different possible formats
const rawValue =
typeof result === "string"
? result
: result?.rawValue || result?.barcode;
if (!rawValue) {
logger.warn("Invalid scan result - no value found:", result);
return;
}
// Debounce duplicate scans
const now = Date.now();
if (
rawValue === this.lastScannedValue &&
now - this.lastScanTime < this.SCAN_DEBOUNCE_MS
) {
logger.info("Ignoring duplicate scan:", rawValue);
return;
}
// Update scan tracking
this.lastScannedValue = rawValue;
this.lastScanTime = now;
logger.info("Processing QR code scan result:", rawValue);
// Extract JWT
const jwt = getContactJwtFromJwtUrl(rawValue);
if (!jwt) {
logger.warn("Invalid QR code format - no JWT found in URL");
this.$notify({
group: "alert",
type: "danger",
title: "Invalid QR Code",
text: "This QR code does not contain valid contact information. Please scan a TimeSafari contact QR code.",
});
return;
}
// Process JWT and contact info
logger.info("Decoding JWT payload from QR code");
const decodedJwt = await decodeEndorserJwt(jwt);
if (!decodedJwt?.payload?.own) {
logger.warn("Invalid JWT payload - missing 'own' field");
this.$notify({
group: "alert",
type: "danger",
title: "Invalid Contact Info",
text: "The contact information is incomplete or invalid.",
});
return;
}
const contactInfo = decodedJwt.payload.own;
if (!contactInfo.did) {
logger.warn("Invalid contact info - missing DID");
this.$notify({
group: "alert",
type: "danger",
title: "Invalid Contact",
text: "The contact DID is missing.",
});
return;
}
// Create contact object
const contact = {
did: contactInfo.did,
name: contactInfo.name || "",
email: contactInfo.email || "",
phone: contactInfo.phone || "",
company: contactInfo.company || "",
title: contactInfo.title || "",
notes: contactInfo.notes || "",
};
// Add contact and stop scanning
logger.info("Adding new contact to database:", {
did: contact.did,
name: contact.name,
});
await this.addNewContact(contact);
await this.stopScanning();
this.$router.back(); // Return to previous view after successful scan
} catch (error) {
logger.error("Error processing contact QR code:", {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
this.$notify({
group: "alert",
type: "danger",
title: "Error",
text:
error instanceof Error
? error.message
: "Could not process QR code. Please try again.",
});
}
}
onScanError(error: Error) {
this.error = error.message;
logger.error("QR code scan error:", {
error: error.message,
stack: error.stack,
});
}
async setVisibility(contact: Contact, visibility: boolean) {
const result = await setVisibilityUtil(
this.activeDid,
this.apiServer,
this.axios,
db,
contact,
visibility,
);
if (result.error) {
this.$notify({
group: "alert",
type: "danger",
title: "Error Setting Visibility",
text: result.error as string,
});
} else if (!result.success) {
logger.warn("Unexpected result from setting visibility:", result);
}
}
async addNewContact(contact: Contact) {
try {
logger.info("Opening database connection for new contact");
await db.open();
// Check if contact already exists
const existingContacts = await db.contacts.toArray();
const existingContact = existingContacts.find(
(c) => c.did === contact.did,
);
if (existingContact) {
logger.info("Contact already exists", { did: contact.did });
this.$notify(
{
group: "alert",
type: "warning",
title: "Contact Exists",
text: "This contact has already been added to your list.",
},
3000,
);
return;
}
// Add new contact
await db.contacts.add(contact);
if (this.activeDid) {
logger.info("Setting contact visibility", { did: contact.did });
await this.setVisibility(contact, true);
contact.seesMe = true;
}
this.$notify(
{
group: "alert",
type: "success",
title: "Contact Added",
text: this.activeDid
? "They were added, and your activity is visible to them."
: "They were added.",
},
3000,
);
} catch (error) {
logger.error("Error saving contact to database:", {
did: contact.did,
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
this.$notify(
{
group: "alert",
type: "danger",
title: "Contact Error",
text: "Could not save contact. Check if it already exists.",
},
5000,
);
}
}
// Lifecycle hooks
mounted() {
this.isMounted = true;
document.addEventListener("pause", this.handleAppPause);
document.addEventListener("resume", this.handleAppResume);
this.startScanning(); // Automatically start scanning when view is mounted
}
beforeDestroy() {
this.isMounted = false;
document.removeEventListener("pause", this.handleAppPause);
document.removeEventListener("resume", this.handleAppResume);
this.cleanupScanner();
}
async handleAppPause() {
if (!this.isMounted) return;
logger.info("App paused, stopping scanner");
await this.stopScanning();
}
handleAppResume() {
if (!this.isMounted) return;
logger.info("App resumed, scanner can be restarted by user");
this.isScanning = false;
}
async handleBack() {
await this.cleanupScanner();
this.$router.back();
}
}
</script>
<style scoped>
.aspect-square {
aspect-ratio: 1 / 1;
}
</style>

92
src/views/ContactScanView.vue

@ -1,92 +0,0 @@
<template>
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
<!-- Cancel -->
<router-link
:to="{ name: 'account' }"
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
><font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
</router-link>
Scan Contact
</h1>
</div>
<h3 class="text-sm uppercase font-semibold mb-2">Scan a QR Code</h3>
<div class="bg-black rounded overflow-hidden relative mb-4">
<img src="https://picsum.photos/400/400?random=1" class="w-full" />
<!-- Darken overlay -->
<!-- Top -->
<div class="absolute top-0 left-0 right-0 bg-black/50 h-1/4"></div>
<!-- Reft -->
<div class="absolute top-1/4 bottom-1/4 left-0 bg-black/50 w-1/4"></div>
<!-- Right -->
<div class="absolute top-1/4 bottom-1/4 right-0 bg-black/50 w-1/4"></div>
<!-- Bottom -->
<div class="absolute bottom-0 left-0 right-0 bg-black/50 h-1/4"></div>
<!-- Reticle overlay -->
<!-- Top-left -->
<div
class="absolute top-1/4 left-1/4 h-6 w-6 border-white border-t-4 border-l-4 drop-shadow"
></div>
<!-- Top-right -->
<div
class="absolute top-1/4 right-1/4 h-6 w-6 border-white border-t-4 border-r-4 drop-shadow"
></div>
<!-- Bottom-left -->
<div
class="absolute bottom-1/4 left-1/4 h-6 w-6 border-white border-b-4 border-l-4 drop-shadow"
></div>
<!-- Bottom-right -->
<div
class="absolute bottom-1/4 right-1/4 h-6 w-6 border-white border-b-4 border-r-4 drop-shadow"
></div>
</div>
<h3 class="text-sm uppercase font-semibold mb-2">or Enter Contact Data</h3>
<input
type="text"
placeholder="Name (optional)"
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
/>
<input
type="text"
placeholder="ID"
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
/>
<input
type="text"
placeholder="Public Key (optional)"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
/>
<div class="mt-8">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<input
type="submit"
class="block w-full text-center text-lg font-bold uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
value="Look Up Contact"
/>
<button
type="button"
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
>
Cancel
</button>
</div>
</div>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
@Component({
components: {},
})
export default class ContactScanView extends Vue {}
</script>
Loading…
Cancel
Save