Compare commits
11 Commits
playwright
...
ask-for-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
215c37f00a | ||
| 799981d1cb | |||
|
|
37559e1bad | ||
|
|
379056aae1 | ||
|
|
ef4f845f74 | ||
|
|
bc618bb13b | ||
|
|
404f23c118 | ||
|
|
303f1bc565 | ||
| 328ac0f14b | |||
| e07da3ffe1 | |||
|
|
6007bc34e4 |
172
.cursor/rules/app/architectural_decision_record.mdc
Normal file
172
.cursor/rules/app/architectural_decision_record.mdc
Normal file
@@ -0,0 +1,172 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
# TimeSafari Cross-Platform Architecture Guide
|
||||
|
||||
## 1. Platform Support Matrix
|
||||
|
||||
| Feature | Web (PWA) | Capacitor (Mobile) | Electron (Desktop) |
|
||||
|---------|-----------|--------------------|-------------------|
|
||||
| QR Code Scanning | WebInlineQRScanner | @capacitor-mlkit/barcode-scanning | Not Implemented |
|
||||
| Deep Linking | URL Parameters | App URL Open Events | Not Implemented |
|
||||
| File System | Limited (Browser API) | Capacitor Filesystem | Electron fs |
|
||||
| Camera Access | MediaDevices API | Capacitor Camera | Not Implemented |
|
||||
| Platform Detection | Web APIs | Capacitor.isNativePlatform() | process.env checks |
|
||||
|
||||
---
|
||||
|
||||
## 2. Project Structure
|
||||
|
||||
### Core Directories
|
||||
```
|
||||
src/
|
||||
├── components/ # Vue components
|
||||
├── services/ # Platform services and business logic
|
||||
├── views/ # Page components
|
||||
├── router/ # Vue router configuration
|
||||
├── types/ # TypeScript type definitions
|
||||
├── utils/ # Utility functions
|
||||
├── lib/ # Core libraries
|
||||
├── platforms/ # Platform-specific implementations
|
||||
├── electron/ # Electron-specific code
|
||||
├── constants/ # Application constants
|
||||
├── db/ # Database related code
|
||||
├── interfaces/ # TypeScript interfaces
|
||||
└── assets/ # Static assets
|
||||
```
|
||||
|
||||
### Entry Points
|
||||
- `main.ts` → Base entry
|
||||
- `main.common.ts` → Shared init
|
||||
- `main.capacitor.ts` → Mobile entry
|
||||
- `main.electron.ts` → Electron entry
|
||||
- `main.web.ts` → Web entry
|
||||
|
||||
---
|
||||
|
||||
## 3. Service Architecture
|
||||
|
||||
### Service Organization
|
||||
|
||||
```tree
|
||||
services/
|
||||
├── QRScanner/
|
||||
│ ├── WebInlineQRScanner.ts
|
||||
│ └── interfaces.ts
|
||||
├── platforms/
|
||||
│ ├── WebPlatformService.ts
|
||||
│ ├── CapacitorPlatformService.ts
|
||||
│ └── ElectronPlatformService.ts
|
||||
└── factory/
|
||||
└── PlatformServiceFactory.ts
|
||||
```
|
||||
|
||||
### Factory Pattern
|
||||
Use a **singleton factory** to select platform services via `process.env.VITE_PLATFORM`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Feature Guidelines
|
||||
|
||||
### QR Code Scanning
|
||||
- Define `QRScannerService` interface.
|
||||
- Implement platform-specific classes (`WebInlineQRScanner`, Capacitor, etc).
|
||||
- Provide `addListener` and `onStream` hooks for composability.
|
||||
|
||||
### Deep Linking
|
||||
- URL format: `timesafari://<route>[/<param>][?query=value]`
|
||||
- Web: `router.beforeEach` → parse query
|
||||
- Capacitor: `App.addListener("appUrlOpen", …)`
|
||||
|
||||
---
|
||||
|
||||
## 5. Build Process
|
||||
|
||||
- `vite.config.common.mts` → shared config
|
||||
- Platform configs: `vite.config.web.mts`, `.capacitor.mts`, `.electron.mts`
|
||||
- Use `process.env.VITE_PLATFORM` for conditional loading.
|
||||
|
||||
```bash
|
||||
npm run build:web
|
||||
npm run build:capacitor
|
||||
npm run build:electron
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Testing Strategy
|
||||
|
||||
- **Unit tests** for services.
|
||||
- **Playwright** for Web + Capacitor:
|
||||
- `playwright.config-local.ts` includes web + Pixel 5.
|
||||
- **Electron tests**: add `spectron` or Playwright-Electron.
|
||||
- Mark tests with platform tags:
|
||||
|
||||
```ts
|
||||
test.skip(!process.env.MOBILE_TEST, "Mobile-only test");
|
||||
```
|
||||
|
||||
> 🔗 **Human Hook:** Before merging new tests, hold a short sync (≤15 min) with QA to align on coverage and flaky test risks.
|
||||
|
||||
---
|
||||
|
||||
## 7. Error Handling
|
||||
|
||||
- Global Vue error handler → logs with component name.
|
||||
- Platform-specific wrappers log API errors with platform prefix (`[Capacitor API Error]`, etc).
|
||||
- Use structured logging (not `console.log`).
|
||||
|
||||
---
|
||||
|
||||
## 8. Best Practices
|
||||
|
||||
- Keep platform code **isolated** in `platforms/`.
|
||||
- Always define a **shared interface** first.
|
||||
- Use feature detection, not platform detection, when possible.
|
||||
- Dependency injection for services → improves testability.
|
||||
- Maintain **Competence Hooks** in PRs (2–3 prompts for dev discussion).
|
||||
|
||||
---
|
||||
|
||||
## 9. Dependency Management
|
||||
|
||||
- Key deps: `@capacitor/core`, `electron`, `vue`.
|
||||
- Use conditional `import()` for platform-specific libs.
|
||||
|
||||
---
|
||||
|
||||
## 10. Security Considerations
|
||||
|
||||
- **Permissions**: Always check + request gracefully.
|
||||
- **Storage**: Secure storage for sensitive data; encrypt when possible.
|
||||
- **Audits**: Schedule quarterly security reviews.
|
||||
|
||||
---
|
||||
|
||||
## 11. ADR Process
|
||||
|
||||
- All major architecture choices → log in `doc/adr/`.
|
||||
- Use ADR template with Context, Decision, Consequences, Status.
|
||||
- Link related ADRs in PR descriptions.
|
||||
|
||||
> 🔗 **Human Hook:** When proposing a new ADR, schedule a 30-min design sync for discussion, not just async review.
|
||||
|
||||
---
|
||||
|
||||
## 12. Collaboration Hooks
|
||||
|
||||
- **QR features**: Sync with Security before merging → permissions & privacy.
|
||||
- **New platform builds**: Demo in team meeting → confirm UX differences.
|
||||
- **Critical ADRs**: Present in guild or architecture review.
|
||||
|
||||
---
|
||||
|
||||
# Self-Check
|
||||
|
||||
- [ ] Does this feature implement a shared interface?
|
||||
- [ ] Are fallbacks + errors handled gracefully?
|
||||
- [ ] Have relevant ADRs been updated/linked?
|
||||
- [ ] Did I add competence hooks or prompts for the team?
|
||||
- [ ] Was human interaction (sync/review/demo) scheduled?
|
||||
@@ -1,287 +0,0 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
# TimeSafari Cross-Platform Architecture Guide
|
||||
|
||||
## 1. Platform Support Matrix
|
||||
|
||||
| Feature | Web (PWA) | Capacitor (Mobile) | Electron (Desktop) |
|
||||
|---------|-----------|-------------------|-------------------|
|
||||
| QR Code Scanning | WebInlineQRScanner | @capacitor-mlkit/barcode-scanning | Not Implemented |
|
||||
| Deep Linking | URL Parameters | App URL Open Events | Not Implemented |
|
||||
| File System | Limited (Browser API) | Capacitor Filesystem | Electron fs |
|
||||
| Camera Access | MediaDevices API | Capacitor Camera | Not Implemented |
|
||||
| Platform Detection | Web APIs | Capacitor.isNativePlatform() | process.env checks |
|
||||
|
||||
## 2. Project Structure
|
||||
|
||||
### 2.1 Core Directories
|
||||
```
|
||||
src/
|
||||
├── components/ # Vue components
|
||||
├── services/ # Platform services and business logic
|
||||
├── views/ # Page components
|
||||
├── router/ # Vue router configuration
|
||||
├── types/ # TypeScript type definitions
|
||||
├── utils/ # Utility functions
|
||||
├── lib/ # Core libraries
|
||||
├── platforms/ # Platform-specific implementations
|
||||
├── electron/ # Electron-specific code
|
||||
├── constants/ # Application constants
|
||||
├── db/ # Database related code
|
||||
├── interfaces/ # TypeScript interfaces and type definitions
|
||||
└── assets/ # Static assets
|
||||
```
|
||||
|
||||
### 2.2 Entry Points
|
||||
```
|
||||
src/
|
||||
├── main.ts # Base entry
|
||||
├── main.common.ts # Shared initialization
|
||||
├── main.capacitor.ts # Mobile entry
|
||||
├── main.electron.ts # Electron entry
|
||||
└── main.web.ts # Web/PWA entry
|
||||
```
|
||||
|
||||
### 2.3 Build Configurations
|
||||
```
|
||||
root/
|
||||
├── vite.config.common.mts # Shared config
|
||||
├── vite.config.capacitor.mts # Mobile build
|
||||
├── vite.config.electron.mts # Electron build
|
||||
└── vite.config.web.mts # Web/PWA build
|
||||
```
|
||||
|
||||
## 3. Service Architecture
|
||||
|
||||
### 3.1 Service Organization
|
||||
```
|
||||
services/
|
||||
├── QRScanner/ # QR code scanning service
|
||||
│ ├── WebInlineQRScanner.ts
|
||||
│ └── interfaces.ts
|
||||
├── platforms/ # Platform-specific services
|
||||
│ ├── WebPlatformService.ts
|
||||
│ ├── CapacitorPlatformService.ts
|
||||
│ └── ElectronPlatformService.ts
|
||||
└── factory/ # Service factories
|
||||
└── PlatformServiceFactory.ts
|
||||
```
|
||||
|
||||
### 3.2 Service Factory Pattern
|
||||
```typescript
|
||||
// PlatformServiceFactory.ts
|
||||
export class PlatformServiceFactory {
|
||||
private static instance: PlatformService | null = null;
|
||||
|
||||
public static getInstance(): PlatformService {
|
||||
if (!PlatformServiceFactory.instance) {
|
||||
const platform = process.env.VITE_PLATFORM || "web";
|
||||
PlatformServiceFactory.instance = createPlatformService(platform);
|
||||
}
|
||||
return PlatformServiceFactory.instance;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Feature Implementation Guidelines
|
||||
|
||||
### 4.1 QR Code Scanning
|
||||
|
||||
1. **Service Interface**
|
||||
```typescript
|
||||
interface QRScannerService {
|
||||
checkPermissions(): Promise<boolean>;
|
||||
requestPermissions(): Promise<boolean>;
|
||||
isSupported(): Promise<boolean>;
|
||||
startScan(): Promise<void>;
|
||||
stopScan(): Promise<void>;
|
||||
addListener(listener: ScanListener): void;
|
||||
onStream(callback: (stream: MediaStream | null) => void): void;
|
||||
cleanup(): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Platform-Specific Implementation**
|
||||
```typescript
|
||||
// WebInlineQRScanner.ts
|
||||
export class WebInlineQRScanner implements QRScannerService {
|
||||
private scanListener: ScanListener | null = null;
|
||||
private isScanning = false;
|
||||
private stream: MediaStream | null = null;
|
||||
private events = new EventEmitter();
|
||||
|
||||
// Implementation of interface methods
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Deep Linking
|
||||
|
||||
1. **URL Structure**
|
||||
```typescript
|
||||
// Format: timesafari://<route>[/<param>][?queryParam1=value1]
|
||||
interface DeepLinkParams {
|
||||
route: string;
|
||||
params?: Record<string, string>;
|
||||
query?: Record<string, string>;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Platform Handlers**
|
||||
```typescript
|
||||
// Capacitor
|
||||
App.addListener("appUrlOpen", handleDeepLink);
|
||||
|
||||
// Web
|
||||
router.beforeEach((to, from, next) => {
|
||||
handleWebDeepLink(to.query);
|
||||
});
|
||||
```
|
||||
|
||||
## 5. Build Process
|
||||
|
||||
### 5.1 Environment Configuration
|
||||
```typescript
|
||||
// vite.config.common.mts
|
||||
export function createBuildConfig(mode: string) {
|
||||
return {
|
||||
define: {
|
||||
'process.env.VITE_PLATFORM': JSON.stringify(mode),
|
||||
// PWA is automatically enabled for web platforms via build configuration
|
||||
__IS_MOBILE__: JSON.stringify(isCapacitor),
|
||||
__USE_QR_READER__: JSON.stringify(!isCapacitor)
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Platform-Specific Builds
|
||||
|
||||
```bash
|
||||
# Build commands from package.json
|
||||
"build:web": "vite build --config vite.config.web.mts",
|
||||
"build:capacitor": "vite build --config vite.config.capacitor.mts",
|
||||
"build:electron": "vite build --config vite.config.electron.mts"
|
||||
```
|
||||
|
||||
## 6. Testing Strategy
|
||||
|
||||
### 6.1 Test Configuration
|
||||
```typescript
|
||||
// playwright.config-local.ts
|
||||
const config: PlaywrightTestConfig = {
|
||||
projects: [
|
||||
{
|
||||
name: 'web',
|
||||
use: { browserName: 'chromium' }
|
||||
},
|
||||
{
|
||||
name: 'mobile',
|
||||
use: { ...devices['Pixel 5'] }
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
### 6.2 Platform-Specific Tests
|
||||
```typescript
|
||||
test('QR scanning works on mobile', async ({ page }) => {
|
||||
test.skip(!process.env.MOBILE_TEST, 'Mobile-only test');
|
||||
// Test implementation
|
||||
});
|
||||
```
|
||||
|
||||
## 7. Error Handling
|
||||
|
||||
### 7.1 Global Error Handler
|
||||
```typescript
|
||||
function setupGlobalErrorHandler(app: VueApp) {
|
||||
app.config.errorHandler = (err, instance, info) => {
|
||||
logger.error("[App Error]", {
|
||||
error: err,
|
||||
info,
|
||||
component: instance?.$options.name
|
||||
});
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Platform-Specific Error Handling
|
||||
```typescript
|
||||
// API error handling for Capacitor
|
||||
if (process.env.VITE_PLATFORM === 'capacitor') {
|
||||
logger.error(`[Capacitor API Error] ${endpoint}:`, {
|
||||
message: error.message,
|
||||
status: error.response?.status
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 8. Best Practices
|
||||
|
||||
### 8.1 Code Organization
|
||||
- Use platform-specific directories for unique implementations
|
||||
- Share common code through service interfaces
|
||||
- Implement feature detection before using platform capabilities
|
||||
- Keep platform-specific code isolated in dedicated directories
|
||||
- Use TypeScript interfaces for cross-platform compatibility
|
||||
|
||||
### 8.2 Platform Detection
|
||||
```typescript
|
||||
const platformService = PlatformServiceFactory.getInstance();
|
||||
const capabilities = platformService.getCapabilities();
|
||||
|
||||
if (capabilities.hasCamera) {
|
||||
// Implement camera features
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 Feature Implementation
|
||||
1. Define platform-agnostic interface
|
||||
2. Create platform-specific implementations
|
||||
3. Use factory pattern for instantiation
|
||||
4. Implement graceful fallbacks
|
||||
5. Add comprehensive error handling
|
||||
6. Use dependency injection for better testability
|
||||
|
||||
## 9. Dependency Management
|
||||
|
||||
### 9.1 Platform-Specific Dependencies
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@capacitor/core": "^6.2.0",
|
||||
"electron": "^33.2.1",
|
||||
"vue": "^3.4.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Conditional Loading
|
||||
```typescript
|
||||
if (process.env.VITE_PLATFORM === 'capacitor') {
|
||||
await import('@capacitor/core');
|
||||
}
|
||||
```
|
||||
|
||||
## 10. Security Considerations
|
||||
|
||||
### 10.1 Permission Handling
|
||||
```typescript
|
||||
async checkPermissions(): Promise<boolean> {
|
||||
if (platformService.isCapacitor()) {
|
||||
return await checkNativePermissions();
|
||||
}
|
||||
return await checkWebPermissions();
|
||||
}
|
||||
```
|
||||
|
||||
### 10.2 Data Storage
|
||||
- Use secure storage mechanisms for sensitive data
|
||||
- Implement proper encryption for stored data
|
||||
- Follow platform-specific security guidelines
|
||||
- Regular security audits and updates
|
||||
|
||||
This document should be updated as new features are added or platform-specific implementations change. Regular reviews ensure it remains current with the codebase.
|
||||
106
.cursor/rules/base_context.mdc
Normal file
106
.cursor/rules/base_context.mdc
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
```json
|
||||
{
|
||||
"coaching_level": "standard",
|
||||
"socratic_max_questions": 7,
|
||||
"verbosity": "normal",
|
||||
"timebox_minutes": null,
|
||||
"format_enforcement": "strict"
|
||||
}
|
||||
```
|
||||
|
||||
# Base Context — Human Competence First
|
||||
|
||||
## Purpose
|
||||
All interactions must *increase the human’s competence over time* while
|
||||
completing the task efficiently. The model may handle menial work and memory
|
||||
extension, but must also promote learning, autonomy, and healthy work habits.
|
||||
The model should also **encourage human interaction and collaboration** rather
|
||||
than replacing it — outputs should be designed to **facilitate human discussion,
|
||||
decision-making, and creativity**, not to atomize tasks into isolated, purely
|
||||
machine-driven steps.
|
||||
|
||||
## Principles
|
||||
|
||||
1) Competence over convenience: finish the task *and* leave the human more
|
||||
capable next time.
|
||||
2) Mentorship, not lectures: be concise, concrete, and immediately applicable.
|
||||
3) Transparency: show assumptions, limits, and uncertainty; cite when non-obvious.
|
||||
4) Optional scaffolding: include small, skimmable learning hooks that do not
|
||||
bloat output.
|
||||
5) Time respect: default to **lean output**; offer opt-in depth via toggles.
|
||||
6) Psychological safety: encourage, never condescend; no medical/clinical advice.
|
||||
No censorship!
|
||||
7) Reusability: structure outputs so they can be saved, searched, reused, and repurposed.
|
||||
8) **Collaborative Bias**: Favor solutions that invite human review, discussion,
|
||||
and iteration. When in doubt, ask “Who should this be shown to?” or “Which human
|
||||
input would improve this?”
|
||||
|
||||
## Toggle Definitions
|
||||
|
||||
### coaching_level
|
||||
|
||||
Determines the depth of learning support: `light` (short hooks), `standard`
|
||||
(balanced), `deep` (detailed).
|
||||
|
||||
### socratic_max_questions
|
||||
|
||||
The number of clarifying questions the model may ask before proceeding.
|
||||
If >0, questions should be targeted, minimal, and followed by reasonable assumptions if unanswered.
|
||||
|
||||
### verbosity
|
||||
'terse' (just a sentence), `concise` (minimum commentary), `normal` (balanced explanation), or other project-defined levels.
|
||||
|
||||
### timebox_minutes
|
||||
*integer or null* — When set to a positive integer (e.g., `5`), this acts as a **time budget** guiding the model to prioritize delivering the most essential parts of the task within that constraint.
|
||||
Behavior when set:
|
||||
1. **Prioritize Core Output** — Deliver the minimum viable solution or result first.
|
||||
2. **Limit Commentary** — Competence Hooks and Collaboration Hooks must be shorter than normal.
|
||||
3. **Signal Skipped Depth** — Omitted details should be listed under *Deferred for depth*.
|
||||
4. **Order by Value** — Start with blocking or high-value items, then proceed to nice-to-haves if budget allows.
|
||||
If `null`, there is no timebox — the model can produce full-depth responses.
|
||||
|
||||
### format_enforcement
|
||||
`strict` (reject outputs with format drift) or `relaxed` (minor deviations acceptable).
|
||||
|
||||
## Modes (select or combine)
|
||||
- **Doer**: produce the artifact fast, minimal commentary.
|
||||
- **Mentor**: add short “why/how” notes + next-step pointers.
|
||||
- **Socratic**: ask up to N targeted questions when requirements are ambiguous.
|
||||
- **Pair-Programmer/Pair-Writer**: explain tradeoffs as you implement.
|
||||
- **Facilitator**: structure output to be reviewable, commentable, and ready for group discussion.
|
||||
|
||||
Default: Doer + short Mentor notes.
|
||||
|
||||
## Competence & Collaboration Levers (keep lightweight)
|
||||
- “Why this works” (≤3 bullets)
|
||||
- “Common pitfalls” (≤3 bullets)
|
||||
- “Next skill unlock” (1 tiny action or reading)
|
||||
- “Teach-back” (1 sentence prompt the human can answer to self-check)
|
||||
- “Discussion prompts” (≤2 short questions for peers/stakeholders)
|
||||
|
||||
## Output Contract (apply to every deliverable)
|
||||
- Clear **Objective** (1 line)
|
||||
- **Result** (artifact/code/answer)
|
||||
- **Use/Run** (how to apply/test)
|
||||
- **Competence Hooks** (the 4 learning levers above, kept terse)
|
||||
- **Collaboration Hooks** (discussion prompts or group review steps)
|
||||
- **Assumptions & Limits**
|
||||
- **References** (if used; links or titles)
|
||||
|
||||
## Do-Not
|
||||
- No filler, hedging, or moralizing.
|
||||
- No medical/mental-health advice; keep “healthy habits” to general work practices.
|
||||
- No invented facts; mark uncertainty plainly.
|
||||
- No censorship.
|
||||
- Avoid outputs that bypass human review when such review is valuable.
|
||||
|
||||
## Self-Check (model, before responding)
|
||||
- [ ] Task done *and* at least one competence lever included (≤120 words total).
|
||||
- [ ] At least one collaboration/discussion hook present.
|
||||
- [ ] Output follows the **Output Contract** sections.
|
||||
- [ ] Toggles respected; verbosity remains concise.
|
||||
- [ ] Uncertainties/assumptions surfaced.
|
||||
- [ ] No disallowed content.
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
globs: **/db/databaseUtil.ts, **/interfaces/absurd-sql.d.ts, **/src/registerSQLWorker.js, **/services/AbsurdSqlDatabaseService.ts
|
||||
alwaysApply: false
|
||||
---
|
||||
# Absurd SQL - Cursor Development Guide
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
globs: **/databaseUtil.ts,**/AccountViewView.vue,**/ContactsView.vue,**/DatabaseMigration.vue,**/NewIdentifierView.vue
|
||||
alwaysApply: false
|
||||
---
|
||||
All references in the codebase to Dexie apply only to migration from IndexedDb to Sqlite and will be deprecated in future versions.
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
description: rules used while developing
|
||||
globs:
|
||||
alwaysApply: true
|
||||
globs: **/src/**/*
|
||||
alwaysApply: false
|
||||
---
|
||||
✅ use system date command to timestamp all interactions with accurate date and time
|
||||
✅ python script files must always have a blank line at their end
|
||||
108
.cursor/rules/development/type_safety_guide.mdc
Normal file
108
.cursor/rules/development/type_safety_guide.mdc
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
globs: **/src/**/*,**/scripts/**/*,**/electron/**/*
|
||||
alwaysApply: false
|
||||
---
|
||||
```json
|
||||
{
|
||||
"coaching_level": "light",
|
||||
"socratic_max_questions": 7,
|
||||
"verbosity": "concise",
|
||||
"timebox_minutes": null,
|
||||
"format_enforcement": "strict"
|
||||
}
|
||||
```
|
||||
|
||||
# TypeScript Type Safety Guidelines
|
||||
|
||||
**Author**: Matthew Raymer
|
||||
**Date**: 2025-08-16
|
||||
**Status**: 🎯 **ACTIVE**
|
||||
|
||||
## Overview
|
||||
|
||||
Practical rules to keep TypeScript strict and predictable. Minimize exceptions.
|
||||
|
||||
## Core Rules
|
||||
|
||||
1. **No `any`**
|
||||
- Use explicit types. If unknown, use `unknown` and **narrow** via guards.
|
||||
|
||||
2. **Error handling uses guards**
|
||||
- Reuse guards from `src/interfaces/**` (e.g., `isDatabaseError`, `isApiError`).
|
||||
- Catch with `unknown`; never cast to `any`.
|
||||
|
||||
3. **Dynamic property access is type‑safe**
|
||||
- Use `keyof` + `in` checks:
|
||||
|
||||
```ts
|
||||
obj[k as keyof typeof obj]
|
||||
```
|
||||
|
||||
- Avoid `(obj as any)[k]`.
|
||||
|
||||
## Minimal Special Cases (document in PR when used)
|
||||
|
||||
- **Vue refs / instances**: Use `ComponentPublicInstance` or specific component
|
||||
types for dynamic refs.
|
||||
- **3rd‑party libs without types**: Narrow immediately to a **known interface**;
|
||||
do not leave `any` hanging.
|
||||
|
||||
## Patterns (short)
|
||||
|
||||
### Database errors
|
||||
|
||||
```ts
|
||||
try { await this.$addContact(contact); }
|
||||
catch (e: unknown) {
|
||||
if (isDatabaseError(e) && e.message.includes("Key already exists")) {
|
||||
/* handle duplicate */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API errors
|
||||
|
||||
```ts
|
||||
try { await apiCall(); }
|
||||
catch (e: unknown) {
|
||||
if (isApiError(e)) {
|
||||
const msg = e.response?.data?.error?.message;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic keys
|
||||
|
||||
```ts
|
||||
const keys = Object.keys(newSettings).filter(
|
||||
k => k in newSettings && newSettings[k as keyof typeof newSettings] !== undefined
|
||||
);
|
||||
```
|
||||
|
||||
## Checklists
|
||||
|
||||
**Before commit**
|
||||
|
||||
- [ ] No `any` (except documented, justified cases)
|
||||
- [ ] Errors handled via guards
|
||||
- [ ] Dynamic access uses `keyof`/`in`
|
||||
- [ ] Imports point to correct interfaces/types
|
||||
|
||||
**Code review**
|
||||
|
||||
- [ ] Hunt hidden `as any`
|
||||
- [ ] Guard‑based error paths verified
|
||||
- [ ] Dynamic ops are type‑safe
|
||||
- [ ] Prefer existing types over re‑inventing
|
||||
|
||||
## Tools
|
||||
|
||||
- `npm run lint-fix` — lint & auto‑fix
|
||||
- `npm run type-check` — strict type compilation (CI + pre‑release)
|
||||
- IDE: enable strict TS, ESLint/TS ESLint, Volar (Vue 3)
|
||||
|
||||
## References
|
||||
|
||||
- TS Handbook — https://www.typescriptlang.org/docs/
|
||||
- TS‑ESLint — https://typescript-eslint.io/rules/
|
||||
- Vue 3 + TS — https://vuejs.org/guide/typescript/
|
||||
135
.cursor/rules/research_diagnostic.mdc
Normal file
135
.cursor/rules/research_diagnostic.mdc
Normal file
@@ -0,0 +1,135 @@
|
||||
---
|
||||
description: Use this workflow when doing **pre-implementation research, defect investigations with uncertain repros, or clarifying system architecture and behaviors**.
|
||||
alwaysApply: false
|
||||
---
|
||||
```json
|
||||
{
|
||||
"coaching_level": "light",
|
||||
"socratic_max_questions": 2,
|
||||
"verbosity": "concise",
|
||||
"timebox_minutes": null,
|
||||
"format_enforcement": "strict"
|
||||
}
|
||||
```
|
||||
|
||||
# Research & Diagnostic Workflow (R&D)
|
||||
|
||||
## Purpose
|
||||
|
||||
Provide a **repeatable, evidence-first** workflow to investigate features and
|
||||
defects **before coding**. Outputs are concise reports, hypotheses, and next
|
||||
steps—**not** code changes.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Pre-implementation research for new features
|
||||
- Defect investigations (repros uncertain, user-specific failures)
|
||||
- Architecture/behavior clarifications (e.g., auth flows, merges, migrations)
|
||||
|
||||
---
|
||||
|
||||
## Output Contract (strict)
|
||||
|
||||
1) **Objective** — 1–2 lines
|
||||
2) **System Map (if helpful)** — short diagram or bullet flow (≤8 bullets)
|
||||
3) **Findings (Evidence-linked)** — bullets; each with file/function refs
|
||||
4) **Hypotheses & Failure Modes** — short list, each testable
|
||||
5) **Corrections** — explicit deltas from earlier assumptions (if any)
|
||||
6) **Diagnostics** — what to check next (logs, DB, env, repro steps)
|
||||
7) **Risks & Scope** — what could break; affected components
|
||||
8) **Decision/Next Steps** — what we’ll do, who’s involved, by when
|
||||
9) **References** — code paths, ADRs, docs
|
||||
10) **Competence & Collaboration Hooks** — brief, skimmable
|
||||
|
||||
> Keep total length lean. Prefer links and bullets over prose.
|
||||
|
||||
---
|
||||
|
||||
## Quickstart Template
|
||||
|
||||
Copy/paste and fill:
|
||||
|
||||
```md
|
||||
# Investigation — <short title>
|
||||
|
||||
## Objective
|
||||
<one or two lines>
|
||||
|
||||
## System Map
|
||||
- <module> → <function> → <downstream>
|
||||
- <data path> → <db table> → <api>
|
||||
|
||||
## Findings (Evidence)
|
||||
- <claim> — evidence: `src/path/file.ts:function` (lines X–Y); log snippet/trace id
|
||||
- <claim> — evidence: `...`
|
||||
|
||||
## Hypotheses & Failure Modes
|
||||
- H1: <hypothesis>; would fail when <condition>
|
||||
- H2: <hypothesis>; watch for <signal>
|
||||
|
||||
## Corrections
|
||||
- Updated: <old statement> → <new statement with evidence>
|
||||
|
||||
## Diagnostics (Next Checks)
|
||||
- [ ] Repro on <platform/version>
|
||||
- [ ] Inspect <table/store> for <record>
|
||||
- [ ] Capture <log/trace>
|
||||
|
||||
## Risks & Scope
|
||||
- Impacted: <areas/components>; Data: <tables/keys>; Users: <segments>
|
||||
|
||||
## Decision / Next Steps
|
||||
- Owner: <name>; By: <date> (YYYY-MM-DD)
|
||||
- Action: <spike/bugfix/ADR>; Exit criteria: <binary checks>
|
||||
|
||||
## References
|
||||
- `src/...`
|
||||
- ADR: `docs/adr/xxxx-yy-zz-something.md`
|
||||
- Design: `docs/...`
|
||||
|
||||
## Competence Hooks
|
||||
- Why this works: <≤3 bullets>
|
||||
- Common pitfalls: <≤3 bullets>
|
||||
- Next skill: <≤1 item>
|
||||
- Teach-back: "<one question>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Evidence Quality Bar
|
||||
|
||||
- **Cite the source** (file:func, line range if possible).
|
||||
- **Prefer primary evidence** (code, logs) over inference.
|
||||
- **Disambiguate platform** (Web/Capacitor/Electron) and **state** (migration, auth).
|
||||
- **Note uncertainty** explicitly.
|
||||
|
||||
---
|
||||
|
||||
## Collaboration Hooks
|
||||
|
||||
- **Syncs:** 10–15m with QA/Security/Platform owners for high-risk areas.
|
||||
- **ADR:** Record major decisions; link here.
|
||||
- **Review:** Share repro + diagnostics checklist in PR/issue.
|
||||
|
||||
---
|
||||
|
||||
## Self-Check (model, before responding)
|
||||
|
||||
- [ ] Output matches the **Output Contract** sections.
|
||||
- [ ] Each claim has **evidence** or **uncertainty** is flagged.
|
||||
- [ ] Hypotheses are testable; diagnostics are actionable.
|
||||
- [ ] Competence + collaboration hooks present (≤120 words total).
|
||||
- [ ] Respect toggles; keep it concise.
|
||||
|
||||
---
|
||||
|
||||
## Optional Globs (examples)
|
||||
|
||||
> Uncomment `globs` in the header if you want auto-attach behavior.
|
||||
|
||||
- `src/platforms/**`, `src/services/**` — attach during service/feature investigations
|
||||
- `docs/adr/**` — attach when editing ADRs
|
||||
|
||||
## Referenced Files
|
||||
|
||||
- Consider including templates as context: `@adr_template.md`, `@investigation_report_example.md`
|
||||
@@ -30,7 +30,7 @@ module.exports = {
|
||||
}],
|
||||
"no-console": process.env.NODE_ENV === "production" ? "error" : "warn",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "warn",
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-unnecessary-type-constraint": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
|
||||
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src",
|
||||
"type-safety-check": "./scripts/type-safety-check.sh",
|
||||
"type-check": "tsc --noEmit",
|
||||
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js && node scripts/copy-wasm.js",
|
||||
"test:prerequisites": "node scripts/check-prerequisites.js",
|
||||
@@ -53,6 +54,8 @@
|
||||
"build:web:docker:test": "./scripts/build-web.sh --docker:test",
|
||||
"build:web:docker:prod": "./scripts/build-web.sh --docker:prod",
|
||||
"build:web:serve": "./scripts/build-web.sh --serve",
|
||||
"build:web:serve:test": "./scripts/build-web.sh --serve --test",
|
||||
"build:web:serve:prod": "./scripts/build-web.sh --serve --prod",
|
||||
"docker:up": "docker-compose up",
|
||||
"docker:up:test": "npm run build:web:build -- --mode test && docker-compose up test",
|
||||
"docker:up:prod": "npm run build:web:build -- --mode production && docker-compose up production",
|
||||
|
||||
@@ -184,7 +184,7 @@ log_info "Build mode: $BUILD_MODE"
|
||||
log_info "Build type: $BUILD_TYPE"
|
||||
|
||||
# Setup environment for Capacitor build
|
||||
setup_build_env "capacitor"
|
||||
setup_build_env "capacitor" "$BUILD_MODE"
|
||||
|
||||
# Override API servers for Android development
|
||||
if [ "$BUILD_MODE" = "development" ]; then
|
||||
|
||||
@@ -339,7 +339,7 @@ main_electron_build() {
|
||||
fi
|
||||
|
||||
# Setup environment
|
||||
setup_build_env "electron"
|
||||
setup_build_env "electron" "$BUILD_MODE"
|
||||
setup_app_directories
|
||||
load_env_file ".env"
|
||||
|
||||
|
||||
@@ -311,7 +311,7 @@ log_info "Build mode: $BUILD_MODE"
|
||||
log_info "Build type: $BUILD_TYPE"
|
||||
|
||||
# Setup environment for Capacitor build
|
||||
setup_build_env "capacitor"
|
||||
setup_build_env "capacitor" "$BUILD_MODE"
|
||||
|
||||
# Override API servers for iOS development when custom IP is specified
|
||||
if [ "$BUILD_MODE" = "development" ] && [ -n "$CUSTOM_API_IP" ]; then
|
||||
|
||||
@@ -332,7 +332,7 @@ log_info "Serve build: $SERVE_BUILD"
|
||||
validate_web_environment
|
||||
|
||||
# Setup environment for web build
|
||||
setup_build_env "web"
|
||||
setup_build_env "web" "$BUILD_MODE"
|
||||
|
||||
# Setup application directories
|
||||
setup_app_directories
|
||||
|
||||
@@ -174,9 +174,9 @@ validate_env_vars() {
|
||||
# Function to set environment variables for different build types
|
||||
setup_build_env() {
|
||||
local build_type="$1"
|
||||
local production="${2:-false}"
|
||||
local build_mode="${2:-development}"
|
||||
|
||||
log_info "Setting up environment for $build_type build"
|
||||
log_info "Setting up environment for $build_type build (mode: $build_mode)"
|
||||
|
||||
# Get git hash for versioning
|
||||
local git_hash=$(get_git_hash)
|
||||
@@ -204,19 +204,19 @@ setup_build_env() {
|
||||
esac
|
||||
|
||||
# Set API server environment variables based on build mode
|
||||
if [ "$BUILD_MODE" = "development" ]; then
|
||||
if [ "$build_mode" = "development" ]; then
|
||||
# For Capacitor development, use localhost by default
|
||||
# Android builds will override this in build-android.sh
|
||||
export VITE_DEFAULT_ENDORSER_API_SERVER="http://localhost:3000"
|
||||
export VITE_DEFAULT_PARTNER_API_SERVER="http://localhost:3000"
|
||||
log_debug "Development mode: Using localhost for Endorser and Partner APIs"
|
||||
export VITE_DEFAULT_IMAGE_API_SERVER="https://image-api.timesafari.app"
|
||||
elif [ "$BUILD_MODE" = "test" ]; then
|
||||
elif [ "$build_mode" = "test" ]; then
|
||||
export VITE_DEFAULT_ENDORSER_API_SERVER="https://test-api.endorser.ch"
|
||||
export VITE_DEFAULT_PARTNER_API_SERVER="https://test-partner-api.endorser.ch"
|
||||
log_debug "Test mode: Using test Endorser and Partner APIs"
|
||||
export VITE_DEFAULT_IMAGE_API_SERVER="https://image-api.timesafari.app"
|
||||
elif [ "$BUILD_MODE" = "production" ]; then
|
||||
elif [ "$build_mode" = "production" ]; then
|
||||
export VITE_DEFAULT_ENDORSER_API_SERVER="https://api.endorser.ch"
|
||||
export VITE_DEFAULT_PARTNER_API_SERVER="https://partner-api.endorser.ch"
|
||||
log_debug "Production mode: Using production API servers"
|
||||
|
||||
@@ -17,34 +17,40 @@ parse_args "$@"
|
||||
print_header "Environment Variable Test"
|
||||
log_info "Testing environment variable handling at $(date)"
|
||||
|
||||
# Test 1: Capacitor environment
|
||||
log_info "Test 1: Setting up Capacitor environment..."
|
||||
setup_build_env "capacitor"
|
||||
# Test 1: Capacitor environment (development)
|
||||
log_info "Test 1: Setting up Capacitor environment (development mode)..."
|
||||
setup_build_env "capacitor" "development"
|
||||
print_env_vars "VITE_"
|
||||
echo ""
|
||||
|
||||
# Test 2: Web environment
|
||||
log_info "Test 2: Setting up Web environment..."
|
||||
setup_build_env "web"
|
||||
# Test 2: Web environment (development)
|
||||
log_info "Test 2: Setting up Web environment (development mode)..."
|
||||
setup_build_env "web" "development"
|
||||
print_env_vars "VITE_"
|
||||
echo ""
|
||||
|
||||
# Test 3: Production Capacitor environment
|
||||
log_info "Test 3: Setting up Production Capacitor environment..."
|
||||
setup_build_env "capacitor" "true"
|
||||
# Test 3: Capacitor test environment
|
||||
log_info "Test 3: Setting up Capacitor environment (test mode)..."
|
||||
setup_build_env "capacitor" "test"
|
||||
print_env_vars "VITE_"
|
||||
echo ""
|
||||
|
||||
# Test 4: Application directories
|
||||
log_info "Test 4: Setting up application directories..."
|
||||
# Test 4: Capacitor production environment
|
||||
log_info "Test 4: Setting up Capacitor environment (production mode)..."
|
||||
setup_build_env "capacitor" "production"
|
||||
print_env_vars "VITE_"
|
||||
echo ""
|
||||
|
||||
# Test 5: Application directories
|
||||
log_info "Test 5: Setting up application directories..."
|
||||
setup_app_directories
|
||||
|
||||
# Test 5: Load .env file (if it exists)
|
||||
log_info "Test 5: Loading .env file..."
|
||||
# Test 6: Load .env file (if it exists)
|
||||
log_info "Test 6: Loading .env file..."
|
||||
load_env_file ".env"
|
||||
|
||||
# Test 6: Git hash
|
||||
log_info "Test 6: Getting git hash..."
|
||||
# Test 7: Git hash
|
||||
log_info "Test 7: Getting git hash..."
|
||||
GIT_HASH=$(get_git_hash)
|
||||
log_info "Git hash: $GIT_HASH"
|
||||
|
||||
|
||||
103
scripts/type-safety-check.sh
Executable file
103
scripts/type-safety-check.sh
Executable file
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Type Safety Pre-commit Check Script
|
||||
# This script ensures type safety before commits by running linting and type checking
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔍 Running Type Safety Pre-commit Checks..."
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_status() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
# Check if we're in the right directory
|
||||
if [ ! -f "package.json" ]; then
|
||||
print_error "Must run from project root directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 1: Run ESLint with TypeScript rules
|
||||
print_status "Running ESLint TypeScript checks..."
|
||||
if npm run lint > /dev/null 2>&1; then
|
||||
print_status "ESLint passed - no type safety issues found"
|
||||
else
|
||||
print_error "ESLint failed - type safety issues detected"
|
||||
echo ""
|
||||
echo "Running lint with details..."
|
||||
npm run lint
|
||||
echo ""
|
||||
print_error "Please fix the above type safety issues before committing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 2: Run TypeScript type checking
|
||||
print_status "Running TypeScript type checking..."
|
||||
if npm run type-check > /dev/null 2>&1; then
|
||||
print_status "TypeScript compilation passed"
|
||||
else
|
||||
print_error "TypeScript compilation failed"
|
||||
echo ""
|
||||
echo "Running type check with details..."
|
||||
npm run type-check
|
||||
echo ""
|
||||
print_error "Please fix the above TypeScript errors before committing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 3: Check for any remaining 'any' types
|
||||
print_status "Scanning for any remaining 'any' types..."
|
||||
ANY_COUNT=$(grep -r "any" src/ --include="*.ts" --include="*.vue" | grep -v "// eslint-disable" | grep -v "eslint-disable-next-line" | wc -l)
|
||||
|
||||
if [ "$ANY_COUNT" -eq 0 ]; then
|
||||
print_status "No 'any' types found in source code"
|
||||
else
|
||||
print_warning "Found $ANY_COUNT instances of 'any' type usage"
|
||||
echo ""
|
||||
echo "Instances found:"
|
||||
grep -r "any" src/ --include="*.ts" --include="*.vue" | grep -v "// eslint-disable" | grep -v "eslint-disable-next-line" || true
|
||||
echo ""
|
||||
print_error "Please replace 'any' types with proper TypeScript types before committing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 4: Verify database migration status
|
||||
print_status "Checking database migration status..."
|
||||
if grep -r "databaseUtil" src/ --include="*.ts" --include="*.vue" > /dev/null 2>&1; then
|
||||
print_warning "Found databaseUtil imports - ensure migration is complete"
|
||||
echo ""
|
||||
echo "Files with databaseUtil imports:"
|
||||
grep -r "databaseUtil" src/ --include="*.ts" --include="*.vue" | head -5 || true
|
||||
echo ""
|
||||
print_warning "Consider completing database migration to PlatformServiceMixin"
|
||||
else
|
||||
print_status "No databaseUtil imports found - migration appears complete"
|
||||
fi
|
||||
|
||||
# All checks passed
|
||||
echo ""
|
||||
print_status "All type safety checks passed! 🎉"
|
||||
print_status "Your code is ready for commit"
|
||||
echo ""
|
||||
echo "📚 Remember to follow the Type Safety Guidelines:"
|
||||
echo " - docs/typescript-type-safety-guidelines.md"
|
||||
echo " - Use proper error handling patterns"
|
||||
echo " - Leverage existing type definitions"
|
||||
echo " - Run 'npm run lint-fix' for automatic fixes"
|
||||
|
||||
exit 0
|
||||
@@ -288,8 +288,7 @@ export default class ActivityListItem extends Vue {
|
||||
}
|
||||
|
||||
get fetchAmount(): string {
|
||||
const claim =
|
||||
(this.record.fullClaim as any)?.claim || this.record.fullClaim;
|
||||
const claim = this.record.fullClaim;
|
||||
|
||||
const amount = claim.object?.amountOfThisGood
|
||||
? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood)
|
||||
@@ -299,8 +298,7 @@ export default class ActivityListItem extends Vue {
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
const claim =
|
||||
(this.record.fullClaim as any)?.claim || this.record.fullClaim;
|
||||
const claim = this.record.fullClaim;
|
||||
|
||||
return `${claim?.description || ""}`;
|
||||
}
|
||||
|
||||
@@ -171,6 +171,8 @@ export default class DataExportSection extends Vue {
|
||||
* @throws {Error} If export fails
|
||||
*/
|
||||
public async exportDatabase(): Promise<void> {
|
||||
// Note that similar code is in ContactsView.vue exportContactData()
|
||||
|
||||
if (this.isExporting) {
|
||||
return; // Prevent multiple simultaneous exports
|
||||
}
|
||||
|
||||
@@ -622,7 +622,10 @@ export default class GiftedDialog extends Vue {
|
||||
* Handle edit entity request from GiftDetailsStep
|
||||
* @param data - Object containing entityType and currentEntity
|
||||
*/
|
||||
handleEditEntity(data: { entityType: string; currentEntity: any }) {
|
||||
handleEditEntity(data: {
|
||||
entityType: string;
|
||||
currentEntity: { did: string; name: string };
|
||||
}) {
|
||||
this.goBackToStep1(data.entityType);
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from "vue-facing-decorator";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { EndorserRateLimits, ImageRateLimits } from "@/interfaces/limits";
|
||||
|
||||
@Component({
|
||||
name: "UsageLimitsSection",
|
||||
@@ -94,8 +95,8 @@ export default class UsageLimitsSection extends Vue {
|
||||
@Prop({ required: true }) loadingLimits!: boolean;
|
||||
@Prop({ required: true }) limitsMessage!: string;
|
||||
@Prop({ required: false }) activeDid?: string;
|
||||
@Prop({ required: false }) endorserLimits?: any;
|
||||
@Prop({ required: false }) imageLimits?: any;
|
||||
@Prop({ required: false }) endorserLimits?: EndorserRateLimits;
|
||||
@Prop({ required: false }) imageLimits?: ImageRateLimits;
|
||||
@Prop({ required: true }) onRecheckLimits!: () => void;
|
||||
|
||||
mounted() {
|
||||
|
||||
@@ -846,6 +846,12 @@ export const NOTIFY_CONTACTS_ADDED = {
|
||||
message: "They were added.",
|
||||
};
|
||||
|
||||
// Used in: ContactsView.vue (addContact method - export data prompt after contact addition)
|
||||
export const NOTIFY_EXPORT_DATA_PROMPT = {
|
||||
title: "Export Your Data",
|
||||
message: "Would you like to export your contact data as a backup?",
|
||||
};
|
||||
|
||||
// Used in: ContactsView.vue (showCopySelectionsInfo method - info about copying contacts)
|
||||
export const NOTIFY_CONTACT_INFO_COPY = {
|
||||
title: "Info",
|
||||
@@ -1588,7 +1594,7 @@ export function createImageDialogCameraErrorMessage(error: Error): string {
|
||||
|
||||
// Helper function for dynamic upload error messages
|
||||
// Used in: ImageMethodDialog.vue (uploadImage method - dynamic upload error message)
|
||||
export function createImageDialogUploadErrorMessage(error: any): string {
|
||||
export function createImageDialogUploadErrorMessage(error: unknown): string {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const status = error.response?.status;
|
||||
const data = error.response?.data;
|
||||
|
||||
@@ -98,3 +98,81 @@ export interface VerifiableCredentialClaim {
|
||||
credentialSubject: ClaimObject;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database constraint error types for consistent error handling
|
||||
*/
|
||||
export interface DatabaseConstraintError extends Error {
|
||||
name: "ConstraintError";
|
||||
message: string;
|
||||
constraint?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database storage error types for IndexedDB/SQLite operations
|
||||
*/
|
||||
export interface DatabaseStorageError extends Error {
|
||||
name: "StorageError";
|
||||
message: string;
|
||||
code?: string;
|
||||
constraint?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy Dexie error types for migration compatibility
|
||||
*/
|
||||
export interface DexieError extends Error {
|
||||
name: string;
|
||||
message: string;
|
||||
inner?: unknown;
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for database constraint errors
|
||||
*/
|
||||
export function isDatabaseConstraintError(
|
||||
error: unknown,
|
||||
): error is DatabaseConstraintError {
|
||||
return error instanceof Error && error.name === "ConstraintError";
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for database storage errors
|
||||
*/
|
||||
export function isDatabaseStorageError(
|
||||
error: unknown,
|
||||
): error is DatabaseStorageError {
|
||||
return error instanceof Error && error.name === "StorageError";
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for legacy Dexie errors
|
||||
*/
|
||||
export function isDexieError(error: unknown): error is DexieError {
|
||||
return (
|
||||
error instanceof Error &&
|
||||
(error.name === "DexieError" ||
|
||||
error.message.includes("Key already exists in the object store") ||
|
||||
error.message.includes("ConstraintError"))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified error type for database operations
|
||||
*/
|
||||
export type DatabaseError =
|
||||
| DatabaseConstraintError
|
||||
| DatabaseStorageError
|
||||
| DexieError;
|
||||
|
||||
/**
|
||||
* Type guard for any database error
|
||||
*/
|
||||
export function isDatabaseError(error: unknown): error is DatabaseError {
|
||||
return (
|
||||
isDatabaseConstraintError(error) ||
|
||||
isDatabaseStorageError(error) ||
|
||||
isDexieError(error)
|
||||
);
|
||||
}
|
||||
|
||||
99
src/services/QRNavigationService.ts
Normal file
99
src/services/QRNavigationService.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { PlatformServiceFactory } from "./PlatformServiceFactory";
|
||||
import { PlatformService } from "./PlatformService";
|
||||
import { logger } from "@/utils/logger";
|
||||
|
||||
/**
|
||||
* QR Navigation Service
|
||||
*
|
||||
* Handles platform-specific routing logic for QR scanning operations.
|
||||
* Removes coupling between views and routing logic by centralizing
|
||||
* navigation decisions based on platform capabilities.
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
*/
|
||||
export class QRNavigationService {
|
||||
private static instance: QRNavigationService | null = null;
|
||||
private platformService: PlatformService;
|
||||
|
||||
private constructor() {
|
||||
this.platformService = PlatformServiceFactory.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get singleton instance of QRNavigationService
|
||||
*/
|
||||
public static getInstance(): QRNavigationService {
|
||||
if (!QRNavigationService.instance) {
|
||||
QRNavigationService.instance = new QRNavigationService();
|
||||
}
|
||||
return QRNavigationService.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate QR scanner route based on platform
|
||||
*
|
||||
* @returns Object with route name and parameters for QR scanning
|
||||
*/
|
||||
public getQRScannerRoute(): {
|
||||
name: string;
|
||||
params?: Record<string, string | number>;
|
||||
} {
|
||||
const isCapacitor = this.platformService.isCapacitor();
|
||||
|
||||
logger.debug("QR Navigation - Platform detection:", {
|
||||
isCapacitor,
|
||||
platform: this.platformService.getCapabilities(),
|
||||
});
|
||||
|
||||
if (isCapacitor) {
|
||||
// Use native scanner on mobile platforms
|
||||
return { name: "contact-qr-scan-full" };
|
||||
} else {
|
||||
// Use web scanner on other platforms
|
||||
return { name: "contact-qr" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate QR display route based on platform
|
||||
*
|
||||
* @returns Object with route name and parameters for QR display
|
||||
*/
|
||||
public getQRDisplayRoute(): {
|
||||
name: string;
|
||||
params?: Record<string, string | number>;
|
||||
} {
|
||||
const isCapacitor = this.platformService.isCapacitor();
|
||||
|
||||
logger.debug("QR Navigation - Display route detection:", {
|
||||
isCapacitor,
|
||||
platform: this.platformService.getCapabilities(),
|
||||
});
|
||||
|
||||
if (isCapacitor) {
|
||||
// Use dedicated display view on mobile
|
||||
return { name: "contact-qr-scan-show" };
|
||||
} else {
|
||||
// Use combined view on web
|
||||
return { name: "contact-qr" };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if native QR scanning is available on current platform
|
||||
*
|
||||
* @returns true if native scanning is available, false otherwise
|
||||
*/
|
||||
public isNativeScanningAvailable(): boolean {
|
||||
return this.platformService.isCapacitor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get platform capabilities for QR operations
|
||||
*
|
||||
* @returns Platform capabilities object
|
||||
*/
|
||||
public getPlatformCapabilities() {
|
||||
return this.platformService.getCapabilities();
|
||||
}
|
||||
}
|
||||
@@ -714,8 +714,16 @@ export default class ContactQRScanShow extends Vue {
|
||||
|
||||
// Add new contact
|
||||
// @ts-expect-error because we're just using the value to store to the DB
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
contact.contactMethods = JSON.stringify(
|
||||
(this as any)._parseJsonField(contact.contactMethods, []),
|
||||
(
|
||||
this as {
|
||||
_parseJsonField: (
|
||||
value: unknown,
|
||||
defaultValue: unknown[],
|
||||
) => unknown[];
|
||||
}
|
||||
)._parseJsonField(contact.contactMethods, []),
|
||||
);
|
||||
await this.$insertContact(contact);
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ import * as R from "ramda";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
// Capacitor import removed - using PlatformService instead
|
||||
|
||||
import QuickNav from "../components/QuickNav.vue";
|
||||
import EntityIcon from "../components/EntityIcon.vue";
|
||||
@@ -165,13 +165,18 @@ import { GiveSummaryRecord } from "@/interfaces/records";
|
||||
import { UserInfo } from "@/interfaces/common";
|
||||
import { VerifiableCredential } from "@/interfaces/claims-result";
|
||||
import * as libsUtil from "../libs/util";
|
||||
import { generateSaveAndActivateIdentity } from "../libs/util";
|
||||
import {
|
||||
generateSaveAndActivateIdentity,
|
||||
contactsToExportJson,
|
||||
} from "../libs/util";
|
||||
import { logger } from "../utils/logger";
|
||||
// No longer needed - using PlatformServiceMixin methods
|
||||
// import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { isDatabaseError } from "@/interfaces/common";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||
import { APP_SERVER } from "@/constants/app";
|
||||
import { QRNavigationService } from "@/services/QRNavigationService";
|
||||
import {
|
||||
NOTIFY_CONTACT_NO_INFO,
|
||||
NOTIFY_CONTACTS_ADD_ERROR,
|
||||
@@ -197,6 +202,7 @@ import {
|
||||
NOTIFY_REGISTRATION_ERROR_FALLBACK,
|
||||
NOTIFY_REGISTRATION_ERROR_GENERIC,
|
||||
NOTIFY_VISIBILITY_ERROR_FALLBACK,
|
||||
NOTIFY_EXPORT_DATA_PROMPT,
|
||||
getRegisterPersonSuccessMessage,
|
||||
getVisibilitySuccessMessage,
|
||||
getGivesRetrievalErrorMessage,
|
||||
@@ -376,7 +382,11 @@ export default class ContactsView extends Vue {
|
||||
"",
|
||||
async (name) => {
|
||||
await this.addContact({
|
||||
did: (registration.vc.credentialSubject.agent as any).identifier,
|
||||
did: (
|
||||
registration.vc.credentialSubject.agent as {
|
||||
identifier: string;
|
||||
}
|
||||
).identifier,
|
||||
name: name,
|
||||
registered: true,
|
||||
});
|
||||
@@ -387,7 +397,11 @@ export default class ContactsView extends Vue {
|
||||
async () => {
|
||||
// on cancel, will still add the contact
|
||||
await this.addContact({
|
||||
did: (registration.vc.credentialSubject.agent as any).identifier,
|
||||
did: (
|
||||
registration.vc.credentialSubject.agent as {
|
||||
identifier: string;
|
||||
}
|
||||
).identifier,
|
||||
name: "(person who invited you)",
|
||||
registered: true,
|
||||
});
|
||||
@@ -396,8 +410,7 @@ export default class ContactsView extends Vue {
|
||||
this.showOnboardingInfo();
|
||||
},
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
const fullError = "Error redeeming invite: " + errorStringForLog(error);
|
||||
this.$logAndConsole(fullError, true);
|
||||
let message = "Got an error sending the invite.";
|
||||
@@ -784,6 +797,9 @@ export default class ContactsView extends Vue {
|
||||
|
||||
// Show success notification
|
||||
this.notify.success(addedMessage);
|
||||
|
||||
// Show export data prompt after successful contact addition
|
||||
await this.showExportDataPrompt();
|
||||
} catch (err) {
|
||||
this.handleContactAddError(err);
|
||||
}
|
||||
@@ -881,20 +897,21 @@ export default class ContactsView extends Vue {
|
||||
/**
|
||||
* Handle errors during contact addition
|
||||
*/
|
||||
private handleContactAddError(err: any): void {
|
||||
private handleContactAddError(err: unknown): void {
|
||||
const fullError =
|
||||
"Error when adding contact to storage: " + errorStringForLog(err);
|
||||
this.$logAndConsole(fullError, true);
|
||||
|
||||
let message = NOTIFY_CONTACT_IMPORT_ERROR.message;
|
||||
if (
|
||||
(err as any).message?.indexOf("Key already exists in the object store.") >
|
||||
-1
|
||||
) {
|
||||
message = NOTIFY_CONTACT_IMPORT_CONFLICT.message;
|
||||
}
|
||||
if ((err as any).name === "ConstraintError") {
|
||||
message += " " + NOTIFY_CONTACT_IMPORT_CONSTRAINT.message;
|
||||
|
||||
// Use type-safe error checking with our new type guards
|
||||
if (isDatabaseError(err)) {
|
||||
if (err.message.includes("Key already exists in the object store")) {
|
||||
message = NOTIFY_CONTACT_IMPORT_CONFLICT.message;
|
||||
}
|
||||
if (err.name === "ConstraintError") {
|
||||
message += " " + NOTIFY_CONTACT_IMPORT_CONSTRAINT.message;
|
||||
}
|
||||
}
|
||||
|
||||
this.notify.error(message, TIMEOUTS.LONG);
|
||||
@@ -1246,19 +1263,76 @@ export default class ContactsView extends Vue {
|
||||
|
||||
/**
|
||||
* Handle QR code button click - route to appropriate scanner
|
||||
* Uses native scanner on mobile platforms, web scanner otherwise
|
||||
* Uses QRNavigationService to determine scanner type and route
|
||||
*/
|
||||
|
||||
public handleQRCodeClick() {
|
||||
this.$logAndConsole(
|
||||
"[ContactsView] handleQRCodeClick method called",
|
||||
false,
|
||||
);
|
||||
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
this.$router.push({ name: "contact-qr-scan-full" });
|
||||
} else {
|
||||
this.$router.push({ name: "contact-qr" });
|
||||
const qrNavigationService = QRNavigationService.getInstance();
|
||||
const route = qrNavigationService.getQRScannerRoute();
|
||||
|
||||
this.$router.push(route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show export data prompt after adding a contact
|
||||
* Prompts user to export their contact data as a backup
|
||||
*/
|
||||
private async showExportDataPrompt(): Promise<void> {
|
||||
setTimeout(() => {
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: NOTIFY_EXPORT_DATA_PROMPT.title,
|
||||
text: NOTIFY_EXPORT_DATA_PROMPT.message,
|
||||
onYes: async () => {
|
||||
await this.exportContactData();
|
||||
},
|
||||
yesText: "Export Data",
|
||||
onNo: async () => {
|
||||
// User chose not to export - no action needed
|
||||
},
|
||||
noText: "Not Now",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}, 1000); // Small delay to ensure success notification is shown first
|
||||
}
|
||||
|
||||
/**
|
||||
* Export contact data to JSON file
|
||||
* Uses platform service to handle platform-specific export logic
|
||||
*/
|
||||
private async exportContactData(): Promise<void> {
|
||||
// Note that similar code is in DataExportSection.vue exportDatabase()
|
||||
try {
|
||||
// Fetch all contacts from database
|
||||
const allContacts = await this.$contacts();
|
||||
|
||||
// Convert contacts to export format
|
||||
const exportData = contactsToExportJson(allContacts);
|
||||
const jsonStr = JSON.stringify(exportData, null, 2);
|
||||
|
||||
// Generate filename with current date
|
||||
const today = new Date();
|
||||
const dateString = today.toISOString().split("T")[0]; // YYYY-MM-DD format
|
||||
const fileName = `timesafari-backup-contacts-${dateString}.json`;
|
||||
|
||||
// Use platform service to handle export
|
||||
await this.platformService.writeAndShareFile(fileName, jsonStr);
|
||||
|
||||
this.notify.success(
|
||||
"Contact export completed successfully. Check your downloads or share dialog.",
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("Export Error:", error);
|
||||
this.notify.error(
|
||||
`There was an error exporting the data: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +458,9 @@ export default class DiscoverView extends Vue {
|
||||
if (this.isLocalActive) {
|
||||
await this.searchLocal();
|
||||
} else if (this.isMappedActive) {
|
||||
const mapRef = this.$refs.projectMap as any;
|
||||
const mapRef = this.$refs.projectMap as {
|
||||
leafletObject: L.Map;
|
||||
};
|
||||
this.requestTiles(mapRef.leafletObject); // not ideal because I found this from experimentation, not documentation
|
||||
} else {
|
||||
await this.searchAll();
|
||||
@@ -518,11 +520,11 @@ export default class DiscoverView extends Vue {
|
||||
throw JSON.stringify(results);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
logger.error("Error with search all: " + errorStringForLog(e));
|
||||
this.notify.error(
|
||||
e.userMessage || NOTIFY_DISCOVER_SEARCH_ERROR.message,
|
||||
(e as { userMessage?: string })?.userMessage ||
|
||||
NOTIFY_DISCOVER_SEARCH_ERROR.message,
|
||||
TIMEOUTS.LONG,
|
||||
);
|
||||
} finally {
|
||||
|
||||
@@ -565,22 +565,22 @@
|
||||
<h2 class="text-xl font-semibold">What app version is this?</h2>
|
||||
<p>{{ package.version }} ({{ commitHash }})</p>
|
||||
|
||||
<div v-if="Capacitor.isNativePlatform()">
|
||||
<div v-if="isCapacitor">
|
||||
<h2 class="text-xl font-semibold">
|
||||
Do I have the latest version?
|
||||
</h2>
|
||||
<p v-if="Capacitor.getPlatform() === 'ios'">
|
||||
<p v-if="capabilities.isIOS">
|
||||
<a href="https://apps.apple.com/us/app/time-safari/id6742664907" target="_blank" class="text-blue-500">
|
||||
Check the App Store.
|
||||
</a>
|
||||
</p>
|
||||
<p v-else-if="Capacitor.getPlatform() === 'android'">
|
||||
<p v-else-if="!capabilities.isIOS && capabilities.isMobile">
|
||||
<a href="https://play.google.com/store/apps/details?id=app.timesafari.app" target="_blank" class="text-blue-500">
|
||||
Check the Play Store.
|
||||
</a>
|
||||
</p>
|
||||
<p v-else>
|
||||
Sorry, your platform of '{{ Capacitor.getPlatform() }}' is not recognized.
|
||||
Sorry, your platform is not recognized.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -592,12 +592,13 @@
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { Router } from "vue-router";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
// Capacitor import removed - using QRNavigationService instead
|
||||
|
||||
import * as Package from "../../package.json";
|
||||
import QuickNav from "../components/QuickNav.vue";
|
||||
import { APP_SERVER } from "../constants/app";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { QRNavigationService } from "@/services/QRNavigationService";
|
||||
|
||||
/**
|
||||
* HelpView.vue - Comprehensive Help System Component
|
||||
@@ -643,7 +644,7 @@ export default class HelpView extends Vue {
|
||||
showVerifiable = false;
|
||||
|
||||
APP_SERVER = APP_SERVER;
|
||||
Capacitor = Capacitor;
|
||||
// Capacitor reference removed - using QRNavigationService instead
|
||||
|
||||
// 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.
|
||||
@@ -711,11 +712,10 @@ export default class HelpView extends Vue {
|
||||
* @private
|
||||
*/
|
||||
private handleQRCodeClick(): void {
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
this.$router.push({ name: "contact-qr-scan-full" });
|
||||
} else {
|
||||
this.$router.push({ name: "contact-qr" });
|
||||
}
|
||||
const qrNavigationService = QRNavigationService.getInstance();
|
||||
const route = qrNavigationService.getQRScannerRoute();
|
||||
|
||||
this.$router.push(route);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -234,7 +234,9 @@ export default class IdentitySwitcherView extends Vue {
|
||||
{
|
||||
did,
|
||||
settingsKeys: Object.keys(newSettings).filter(
|
||||
(k) => (newSettings as any)[k] !== undefined,
|
||||
(k) =>
|
||||
k in newSettings &&
|
||||
newSettings[k as keyof typeof newSettings] !== undefined,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -221,10 +221,11 @@ export default class ImportAccountView extends Vue {
|
||||
|
||||
this.notify.success("Account imported successfully!", TIMEOUTS.STANDARD);
|
||||
this.$router.push({ name: "account" });
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
this.$logError("Import failed: " + error);
|
||||
this.notify.error(
|
||||
error.message || "Failed to import account.",
|
||||
(error instanceof Error ? error.message : String(error)) ||
|
||||
"Failed to import account.",
|
||||
TIMEOUTS.LONG,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ export default class OnboardMeetingListView extends Vue {
|
||||
if (response2.data?.data) {
|
||||
this.meetings = response2.data.data;
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
this.$logAndConsole(
|
||||
"Error fetching meetings: " + errorStringForLog(error),
|
||||
true,
|
||||
|
||||
@@ -345,7 +345,9 @@ export default class OnboardMeetingView extends Vue {
|
||||
}
|
||||
|
||||
async created() {
|
||||
this.notify = createNotifyHelpers(this.$notify as any);
|
||||
this.notify = createNotifyHelpers(
|
||||
this.$notify as Parameters<typeof createNotifyHelpers>[0],
|
||||
);
|
||||
const settings = await this.$accountSettings();
|
||||
this.activeDid = settings?.activeDid || "";
|
||||
this.apiServer = settings?.apiServer || "";
|
||||
@@ -419,7 +421,7 @@ export default class OnboardMeetingView extends Vue {
|
||||
} else {
|
||||
this.newOrUpdatedMeetingInputs = this.blankMeeting();
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
this.newOrUpdatedMeetingInputs = this.blankMeeting();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
import { AxiosRequestConfig } from "axios";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { Router } from "vue-router";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
// Capacitor import removed - using QRNavigationService instead
|
||||
|
||||
import { NotificationIface } from "../constants/app";
|
||||
import EntityIcon from "../components/EntityIcon.vue";
|
||||
@@ -281,6 +281,7 @@ import { OnboardPage, iconForUnitCode } from "../libs/util";
|
||||
import { logger } from "../utils/logger";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||
import { QRNavigationService } from "@/services/QRNavigationService";
|
||||
import {
|
||||
NOTIFY_NO_ACCOUNT_ERROR,
|
||||
NOTIFY_PROJECT_LOAD_ERROR,
|
||||
@@ -464,8 +465,10 @@ export default class ProjectsView extends Vue {
|
||||
);
|
||||
this.notify.error(NOTIFY_PROJECT_LOAD_ERROR.message, TIMEOUTS.LONG);
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error("Got error loading plans:", error.message || error);
|
||||
} catch (error: unknown) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
logger.error("Got error loading plans:", errorMessage);
|
||||
this.notify.error(NOTIFY_PROJECT_LOAD_ERROR.message, TIMEOUTS.LONG);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
@@ -578,8 +581,10 @@ export default class ProjectsView extends Vue {
|
||||
);
|
||||
this.notify.error(NOTIFY_OFFERS_LOAD_ERROR.message, TIMEOUTS.LONG);
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error("Got error loading offers:", error.message || error);
|
||||
} catch (error: unknown) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
logger.error("Got error loading offers:", errorMessage);
|
||||
this.notify.error(NOTIFY_OFFERS_FETCH_ERROR.message, TIMEOUTS.LONG);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
@@ -757,11 +762,10 @@ export default class ProjectsView extends Vue {
|
||||
* - Web-based QR interface for browser environments
|
||||
*/
|
||||
private handleQRCodeClick() {
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
this.$router.push({ name: "contact-qr-scan-full" });
|
||||
} else {
|
||||
this.$router.push({ name: "contact-qr" });
|
||||
}
|
||||
const qrNavigationService = QRNavigationService.getInstance();
|
||||
const route = qrNavigationService.getQRScannerRoute();
|
||||
|
||||
this.$router.push(route);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user