diff --git a/cypress/fixtures/exampleItem.json b/cypress/fixtures/exampleItem.json new file mode 100644 index 0000000000000000000000000000000000000000..fd91203abd4b81b3f1440f40147260560da28c9f --- /dev/null +++ b/cypress/fixtures/exampleItem.json @@ -0,0 +1,10 @@ +{ + "item":{ + "id": "1", + "name":"eple", + "amount": {"quantity": "4","unit": "stk"}, + "image_url": "" + }, + "exp_date": "2222-02-22T00:00:00+00:00", + "amount": {"quantity": "2","unit": "stk"} +} diff --git a/package-lock.json b/package-lock.json index 99cf8a0fe5a1469e2b32c857cfa7a62d5f0afa91..325009f43a1e9ac5e917359abdc88e3764ea8973 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,16 +8,15 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@iconify/vue": "^4.1.1", + "@pinia/testing": "^0.0.16", "jwt-decode": "^3.1.2", - "pinia": "^2.0.28", + "pinia": "^2.0.35", "pinia-plugin-persistedstate": "^3.1.0", - "sass": "^1.62.0", "vue": "^3.2.45", "vue-router": "^4.1.6" }, "devDependencies": { - "@iconify/vue": "^4.1.1", - "@pinia/testing": "^0.0.16", "@vitejs/plugin-vue": "^4.0.0", "@vue/test-utils": "^2.2.6", "cypress": "^12.0.2", @@ -568,6 +567,162 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==" + }, + "node_modules/@iconify/vue": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-4.1.1.tgz", + "integrity": "sha512-RL85Bm/DAe8y6rT6pux7D2FJSiUEM/TPfyK7GrbAOfTSwrhvwJW+S5yijdGcmtXouA8MtuH9C7l4hiSE4mLMjg==", + "dependencies": { + "@iconify/types": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/cyberalien" + }, + "peerDependencies": { + "vue": ">=3" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true, + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true, + "peer": true + }, + "node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@pinia/testing": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-0.0.16.tgz", + "integrity": "sha512-oa5kO82hzWekqMq1HTnS/b+ZM+ZKEcEApuuCTelvKK79jTxg7P026Qw8/2RbVn5eUGAvRWQu4ubObrshVqCRjQ==", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "pinia": ">=2.0.34" + } + }, + "node_modules/@pinia/testing/node_modules/vue-demi": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.0.tgz", + "integrity": "sha512-gt58r2ogsNQeVoQ3EhoUAvUsH9xviydl0dWJj7dabBC/2L4uBId7ujtCwDRD0JhkGsV1i0CtfLAeyYKBht9oWg==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -1169,6 +1324,27 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "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, + "optional": true, + "peer": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, "node_modules/arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -1361,6 +1537,9 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -1386,6 +1565,49 @@ "balanced-match": "^1.0.0" } }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -1603,6 +1825,54 @@ "node": ">= 0.8.0" } }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "optional": true, + "peer": true, + "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" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", @@ -2071,9 +2341,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.369", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.369.tgz", - "integrity": "sha512-LfxbHXdA/S+qyoTEA4EbhxGjrxx7WK2h6yb5K2v0UCOufUKX+VZaHbl3svlzZfv9sGseym/g3Ne4DpsgRULmqg==", + "version": "1.4.374", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.374.tgz", + "integrity": "sha512-dNP9tQNTrjgVlSXMqGaj0BdrCS+9pcUvy5/emB6x8kh0YwCoDZ0Z4ce1+7aod+KhybHUd5o5LgKrc5al4kVmzQ==", "dev": true, "peer": true }, @@ -2466,6 +2736,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -2552,6 +2849,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -2691,6 +2989,27 @@ "url": "https://github.com/sponsors/isaacs" } }, + "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, + "optional": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "peer": true + }, "node_modules/global-dirs": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", @@ -2955,6 +3274,23 @@ } ] }, + "node_modules/immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -2995,6 +3331,32 @@ "node": ">=10" } }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "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, + "optional": true, + "peer": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-ci": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", @@ -3023,6 +3385,9 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3040,6 +3405,9 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -3063,6 +3431,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -4238,6 +4623,32 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "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, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -4480,6 +4891,9 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=8.6" }, @@ -4497,9 +4911,9 @@ } }, "node_modules/pinia": { - "version": "2.0.34", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.34.tgz", - "integrity": "sha512-cgOoGUiyqX0SSgX8XelK9+Ri4XA2/YyNtgjogwfzIx1g7iZTaZPxm7/bZYMCLU2qHRiHhxG7SuQO0eBacFNc2Q==", + "version": "2.0.35", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.35.tgz", + "integrity": "sha512-P1IKKQWhxGXiiZ3atOaNI75bYlFUbRxtJdhPLX059Z7+b9Z04rnTZdSY8Aph1LA+/4QEMAYHsTQ638Wfe+6K5g==", "dependencies": { "@vue/devtools-api": "^6.5.0", "vue-demi": "*" @@ -4706,6 +5120,143 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "peer": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "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, + "optional": true, + "peer": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/request-progress": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", @@ -4883,6 +5434,123 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/sass": { + "version": "1.62.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.0.tgz", + "integrity": "sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-graph": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.1.tgz", + "integrity": "sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "lodash": "^4.17.11", + "scss-tokenizer": "^0.4.3", + "yargs": "^17.2.1" + }, + "bin": { + "sassgraph": "bin/sassgraph" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/sass-graph/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/sass-graph/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sass-graph/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/sass-loader": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.2.tgz", + "integrity": "sha512-nrIdVAAte3B9icfBiGWvmMhT/D+eCDwnk+yA7VE/76dp/WkHX+i44Q/pfo71NYbwj0Ap+PGsn0ekOuU1WFJ2AA==", + "dev": true, + "dependencies": { + "klona": "^2.0.6", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -5567,6 +6235,9 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "is-number": "^7.0.0" }, @@ -5988,9 +6659,9 @@ } }, "node_modules/webpack": { - "version": "5.80.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz", - "integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==", + "version": "5.81.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.81.0.tgz", + "integrity": "sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q==", "dev": true, "peer": true, "dependencies": { diff --git a/package.json b/package.json index 103fbd2a1db13c5e6262701aa8d05624babfb997..0eb0aa1f4754b48a7823428f190f1a111aca44af 100644 --- a/package.json +++ b/package.json @@ -11,17 +11,15 @@ "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress run --e2e'" }, "dependencies": { - "@iconify/iconify": "^3.1.0", + "@iconify/vue": "^4.1.1", + "@pinia/testing": "^0.0.16", "jwt-decode": "^3.1.2", - "pinia": "^2.0.28", - "sass": "^1.62.0", + "pinia": "^2.0.35", "pinia-plugin-persistedstate": "^3.1.0", "vue": "^3.2.45", "vue-router": "^4.1.6" }, "devDependencies": { - "@iconify/vue": "^4.1.1", - "@pinia/testing": "^0.0.16", "@vitejs/plugin-vue": "^4.0.0", "@vue/test-utils": "^2.2.6", "cypress": "^12.0.2", diff --git a/src/App.vue b/src/App.vue index bd2caebb74d9bb11531b80b2813fb87246000c71..1358b7e3e414d1466e1288bbfed8cdcabdafb3fc 100644 --- a/src/App.vue +++ b/src/App.vue @@ -7,7 +7,10 @@ import Navbar from "@/components/Navbar.vue"; <template> <Navbar></Navbar> - <RouterView /> + <body> + <RouterView /> + </body> + </template> <style scoped> @@ -21,6 +24,9 @@ header { margin: 0 auto 2rem; } +body { + padding-bottom: 2em; +} nav { width: 100%; font-size: 12px; diff --git a/src/components/EatFridgeItemModal.vue b/src/components/EatFridgeItemModal.vue new file mode 100644 index 0000000000000000000000000000000000000000..eacb71ff0f8409bb286506a7ef6f28c913ebb835 --- /dev/null +++ b/src/components/EatFridgeItemModal.vue @@ -0,0 +1,201 @@ +<template> + <div v-if="visible" id="popup"> + <div id="exitBtn" @click="close"><Icon icon="mdi:alpha-x-box" :color="redIconColor" :style="{ fontSize: redIconSize }" /></div> + + <h2>{{ this.fridgeItem.item.name }}</h2> + <p id="sliderDisplay">{{sliderValue}} {{this.fridgeItem.amount.unit}}</p> + <label for="slider">Mengde tatt av varen:</label> + + <input type = "range" id = "slider" name = "slider" min="0" :max = "this.maxValue" :step = this.getStep v-model="sliderValue"> + + <div id="buttons"> + <button @click="logFoodAsDiscarded">Ble kastet</button> + <button id="eatenBtn" @click="logFoodAsEaten" >Ble spist</button> + </div> + <div v-if="isExpired" id = "itemHasExpiredMessage"> + <h2>Varen har gått ut på dato</h2> + <p>Se, lukt, smak : Hvis varen fortsatt ser fin ut, kan du forlenge utløpsdatoen med 5 dager</p> + <button id= "eatenBtn" @click="extendExpiration">Utsett utløpsdato med 5 dager</button> + </div> + </div> +</template> + +<script> +import {Icon} from "@iconify/vue"; +import fridgeItem from "@/components/FridgeItem.vue"; +import {API} from "@/util/API"; + +export default { + name: "EatFridgeItemModal", + components: {Icon}, + props: { + fridgeItem: { + type: Object, + required: false + }, + }, + data() { + return { + visible:true, + sliderValue: 0, + } + }, + methods:{ + close(){ + this.$emit('closeModal',this); + }, + logFoodAsEaten(){ + const fridgeAction = "CONSUMED"; + this.updateFridgeItem(fridgeAction) + }, + logFoodAsDiscarded(){ + const fridgeAction = "DISCARDED"; + this.updateFridgeItem(fridgeAction) + }, + updateFridgeItem(fridgeAction){ + const ingredientId = this.fridgeItem.ingredient_id; + + const request = { + action: fridgeAction, + ingredients: { + [ingredientId]:{ + "quantity": this.sliderValue, + "unit": this.fridgeItem.amount.unit + } + } + }; + + API.updateFridge(request) + .catch((error)=> + console.log(error) + ); + this.close(); + }, + extendExpiration(){ + const id = this.fridgeItem.ingredient_id; + const today = new Date(); + const fiveDays = new Date(); + fiveDays.setDate(today.getDate()+5); + + API.changeExpirationOfIngredient({"ingredientId": id, "newExpDate":fiveDays}).catch((error)=> { + console.log("could not change date") + }) + this.close(); + } + }, + computed:{ + redIconColor() { + return '#EE6D6D'; + }, + redIconSize() { + return '40px'; + }, + maxValue(){ + return this.fridgeItem.amount.quantity; + }, + isExpired(){ + let date = this.fridgeItem.exp_date; + const expirationDate = new Date(date); + + const parsedDate = Date.parse(expirationDate) + const today = new Date(); + + const ms = parsedDate-today; + const numOfDays = Math.ceil(ms/(86400000)) + + return numOfDays < 1 ; + }, + getStep(){ + const itemQuantity = this.fridgeItem.amount.quantity.toString(); + const isDecimal = itemQuantity.split('.').length === 2 || itemQuantity==='1'; + + switch(isDecimal){ + case true: + return '0.1'; + case false: + return '1'; + } + return 0.1; + }, + } +} +</script> +<style scoped lang="scss"> +#popup { + background-color: base.$white; + color: black; + width: 90%; + padding:2em; + display:flex; + flex-direction: column; + align-items: center; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 999; + box-shadow: 0 0 0 2000px rgba(0, 0, 0, 0.5); + border-radius: 2px; +} + +@media (min-width: 800px){ + #popup { + width: 40%; + } +} + +#buttons { + justify-content: space-between; + display: flex; + width: 100%; + margin: 1em; + margin-top: 3em; +} + +#exitBtn{ + width:100%; + display: flex; + justify-content: right; + +} + +button { + margin:.1em; + font-weight: bolder; + width: 50%; + padding:1em; + background-color: base.$red; + color:white; + border:none; +} +button:hover { + background-color: base.$red-hover; + cursor:pointer; +} +#eatenBtn:hover{ + background-color: base.$green-hover; +} +#eatenBtn { + background-color: base.$green; +} + +#itemHasExpiredMessage { + border: 3px dashed base.$red; + padding: 1em; +} + +#itemHasExpiredMessage button { + padding: 1em; + width:100%; + border:none; + border-radius:5px; +} + +input:hover{ + cursor:pointer; +} + +#exitBtn:hover{ + cursor:pointer; +} +</style> \ No newline at end of file diff --git a/src/components/FridgeItem.vue b/src/components/FridgeItem.vue new file mode 100644 index 0000000000000000000000000000000000000000..d259d5db29f607aa03b9464483dd6c51132e4164 --- /dev/null +++ b/src/components/FridgeItem.vue @@ -0,0 +1,147 @@ +<template> +<div id ="item"> + <img :src="getImage" alt=""> + <div id="itemInfo"> + <p id="fridgeItemName">{{this.actualItem.item.name }} {{ this.actualItem.amount.quantity }}{{this.actualItem.item.amount.unit}}</p> + <p class="expText" :style="{color:expirationTextColor}">{{expirationText}}</p> + </div> + <div id = "appleBtn" @click="appleBtnPressed"> + <Icon icon="game-icons:apple-core" :color="iconColor" :style="{ fontSize: iconSize }" /> + </div> + +</div> + <hr> +</template> + +<script> +import {Icon} from "@iconify/vue"; + +export default { + name: "FridgeItem", + components: {Icon}, + emits: ['appleBtnPressed'], + computed: { + iconColor(){ + return 'black'; + }, + iconSize() { + return '3rem' + }, + expirationTextColor(){ + if(this.expirationText.split("Utgikk").length===2){ + return '#EE6D6D'; //rød + } + else{ + return '#737573'; //grå + } + }, + getImage(){ + return this.actualItem.item.image_url; + + }, + expirationText() { + const expirationDays = this.getDateDifference(); + const dateString = this.formatDate(); + + if(expirationDays<0) { + return "Utgikk " + dateString + } + + switch (expirationDays){ + case expirationDays < -1: + return "Utgikk " + dateString + case 0: + return "Utgår i dag" + case -0: + return "Utgår i dag" + case 1: + return "Utgår i morgen" + case 2: + return "Utgår om to dager" + case 3: + return "Utgår om tre dager" + default: + return "Utgår " + dateString + } + } + }, + props: { + item: { + type:Object, + required: false, + }, + actualItem: { + type: Object, + required:false, + }, + itemName: String, + expiration: String, + image: String, + quantity: String, + quantityUnit: String, + }, + methods: { + getDateDifference(){ //returns the difference in days between the expiration date and today + let date = this.actualItem.exp_date; + const epDate = new Date(date); + + const parsedDate = Date.parse(epDate) + const today = new Date(); + + const ms = parsedDate-today; + const numOfDays = Math.ceil(ms/(86400000)) + + return numOfDays; + }, + appleBtnPressed(){ + this.$emit('appleBtnPressed', this.actualItem); + }, + formatDate(){ //formats expiration date as dd.mm.yyyy + let fullExpirationDate = new Date(this.actualItem.exp_date); + let day = fullExpirationDate.getDate(); + let month= (fullExpirationDate.getMonth()+1).toString(); + let year= fullExpirationDate.getFullYear().toString(); + return day + "." + month + "." + year; + } + + } +} +</script> + +<style scoped lang="scss"> +#item { + background-color: base.$white; + color: black; + qborder-radius: 10px; + padding: 1em; + padding-left: 2em; + padding-right:2em; + display:flex; + align-items: center; + justify-content: space-between; +} + +#fridgeItemName { + font-weight: normal; + font-size: 1.2em; +} + + +img { + width: 3rem; + height: 3em; + border-radius: 50%; + background-color: #00663C; + margin-right:.6em; + object-fit: cover; +} + +#itemInfo{ + width: 100%; +} + +#appleBtn:hover { + cursor:pointer; +} + +</style> \ No newline at end of file diff --git a/src/components/GroceryOffers.vue b/src/components/GroceryOffers.vue new file mode 100644 index 0000000000000000000000000000000000000000..c970238dc31d8463b342f7f404db08c9988d6014 --- /dev/null +++ b/src/components/GroceryOffers.vue @@ -0,0 +1,83 @@ +<template> + <div> + <h2>Tilbudaviser</h2> + </div> + + <div id="list"> + <ul> + <li> + <a href="https://etilbudsavis.no/REMA-1000" target="_blank" rel="noopener noreferrer"> + <img src="https://d1r8abvi6c1fdj.cloudfront.net/media?url=s3%3A%2F%2Fsgn-prd-assets%2Fuploads%2F9tf8gmfaGBLcbjK3rhBlW&w=300&sign=Zq9DkNZRVTrnWUEb8C9driKqS1InuTFi_5b1XbIKTnM" alt="Rema 1000 logo"></a> + </li> + <li> + <a href="https://etilbudsavis.no/KIWI" target="_blank" rel="noopener noreferrer"> + <img src = "https://d1r8abvi6c1fdj.cloudfront.net/media?url=s3%3A%2F%2Fsgn-prd-assets%2Fuploads%2FG3XDq4dn6xRNyk-1gOZgb&w=300&sign=HIt49jOaHo0lFALYmhFwY3UWt8zkPkVtVkLaCVxVcE0" alt="Kiwi logo"></a></li> + <li> + <a href="https://etilbudsavis.no/Bunnpris/" target="_blank" rel="noopener noreferrer"> + <img src="https://d1r8abvi6c1fdj.cloudfront.net/media?url=s3%3A%2F%2Fetaproduction%2Fimg%2Flogo%2Fpageflip%2F00ibuh349ezgcwz3.png&w=300&sign=ryffYtZjdRMOoL63Nm5VY4YoigiOMaI5Lgrnb0hZhkE" alt="Bunnpris logo"></a></li> + <li> + <a href="https://etilbudsavis.no/MENY/" target="_blank" rel="noopener noreferrer"> + <img src="https://d1r8abvi6c1fdj.cloudfront.net/media?url=s3%3A%2F%2Fsgn-prd-assets%2Fuploads%2FgMULu5EGk-l57npG294B7&w=300&sign=hMQoYjgxL7F0XlPGCoZJXny-him1emA8RMoO-5W3lXU" alt="Meny logo"></a></li> + <li> + <a href="https://etilbudsavis.no/Coop-Mega/" target="_blank" rel="noopener noreferrer"> + <img src="https://d1r8abvi6c1fdj.cloudfront.net/media?url=s3%3A%2F%2Fsgn-prd-assets%2Fuploads%2Fk5LYCCJcujAJedeFOFRaj&w=300&sign=Gb0VlcY80QDtjFUs50oOOMPGhwUCOPVzBQ4bw2HpQ0Q" alt="Coop mega logo"></a></li> + <li> + <a href="https://etilbudsavis.no/Coop-Extra" target="_blank" rel="noopener noreferrer"> + <img src="https://d1r8abvi6c1fdj.cloudfront.net/media?url=s3%3A%2F%2Fsgn-prd-assets%2Fuploads%2FnmLKqrg-wVDfXHyI_iS9D&w=300&sign=AaqQeyqkDZi_3r4xWetXNNTGRvvz3w74jexExHFhjBA" alt="Coop extra logo"> + </a> + </li> + </ul> + </div> +</template> + +<script> +export default { + name: "GroceryOffers" +} +</script> + +<style scoped lang="scss"> +div { + display: flex; + justify-content: center; +} + +h3{ + padding-top:1em; +} + +li{ + padding:1em; +} + +img{ + width: 10em; +} + +ul { + + padding:0; + text-align: center; + list-style-type: none; + + width: 100%; +} + +@media (min-width: 800px){ + ul{ + display: grid; + width: 100%; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + } + + li { + padding:0.3em; + align-items: center; + text-align: center; + display: flex; + justify-content: center; + } + +} +</style> \ No newline at end of file diff --git a/src/components/ItemSearch.vue b/src/components/ItemSearch.vue new file mode 100644 index 0000000000000000000000000000000000000000..cfa6e6c5d551e11c2a17317003dafdd9055ef909 --- /dev/null +++ b/src/components/ItemSearch.vue @@ -0,0 +1,115 @@ +<template> + <div v-if="showSearch" id="wrapper"> + <h3>SØK ETTER VARE</h3> + <div id="searchBoxDiv"> + <input type="text" id="searchBox" v-model="itemSearch"> + <button @click="search">Søk</button> + </div> + + <p>Resultater: ({{searchResult.length}})</p> + + <select v-model="selectedItem" name="searchR" id="searchR"> + <option v-for ="item in searchResult" :value="item" :key="item.ean">{{item.name}} ({{item.amount.quantity}}{{item.amount.unit}})</option> + </select> + <p>Antall varer: <span v-if="numOfItemsToAdd>1 && selectedItem!=null">(totalt: {{this.totalNumOfItems}} {{selectedItem.amount.unit}})</span></p> + <input type="number" min='1' v-model="numOfItemsToAdd"><br> + + <button id = "addToFridgeBtn" @click="addToFridge">Legg i kjøleskap</button> + + + </div> +</template> + +<script> +import {API} from "@/util/API"; + + +export default { + name: "itemSearch", + data(){ + return{ + itemSearch:'', + searchResult: [], + selectedItem: null, + showSearch:true, + numOfItemsToAdd:1, + } + }, + computed:{ + totalNumOfItems(){ + return this.selectedItem.amount.quantity*this.numOfItemsToAdd + } + }, + methods: { + async search() { + this.searchResult = await API.searchItems(this.itemSearch); + this.selectedItem = this.searchResult[0]; + + }, + addToFridge(){ + const num = this.numOfItemsToAdd; + API.addToFridge( + { + "itemId": Number(this.selectedItem.id), + "amount": + { + "quantity": this.selectedItem.amount.quantity*num, + "unit": this.selectedItem.amount.unit}} + ).then(() => this.$emit('itemsAdded',this.selectedItem)).catch((_)=> console.log("No items were added to the fridge")) + } + } +} +</script> + +<style scoped lang = "scss"> +select { + width:100%; +} + +#wrapper{ + background-color: base.$white; + padding: 2em; + border: 3px dashed base.$light-green; + + +} + +#searchBoxDiv { + display:flex; + width:100%; +} + +input[type="text"], +input[type="select"], +input[type="number"], +select{ + padding: .4em; + margin: .1em; +} + +button { + padding: .5em; + background-color: base.$light-green; + border-radius: 5%; + + border: 1px solid base.$green; + +} + +button:hover { + border: 1px solid base.$grey; + background-color: base.$light-green-hover; + cursor: pointer; + + +} + +addToFridgeBtn { + width: 100%; +} + +span { + qfont-size: 1.3em; +} +</style> + diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue index 131108c761a8683cd36279b1e2176a3e943515fa..b3f9d0433378866d59f82cebe2d65d336405d630 100644 --- a/src/components/Navbar.vue +++ b/src/components/Navbar.vue @@ -8,7 +8,7 @@ </RouterLink> </li> <li> - <RouterLink :to="'/'" :aria-label="'link to fridge page'"> + <RouterLink :to="'/myFridge'" :aria-label="'link to fridge page'"> <Icon icon="mingcute:fridge-line" :color="iconColor" :style="{ fontSize: iconSize }"/> </RouterLink> </li> diff --git a/src/components/__tests__/FridgeItem.spec.js b/src/components/__tests__/FridgeItem.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..5a7b241b63107426dc1f3aad53e793c412764e74 --- /dev/null +++ b/src/components/__tests__/FridgeItem.spec.js @@ -0,0 +1,83 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import FridgeItem from "@/components/FridgeItem.vue"; + +const normalItem = { + "item":{ + "id": "1", + "name":"eple", + "amount": {"quantity": "4","unit": "stk"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }, + "exp_date": "2222-02-22T00:00:00+00:00", + "amount": {"quantity": "6","unit": "stk"} +} + +const expiredItem = { + "item":{ + "id": "1", + "name":"eple", + "amount": {"quantity": "4","unit": "stk"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }, + "exp_date": "2002-02-22T00:00:00+00:00", + "amount": {"quantity": "6","unit": "stk"} +} +describe('Fridge items render correctly', () => { + + it('displays the name of the item', () => { + const wrapper = mount(FridgeItem, { + props: { + actualItem: normalItem, + }, + }); + expect(wrapper.text()).toContain('eple'); + }); + + it('displays the amount of the item in the fridge' , () => { + const wrapper = mount(FridgeItem, { + props: { + actualItem: normalItem, + }, + }); + expect(wrapper.text()).toContain('6'); + expect(wrapper.text()).toContain('stk'); + }); + + it('displays item image', () => { + const wrapper = mount(FridgeItem, {props: { + actualItem: normalItem, + }, + }); + const itemImage = wrapper.find('img'); + expect(itemImage.exists()).toBe(true); + expect(itemImage.element.getAttribute('src')).not.toBe(''); + expect(itemImage.element.getAttribute('src')).toBe('https://bilder.ngdata.no/7040512550818/meny/large.jpg'); + }); + + it('displays text of different color when item has expired', () => { + const wrapper = mount(FridgeItem, { + props: { + actualItem: expiredItem, + }, + }); + + const expiration = wrapper.find('.expText'); + expect(expiration.element.style.color).not.toBe('black'); + }); + +}) +describe('Behaves as expected', () => { + it('emits when the apple button is pressed', async () => { + const wrapper = mount(FridgeItem, { + props: { + actualItem: normalItem, + }, + }); + + await wrapper.find('#appleBtn').trigger('click'); + await wrapper.vm.$nextTick(); + expect(wrapper.emitted().appleBtnPressed).toBeTruthy(); + + }) +}) diff --git a/src/components/__tests__/FridgeItemModal.spec.js b/src/components/__tests__/FridgeItemModal.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..cc180e0fede1939270d6467a1cc723feff53cf4d --- /dev/null +++ b/src/components/__tests__/FridgeItemModal.spec.js @@ -0,0 +1,88 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import EatFridgeItemModal from "@/components/EatFridgeItemModal.vue"; + +const normalItem = { + "item":{ + "id": "1", + "name":"purre", + "amount": {"quantity": "500","unit": "g"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }, + "exp_date": "2222-02-22T00:00:00+00:00", + "amount": {"quantity": "500","unit": "g"} +} + +const expiredItem = { + "item":{ + "id": "1", + "name":"purre", + "amount": {"quantity": "500","unit": "g"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }, + "exp_date": "2002-02-22T00:00:00+00:00", + "amount": {"quantity": "500","unit": "g"} +} +describe('Renders correctly', () => { + it('displays the name of the item', () => { + const wrapper = mount(EatFridgeItemModal, { + props: { + fridgeItem: normalItem, + }, + }); + + const itemName = wrapper.find('h2') + expect(itemName.text()).toContain('purre') + + }); + + it('displays the correct unit', () => { + const wrapper = mount(EatFridgeItemModal, { + props: { + fridgeItem: normalItem, + }, + }); + + const sliderValueUnit = wrapper.find('#sliderDisplay') + expect(sliderValueUnit.text()).toContain('g') + + }); + + it('Displays a message if item has expired', () => { + const wrapper = mount(EatFridgeItemModal, { + props: { + fridgeItem: expiredItem, + }, + }); + + expect(wrapper.find('#itemHasExpiredMessage').exists()).toBe(true); + const expiredMsgBox = wrapper.find('#itemHasExpiredMessage') + expect(expiredMsgBox.isVisible()).toBe(true) + + }); + + it('Does not display message if item has not expired', () => { + const wrapper = mount(EatFridgeItemModal, { + props: { + fridgeItem: normalItem, + }, + }); + expect(wrapper.find('#itemHasExpiredMessage').exists()).toBe(false); + }); + +}) + +describe('Behaves correctly', () => { + it('emits close when X is pressed', async () => { + const wrapper = mount(EatFridgeItemModal, { + props: { + fridgeItem: normalItem, + }, + }); + + await wrapper.find('#exitBtn').trigger('click'); + await wrapper.vm.$nextTick(); + expect(wrapper.emitted().closeModal).toBeTruthy(); + + }); +}) \ No newline at end of file diff --git a/src/components/__tests__/ItemSearch.spec.js b/src/components/__tests__/ItemSearch.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..49c6597c263c7f2161eeb11a6024b02cfa42864d --- /dev/null +++ b/src/components/__tests__/ItemSearch.spec.js @@ -0,0 +1,68 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import ItemSearch from "@/components/ItemSearch.vue"; + +const SearchReturn = [ + { + "id": "1", + "ean": "7040512550214", + "name":"eplekake", + "amount": {"quantity": "4","unit": "stk"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }, + { + "id": "2", + "ean": "7040512550254", + "name":"eple", + "amount": {"quantity": "4","unit": "stk"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }]; + +describe('Behaves as expected', () => { + it('shows correct number of results', () => { + const wrapper = mount(ItemSearch, { + data() { + return { + searchResult: SearchReturn, + selectedItem: SearchReturn[0], + numOfItemsToAdd:3, + }; + }, + }); + + expect(wrapper.text()).toContain('Resultater: ') + expect(wrapper.text()).toContain('(2)') + }); + + it('When adding more items, the total ingredient amount is shown', () => { + const wrapper = mount(ItemSearch, { + data() { + return { + searchResult: SearchReturn, + selectedItem: SearchReturn[0], + numOfItemsToAdd:3, + }; + }, + }); + + // total = 3*4 + const totalItemCount = wrapper.find('span') + expect(totalItemCount.text()).toContain('12') + expect(totalItemCount.text()).toContain('stk') + }); + + it('Select contains all items ', () => { + const wrapper = mount(ItemSearch, { + data() { + return { + searchResult: SearchReturn, + selectedItem: SearchReturn[0], + numOfItemsToAdd:3, + }; + }, + }); + const itemSelect = wrapper.find('select') + expect(itemSelect.text()).toContain('eple') + expect(itemSelect.text()).toContain('eplekake') + }); +}) diff --git a/src/components/__tests__/Navbar.spec.js b/src/components/__tests__/Navbar.spec.js index a212573ecee960d8a98e37d63d093b3b2b2a2066..86579bf2b896c4c3a22df76bf4eb7013a334acae 100644 --- a/src/components/__tests__/Navbar.spec.js +++ b/src/components/__tests__/Navbar.spec.js @@ -3,6 +3,8 @@ import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import Navbar from "@/components/Navbar.vue"; import { Icon } from '@iconify/vue'; +import { RouterLink } from 'vue-router'; + describe('Navbar', () => { it('contains 5 links', () => { const wrapper = mount(Navbar) diff --git a/src/router/index.js b/src/router/index.js index 642a5aecd5c1e5b8a3b2999910473c940ab01d51..e1a21f65fb0daf61e458ded6aa41bdf2b4dbf3fe 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -7,6 +7,9 @@ import SelectProfileView from '../views/SelectProfileView.vue' import ProfileCreationView from '../views/ProfileCreationView.vue' import RegisterAccountView from '../views/RegisterAccountView.vue' +import FridgeView from "@/views/FridgeView.vue"; + + const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ @@ -34,7 +37,10 @@ const router = createRouter({ path: '/registerAccount', name: 'registerAccount', component: RegisterAccountView - + },{ + path: '/myFridge', + name: 'myFridge', + component: FridgeView }, { path: '/profileSettings', diff --git a/src/stores/fridgeStore.js b/src/stores/fridgeStore.js new file mode 100644 index 0000000000000000000000000000000000000000..cf82e0189950e1d14d2060f13049af87b704993a --- /dev/null +++ b/src/stores/fridgeStore.js @@ -0,0 +1,29 @@ +import { defineStore } from "pinia"; + +export const useFridgeStore = defineStore("fridge", { + state: () => { + return { + items: [] + }; + }, + persist: { + storage: localStorage + }, + getters: { + getItems() { + return this.items; + }, + getItem(state){ + return(id) => state.items.filter(item => + item.id === id) + } + }, + actions: { + reset() { + this.$reset(); + }, + addToFridge(item){ + this.items.push(item); + } + } +}); \ No newline at end of file diff --git a/src/style.scss b/src/style.scss index df31d235654012419a14e80b1004c0998fb4b68b..c7158435ec70a7af663f63d364e5946679272560 100644 --- a/src/style.scss +++ b/src/style.scss @@ -5,6 +5,10 @@ $white:#FFFFFF; $grey:#D9D9D9; $red:#EE6D6D; +$red-hover: darken( $red, 5% ); +$green-hover: darken( $green, 8% ); +$light-green-hover: darken( $light-green, 10% ); + $desktop-min: 800px; $phone-min : 360px; diff --git a/src/util/API.js b/src/util/API.js index 43396b8210de1ed199dcfedc72eb69a9b97d91cc..39e994f91f0f6f93c5f19266c266ebaede117b4b 100644 --- a/src/util/API.js +++ b/src/util/API.js @@ -26,7 +26,7 @@ export const API = { .then(async (response) => { token = response.data; const id = (jwt_decode(token)).id; - + return API.getAccount(id, token) .then((user) => { authStore.setAccount(user); @@ -73,7 +73,7 @@ export const API = { .then((response) => { authStore.setProfile(response.data) router.push("/") - + }) .catch(() => { throw new Error("Profile not found or not accessible") @@ -84,10 +84,10 @@ export const API = { /** * Sends a request to create a new profile on the currently logged in account - * + * * @typedef {{name: string, profileImageUrl: string, isRestricted: boolean}} ProfileType - * @param {ProfileType} profile - * @returns + * @param {ProfileType} profile + * @returns */ addProfile: async (profile) => { const authStore = useAuthStore(); @@ -136,6 +136,107 @@ export const API = { .catch(() => {throw new Error()}) }, + /** + * fetches all fridge items belonging to the user that is currently logged in + * @returns {Promise<*>} + */ + getFridgeItems: async () =>{ + const authStore = useAuthStore(); + + return axios.get(`${import.meta.env.VITE_BACKEND_URL}/fridge`, { + headers: { Authorization: `Bearer ${authStore.token}` }, + }) + .then((response) => { + return response.data; + }).catch(() => { + throw new Error("Could not fetch fridge items"); + }); + }, + + /** + * Adds item(s) to the fridge + * @param request List<Ingredient> listOfIngredients + * @returns {Promise<void>} + */ + addToFridge: async(request) =>{ + + const authStore = useAuthStore(); + axios.post(`${import.meta.env.VITE_BACKEND_URL}/fridge/items`, request,{ + headers: { Authorization: `Bearer ${authStore.token}` }, + }).then((response) => { + return response.data; + }).catch(()=> { + throw new Error("Could not add item to fridge: "); + }) +}, + + /** + * Searches for registered items. + * @param searchPhrase Name of the item that one is looking for (e.g: "purre") + * @returns {Promise<*>} list containing items that match the search phrase + */ + searchItems: async(searchPhrase)=> { + return axios.get(`${import.meta.env.VITE_BACKEND_URL}/item/search?name=${searchPhrase}`, { + }).then((response) => { + console.log(response.data.content); + return response.data.content; + }).catch(()=> { + throw new Error("Error when searching for item "); + }) + }, + + /** + * Removes an amount of an ingredient from the frodge, and the action is then logged + * @param request Log.Action action, Map<Integer, Amount> + * Action available: CONSUMED, DISCARDED,ADDED_TO_FRIDGE + * @returns {Promise<void>} + */ + updateFridge: async(request) => { + const authStore = useAuthStore(); + + axios.put(`${import.meta.env.VITE_BACKEND_URL}/fridge/ingredientsAmount`, request,{ + headers: { Authorization: `Bearer ${authStore.token}` }, + }).then((response) => { + return response.data; + }).catch(()=> { + throw new Error("Could modify ingredient. "); + }) + }, + + /** + * Changes the expiration date of an ingredient. + * @param request ingredientId: id of the ingredient, newExpDate: the new expiration date of the ingredient + * @returns {Promise<void>} + */ + changeExpirationOfIngredient: async(request) => { + const authStore = useAuthStore(); + + axios.put(`${import.meta.env.VITE_BACKEND_URL}/fridge/ingredient`, request,{ + headers: { Authorization: `Bearer ${authStore.token}` }, + }).then((response) => { + return response.data; + }).catch(()=> { + throw new Error("Could modify ingredient. "); + }) + }, + + + //returns fridgeItem of specific id + getFridgeItem: async (id) =>{ + const authStore = useAuthStore(); + + axios.get(`${import.meta.env.VITE_BACKEND_URL}/fridge/${id}`, { + headers: { Authorization: `Bearer ${authStore.token}` }, + }) + .then((response) => { + return response.data; + }) + .catch(() => { + throw new Error("Could not fetch fridge item"); + }); + }, + }, + /** * Deletes account from the * @param id diff --git a/src/views/FridgeView.vue b/src/views/FridgeView.vue new file mode 100644 index 0000000000000000000000000000000000000000..cd8088e689767f9daa549a4dcaa98a65b30c4b68 --- /dev/null +++ b/src/views/FridgeView.vue @@ -0,0 +1,138 @@ +<template><h1>Kjøleskap</h1><br><br> + <main> + <ItemSearch v-if="searchVisible" @itemsAdded="updateFridge"></ItemSearch> + + <div id = "fridgeMsg"><p>Melding fra kjøleskapet:</p><span>{{this.fridgeMsg}}</span></div> + <eat-fridge-item-modal @closeModal="hideModal" v-if="visible" :fridge-item="selectedItem"></eat-fridge-item-modal> + + <div id = "itemContainer" > + <FridgeItem v-for="item in fridgeItems" :actualItem = "item" @appleBtnPressed="showModal"></FridgeItem> + </div> + + <div id="addItemBtn-container"> + <button @click="showItemSearch" id="addItemBtn"> + <span class="plus">+</span> + </button> + </div> + </main> +</template> + +<script> +import FridgeItem from "@/components/FridgeItem.vue"; +import EatFridgeItemModal from "@/components/EatFridgeItemModal.vue"; +import {API} from "@/util/API"; +import {mapState, mapStores} from "pinia"; +import {useAuthStore} from "@/stores/authStore"; +import {useFridgeStore} from "@/stores/fridgeStore"; +import ItemSearch from "@/components/ItemSearch.vue"; +import fridgeItem from "@/components/FridgeItem.vue"; + +export default { + name: "FridgeView", + components: {ItemSearch, EatFridgeItemModal, FridgeItem}, + computed:{ + ...mapState(useAuthStore, ['account']), + ...mapStores(useFridgeStore), + }, + data() { + return { + visible: false, //is the useitemModal visible + selectedItem: null, + fridgeItems: [], + searchVisible: false, + fridgeMsg: "" + } + }, + methods: { + showModal(item){ + this.visible = true; + this.selectedItem = item; + }, + hideModal(){ + this.visible=false; + }, + showItemSearch() { + this.searchVisible=!this.searchVisible; + window.scrollTo({ top: 0, behavior: 'smooth' }); + }, + hideItemSearch() { + this.searchVisible=false; + }, + updateFridgeMessage(msg){ + this.fridgeMsg=msg; + }, + async updateFridge(addedItem) { + this.fridgeItems = await API.getFridgeItems(); + this.fridgeItems = await API.getFridgeItems(); + this.hideItemSearch(); + this.updateFridgeMessage(addedItem.name + " ble lagt i kjøleskapet.") + }, + + }, + async mounted() { + this.fridgeItems = await API.getFridgeItems(); + if(this.fridgeItems.length===0){ + this.fridgeMsg="Kjøleskapet ditt er tomt. Legg inn varer ved å trykke på pluss-knappen nederst i høyre hjørne" + } + } +} +</script> + +<style scoped lang="scss"> +main { + + color:black; + background-color: base.$grey; + padding: 2em; + min-height: 600px; +} + +h1 { + width:100%; + padding-left: 1em; + padding-top: 2em; + font-weight: bold; +} + +#itemContainer { + background-color: base.$grey; + padding-bottom: 5em; +} + +#addItemBtn { + width: 2.5em; + height: 2.5em; + border-radius: 50%; + font-size: 30px; + border:none; + background-color: base.$light-green; + color: white; + box-shadow: 0px 0px 20px rgba(0,0,0,0.3); +} + +#addItemBtn:hover { + background-color: base.$light-green-hover; + color: white; + box-shadow: 0px 0px 20px rgba(0,0,0,0.5); + cursor: pointer; +} + +#addItemBtn-container { + padding-bottom: 3em; + z-index: 9999; + position: fixed; + bottom: 15px; + right: 1em; +} + +#fridgeMsg { + background-color: base.$light-green; + padding: 1em; +} + +#fridgeMsg span, #fridgeMsg p{ + + font-weight: bolder; + font-size: 1.2em; +} +</style> \ No newline at end of file diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index cc7e702ec407ed9963f523c12b331c58c3b0cf47..1bd7463494c91b036111d7a97d4983d6e3395c12 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -2,12 +2,15 @@ import { useAuthStore } from "@/stores/authStore.js"; import { mapState } from 'pinia' import router from "@/router"; + import GroceryOffers from "@/components/GroceryOffers.vue"; export default { + components: {GroceryOffers}, data() { return { } }, + computed: { ...mapState(useAuthStore, ['profile']) } @@ -31,6 +34,8 @@ <p id="tips-text">Her kommer tips du kanskje kan ha nytte av, trykk på meg for å gå til neste tips!</p> </div> + <grocery-offers></grocery-offers> + </div> </main> diff --git a/src/views/SelectProfileView.vue b/src/views/SelectProfileView.vue index 9ae4d2497cbd43e9b657a87307eed7ddccc92a38..01ed39912f2343e22898718b05ac76d21a551b00 100644 --- a/src/views/SelectProfileView.vue +++ b/src/views/SelectProfileView.vue @@ -45,11 +45,11 @@ <h1>Hvem bruker appen?</h1> <div class="icons"> - <div v-for="profile in this.profiles" @click=selectProfile(profile.id) class="icon"> + <div v-for="profile in this.profiles" @click=selectProfile(profile.id) class="icon" > <img v-if="profile.profileImageUrl == ''" src="https://t4.ftcdn.net/jpg/02/15/84/43/360_F_215844325_ttX9YiIIyeaR7Ne6EaLLjMAmy4GvPC69.jpg" alt="profile image"> <img v-else :src=profile.profileImageUrl alt="profile image"> - <p>{{profile.name}}</p> + <button id="selectProfileBtn" @click=selectProfile(profile.id)><p>{{profile.name}}</p></button> </div> </div> @@ -117,4 +117,12 @@ padding-bottom: 10px; } + #selectProfileBtn { + background-color: transparent; + border:none; + font-size: 1em; + border-radius: 0; + + } + </style> \ No newline at end of file diff --git a/src/views/__tests__/FridgeView.spec.js b/src/views/__tests__/FridgeView.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..4790f4ad2639935f3da4a5694dafcb3a27738181 --- /dev/null +++ b/src/views/__tests__/FridgeView.spec.js @@ -0,0 +1,70 @@ +import { describe, it, expect, vi } from 'vitest' +import { createTestingPinia } from '@pinia/testing' +import { mount } from '@vue/test-utils' +import FridgeView from "@/views/FridgeView.vue"; + + +const fridgeIngredients = [ + { + "item":{ + "id": "1", + "name":"eple", + "amount": {"quantity": "4","unit": "stk"}, + "image_url": "https://bilder.ngdata.no/7040512550818/meny/large.jpg" + }, + "exp_date": "2222-02-22T00:00:00+00:00", + "amount": {"quantity": "6","unit": "stk"} + }]; + +describe('Fridge', () => { + it('renders', () => { + const wrapper = mount(FridgeView, { + global: { + plugins: [createTestingPinia({ + createSpy: vi.fn, + })], + }, + }) + expect(wrapper.text()).toContain('Kjøleskap') + }); + + it('pressing the plus button makes search visible', async () => { + const wrapper = mount(FridgeView, { + global: { + plugins: [createTestingPinia({ + createSpy: vi.fn, + })], + }, + + }) + expect(wrapper.find('ItemSearch').exists()).toBe(false); + await wrapper.find('#addItemBtn').trigger('click'); + + setTimeout(() => { + expect(wrapper.find('ItemSearch').exists()).toBe(true); + }, 1000); + }); + + it('when appleBtnEmit, the EatFridgeItemModal is displayed', () => { + const wrapper = mount(FridgeView, { + global: { + plugins: [createTestingPinia({ + createSpy: vi.fn, + })], + }, + data() { + return { + fridgeItems: fridgeIngredients, + }; + }, + + }) + + expect(wrapper.find('eat-fridge-item-modal').exists()).toBe(false); + + wrapper.find('#appleBtn').trigger('click'); + setTimeout(() => { + expect(wrapper.find('eat-fridge-item-modal').exists()).toBe(true); + }, 1000); + }) +}) \ No newline at end of file