[FEAT] Add membership expiry notification option

This commit is contained in:
leander committed 2025-06-05 22:01:01 +02:00
1 parent be4ddc2018
commit 67206e30d3
7 files changed
+325 -87

No files matched your search

+15 -6
View File
@@ -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": {
+125 -55
View File
@@ -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",
+2 -1
View File
@@ -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",
+10
View File
@@ -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(),
+71
View File
@@ -0,0 +1,71 @@
import * as Notifications from 'expo-notifications';
import { useEffect, useState } from 'react';
export const useNotifications = () => {
const [hasPermission, setHasPermission] = useState<boolean | null>(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,
};
};
+96 -25
View File
@@ -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 (
<View style={styles.notificationToggle}>
<Text style={[theme.text, styles.notificationText]}>Varsle meg når medlemskapet utløper</Text>
<Switch
value={hasPermission || false}
onValueChange={handleToggle}
trackColor={{ false: '#E0E0E0', true: theme.colors.neufOrange }}
thumbColor={hasPermission ? '#FFFFFF' : '#FFFFFF'}
ios_backgroundColor="#E0E0E0"
style={styles.switch}
/>
<Toast
message={toast?.message}
visible={toast?.visible || false}
position="bottom"
autoDismiss={3000}
onDismiss={() => setToast(null)}
/>
</View>
);
};
interface ProofProps {
onLogoutPress: () => void;
fetchUser: () => void;
@@ -212,30 +261,33 @@ const Proof = ({ onLogoutPress, fetchUser, isFetchingUserData, onDeletePress }:
return (
<>
<UserConfetti user={user} confettiRef={confettiRef} />
<ScrollView refreshControl={<RefreshControl refreshing={isFetchingUserData} onRefresh={fetchUser} />}>
<View style={[theme.card, { paddingVertical: 16, backgroundColor: 'transparent' }]}>
<MembershipName user={user} />
<Logo user={user} />
<MembershipStatus user={user} onMembershipPress={onMembershipPress} />
<PurchaseButton
user={user}
paymentLoading={paymentLoading}
isLoadingMembershipType={isLoadingMembershipType}
membershipPrice={membershipPrice}
onPurchasePress={onPurchasePress}
setMembershipTypeSlug={setMembershipTypeSlug}
/>
<MembershipValidTo user={user} />
</View>
<View>
<View style={styles.container}>
<ScrollView refreshControl={<RefreshControl refreshing={isFetchingUserData} onRefresh={fetchUser} />}>
<View style={[theme.card, { paddingVertical: 16, backgroundColor: 'transparent' }]}>
<MembershipName user={user} />
<Logo user={user} />
<MembershipStatus user={user} onMembershipPress={onMembershipPress} />
<PurchaseButton
user={user}
paymentLoading={paymentLoading}
isLoadingMembershipType={isLoadingMembershipType}
membershipPrice={membershipPrice}
onPurchasePress={onPurchasePress}
setMembershipTypeSlug={setMembershipTypeSlug}
/>
<MembershipValidTo user={user} />
<NotificationToggle user={user} />
</View>
</ScrollView>
<View style={styles.buttonContainer}>
<Button onPress={onLogoutPress} style={[theme.button, styles.logoutButton]} small>
<Text style={theme.buttonText}>Logg ut</Text>
</Button>
<Button onPress={onDeletePress} style={[theme.button, styles.deleteButton]} small>
<Text style={styles.whiteText}>Slett bruker</Text>
</Button>
</View>
<Button onPress={onDeletePress} style={[theme.button, styles.deleteButton]} small>
<Text style={styles.whiteText}>Slett bruker</Text>
</Button>
</ScrollView>
</View>
<Toast
message={toast?.message}
visible={toast?.visible}
@@ -303,12 +355,9 @@ const styles = StyleSheet.create({
marginTop: 8,
},
logoutButton: {
alignSelf: 'center',
marginVertical: 20,
marginBottom: 8,
},
deleteButton: {
alignSelf: 'center',
marginVertical: 20,
backgroundColor: theme.colors.danger,
},
whiteText: {
@@ -347,4 +396,26 @@ const styles = StyleSheet.create({
fontWeight: 'bold',
marginTop: 30,
},
notificationToggle: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginTop: 32,
},
notificationText: {
fontSize: 18,
},
switch: {
transform: [{ scale: 1.25 }],
},
container: {
flex: 1,
display: 'flex',
flexDirection: 'column',
},
buttonContainer: {
paddingHorizontal: 16,
paddingBottom: 16,
backgroundColor: 'transparent',
},
});
+6
View File
@@ -5,6 +5,7 @@ import { useEffect, useState } from 'react';
import { requestStripePaymentSheet } from 'src/api';
import { useAppSelector } from 'src/hooks';
import { useNotifications } from 'src/hooks/useNotifications';
import { ToastProps } from 'src/types/toast';
const PROOF_QUERY = gql`
@@ -25,6 +26,7 @@ const useProof = ({ fetchUser }: UseProofProps) => {
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]);