Compare commits

...

8 Commits

Author SHA1 Message Date
Matthew Raymer 80a268ffdc feat(test-app): add debug component and optimize build configuration 6 days ago
Matthew Raymer 29fba0310d refactor(test-app): convert secondary view components to Class API 6 days ago
Matthew Raymer 22a52cc5f0 refactor(test-app): convert main view components to Class API 6 days ago
Matthew Raymer 6c21a67088 refactor(test-app): convert dialog and overlay components to Class API 6 days ago
Matthew Raymer 8c3825363e refactor(test-app): convert UI components to vue-facing-decorator Class API 6 days ago
Matthew Raymer eb0ca324d7 refactor(test-app): convert core app structure to vue-facing-decorator 6 days ago
Matthew Raymer 7805aef198 feat(test-app): configure TypeScript for vue-facing-decorator compatibility 6 days ago
Matthew Raymer 791a0635ba fix(plugin): resolve package.json export warnings and dependency conflicts 6 days ago
  1. 8
      package.json
  2. 296
      test-apps/daily-notification-test/package-lock.json
  3. 5
      test-apps/daily-notification-test/package.json
  4. 94
      test-apps/daily-notification-test/src/App.vue
  5. 29
      test-apps/daily-notification-test/src/components/cards/ActionCard.vue
  6. 29
      test-apps/daily-notification-test/src/components/cards/StatusCard.vue
  7. 26
      test-apps/daily-notification-test/src/components/layout/AppFooter.vue
  8. 50
      test-apps/daily-notification-test/src/components/layout/AppHeader.vue
  9. 31
      test-apps/daily-notification-test/src/components/ui/ErrorDialog.vue
  10. 10
      test-apps/daily-notification-test/src/components/ui/LoadingOverlay.vue
  11. 57
      test-apps/daily-notification-test/src/env.d.ts
  12. 7
      test-apps/daily-notification-test/src/globals.d.ts
  13. 1
      test-apps/daily-notification-test/src/main.ts
  14. 4
      test-apps/daily-notification-test/src/router/index.ts
  15. 43
      test-apps/daily-notification-test/src/types/global.d.ts
  16. 5
      test-apps/daily-notification-test/src/views/HistoryView.vue
  17. 241
      test-apps/daily-notification-test/src/views/HomeView.vue
  18. 258
      test-apps/daily-notification-test/src/views/HomeViewSimple.vue
  19. 223
      test-apps/daily-notification-test/src/views/LogsView.vue
  20. 5
      test-apps/daily-notification-test/src/views/NotFoundView.vue
  21. 5
      test-apps/daily-notification-test/src/views/NotificationsView.vue
  22. 21
      test-apps/daily-notification-test/src/views/ScheduleView.vue
  23. 5
      test-apps/daily-notification-test/src/views/SettingsView.vue
  24. 5
      test-apps/daily-notification-test/src/views/StatusView.vue
  25. 12
      test-apps/daily-notification-test/tsconfig.app.json
  26. 14
      test-apps/daily-notification-test/vite.config.ts

8
package.json

@ -55,14 +55,14 @@
}, },
"exports": { "exports": {
".": { ".": {
"types": "./dist/esm/index.d.ts",
"import": "./dist/esm/index.js", "import": "./dist/esm/index.js",
"require": "./dist/plugin.js", "require": "./dist/plugin.js"
"types": "./dist/esm/index.d.ts"
}, },
"./web": { "./web": {
"types": "./dist/esm/web/index.d.ts",
"import": "./dist/esm/web/index.js", "import": "./dist/esm/web/index.js",
"require": "./dist/web/index.js", "require": "./dist/web/index.js"
"types": "./dist/esm/web/index.d.ts"
} }
}, },
"sideEffects": false, "sideEffects": false,

296
test-apps/daily-notification-test/package-lock.json

@ -14,7 +14,7 @@
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"vue": "^3.5.22", "vue": "^3.5.22",
"vue-facing-decorator": "^4.0.1", "vue-facing-decorator": "^3.0.4",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
}, },
"devDependencies": { "devDependencies": {
@ -22,12 +22,15 @@
"@types/node": "^22.18.6", "@types/node": "^22.18.6",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.1",
"@vue/eslint-config-typescript": "^14.6.0", "@vue/eslint-config-typescript": "^14.6.0",
"@vue/runtime-core": "^3.5.22",
"@vue/tsconfig": "^0.8.1", "@vue/tsconfig": "^0.8.1",
"eslint": "^9.33.0", "eslint": "^9.33.0",
"eslint-plugin-vue": "~10.4.0", "eslint-plugin-vue": "~10.4.0",
"jiti": "^2.5.1", "jiti": "^2.5.1",
"npm-run-all2": "^8.0.4", "npm-run-all2": "^8.0.4",
"reflect-metadata": "^0.2.2",
"typescript": "~5.9.0", "typescript": "~5.9.0",
"unplugin-vue-components": "^29.1.0",
"vite": "^7.1.7", "vite": "^7.1.7",
"vite-plugin-vue-devtools": "^8.0.2", "vite-plugin-vue-devtools": "^8.0.2",
"vue-tsc": "^3.1.0" "vue-tsc": "^3.1.0"
@ -2687,6 +2690,20 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@ -2757,6 +2774,19 @@
"node": ">=0.6" "node": ">=0.6"
} }
}, },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/birpc": { "node_modules/birpc": {
"version": "2.6.1", "version": "2.6.1",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.6.1.tgz", "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.6.1.tgz",
@ -2914,6 +2944,44 @@
"url": "https://github.com/chalk/chalk?sponsor=1" "url": "https://github.com/chalk/chalk?sponsor=1"
} }
}, },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/chokidar/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/chownr": { "node_modules/chownr": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
@ -2957,6 +3025,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/confbox": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
"dev": true,
"license": "MIT"
},
"node_modules/convert-source-map": { "node_modules/convert-source-map": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@ -3476,10 +3551,11 @@
"url": "https://github.com/sindresorhus/execa?sponsor=1" "url": "https://github.com/sindresorhus/execa?sponsor=1"
} }
}, },
"node_modules/facing-metadata": { "node_modules/exsolve": {
"version": "2.0.3", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/facing-metadata/-/facing-metadata-2.0.3.tgz", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
"integrity": "sha512-cznIFYEbjy5LCph7aolYr6+5B1PzJAPsW4q3xdsmJDdZgsepFCBq4vZkBgCGt2LZZKNwji0Qt1xU24JOqg/6xQ==", "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
@ -3875,6 +3951,19 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0" "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
} }
}, },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-docker": { "node_modules/is-docker": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
@ -4164,6 +4253,24 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/local-pkg": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
"integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==",
"dev": true,
"license": "MIT",
"dependencies": {
"mlly": "^1.7.4",
"pkg-types": "^2.3.0",
"quansync": "^0.2.11"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/locate-path": { "node_modules/locate-path": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -4313,6 +4420,38 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/mlly": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
"integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.15.0",
"pathe": "^2.0.3",
"pkg-types": "^1.3.1",
"ufo": "^1.6.1"
}
},
"node_modules/mlly/node_modules/confbox": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
"dev": true,
"license": "MIT"
},
"node_modules/mlly/node_modules/pkg-types": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"confbox": "^0.1.8",
"mlly": "^1.7.4",
"pathe": "^2.0.1"
}
},
"node_modules/mrmime": { "node_modules/mrmime": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
@ -4393,6 +4532,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/npm-normalize-package-bin": { "node_modules/npm-normalize-package-bin": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz",
@ -4756,6 +4905,18 @@
} }
} }
}, },
"node_modules/pkg-types": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
"dev": true,
"license": "MIT",
"dependencies": {
"confbox": "^0.2.2",
"exsolve": "^1.0.7",
"pathe": "^2.0.3"
}
},
"node_modules/plist": { "node_modules/plist": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
@ -4870,6 +5031,23 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/quansync": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/antfu"
},
{
"type": "individual",
"url": "https://github.com/sponsors/sxzz"
}
],
"license": "MIT"
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -4919,6 +5097,26 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/reflect-metadata": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/resolve-from": { "node_modules/resolve-from": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@ -5470,6 +5668,13 @@
"typescript": ">=4.8.4 <6.0.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/ufo": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
"integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
"dev": true,
"license": "MIT"
},
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
@ -5498,6 +5703,22 @@
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
}, },
"node_modules/unplugin": {
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz",
"integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.5",
"acorn": "^8.15.0",
"picomatch": "^4.0.3",
"webpack-virtual-modules": "^0.6.2"
},
"engines": {
"node": ">=18.12.0"
}
},
"node_modules/unplugin-utils": { "node_modules/unplugin-utils": {
"version": "0.3.1", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz",
@ -5528,6 +5749,55 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/unplugin-vue-components": {
"version": "29.1.0",
"resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-29.1.0.tgz",
"integrity": "sha512-z/9ACPXth199s9aCTCdKZAhe5QGOpvzJYP+Hkd0GN1/PpAmsu+W3UlRY3BJAewPqQxh5xi56+Og6mfiCV1Jzpg==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.6.0",
"debug": "^4.4.3",
"local-pkg": "^1.1.2",
"magic-string": "^0.30.19",
"mlly": "^1.8.0",
"tinyglobby": "^0.2.15",
"unplugin": "^2.3.10",
"unplugin-utils": "^0.3.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@babel/parser": "^7.15.8",
"@nuxt/kit": "^3.2.2 || ^4.0.0",
"vue": "2 || 3"
},
"peerDependenciesMeta": {
"@babel/parser": {
"optional": true
},
"@nuxt/kit": {
"optional": true
}
}
},
"node_modules/unplugin/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/untildify": { "node_modules/untildify": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
@ -5901,13 +6171,10 @@
} }
}, },
"node_modules/vue-facing-decorator": { "node_modules/vue-facing-decorator": {
"version": "4.0.1", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/vue-facing-decorator/-/vue-facing-decorator-4.0.1.tgz", "resolved": "https://registry.npmjs.org/vue-facing-decorator/-/vue-facing-decorator-3.0.4.tgz",
"integrity": "sha512-zBvPoiXVVCOSctoKmcYUHFu2qu0AGYAZOfT6LnPqcrSSOpElTJTd21cOIrGrnA1rYwzlp8jhQLmlcBKv1ETNRA==", "integrity": "sha512-Lk90PevJllB6qRRdLvLFjATZrv00nof1Ob6afavKL7Pc7V3eEin3vhdvEDRORdWKVvNoXhJbHejngWVuT0Pt0g==",
"license": "MIT", "license": "MIT",
"dependencies": {
"facing-metadata": "^2.0.0"
},
"peerDependencies": { "peerDependencies": {
"vue": "^3.0.0" "vue": "^3.0.0"
} }
@ -5950,6 +6217,13 @@
"typescript": ">=5.0.0" "typescript": ">=5.0.0"
} }
}, },
"node_modules/webpack-virtual-modules": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
"dev": true,
"license": "MIT"
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

5
test-apps/daily-notification-test/package.json

@ -21,7 +21,7 @@
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"vue": "^3.5.22", "vue": "^3.5.22",
"vue-facing-decorator": "^4.0.1", "vue-facing-decorator": "^3.0.4",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
}, },
"devDependencies": { "devDependencies": {
@ -29,12 +29,15 @@
"@types/node": "^22.18.6", "@types/node": "^22.18.6",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.1",
"@vue/eslint-config-typescript": "^14.6.0", "@vue/eslint-config-typescript": "^14.6.0",
"@vue/runtime-core": "^3.5.22",
"@vue/tsconfig": "^0.8.1", "@vue/tsconfig": "^0.8.1",
"eslint": "^9.33.0", "eslint": "^9.33.0",
"eslint-plugin-vue": "~10.4.0", "eslint-plugin-vue": "~10.4.0",
"jiti": "^2.5.1", "jiti": "^2.5.1",
"npm-run-all2": "^8.0.4", "npm-run-all2": "^8.0.4",
"reflect-metadata": "^0.2.2",
"typescript": "~5.9.0", "typescript": "~5.9.0",
"unplugin-vue-components": "^29.1.0",
"vite": "^7.1.7", "vite": "^7.1.7",
"vite-plugin-vue-devtools": "^8.0.2", "vite-plugin-vue-devtools": "^8.0.2",
"vue-tsc": "^3.1.0" "vue-tsc": "^3.1.0"

94
test-apps/daily-notification-test/src/App.vue

@ -34,95 +34,19 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts">
import { onMounted, computed } from 'vue' import { Vue, Component, toNative } from 'vue-facing-decorator'
import { Capacitor } from '@capacitor/core'
import AppHeader from '@/components/layout/AppHeader.vue'
import AppFooter from '@/components/layout/AppFooter.vue'
import LoadingOverlay from '@/components/ui/LoadingOverlay.vue'
import ErrorDialog from '@/components/ui/ErrorDialog.vue'
import { useAppStore } from '@/stores/app'
const appStore = useAppStore() @Component
class App extends Vue {
isLoading = false
errorMessage = ''
const isLoading = computed(() => appStore.isLoading) clearError() {
const errorMessage = computed(() => appStore.errorMessage) this.errorMessage = ''
const initializeApp = async (): Promise<void> => {
try {
appStore.setLoading(true)
// Initialize platform detection
const platform = Capacitor.getPlatform()
const isNative = Capacitor.isNativePlatform()
appStore.setPlatform(platform, isNative)
console.log('🚀 Daily Notification Test App Started')
console.log('📱 Platform:', platform)
console.log('🔧 Native Platform:', isNative)
// Check if DailyNotification plugin is available
if (isNative) {
// Wait a bit for Capacitor to fully initialize
await new Promise(resolve => setTimeout(resolve, 100))
// Try multiple ways to access the plugin
const plugin = (window as any).DailyNotification ||
(window as any).Capacitor?.Plugins?.DailyNotification ||
(window as any).Capacitor?.Plugins?.['DailyNotification']
console.log('🔍 Plugin detection debug:')
console.log(' - window.DailyNotification:', (window as any).DailyNotification)
console.log(' - Capacitor.Plugins:', (window as any).Capacitor?.Plugins)
console.log(' - Available plugins:', Object.keys((window as any).Capacitor?.Plugins || {}))
if (plugin) {
console.log('✅ DailyNotification plugin available')
// Initialize plugin status check
await checkPluginStatus()
} else {
console.warn('⚠️ DailyNotification plugin not available')
// Don't show error dialog - just log the issue
console.log('🔍 Plugin not found, but continuing without error dialog')
}
} else {
console.log('🌐 Running in web mode - plugin not available')
}
} catch (error) {
console.error('❌ App initialization failed:', error)
appStore.setError('Failed to initialize app: ' + (error as Error).message)
} finally {
appStore.setLoading(false)
}
}
const checkPluginStatus = async (): Promise<void> => {
try {
// Try multiple ways to access the plugin
const plugin = (window as any).DailyNotification ||
(window as any).Capacitor?.Plugins?.DailyNotification ||
(window as any).Capacitor?.Plugins?.['DailyNotification']
if (plugin) {
const status = await plugin.checkStatus()
appStore.setNotificationStatus(status)
console.log('📊 Plugin status:', status)
} else {
console.warn('⚠️ Plugin not accessible for status check')
} }
} catch (error) {
console.error('❌ Plugin status check failed:', error)
} }
} export default toNative(App)
const clearError = (): void => {
appStore.clearError()
}
onMounted(() => {
initializeApp()
})
</script> </script>
<style scoped> <style scoped>

29
test-apps/daily-notification-test/src/components/cards/ActionCard.vue

@ -25,27 +25,22 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts">
interface Props { import { Vue, Component, Prop, toNative } from 'vue-facing-decorator'
icon: string
title: string
description: string
loading?: boolean
}
const props = withDefaults(defineProps<Props>(), {
loading: false
})
const emit = defineEmits<{ @Component
click: [] class ActionCard extends Vue {
}>() @Prop({ type: String, required: true }) title!: string
@Prop({ type: String }) icon?: string
@Prop({ type: String }) description?: string
@Prop({ type: Boolean, default: false }) loading!: boolean
const handleClick = (): void => { handleClick() {
if (!props.loading) { if (!this.loading) this.$emit('click')
emit('click')
} }
} }
export default toNative(ActionCard)
</script> </script>
<style scoped> <style scoped>

29
test-apps/daily-notification-test/src/components/cards/StatusCard.vue

@ -37,26 +37,27 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Prop } from 'vue-facing-decorator' import { Vue, Component, toNative } from 'vue-facing-decorator'
interface StatusItem { type StatusItem = { label: string; value: string; status: string }
label: string
value: string
status: 'success' | 'error' | 'warning' | 'info'
}
@Component @Component
export default class StatusCard extends Vue { class StatusCard extends Vue {
@Prop({ required: true }) isRefreshing = false
statusItems!: StatusItem[] statusItems: StatusItem[] = []
@Prop({ default: false })
isRefreshing!: boolean
refreshStatus(): void { async refreshStatus() {
this.$emit('refresh') this.isRefreshing = true
try {
// TODO: real fetch
this.statusItems = [{ label: 'Plugin', value: 'OK', status: 'success' }]
} finally {
this.isRefreshing = false
} }
} }
}
export default toNative(StatusCard)
</script> </script>
<style scoped> <style scoped>

26
test-apps/daily-notification-test/src/components/layout/AppFooter.vue

@ -25,29 +25,15 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-facing-decorator' import { Vue, Component, toNative } from 'vue-facing-decorator'
import { useAppStore } from '@/stores/app'
@Component @Component
export default class AppFooter extends Vue { class AppFooter extends Vue {
private appStore = useAppStore() get platformInfo() { return 'vTest • Capacitor' }
showAbout() { this.$emit('about') }
get platformInfo(): string { showHelp() { this.$emit('help') }
const platform = this.appStore.platform
const isNative = this.appStore.isNative
return `${platform.charAt(0).toUpperCase() + platform.slice(1)} ${isNative ? '(Native)' : '(Web)'}`
}
showAbout(): void {
// TODO: Show about dialog
console.log('About clicked')
}
showHelp(): void {
// TODO: Show help dialog
console.log('Help clicked')
}
} }
export default toNative(AppFooter)
</script> </script>
<style scoped> <style scoped>

50
test-apps/daily-notification-test/src/components/layout/AppHeader.vue

@ -36,7 +36,7 @@
:key="item.name" :key="item.name"
:to="item.path" :to="item.path"
class="nav-tab" class="nav-tab"
:class="{ active: $route.name === item.name }" :class="{ active: true }"
> >
<span class="nav-icon">{{ item.icon }}</span> <span class="nav-icon">{{ item.icon }}</span>
<span class="nav-label">{{ item.label }}</span> <span class="nav-label">{{ item.label }}</span>
@ -46,50 +46,28 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-facing-decorator' import { Vue, Component, toNative } from 'vue-facing-decorator'
import { useAppStore } from '@/stores/app'
interface NavigationItem {
name: string
path: string
label: string
icon: string
}
@Component type NavItem = { name: string; path: string; label: string; icon: string }
export default class AppHeader extends Vue {
private appStore = useAppStore()
navigationItems: NavigationItem[] = [ @Component
class AppHeader extends Vue {
get platformName() { return 'Daily Notification Test' }
get platformClass() { return 'platform-generic' }
get statusText() { return 'OK' }
get statusClass() { return 'status-ok' }
get navigationItems(): NavItem[] {
return [
{ name: 'Home', path: '/', label: 'Home', icon: '🏠' }, { name: 'Home', path: '/', label: 'Home', icon: '🏠' },
{ name: 'Schedule', path: '/schedule', label: 'Schedule', icon: '📅' }, { name: 'Schedule', path: '/schedule', label: 'Schedule', icon: '📅' },
{ name: 'Notifications', path: '/notifications', label: 'Notifications', icon: '🔔' }, { name: 'Notifications', path: '/notifications', label: 'Notifications', icon: '🔔' },
{ name: 'Status', path: '/status', label: 'Status', icon: '📊' } { name: 'Logs', path: '/logs', label: 'Logs', icon: '📋' },
] ]
get platformName(): string {
const platform = this.appStore.platform
return platform.charAt(0).toUpperCase() + platform.slice(1)
} }
get platformClass(): string {
return `platform-${this.appStore.platform}`
} }
get statusClass(): string { export default toNative(AppHeader)
const status = this.appStore.notificationStatus
if (!status) return 'unknown'
if (status.canScheduleNow) return 'ready'
return 'not-ready'
}
get statusText(): string {
const status = this.appStore.notificationStatus
if (!status) return 'Unknown'
if (status.canScheduleNow) return 'Ready'
return 'Not Ready'
}
}
</script> </script>
<style scoped> <style scoped>

31
test-apps/daily-notification-test/src/components/ui/ErrorDialog.vue

@ -33,30 +33,17 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts">
interface Props { import { Vue, Component, Prop, toNative } from 'vue-facing-decorator'
message: string
}
const props = defineProps<Props>()
const emit = defineEmits<{
close: []
retry: []
}>()
const handleClose = (): void => {
emit('close')
}
const handleRetry = (): void => {
emit('retry')
emit('close')
}
const handleOverlayClick = (): void => { @Component
handleClose() class ErrorDialog extends Vue {
@Prop({ type: String, required: true }) message!: string
handleOverlayClick() { this.$emit('close') }
handleClose() { this.$emit('close') }
handleRetry() { this.$emit('retry') }
} }
export default toNative(ErrorDialog)
</script> </script>
<style scoped> <style scoped>

10
test-apps/daily-notification-test/src/components/ui/LoadingOverlay.vue

@ -18,8 +18,14 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script lang="ts">
// Loading overlay component - no props or logic needed import { Vue, Component, Prop, toNative } from 'vue-facing-decorator'
@Component
class LoadingOverlay extends Vue {
@Prop({ type: Boolean, default: false }) visible!: boolean
}
export default toNative(LoadingOverlay)
</script> </script>
<style scoped> <style scoped>

57
test-apps/daily-notification-test/src/env.d.ts

@ -0,0 +1,57 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// Vue-facing-decorator type declarations
declare module 'vue-facing-decorator' {
import { ComponentOptions } from 'vue'
export interface ComponentOptionsWithProps<T = any> extends ComponentOptions<T> {
props?: any
}
export function Component(options?: any): any
export function toNative<T>(component: T): T
export class Vue {
$emit(event: string, ...args: any[]): void
$props: any
$data: any
$refs: any
$slots: any
$scopedSlots: any
$attrs: any
$listeners: any
$parent: Vue | null
$root: Vue
$children: Vue[]
$el: Element | undefined
$options: ComponentOptions
$isServer: boolean
$ssrContext: any
$vnode: any
$createElement: any
$mount: any
$forceUpdate: () => void
$destroy: () => void
$nextTick: (callback?: () => void) => Promise<void>
$set: (target: any, key: string | number, value: any) => void
$delete: (target: any, key: string | number) => void
$watch: (expOrFn: string | Function, callback: Function, options?: any) => Function
$on: (event: string | string[], callback: Function) => Vue
$once: (event: string | string[], callback: Function) => Vue
$off: (event?: string | string[], callback?: Function) => Vue
}
export function Prop(options?: any): PropertyDecorator
export function Emit(event?: string): MethodDecorator
export function Watch(path: string, options?: any): MethodDecorator
export function Inject(key?: string | symbol): PropertyDecorator
export function Provide(key?: string | symbol): PropertyDecorator
export function Model(event?: string, options?: any): PropertyDecorator
export function Ref(refKey?: string): PropertyDecorator
}

7
test-apps/daily-notification-test/src/globals.d.ts

@ -0,0 +1,7 @@
export {}
declare global {
interface Window {
Capacitor?: any
}
}

1
test-apps/daily-notification-test/src/main.ts

@ -1,3 +1,4 @@
import 'reflect-metadata'
import './assets/main.css' import './assets/main.css'
import { createApp } from 'vue' import { createApp } from 'vue'

4
test-apps/daily-notification-test/src/router/index.ts

@ -1,5 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue' import HomeViewSimple from '../views/HomeViewSimple.vue'
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@ -7,7 +7,7 @@ const router = createRouter({
{ {
path: '/', path: '/',
name: 'Home', name: 'Home',
component: HomeView, component: HomeViewSimple,
meta: { meta: {
title: 'Daily Notification Test', title: 'Daily Notification Test',
requiresAuth: false requiresAuth: false

43
test-apps/daily-notification-test/src/types/global.d.ts

@ -0,0 +1,43 @@
/**
* Global Type Declarations
*
* Type definitions for global objects and plugins
*
* @author Matthew Raymer
* @version 1.0.0
*/
declare global {
interface Window {
DailyNotification?: {
checkStatus(): Promise<{
canScheduleNow: boolean
postNotificationsGranted: boolean
channelEnabled: boolean
channelImportance: number
channelId: string
exactAlarmsGranted: boolean
exactAlarmsSupported: boolean
androidVersion: number
nextScheduledAt: number
}>
scheduleNotification(options: {
title: string
body: string
scheduledTime: number
}): Promise<void>
cancelNotification(id: string): Promise<void>
requestPermissions(): Promise<boolean>
}
Capacitor?: {
Plugins?: {
DailyNotification?: any
Clipboard?: {
write(options: { string: string }): Promise<void>
}
}
}
}
}
export {}

5
test-apps/daily-notification-test/src/views/HistoryView.vue

@ -11,10 +11,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-facing-decorator' import { Vue, Component, toNative } from 'vue-facing-decorator'
@Component @Component
export default class HistoryView extends Vue {} class HistoryView extends Vue {}
export default toNative(HistoryView)
</script> </script>
<style scoped> <style scoped>

241
test-apps/daily-notification-test/src/views/HomeView.vue

@ -100,167 +100,35 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-facing-decorator' import { Vue, Component, toNative } from 'vue-facing-decorator'
import { useRouter } from 'vue-router'
import { useAppStore } from '@/stores/app'
import ActionCard from '@/components/cards/ActionCard.vue'
import StatusCard from '@/components/cards/StatusCard.vue'
@Component({ @Component
components: { class HomeView extends Vue {
ActionCard, get platformName() { return 'Daily Notification Test' }
StatusCard get platformClass() { return 'platform-generic' }
} get statusText() { return 'OK' }
}) get statusClass() { return 'status-ok' }
export default class HomeView extends Vue {
private router = useRouter()
private appStore = useAppStore()
isScheduling = false isScheduling = false
isCheckingStatus = false isCheckingStatus = false
systemStatus: Record<string, unknown> | null = null
get platformName(): string { navigateToSchedule() {}
const platform = this.appStore.platform navigateToNotifications() {}
return platform.charAt(0).toUpperCase() + platform.slice(1) navigateToHistory() {}
} navigateToLogs() {}
navigateToSettings() {}
get platformClass(): string {
return `platform-${this.appStore.platform}`
}
get statusClass(): string {
const status = this.appStore.notificationStatus
if (!status) return 'unknown'
if (status.canScheduleNow) return 'ready'
return 'not-ready'
}
get statusText(): string {
const status = this.appStore.notificationStatus
if (!status) return 'Unknown'
if (status.canScheduleNow) return 'Ready'
return 'Not Ready'
}
get systemStatus() {
const status = this.appStore.notificationStatus
if (!status) {
return [
{ label: 'Platform', value: this.platformName, status: 'info' },
{ label: 'Plugin', value: 'Not Available', status: 'error' }
]
}
return [
{ label: 'Platform', value: this.platformName, status: 'success' },
{ label: 'Plugin', value: 'Available', status: 'success' },
{ label: 'Notifications', value: status.postNotificationsGranted ? 'Granted' : 'Denied', status: status.postNotificationsGranted ? 'success' : 'error' },
{ label: 'Exact Alarms', value: status.exactAlarmsGranted ? 'Granted' : 'Denied', status: status.exactAlarmsGranted ? 'success' : 'error' },
{ label: 'Channel', value: status.channelEnabled ? 'Enabled' : 'Disabled', status: status.channelEnabled ? 'success' : 'warning' }
]
}
navigateToSchedule(): void {
this.router.push('/schedule')
}
navigateToNotifications(): void {
this.router.push('/notifications')
}
navigateToHistory(): void {
this.router.push('/history')
}
navigateToLogs(): void {
this.router.push('/logs')
}
navigateToSettings(): void {
this.router.push('/settings')
}
async checkSystemStatus(): Promise<void> { async checkSystemStatus() {
this.isCheckingStatus = true this.isCheckingStatus = true
try { try { this.systemStatus = { ok: true, ts: Date.now() } }
// Refresh plugin status finally { this.isCheckingStatus = false }
await this.refreshSystemStatus()
} catch (error) {
console.error('❌ Status check failed:', error)
this.appStore.setError('Failed to check system status: ' + (error as Error).message)
} finally {
this.isCheckingStatus = false
}
}
async refreshSystemStatus(): Promise<void> {
try {
if (window.DailyNotification) {
const status = await window.DailyNotification.checkStatus()
this.appStore.setNotificationStatus(status)
console.log('✅ System status refreshed:', status)
}
} catch (error) {
console.error('❌ Failed to refresh system status:', error)
throw error
}
}
async runPluginDiagnostics(): Promise<void> {
try {
console.log('🔧 Running plugin diagnostics...')
console.log('🔧 BUTTON CLICKED - METHOD CALLED!')
// Check if we're on a native platform
const { Capacitor } = await import('@capacitor/core')
const platform = Capacitor.getPlatform()
const isNative = Capacitor.isNativePlatform()
console.log('📱 Platform:', platform)
console.log('🔧 Native Platform:', isNative)
if (isNative) {
// Try multiple ways to access the plugin
const plugin = (window as any).DailyNotification ||
(window as any).Capacitor?.Plugins?.DailyNotification ||
(window as any).Capacitor?.Plugins?.['DailyNotification']
console.log('🔍 Plugin detection debug:')
console.log(' - window.DailyNotification:', (window as any).DailyNotification)
console.log(' - Capacitor.Plugins:', (window as any).Capacitor?.Plugins)
console.log(' - Available plugins:', Object.keys((window as any).Capacitor?.Plugins || {}))
if (plugin) {
console.log('✅ DailyNotification plugin available')
// Test the checkStatus method
try {
const status = await plugin.checkStatus()
console.log('📊 Plugin status check result:', status)
alert(`✅ Plugin Diagnostics Complete!\n\nPlatform: ${platform}\nPlugin Available: Yes\nStatus: ${JSON.stringify(status, null, 2)}`)
} catch (error) {
console.error('❌ Plugin status check failed:', error)
alert(`⚠️ Plugin Diagnostics Complete!\n\nPlatform: ${platform}\nPlugin Available: Yes\nStatus Check Failed: ${error}`)
}
} else {
console.warn('⚠️ DailyNotification plugin not available')
alert(`❌ Plugin Diagnostics Complete!\n\nPlatform: ${platform}\nPlugin Available: No\nAvailable Plugins: ${Object.keys((window as any).Capacitor?.Plugins || {}).join(', ')}`)
}
} else {
console.log('🌐 Running in web mode - plugin not available')
alert(`ℹ️ Plugin Diagnostics Complete!\n\nPlatform: ${platform}\nNative Platform: No\nPlugin Available: No (Web mode)`)
}
} catch (error) {
console.error('❌ Plugin diagnostics failed:', error)
alert(`❌ Plugin Diagnostics Failed!\n\nError: ${error}`)
}
}
openConsole(): void {
console.log('📖 Console opened - check browser developer tools for detailed logs')
alert('📖 Console Logs\n\nOpen your browser\'s Developer Tools (F12) and check the Console tab for detailed diagnostic information.')
} }
async refreshSystemStatus() { await this.checkSystemStatus() }
async runPluginDiagnostics() {}
openConsole() {}
} }
export default toNative(HomeView)
</script> </script>
<style scoped> <style scoped>
@ -273,23 +141,19 @@ export default class HomeView extends Vue {
.welcome-section { .welcome-section {
text-align: center; text-align: center;
margin-bottom: 40px; margin-bottom: 40px;
padding: 40px 20px;
background: rgba(255, 255, 255, 0.1);
border-radius: 16px;
backdrop-filter: blur(10px);
} }
.welcome-title { .welcome-title {
margin: 0 0 12px 0;
font-size: 32px; font-size: 32px;
font-weight: 700; font-weight: 700;
color: white; color: white;
margin: 0 0 8px 0;
} }
.welcome-subtitle { .welcome-subtitle {
margin: 0 0 20px 0;
font-size: 16px; font-size: 16px;
color: rgba(255, 255, 255, 0.8); color: rgba(255, 255, 255, 0.8);
margin: 0 0 20px 0;
} }
.platform-info { .platform-info {
@ -302,75 +166,34 @@ export default class HomeView extends Vue {
.platform-badge { .platform-badge {
padding: 6px 12px; padding: 6px 12px;
border-radius: 16px; border-radius: 16px;
font-size: 14px; font-size: 12px;
font-weight: 500; font-weight: 500;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
} }
.platform-android {
background: rgba(76, 175, 80, 0.2);
color: #4caf50;
border: 1px solid rgba(76, 175, 80, 0.3);
}
.platform-ios {
background: rgba(0, 122, 255, 0.2);
color: #007aff;
border: 1px solid rgba(0, 122, 255, 0.3);
}
.platform-electron {
background: rgba(138, 43, 226, 0.2);
color: #8a2be2;
border: 1px solid rgba(138, 43, 226, 0.3);
}
.platform-web {
background: rgba(255, 152, 0, 0.2);
color: #ff9800;
border: 1px solid rgba(255, 152, 0, 0.3);
}
.status-badge { .status-badge {
padding: 6px 12px; padding: 6px 12px;
border-radius: 16px; border-radius: 16px;
font-size: 14px; font-size: 12px;
font-weight: 500; font-weight: 500;
} }
.status-badge.ready {
background: rgba(76, 175, 80, 0.2);
color: #4caf50;
border: 1px solid rgba(76, 175, 80, 0.3);
}
.status-badge.not-ready {
background: rgba(244, 67, 54, 0.2);
color: #f44336;
border: 1px solid rgba(244, 67, 54, 0.3);
}
.status-badge.unknown {
background: rgba(158, 158, 158, 0.2);
color: #9e9e9e;
border: 1px solid rgba(158, 158, 158, 0.3);
}
.quick-actions { .quick-actions {
margin-bottom: 40px; margin-bottom: 40px;
} }
.section-title { .section-title {
margin: 0 0 20px 0; font-size: 20px;
font-size: 24px;
font-weight: 600; font-weight: 600;
color: white; color: white;
margin: 0 0 20px 0;
text-align: center;
} }
.action-grid { .action-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px; gap: 16px;
} }
@ -378,16 +201,16 @@ export default class HomeView extends Vue {
margin-bottom: 40px; margin-bottom: 40px;
} }
.section {
margin-bottom: 30px;
}
/* Mobile responsiveness */ /* Mobile responsiveness */
@media (max-width: 768px) { @media (max-width: 768px) {
.home-view { .home-view {
padding: 16px; padding: 16px;
} }
.welcome-section {
padding: 24px 16px;
}
.welcome-title { .welcome-title {
font-size: 24px; font-size: 24px;
} }

258
test-apps/daily-notification-test/src/views/HomeViewSimple.vue

@ -0,0 +1,258 @@
<!--
/**
* Simple Home View - Minimal Performance Version
*
* Simplified version to avoid main thread blocking
*
* @author Matthew Raymer
* @version 1.0.0
*/
-->
<template>
<div class="home-view">
<!-- Welcome Section -->
<div class="welcome-section">
<h1 class="welcome-title">
🔔 Daily Notification Test
</h1>
<p class="welcome-subtitle">
Vue 3 + vue-facing-decorator + Capacitor
</p>
<div class="platform-info">
<span class="platform-badge">
{{ platformName }}
</span>
<span class="status-badge">
{{ statusText }}
</span>
</div>
</div>
<!-- Simple Actions -->
<div class="quick-actions">
<h2 class="section-title">Quick Actions</h2>
<div class="action-grid">
<div class="action-card" @click="navigateToSchedule">
<div class="card-content">
<div class="card-icon">📅</div>
<div class="card-text">
<h3 class="card-title">Schedule Notification</h3>
<p class="card-description">Schedule a new daily notification</p>
</div>
<div class="card-arrow"></div>
</div>
</div>
<div class="action-card" @click="checkSystemStatus">
<div class="card-content">
<div class="card-icon">📊</div>
<div class="card-text">
<h3 class="card-title">Check Status</h3>
<p class="card-description">View notification system status</p>
</div>
<div class="card-arrow"></div>
</div>
</div>
<div class="action-card" @click="runPluginDiagnostics">
<div class="card-content">
<div class="card-icon">🔧</div>
<div class="card-text">
<h3 class="card-title">Plugin Diagnostics</h3>
<p class="card-description">Test plugin functionality</p>
</div>
<div class="card-arrow"></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component, toNative } from 'vue-facing-decorator'
@Component
class HomeViewSimple extends Vue {
platformName = 'Android'
statusText = 'Ready'
navigateToSchedule() {}
checkSystemStatus() { alert('System status check - simplified version') }
runPluginDiagnostics() { alert('Plugin diagnostics - simplified version') }
}
export default toNative(HomeViewSimple)
</script>
<style scoped>
.home-view {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.welcome-section {
text-align: center;
margin-bottom: 40px;
padding: 40px 20px;
background: rgba(255, 255, 255, 0.1);
border-radius: 16px;
backdrop-filter: blur(10px);
}
.welcome-title {
margin: 0 0 16px 0;
font-size: 32px;
font-weight: 700;
color: white;
}
.welcome-subtitle {
margin: 0 0 24px 0;
font-size: 18px;
color: rgba(255, 255, 255, 0.8);
}
.platform-info {
display: flex;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
}
.platform-badge, .status-badge {
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.platform-badge {
background: rgba(76, 175, 80, 0.2);
color: #4caf50;
border: 1px solid rgba(76, 175, 80, 0.3);
}
.status-badge {
background: rgba(33, 150, 243, 0.2);
color: #2196f3;
border: 1px solid rgba(33, 150, 243, 0.3);
}
.quick-actions {
margin-bottom: 40px;
}
.section-title {
margin: 0 0 24px 0;
font-size: 24px;
font-weight: 600;
color: white;
text-align: center;
}
.action-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.action-card {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 20px;
cursor: pointer;
transition: all 0.2s ease;
backdrop-filter: blur(10px);
}
.action-card:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
.card-content {
display: flex;
align-items: center;
gap: 16px;
}
.card-icon {
font-size: 24px;
flex-shrink: 0;
}
.card-text {
flex: 1;
}
.card-title {
margin: 0 0 4px 0;
font-size: 16px;
font-weight: 600;
color: white;
}
.card-description {
margin: 0;
font-size: 14px;
color: rgba(255, 255, 255, 0.8);
line-height: 1.4;
}
.card-arrow {
flex-shrink: 0;
color: rgba(255, 255, 255, 0.6);
font-size: 18px;
}
/* Mobile responsiveness */
@media (max-width: 768px) {
.home-view {
padding: 16px;
}
.welcome-section {
padding: 30px 16px;
}
.welcome-title {
font-size: 28px;
}
.welcome-subtitle {
font-size: 16px;
}
.action-grid {
grid-template-columns: 1fr;
}
.action-card {
padding: 16px;
}
.card-content {
gap: 12px;
}
.card-icon {
font-size: 20px;
}
.card-title {
font-size: 15px;
}
.card-description {
font-size: 13px;
}
}
</style>

223
test-apps/daily-notification-test/src/views/LogsView.vue

@ -58,10 +58,8 @@
:key="index" :key="index"
:class="['log-entry', `log-level-${log.level.toLowerCase()}`]" :class="['log-entry', `log-level-${log.level.toLowerCase()}`]"
> >
<span class="log-timestamp">{{ formatTimestamp(log.timestamp) }}</span> <span class="log-timestamp">{{ formatTimestamp(log.ts) }}</span>
<span class="log-level">[{{ log.level }}]</span> <span class="log-message">{{ log.msg }}</span>
<span class="log-tag">{{ log.tag }}:</span>
<span class="log-message">{{ log.message }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -70,146 +68,55 @@
<p class="empty-message">No logs available. Click "Refresh Logs" to fetch them.</p> <p class="empty-message">No logs available. Click "Refresh Logs" to fetch them.</p>
</div> </div>
</div> </div>
<div v-if="feedbackMessage" :class="['feedback-message', feedbackType]">
{{ feedbackMessage }}
</div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-facing-decorator' import { Vue, Component, toNative } from 'vue-facing-decorator'
import { Capacitor } from '@capacitor/core' type LogLine = { ts: number; msg: string; level: string }
import { format } from 'date-fns'
import { useAppStore } from '@/stores/app'
interface LogEntry {
timestamp: number
level: string
tag: string
message: string
}
@Component @Component
export default class LogsView extends Vue { class LogsView extends Vue {
private appStore = useAppStore()
logs: LogEntry[] = []
isRefreshing = false isRefreshing = false
isCopying = false isCopying = false
lastUpdated: number = Date.now() logs: LogLine[] = []
feedbackMessage: string | null = null lastUpdated: number | null = null
feedbackType: 'success' | 'error' = 'success'
get hasLogs(): boolean { get hasLogs() { return this.logs.length > 0 }
return this.logs.length > 0
}
mounted(): void {
this.refreshLogs()
}
async refreshLogs(): Promise<void> { formatTimestamp(ts: number) { return new Date(ts).toLocaleString() }
if (!Capacitor.isNativePlatform()) {
this.appStore.setError('Log fetching is only available on native platforms.')
return
}
async refreshLogs() {
this.isRefreshing = true this.isRefreshing = true
this.clearFeedback()
try { try {
// For now, create mock logs - replace with actual plugin call // TODO: replace with real fetch
// Example: const rawLogs = await DailyNotificationPlugin.getLogs(); this.logs = [{ ts: Date.now(), msg: 'Sample log', level: 'info' }]
const mockLogs: LogEntry[] = [
{ timestamp: Date.now() - 5000, level: 'DEBUG', tag: 'DailyNotificationPlugin', message: 'Plugin initialized' },
{ timestamp: Date.now() - 4000, level: 'INFO', tag: 'DailyNotificationScheduler', message: 'Notification scheduled for 09:00' },
{ timestamp: Date.now() - 3000, level: 'WARN', tag: 'DailyNotificationWorker', message: 'JIT freshness check skipped: content too fresh' },
{ timestamp: Date.now() - 2000, level: 'ERROR', tag: 'DailyNotificationStorage', message: 'Failed to save notification: disk full' },
{ timestamp: Date.now() - 1000, level: 'INFO', tag: 'DailyNotificationReceiver', message: 'Received NOTIFICATION intent' }
]
this.logs = mockLogs.sort((a, b) => a.timestamp - b.timestamp)
this.lastUpdated = Date.now() this.lastUpdated = Date.now()
this.showFeedback(`✅ Fetched ${this.logs.length} log entries.`, 'success')
} catch (error) {
console.error('❌ Failed to refresh logs:', error)
this.appStore.setError('Failed to fetch logs: ' + (error as Error).message)
this.showFeedback('❌ Failed to fetch logs.', 'error')
} finally { } finally {
this.isRefreshing = false this.isRefreshing = false
} }
} }
async copyLogsToClipboard(): Promise<void> { async copyLogsToClipboard() {
if (!this.hasLogs) { if (this.isCopying) return
this.showFeedback('No logs to copy.', 'error')
return
}
this.isCopying = true this.isCopying = true
this.clearFeedback()
const logsText = this.formatLogsForCopy()
try { try {
if (Capacitor.isNativePlatform()) { const text = this.logs.map(l => `[${this.formatTimestamp(l.ts)}] ${l.msg}`).join('\n')
// Use Capacitor Clipboard plugin if available if ((navigator as any)?.clipboard?.writeText) {
// Assuming Clipboard plugin is installed and configured await navigator.clipboard.writeText(text); return
await window.Capacitor.Plugins.Clipboard.write({ }
string: logsText const Cap = (window as any)?.Capacitor
}) const Clip = Cap?.Plugins?.Clipboard
} else { if (Clip?.write) { await Clip.write({ string: text }); return }
// Web clipboard API console.warn('No clipboard API available.')
await navigator.clipboard.writeText(logsText)
}
this.showFeedback(`✅ Copied ${this.logs.length} log entries to clipboard!`, 'success')
} catch (error) {
console.error('❌ Failed to copy logs:', error)
this.appStore.setError('Failed to copy logs: ' + (error as Error).message)
this.showFeedback('❌ Failed to copy logs.', 'error')
} finally { } finally {
this.isCopying = false this.isCopying = false
} }
} }
clearLogs(): void { clearLogs() { this.logs = [] }
this.logs = []
this.clearFeedback()
this.showFeedback('🗑️ Logs cleared.', 'success')
}
formatTimestamp(timestamp: number): string {
return format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss')
}
formatLogsForCopy(): string {
let formattedText = `DailyNotification Plugin Logs\n`
formattedText += `Generated: ${format(new Date(), 'MM/dd/yyyy, h:mm:ss a')}\n`
formattedText += `Total Entries: ${this.logs.length}\n\n`
this.logs.forEach(log => {
formattedText += `${this.formatTimestamp(log.timestamp)} ${log.level}/${log.tag}: ${log.message}\n`
})
return formattedText
}
showFeedback(message: string, type: 'success' | 'error'): void {
this.feedbackMessage = message
this.feedbackType = type
setTimeout(() => {
this.feedbackMessage = null
}, 3000) // Hide after 3 seconds
}
clearFeedback(): void {
this.feedbackMessage = null
this.feedbackType = 'success'
}
} }
export default toNative(LogsView)
</script> </script>
<style scoped> <style scoped>
@ -341,60 +248,12 @@ export default class LogsView extends Vue {
line-height: 1.4; line-height: 1.4;
} }
.log-level-debug {
background: rgba(33, 150, 243, 0.1);
border-left: 3px solid #2196f3;
}
.log-level-info {
background: rgba(76, 175, 80, 0.1);
border-left: 3px solid #4caf50;
}
.log-level-warn {
background: rgba(255, 152, 0, 0.1);
border-left: 3px solid #ff9800;
}
.log-level-error {
background: rgba(244, 67, 54, 0.1);
border-left: 3px solid #f44336;
}
.log-timestamp { .log-timestamp {
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.6);
font-size: 12px; font-size: 12px;
flex-shrink: 0; flex-shrink: 0;
} }
.log-level {
font-weight: 600;
flex-shrink: 0;
min-width: 60px;
}
.log-level-debug .log-level {
color: #2196f3;
}
.log-level-info .log-level {
color: #4caf50;
}
.log-level-warn .log-level {
color: #ff9800;
}
.log-level-error .log-level {
color: #f44336;
}
.log-tag {
color: rgba(255, 255, 255, 0.8);
font-weight: 500;
flex-shrink: 0;
}
.log-message { .log-message {
color: white; color: white;
flex: 1; flex: 1;
@ -417,31 +276,6 @@ export default class LogsView extends Vue {
font-size: 16px; font-size: 16px;
} }
.feedback-message {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
padding: 12px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
z-index: 1000;
backdrop-filter: blur(10px);
}
.feedback-message.success {
background: rgba(76, 175, 80, 0.9);
color: white;
border: 1px solid rgba(76, 175, 80, 0.3);
}
.feedback-message.error {
background: rgba(244, 67, 54, 0.9);
color: white;
border: 1px solid rgba(244, 67, 54, 0.3);
}
/* Mobile responsiveness */ /* Mobile responsiveness */
@media (max-width: 768px) { @media (max-width: 768px) {
.logs-view { .logs-view {
@ -450,10 +284,12 @@ export default class LogsView extends Vue {
.logs-controls { .logs-controls {
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: center;
} }
.control-button { .control-button {
width: 100%;
max-width: 300px;
justify-content: center; justify-content: center;
} }
@ -463,11 +299,6 @@ export default class LogsView extends Vue {
align-items: flex-start; align-items: flex-start;
} }
.log-entry {
flex-direction: column;
gap: 4px;
}
.log-timestamp { .log-timestamp {
font-size: 11px; font-size: 11px;
} }

5
test-apps/daily-notification-test/src/views/NotFoundView.vue

@ -12,10 +12,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-facing-decorator' import { Vue, Component, toNative } from 'vue-facing-decorator'
@Component @Component
export default class NotFoundView extends Vue {} class NotFoundView extends Vue {}
export default toNative(NotFoundView)
</script> </script>
<style scoped> <style scoped>

5
test-apps/daily-notification-test/src/views/NotificationsView.vue

@ -11,10 +11,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-facing-decorator' import { Vue, Component, toNative } from 'vue-facing-decorator'
@Component @Component
export default class NotificationsView extends Vue {} class NotificationsView extends Vue {}
export default toNative(NotificationsView)
</script> </script>
<style scoped> <style scoped>

21
test-apps/daily-notification-test/src/views/ScheduleView.vue

@ -40,36 +40,25 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-facing-decorator' import { Vue, Component, toNative } from 'vue-facing-decorator'
@Component @Component
export default class ScheduleView extends Vue { class ScheduleView extends Vue {
scheduleTime = '09:00' scheduleTime = '09:00'
notificationTitle = 'Daily Update' notificationTitle = 'Daily Update'
notificationMessage = 'Your daily notification is ready!' notificationMessage = 'Your daily notification is ready!'
isScheduling = false isScheduling = false
async scheduleNotification(): Promise<void> { async scheduleNotification() {
this.isScheduling = true this.isScheduling = true
try { try {
// TODO: Implement actual scheduling // TODO: call plugin
console.log('Scheduling notification:', {
time: this.scheduleTime,
title: this.notificationTitle,
message: this.notificationMessage
})
// Mock success
await new Promise(resolve => setTimeout(resolve, 1000))
console.log('✅ Notification scheduled successfully')
} catch (error) {
console.error('❌ Failed to schedule notification:', error)
} finally { } finally {
this.isScheduling = false this.isScheduling = false
} }
} }
} }
export default toNative(ScheduleView)
</script> </script>
<style scoped> <style scoped>

5
test-apps/daily-notification-test/src/views/SettingsView.vue

@ -11,10 +11,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-facing-decorator' import { Vue, Component, toNative } from 'vue-facing-decorator'
@Component @Component
export default class SettingsView extends Vue {} class SettingsView extends Vue {}
export default toNative(SettingsView)
</script> </script>
<style scoped> <style scoped>

5
test-apps/daily-notification-test/src/views/StatusView.vue

@ -11,10 +11,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-facing-decorator' import { Vue, Component, toNative } from 'vue-facing-decorator'
@Component @Component
export default class StatusView extends Vue {} class StatusView extends Vue {}
export default toNative(StatusView)
</script> </script>
<style scoped> <style scoped>

12
test-apps/daily-notification-test/tsconfig.app.json

@ -4,9 +4,21 @@
"exclude": ["src/**/__tests__/*"], "exclude": ["src/**/__tests__/*"],
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"useDefineForClassFields": false, "useDefineForClassFields": false,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"strict": false,
"noImplicitAny": false,
"noImplicitReturns": false,
"noImplicitThis": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
} }

14
test-apps/daily-notification-test/vite.config.ts

@ -7,7 +7,13 @@ import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
vue(), vue({
script: {
defineModel: true,
propsDestructure: true,
babelParserPlugins: ['decorators-legacy', 'classProperties']
}
}),
vueDevTools(), vueDevTools(),
], ],
resolve: { resolve: {
@ -15,4 +21,10 @@ export default defineConfig({
'@': fileURLToPath(new URL('./src', import.meta.url)) '@': fileURLToPath(new URL('./src', import.meta.url))
}, },
}, },
esbuild: {
target: 'es2020'
},
optimizeDeps: {
include: ['vue-facing-decorator']
}
}) })

Loading…
Cancel
Save