diff --git a/app.json b/app.json index d59ae19..9c31b10 100644 --- a/app.json +++ b/app.json @@ -12,25 +12,34 @@ "backgroundColor": "#E5E2D7" }, "scheme": "no-neuf-chateau", - "assetBundlePatterns": [ - "**/*" - ], + "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true, - "bundleIdentifier": "no.neuf.chateau" + "bundleIdentifier": "no.neuf.chateau", + "infoPlist": { + "UIBackgroundModes": ["remote-notification"] + } }, "android": { "package": "no.neuf.chateau", "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#CD96AB" - } + }, + "permissions": ["NOTIFICATIONS"] }, "web": { "favicon": "./assets/favicon.png" }, "plugins": [ - "expo-asset" + "expo-asset", + [ + "expo-notifications", + { + "icon": "./assets/icon.png", + "color": "#E5E2D7" + } + ] ], "extra": { "eas": { diff --git a/package-lock.json b/package-lock.json index 2cf1353..f077296 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "expo-asset": "~11.0.5", "expo-constants": "~17.0.8", "expo-dev-client": "~5.0.20", + "expo-notifications": "~0.29.14", "expo-status-bar": "~2.0.1", "graphql": "^16.9.0", "metro-react-native-babel-preset": "^0.77.0", @@ -2664,18 +2665,6 @@ "node": ">=8" } }, - "node_modules/@expo/cli/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@expo/cli/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4043,6 +4032,12 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@ide/backoff": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@ide/backoff/-/backoff-1.0.0.tgz", + "integrity": "sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g==", + "license": "MIT" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -7132,6 +7127,19 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, "node_modules/ast-types": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", @@ -7173,7 +7181,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -7485,6 +7492,12 @@ "@babel/core": "^7.0.0" } }, + "node_modules/badgin": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/badgin/-/badgin-1.2.3.tgz", + "integrity": "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw==", + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -7794,7 +7807,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", @@ -7824,7 +7836,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" @@ -8677,7 +8688,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -8704,7 +8714,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -10281,6 +10290,15 @@ } } }, + "node_modules/expo-application": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-6.0.2.tgz", + "integrity": "sha512-qcj6kGq3mc7x5yIb5KxESurFTJCoEKwNEL34RdPEvTB/xhl7SeVZlu05sZBqxB1V4Ryzq/LsCb7NHNfBbb3L7A==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-asset": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-11.0.5.tgz", @@ -10583,6 +10601,26 @@ "invariant": "^2.2.4" } }, + "node_modules/expo-notifications": { + "version": "0.29.14", + "resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-0.29.14.tgz", + "integrity": "sha512-AVduNx9mKOgcAqBfrXS1OHC9VAQZrDQLbVbcorMjPDGXW7m0Q5Q+BG6FYM/saVviF2eO8fhQRsTT40yYv5/bhQ==", + "license": "MIT", + "dependencies": { + "@expo/image-utils": "^0.6.5", + "@ide/backoff": "^1.0.0", + "abort-controller": "^3.0.0", + "assert": "^2.0.0", + "badgin": "^1.1.5", + "expo-application": "~6.0.2", + "expo-constants": "~17.0.8" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/expo-status-bar": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-2.0.1.tgz", @@ -10853,7 +10891,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -11247,7 +11284,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -11640,7 +11676,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -11733,7 +11768,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -11838,7 +11872,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "dev": true, "dependencies": { "call-bound": "^1.0.3", "get-proto": "^1.0.0", @@ -11875,6 +11908,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -11939,7 +11988,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", @@ -12026,7 +12074,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, "dependencies": { "which-typed-array": "^1.1.16" }, @@ -13918,6 +13965,18 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -14122,9 +14181,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -14348,7 +14407,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" @@ -14364,7 +14422,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -14373,7 +14430,6 @@ "version": "4.1.7", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -14934,7 +14990,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -16219,7 +16274,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -16267,9 +16321,10 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -16293,6 +16348,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -16300,28 +16356,20 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/send/node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -16333,6 +16381,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -16347,24 +16396,33 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -17963,6 +18021,19 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -18175,7 +18246,6 @@ "version": "1.1.18", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", diff --git a/package.json b/package.json index 9126d70..d72ca96 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,8 @@ "redux-persist": "^6.0.0", "redux-thunk": "^2.4.2", "styled-system": "^5.1.5", - "expo-dev-client": "~5.0.20" + "expo-dev-client": "~5.0.20", + "expo-notifications": "~0.29.14" }, "devDependencies": { "@babel/core": "^7.24.9", diff --git a/src/App.tsx b/src/App.tsx index cbea0b6..8762607 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,7 @@ import { ApolloClient, HttpLink, InMemoryCache, ApolloProvider } from '@apollo/client'; import { StripeProvider } from '@stripe/stripe-react-native'; import Constants from 'expo-constants'; +import * as Notifications from 'expo-notifications'; import { StatusBar } from 'expo-status-bar'; import React from 'react'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; @@ -13,6 +14,15 @@ import { useDeepLinking } from './hooks'; import { store, persistor } from './reduxStore'; import { loadTheme } from './theme'; +// Configure notifications +Notifications.setNotificationHandler({ + handleNotification: async () => ({ + shouldShowAlert: true, + shouldPlaySound: true, + shouldSetBadge: false, + }), +}); + const apolloClient = new ApolloClient({ link: new HttpLink({ uri: `${process.env.EXPO_PUBLIC_DUSKEN_API_URL}/api/graphql/` }), cache: new InMemoryCache(), diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts new file mode 100644 index 0000000..ee34acb --- /dev/null +++ b/src/hooks/useNotifications.ts @@ -0,0 +1,71 @@ +import * as Notifications from 'expo-notifications'; +import { useEffect, useState } from 'react'; + +export const useNotifications = () => { + const [hasPermission, setHasPermission] = useState(null); + + useEffect(() => { + checkPermission(); + }, []); + + const checkPermission = async () => { + const { status } = await Notifications.getPermissionsAsync(); + setHasPermission(status === 'granted'); + }; + + const requestPermission = async () => { + const { status } = await Notifications.requestPermissionsAsync(); + const granted = status === 'granted'; + setHasPermission(granted); + return granted; + }; + + const scheduleMembershipExpiryNotification = async (expiryDate: string) => { + if (!hasPermission) { + const granted = await requestPermission(); + if (!granted) return; + } + + // Cancel any existing notifications before scheduling a new one + await Notifications.cancelAllScheduledNotificationsAsync(); + + const expiryDateObj = new Date(expiryDate); + const notificationDate = new Date(expiryDateObj); + notificationDate.setDate(notificationDate.getDate() + 1); // 1 day after expiry + + // Don't schedule if the notification date is in the past + if (notificationDate <= new Date()) return; + + await Notifications.scheduleNotificationAsync({ + content: { + title: 'Medlemskapet ditt har utløpt!', + body: 'Ditt medlemskap i Det Norske Studentersamfund har utløpt! Vi håper du ønsker å fornye det :)', + data: { type: 'membership_expiry' }, + }, + trigger: { + type: 'date', + date: notificationDate, + }, + }); + }; + + const toggleNotifications = async (enabled: boolean) => { + if (enabled) { + // When enabling, request permission + const granted = await requestPermission(); + return { success: granted, needsPermission: !granted }; + } else { + // When disabling, cancel any existing notifications and update state + await Notifications.cancelAllScheduledNotificationsAsync(); + setHasPermission(false); + return { success: true, needsPermission: false }; + } + }; + + return { + hasPermission, + requestPermission, + scheduleMembershipExpiryNotification, + toggleNotifications, + }; +}; diff --git a/src/modules/membership/Proof.tsx b/src/modules/membership/Proof.tsx index 405f51b..0a5da49 100644 --- a/src/modules/membership/Proof.tsx +++ b/src/modules/membership/Proof.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useRef } from 'react'; +import * as Notifications from 'expo-notifications'; +import React, { useEffect, useRef, useState } from 'react'; import { Image, ScrollView, @@ -10,11 +11,13 @@ import { ActivityIndicator, Dimensions, GestureResponderEvent, + Switch, } from 'react-native'; import Confetti from 'react-native-confetti'; import { Button, Incubator } from 'react-native-ui-lib'; import RadioButton from 'src/components/RadioButton'; +import { useNotifications } from 'src/hooks/useNotifications'; import theme from 'src/theme'; import type { UserType } from 'src/types/data'; @@ -161,6 +164,52 @@ const MembershipName = ({ user }: { user: UserType }) => { ); }; +const NotificationToggle = ({ user }: { user: UserType }) => { + const { hasPermission, toggleNotifications, scheduleMembershipExpiryNotification } = useNotifications(); + const [toast, setToast] = useState<{ message: string; visible: boolean } | null>(null); + + const handleToggle = async (enabled: boolean) => { + const { success, needsPermission } = await toggleNotifications(enabled); + if (needsPermission) { + await Notifications.requestPermissionsAsync(); + setToast({ + message: 'Du må godta varsler i innstillinger for å motta påminnelser', + visible: true, + }); + setTimeout(() => setToast(null), 3000); + return; + } + if (success && enabled && user.last_membership?.end_date) { + await scheduleMembershipExpiryNotification(user.last_membership.end_date); + } + }; + + if (!user.is_member || !user.last_membership) { + return null; + } + + return ( + + Varsle meg når medlemskapet utløper + + setToast(null)} + /> + + ); +}; + interface ProofProps { onLogoutPress: () => void; fetchUser: () => void; @@ -212,30 +261,33 @@ const Proof = ({ onLogoutPress, fetchUser, isFetchingUserData, onDeletePress }: return ( <> - }> - - - - - - - - + + }> + + + + + + + + + + + - - + { const userToken = useAppSelector((state) => state.userToken); const { data, loading } = useQuery(PROOF_QUERY); const { initPaymentSheet, presentPaymentSheet } = useStripe(); + const { scheduleMembershipExpiryNotification, hasPermission } = useNotifications(); const [paymentLoading, setPaymentLoading] = useState(false); const [membershipTypeSlug, setMembershipTypeSlug] = useState('standard'); const [membershipPrice, setMembershipPrice] = useState(25000); // WARNING: Hardcoded due to state not being loaded initially. Should probably be fixed. @@ -78,6 +80,10 @@ const useProof = ({ fetchUser }: UseProofProps) => { user.lastMembership.end_date < todaysDate ) { fetchUser(); + // If payment was successful and notifications are enabled, schedule the notification + if (!error && user?.last_membership?.end_date && hasPermission) { + await scheduleMembershipExpiryNotification(user.last_membership.end_date); + } } }, [user?.last_membership]);