diff --git a/MobileApp/app.json b/MobileApp/app.json
index 6a7599cf83..d170cc70bd 100644
--- a/MobileApp/app.json
+++ b/MobileApp/app.json
@@ -6,12 +6,12 @@
"orientation": "portrait",
"icon": "./assets/icon.png",
"scheme": "oneuptime",
- "userInterfaceStyle": "dark",
+ "userInterfaceStyle": "automatic",
"newArchEnabled": true,
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
- "backgroundColor": "#0D1117"
+ "backgroundColor": "#FFFFFF"
},
"ios": {
"supportsTablet": true,
diff --git a/MobileApp/babel.config.js b/MobileApp/babel.config.js
new file mode 100644
index 0000000000..49fb3bed45
--- /dev/null
+++ b/MobileApp/babel.config.js
@@ -0,0 +1,7 @@
+module.exports = function (api) {
+ api.cache(true);
+ return {
+ presets: ["babel-preset-expo"],
+ plugins: ["nativewind/babel"],
+ };
+};
diff --git a/MobileApp/global.css b/MobileApp/global.css
new file mode 100644
index 0000000000..6c34d154b3
--- /dev/null
+++ b/MobileApp/global.css
@@ -0,0 +1,83 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ --color-bg-primary: #FFFFFF;
+ --color-bg-secondary: #F9FAFB;
+ --color-bg-tertiary: #F3F4F6;
+ --color-bg-elevated: #FFFFFF;
+ --color-border-default: #E5E7EB;
+ --color-border-subtle: #F3F4F6;
+ --color-text-primary: #111827;
+ --color-text-secondary: #6B7280;
+ --color-text-tertiary: #9CA3AF;
+ --color-text-inverse: #FFFFFF;
+ --color-severity-critical: #CF222E;
+ --color-severity-critical-bg: #CF222E1A;
+ --color-severity-major: #BC4C00;
+ --color-severity-major-bg: #BC4C001A;
+ --color-severity-minor: #9A6700;
+ --color-severity-minor-bg: #9A67001A;
+ --color-severity-warning: #BF8700;
+ --color-severity-warning-bg: #BF87001A;
+ --color-severity-info: #0969DA;
+ --color-severity-info-bg: #0969DA1A;
+ --color-state-created: #CF222E;
+ --color-state-acknowledged: #9A6700;
+ --color-state-resolved: #1A7F37;
+ --color-state-investigating: #BC4C00;
+ --color-state-muted: #8C959F;
+ --color-oncall-active: #1A7F37;
+ --color-oncall-active-bg: #1A7F371A;
+ --color-oncall-inactive: #8C959F;
+ --color-oncall-inactive-bg: #8C959F1A;
+ --color-action-primary: #6366F1;
+ --color-action-primary-pressed: #4F46E5;
+ --color-action-destructive: #CF222E;
+ --color-action-destructive-pressed: #A40E26;
+ --color-status-success: #1A7F37;
+ --color-status-success-bg: #1A7F371A;
+ --color-status-error: #CF222E;
+ --color-status-error-bg: #CF222E1A;
+}
+
+.dark {
+ --color-bg-primary: #0D1117;
+ --color-bg-secondary: #161B22;
+ --color-bg-tertiary: #21262D;
+ --color-bg-elevated: #1C2128;
+ --color-border-default: #30363D;
+ --color-border-subtle: #21262D;
+ --color-text-primary: #E6EDF3;
+ --color-text-secondary: #8B949E;
+ --color-text-tertiary: #6E7681;
+ --color-text-inverse: #0D1117;
+ --color-severity-critical: #F85149;
+ --color-severity-critical-bg: #F8514926;
+ --color-severity-major: #F0883E;
+ --color-severity-major-bg: #F0883E26;
+ --color-severity-minor: #D29922;
+ --color-severity-minor-bg: #D2992226;
+ --color-severity-warning: #E3B341;
+ --color-severity-warning-bg: #E3B34126;
+ --color-severity-info: #58A6FF;
+ --color-severity-info-bg: #58A6FF26;
+ --color-state-created: #F85149;
+ --color-state-acknowledged: #D29922;
+ --color-state-resolved: #3FB950;
+ --color-state-investigating: #F0883E;
+ --color-state-muted: #6E7681;
+ --color-oncall-active: #3FB950;
+ --color-oncall-active-bg: #3FB95026;
+ --color-oncall-inactive: #6E7681;
+ --color-oncall-inactive-bg: #6E768126;
+ --color-action-primary: #6366F1;
+ --color-action-primary-pressed: #4F46E5;
+ --color-action-destructive: #F85149;
+ --color-action-destructive-pressed: #DA3633;
+ --color-status-success: #3FB950;
+ --color-status-success-bg: #3FB95026;
+ --color-status-error: #F85149;
+ --color-status-error-bg: #F8514926;
+}
diff --git a/MobileApp/metro.config.js b/MobileApp/metro.config.js
new file mode 100644
index 0000000000..b0963fe7f7
--- /dev/null
+++ b/MobileApp/metro.config.js
@@ -0,0 +1,6 @@
+const { getDefaultConfig } = require("expo/metro-config");
+const { withNativeWind } = require("nativewind/metro");
+
+const config = getDefaultConfig(__dirname);
+
+module.exports = withNativeWind(config, { input: "./global.css" });
diff --git a/MobileApp/nativewind-env.d.ts b/MobileApp/nativewind-env.d.ts
new file mode 100644
index 0000000000..c0d8380737
--- /dev/null
+++ b/MobileApp/nativewind-env.d.ts
@@ -0,0 +1,3 @@
+///
+
+// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.
\ No newline at end of file
diff --git a/MobileApp/package-lock.json b/MobileApp/package-lock.json
index 2aa5566271..76054e808f 100644
--- a/MobileApp/package-lock.json
+++ b/MobileApp/package-lock.json
@@ -27,13 +27,16 @@
"expo-splash-screen": "^31.0.13",
"expo-status-bar": "~3.0.9",
"expo-system-ui": "~6.0.9",
+ "nativewind": "^4.2.1",
+ "postcss": "^8.5.6",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.81.5",
"react-native-keychain": "^10.0.0",
"react-native-safe-area-context": "^5.6.2",
"react-native-screens": "~4.16.0",
- "react-native-web": "^0.21.0"
+ "react-native-web": "^0.21.0",
+ "tailwindcss": "^3.4.19"
},
"devDependencies": {
"@types/react": "~19.1.0",
@@ -54,6 +57,18 @@
}
}
},
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
@@ -1353,6 +1368,21 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/plugin-transform-template-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz",
+ "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/plugin-transform-typescript": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz",
@@ -2733,6 +2763,41 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/@react-native-async-storage/async-storage": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz",
@@ -3526,6 +3591,12 @@
"sprintf-js": "~1.0.2"
}
},
+ "node_modules/array-timsort": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz",
+ "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==",
+ "license": "MIT"
+ },
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
@@ -3922,6 +3993,18 @@
"node": ">=0.6"
}
},
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/bplist-creator": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz",
@@ -4105,6 +4188,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001769",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz",
@@ -4139,6 +4231,42 @@
"node": ">=4"
}
},
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/chownr": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
@@ -4334,6 +4462,20 @@
"node": ">= 10"
}
},
+ "node_modules/comment-json": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.5.1.tgz",
+ "integrity": "sha512-taEtr3ozUmOB7it68Jll7s0Pwm+aoiHyXKrEC8SEodL4rNpdfDLqa7PfBlrgFoCNNdR8ImL+muti5IGvktJAAg==",
+ "license": "MIT",
+ "dependencies": {
+ "array-timsort": "^1.0.3",
+ "core-util-is": "^1.0.3",
+ "esprima": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
@@ -4443,6 +4585,12 @@
"url": "https://opencollective.com/core-js"
}
},
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "license": "MIT"
+ },
"node_modules/cross-fetch": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz",
@@ -4484,6 +4632,18 @@
"hyphenate-style-name": "^1.0.3"
}
},
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -4627,6 +4787,18 @@
"node": ">=8"
}
},
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "license": "MIT"
+ },
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
@@ -5457,6 +5629,34 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/expo/node_modules/postcss": {
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
"node_modules/expo/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -5502,12 +5702,49 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/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==",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"license": "MIT"
},
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
"node_modules/fb-watchman": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
@@ -5849,6 +6086,18 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/glob/node_modules/minimatch": {
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz",
@@ -6151,6 +6400,18 @@
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"license": "MIT"
},
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -6193,6 +6454,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -6221,6 +6491,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-nan": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
@@ -6752,6 +7034,16 @@
"integrity": "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==",
"license": "MIT"
},
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -7102,6 +7394,18 @@
"url": "https://opencollective.com/parcel"
}
},
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -7213,6 +7517,15 @@
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"license": "MIT"
},
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/metro": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro/-/metro-0.83.3.tgz",
@@ -7713,6 +8026,23 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/nativewind": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/nativewind/-/nativewind-4.2.1.tgz",
+ "integrity": "sha512-10uUB2Dlli3MH3NDL5nMHqJHz1A3e/E6mzjTj6cl7hHECClJ7HpE6v+xZL+GXdbwQSnWE+UWMIMsNz7yOQkAJQ==",
+ "license": "MIT",
+ "dependencies": {
+ "comment-json": "^4.2.5",
+ "debug": "^4.3.7",
+ "react-native-css-interop": "0.2.1"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "tailwindcss": ">3.3.0"
+ }
+ },
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -7820,6 +8150,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
@@ -8109,6 +8448,15 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/pirates": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
@@ -8151,9 +8499,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.49",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
- "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [
{
"type": "opencollective",
@@ -8169,8 +8517,9 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
- "nanoid": "^3.3.7",
+ "nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -8178,6 +8527,128 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
@@ -8312,6 +8783,26 @@
"inherits": "~2.0.3"
}
},
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -8445,6 +8936,277 @@
}
}
},
+ "node_modules/react-native-css-interop": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/react-native-css-interop/-/react-native-css-interop-0.2.1.tgz",
+ "integrity": "sha512-B88f5rIymJXmy1sNC/MhTkb3xxBej1KkuAt7TiT9iM7oXz3RM8Bn+7GUrfR02TvSgKm4cg2XiSuLEKYfKwNsjA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.22.15",
+ "@babel/traverse": "^7.23.0",
+ "@babel/types": "^7.23.0",
+ "debug": "^4.3.7",
+ "lightningcss": "~1.27.0",
+ "semver": "^7.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-native": "*",
+ "react-native-reanimated": ">=3.6.2",
+ "tailwindcss": "~3"
+ },
+ "peerDependenciesMeta": {
+ "react-native-safe-area-context": {
+ "optional": true
+ },
+ "react-native-svg": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "license": "Apache-2.0",
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.27.0.tgz",
+ "integrity": "sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-darwin-arm64": "1.27.0",
+ "lightningcss-darwin-x64": "1.27.0",
+ "lightningcss-freebsd-x64": "1.27.0",
+ "lightningcss-linux-arm-gnueabihf": "1.27.0",
+ "lightningcss-linux-arm64-gnu": "1.27.0",
+ "lightningcss-linux-arm64-musl": "1.27.0",
+ "lightningcss-linux-x64-gnu": "1.27.0",
+ "lightningcss-linux-x64-musl": "1.27.0",
+ "lightningcss-win32-arm64-msvc": "1.27.0",
+ "lightningcss-win32-x64-msvc": "1.27.0"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-darwin-arm64": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.27.0.tgz",
+ "integrity": "sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-darwin-x64": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.27.0.tgz",
+ "integrity": "sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-freebsd-x64": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.27.0.tgz",
+ "integrity": "sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.27.0.tgz",
+ "integrity": "sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.27.0.tgz",
+ "integrity": "sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.27.0.tgz",
+ "integrity": "sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.27.0.tgz",
+ "integrity": "sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.27.0.tgz",
+ "integrity": "sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.27.0.tgz",
+ "integrity": "sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/react-native-css-interop/node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.27.0.tgz",
+ "integrity": "sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/react-native-is-edge-to-edge": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz",
@@ -8468,6 +9230,33 @@
"node": ">=16"
}
},
+ "node_modules/react-native-reanimated": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.2.1.tgz",
+ "integrity": "sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg==",
+ "license": "MIT",
+ "dependencies": {
+ "react-native-is-edge-to-edge": "1.2.1",
+ "semver": "7.7.3"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*",
+ "react-native-worklets": ">=0.7.0"
+ }
+ },
+ "node_modules/react-native-reanimated/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/react-native-safe-area-context": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz",
@@ -8528,6 +9317,128 @@
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
"license": "MIT"
},
+ "node_modules/react-native-worklets": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.7.3.tgz",
+ "integrity": "sha512-m/CIUCHvLQulboBn0BtgpsesXjOTeubU7t+V0lCPpBj0t2ExigwqDHoKj3ck7OeErnjgkD27wdAtQCubYATe3g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-transform-arrow-functions": "7.27.1",
+ "@babel/plugin-transform-class-properties": "7.27.1",
+ "@babel/plugin-transform-classes": "7.28.4",
+ "@babel/plugin-transform-nullish-coalescing-operator": "7.27.1",
+ "@babel/plugin-transform-optional-chaining": "7.27.1",
+ "@babel/plugin-transform-shorthand-properties": "7.27.1",
+ "@babel/plugin-transform-template-literals": "7.27.1",
+ "@babel/plugin-transform-unicode-regex": "7.27.1",
+ "@babel/preset-typescript": "7.27.1",
+ "convert-source-map": "2.0.0",
+ "semver": "7.7.3"
+ },
+ "peerDependencies": {
+ "@babel/core": "*",
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-class-properties": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz",
+ "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-classes": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
+ "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-replace-supers": "^7.27.1",
+ "@babel/traverse": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz",
+ "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/react-native-worklets/node_modules/@babel/plugin-transform-optional-chaining": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
+ "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/react-native-worklets/node_modules/@babel/preset-typescript": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz",
+ "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-validator-option": "^7.27.1",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-transform-modules-commonjs": "^7.27.1",
+ "@babel/plugin-transform-typescript": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/react-native-worklets/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/react-native/node_modules/@react-native/virtualized-lists": {
"version": "0.81.5",
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz",
@@ -8622,6 +9533,27 @@
"node": ">=0.10.0"
}
},
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -8790,6 +9722,16 @@
"node": ">=4"
}
},
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -8849,6 +9791,29 @@
"node": "*"
}
},
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -9390,6 +10355,44 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tailwindcss": {
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
+ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.7",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/tar": {
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
@@ -9828,6 +10831,12 @@
"which-typed-array": "^1.1.2"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
diff --git a/MobileApp/package.json b/MobileApp/package.json
index 69f49810f7..3965301ae7 100644
--- a/MobileApp/package.json
+++ b/MobileApp/package.json
@@ -29,13 +29,16 @@
"expo-splash-screen": "^31.0.13",
"expo-status-bar": "~3.0.9",
"expo-system-ui": "~6.0.9",
+ "nativewind": "^4.2.1",
+ "postcss": "^8.5.6",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.81.5",
"react-native-keychain": "^10.0.0",
"react-native-safe-area-context": "^5.6.2",
"react-native-screens": "~4.16.0",
- "react-native-web": "^0.21.0"
+ "react-native-web": "^0.21.0",
+ "tailwindcss": "^3.4.19"
},
"devDependencies": {
"@types/react": "~19.1.0",
diff --git a/MobileApp/src/App.tsx b/MobileApp/src/App.tsx
index 07d2e2e874..a9abfb36c4 100644
--- a/MobileApp/src/App.tsx
+++ b/MobileApp/src/App.tsx
@@ -1,5 +1,6 @@
+import "../global.css";
import React from "react";
-import { View, StyleSheet, ViewStyle } from "react-native";
+import { View } from "react-native";
import { StatusBar } from "expo-status-bar";
import { QueryClient } from "@tanstack/react-query";
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
@@ -29,12 +30,7 @@ function AppContent(): React.JSX.Element {
const { theme } = useTheme();
return (
-
+
@@ -58,9 +54,3 @@ export default function App(): React.JSX.Element {
);
}
-
-const styles: { container: ViewStyle } = StyleSheet.create({
- container: {
- flex: 1,
- },
-});
diff --git a/MobileApp/src/components/AddNoteModal.tsx b/MobileApp/src/components/AddNoteModal.tsx
index f7ec25d2fe..635288de49 100644
--- a/MobileApp/src/components/AddNoteModal.tsx
+++ b/MobileApp/src/components/AddNoteModal.tsx
@@ -6,7 +6,6 @@ import {
TouchableOpacity,
Modal,
ActivityIndicator,
- StyleSheet,
KeyboardAvoidingView,
Platform,
} from "react-native";
@@ -49,35 +48,17 @@ export default function AddNoteModal({
onRequestClose={handleClose}
>
-
-
+
+
Add Note
-
+
-
+
Cancel
@@ -130,14 +97,12 @@ export default function AddNoteModal({
/>
) : (
Submit
@@ -149,41 +114,3 @@ export default function AddNoteModal({
);
}
-
-const styles: ReturnType = StyleSheet.create({
- overlay: {
- flex: 1,
- backgroundColor: "rgba(0,0,0,0.4)",
- justifyContent: "flex-end",
- },
- container: {
- borderTopLeftRadius: 24,
- borderTopRightRadius: 24,
- padding: 20,
- paddingBottom: 36,
- },
- input: {
- minHeight: 120,
- borderRadius: 14,
- borderWidth: 1,
- padding: 12,
- fontSize: 15,
- },
- buttonRow: {
- flexDirection: "row",
- gap: 12,
- marginTop: 16,
- },
- button: {
- flex: 1,
- paddingVertical: 14,
- borderRadius: 14,
- alignItems: "center",
- justifyContent: "center",
- minHeight: 48,
- },
- buttonText: {
- fontSize: 15,
- fontWeight: "700",
- },
-});
diff --git a/MobileApp/src/components/AlertCard.tsx b/MobileApp/src/components/AlertCard.tsx
index 457ba093ec..62a3b83196 100644
--- a/MobileApp/src/components/AlertCard.tsx
+++ b/MobileApp/src/components/AlertCard.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
+import { View, Text, TouchableOpacity } from "react-native";
import { useTheme } from "../theme";
import { rgbToHex } from "../utils/color";
import { formatRelativeTime } from "../utils/date";
@@ -28,49 +28,34 @@ export default function AlertCard({
return (
-
-
+
+
{alert.alertNumberWithPrefix || `#${alert.alertNumber}`}
-
- {timeString}
-
+ {timeString}
{alert.title}
-
+
{alert.currentAlertState ? (
-
-
-
+
+
+
{alert.currentAlertState.name}
@@ -78,9 +63,13 @@ export default function AlertCard({
{alert.alertSeverity ? (
-
+
{alert.alertSeverity.name}
@@ -88,61 +77,10 @@ export default function AlertCard({
{alert.monitor ? (
-
+
{alert.monitor.name}
) : null}
);
}
-
-const styles: ReturnType = StyleSheet.create({
- card: {
- padding: 18,
- borderRadius: 16,
- marginBottom: 12,
- },
- topRow: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- marginBottom: 6,
- },
- number: {
- fontSize: 13,
- fontWeight: "600",
- },
- time: {
- fontSize: 12,
- },
- badgeRow: {
- flexDirection: "row",
- flexWrap: "wrap",
- gap: 8,
- marginTop: 10,
- },
- badge: {
- flexDirection: "row",
- alignItems: "center",
- paddingHorizontal: 8,
- paddingVertical: 4,
- borderRadius: 6,
- },
- dot: {
- width: 8,
- height: 8,
- borderRadius: 4,
- marginRight: 6,
- },
- badgeText: {
- fontSize: 12,
- fontWeight: "600",
- },
- monitor: {
- fontSize: 12,
- marginTop: 8,
- },
-});
diff --git a/MobileApp/src/components/EmptyState.tsx b/MobileApp/src/components/EmptyState.tsx
index 26a9caad2c..b5b3699e90 100644
--- a/MobileApp/src/components/EmptyState.tsx
+++ b/MobileApp/src/components/EmptyState.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { View, Text, StyleSheet } from "react-native";
+import { View, Text } from "react-native";
import { useTheme } from "../theme";
type EmptyIcon = "incidents" | "alerts" | "episodes" | "notes" | "default";
@@ -10,22 +10,32 @@ interface EmptyStateProps {
icon?: EmptyIcon;
}
-function EmptyIcon({
+function EmptyIconView({
icon,
color,
}: {
icon: EmptyIcon;
color: string;
}): React.JSX.Element {
- /*
- * Simple geometric SVG-style icons using View primitives
- * Monochrome, clean, professional — not cartoon/playful
- */
if (icon === "incidents") {
return (
-
-
-
+
+
+
);
@@ -33,9 +43,21 @@ function EmptyIcon({
if (icon === "alerts") {
return (
-
-
-
+
+
+
);
@@ -43,20 +65,29 @@ function EmptyIcon({
if (icon === "episodes") {
return (
-
-
+
+
);
}
- // Default: simple circle with line through it
return (
-
-
-
+
+
+
);
@@ -70,116 +101,16 @@ export default function EmptyState({
const { theme } = useTheme();
return (
-
-
-
+
+
+
{title}
{subtitle ? (
-
+
{subtitle}
) : null}
);
}
-
-const styles: ReturnType = StyleSheet.create({
- container: {
- flex: 1,
- alignItems: "center",
- justifyContent: "center",
- paddingHorizontal: 40,
- paddingVertical: 80,
- },
- iconContainer: {
- width: 64,
- height: 64,
- alignItems: "center",
- justifyContent: "center",
- },
- // Shield icon (incidents)
- iconShield: {
- width: 44,
- height: 52,
- borderWidth: 1.5,
- borderRadius: 6,
- borderBottomLeftRadius: 22,
- borderBottomRightRadius: 22,
- alignItems: "center",
- justifyContent: "center",
- },
- iconCheckmark: {
- width: 16,
- height: 3,
- borderRadius: 1.5,
- transform: [{ rotate: "-45deg" }],
- },
- // Bell icon (alerts)
- iconBell: {
- width: 36,
- height: 36,
- borderWidth: 1.5,
- borderRadius: 18,
- borderBottomLeftRadius: 4,
- borderBottomRightRadius: 4,
- alignItems: "center",
- justifyContent: "flex-end",
- paddingBottom: 4,
- },
- iconBellClapper: {
- width: 8,
- height: 8,
- borderRadius: 4,
- },
- // Stack icon (episodes)
- iconStack: {
- width: 40,
- height: 32,
- borderWidth: 1.5,
- borderRadius: 8,
- position: "absolute",
- top: 12,
- },
- iconStackBack: {
- width: 32,
- height: 28,
- borderWidth: 1.5,
- borderRadius: 6,
- position: "absolute",
- top: 6,
- },
- // Default circle icon
- iconCircle: {
- width: 48,
- height: 48,
- borderWidth: 1.5,
- borderRadius: 24,
- alignItems: "center",
- justifyContent: "center",
- },
- iconLine: {
- width: 20,
- height: 2,
- borderRadius: 1,
- },
-});
diff --git a/MobileApp/src/components/EpisodeCard.tsx b/MobileApp/src/components/EpisodeCard.tsx
index 22a75ea8fe..f0d84ae611 100644
--- a/MobileApp/src/components/EpisodeCard.tsx
+++ b/MobileApp/src/components/EpisodeCard.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
+import { View, Text, TouchableOpacity } from "react-native";
import { useTheme } from "../theme";
import { rgbToHex } from "../utils/color";
import { formatRelativeTime } from "../utils/date";
@@ -56,47 +56,32 @@ export default function EpisodeCard(
return (
-
-
+
+
{episode.episodeNumberWithPrefix || `#${episode.episodeNumber}`}
-
- {timeString}
-
+ {timeString}
{episode.title}
-
+
{state ? (
-
-
-
+
+
+
{state.name}
@@ -104,9 +89,13 @@ export default function EpisodeCard(
{severity ? (
-
+
{severity.name}
@@ -114,9 +103,7 @@ export default function EpisodeCard(
{childCount > 0 ? (
-
+
{childCount} {type === "incident" ? "incident" : "alert"}
{childCount !== 1 ? "s" : ""}
@@ -124,51 +111,3 @@ export default function EpisodeCard(
);
}
-
-const styles: ReturnType = StyleSheet.create({
- card: {
- padding: 18,
- borderRadius: 16,
- marginBottom: 12,
- },
- topRow: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- marginBottom: 6,
- },
- number: {
- fontSize: 13,
- fontWeight: "600",
- },
- time: {
- fontSize: 12,
- },
- badgeRow: {
- flexDirection: "row",
- flexWrap: "wrap",
- gap: 8,
- marginTop: 10,
- },
- badge: {
- flexDirection: "row",
- alignItems: "center",
- paddingHorizontal: 8,
- paddingVertical: 4,
- borderRadius: 6,
- },
- dot: {
- width: 8,
- height: 8,
- borderRadius: 4,
- marginRight: 6,
- },
- badgeText: {
- fontSize: 12,
- fontWeight: "600",
- },
- childCount: {
- fontSize: 12,
- marginTop: 8,
- },
-});
diff --git a/MobileApp/src/components/IncidentCard.tsx b/MobileApp/src/components/IncidentCard.tsx
index 2f7026007f..10a8117155 100644
--- a/MobileApp/src/components/IncidentCard.tsx
+++ b/MobileApp/src/components/IncidentCard.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
+import { View, Text, TouchableOpacity } from "react-native";
import { useTheme } from "../theme";
import { rgbToHex } from "../utils/color";
import { formatRelativeTime } from "../utils/date";
@@ -31,49 +31,34 @@ export default function IncidentCard({
return (
-
-
+
+
{incident.incidentNumberWithPrefix || `#${incident.incidentNumber}`}
-
- {timeString}
-
+ {timeString}
{incident.title}
-
+
{incident.currentIncidentState ? (
-
-
-
+
+
+
{incident.currentIncidentState.name}
@@ -81,9 +66,13 @@ export default function IncidentCard({
{incident.incidentSeverity ? (
-
+
{incident.incidentSeverity.name}
@@ -91,10 +80,7 @@ export default function IncidentCard({
{monitorCount > 0 ? (
-
+
{incident.monitors
.map((m: NamedEntity) => {
return m.name;
@@ -105,51 +91,3 @@ export default function IncidentCard({
);
}
-
-const styles: ReturnType = StyleSheet.create({
- card: {
- padding: 18,
- borderRadius: 16,
- marginBottom: 12,
- },
- topRow: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- marginBottom: 6,
- },
- number: {
- fontSize: 13,
- fontWeight: "600",
- },
- time: {
- fontSize: 12,
- },
- badgeRow: {
- flexDirection: "row",
- flexWrap: "wrap",
- gap: 8,
- marginTop: 10,
- },
- badge: {
- flexDirection: "row",
- alignItems: "center",
- paddingHorizontal: 8,
- paddingVertical: 4,
- borderRadius: 6,
- },
- dot: {
- width: 8,
- height: 8,
- borderRadius: 4,
- marginRight: 6,
- },
- badgeText: {
- fontSize: 12,
- fontWeight: "600",
- },
- monitors: {
- fontSize: 12,
- marginTop: 8,
- },
-});
diff --git a/MobileApp/src/components/OfflineBanner.tsx b/MobileApp/src/components/OfflineBanner.tsx
index 690f315e01..4479d293ee 100644
--- a/MobileApp/src/components/OfflineBanner.tsx
+++ b/MobileApp/src/components/OfflineBanner.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useRef } from "react";
-import { View, Text, StyleSheet, Animated } from "react-native";
+import { View, Text, Animated } from "react-native";
import { useTheme } from "../theme";
import { useNetworkStatus } from "../hooks/useNetworkStatus";
@@ -25,51 +25,18 @@ export default function OfflineBanner(): React.JSX.Element | null {
return (
-
-
-
+
+
+
No internet connection
);
}
-
-const styles: ReturnType = StyleSheet.create({
- container: {
- position: "absolute",
- top: 0,
- left: 0,
- right: 0,
- zIndex: 100,
- paddingTop: 50,
- paddingBottom: 8,
- paddingHorizontal: 16,
- },
- content: {
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "center",
- },
- dot: {
- width: 6,
- height: 6,
- borderRadius: 3,
- backgroundColor: "#FFFFFF",
- marginRight: 8,
- opacity: 0.8,
- },
- text: {
- fontSize: 13,
- fontWeight: "600",
- letterSpacing: 0.2,
- },
-});
diff --git a/MobileApp/src/components/ProjectBadge.tsx b/MobileApp/src/components/ProjectBadge.tsx
index 9799c940a8..7513f3ea56 100644
--- a/MobileApp/src/components/ProjectBadge.tsx
+++ b/MobileApp/src/components/ProjectBadge.tsx
@@ -1,6 +1,5 @@
import React from "react";
-import { View, Text, StyleSheet } from "react-native";
-import { useTheme } from "../theme";
+import { View, Text } from "react-native";
interface ProjectBadgeProps {
name: string;
@@ -11,35 +10,15 @@ export default function ProjectBadge({
name,
color,
}: ProjectBadgeProps): React.JSX.Element {
- const { theme } = useTheme();
-
- const dotColor: string = color || theme.colors.actionPrimary;
-
return (
-
-
-
+
+
+
{name}
);
}
-
-const styles: ReturnType = StyleSheet.create({
- container: {
- flexDirection: "row",
- alignItems: "center",
- },
- dot: {
- width: 8,
- height: 8,
- borderRadius: 4,
- marginRight: 6,
- },
-});
diff --git a/MobileApp/src/components/SeverityBadge.tsx b/MobileApp/src/components/SeverityBadge.tsx
index 1d487abc29..6b5d0d8139 100644
--- a/MobileApp/src/components/SeverityBadge.tsx
+++ b/MobileApp/src/components/SeverityBadge.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { View, Text, StyleSheet } from "react-native";
+import { View, Text } from "react-native";
import { useTheme } from "../theme";
export type SeverityLevel = "critical" | "major" | "minor" | "warning" | "info";
@@ -42,24 +42,16 @@ export default function SeverityBadge({
const displayLabel: string = label || severity;
return (
-
-
+
+
{displayLabel.toUpperCase()}
);
}
-
-const styles: ReturnType = StyleSheet.create({
- badge: {
- paddingHorizontal: 8,
- paddingVertical: 4,
- borderRadius: 6,
- alignSelf: "flex-start",
- },
- text: {
- fontSize: 12,
- fontWeight: "600",
- letterSpacing: 0.5,
- },
-});
diff --git a/MobileApp/src/components/SkeletonCard.tsx b/MobileApp/src/components/SkeletonCard.tsx
index e5c2b35fde..bef6b6578b 100644
--- a/MobileApp/src/components/SkeletonCard.tsx
+++ b/MobileApp/src/components/SkeletonCard.tsx
@@ -1,11 +1,5 @@
import React, { useEffect, useRef } from "react";
-import {
- View,
- StyleSheet,
- Animated,
- DimensionValue,
- AccessibilityInfo,
-} from "react-native";
+import { View, Animated, DimensionValue, AccessibilityInfo } from "react-native";
import { useTheme } from "../theme";
interface SkeletonCardProps {
@@ -60,36 +54,19 @@ export default function SkeletonCard({
if (variant === "compact") {
return (
-
-
-
+
+
+
-
+
);
}
@@ -97,57 +74,28 @@ export default function SkeletonCard({
if (variant === "detail") {
return (
- {/* Badge row */}
-
-
-
+
+
+
- {/* Title */}
+
- {/* Detail card */}
-
{Array.from({ length: 3 }).map((_: unknown, index: number) => {
return (
-
-
-
+
+
+
);
})}
@@ -158,67 +106,31 @@ export default function SkeletonCard({
return (
- {/* Top row: badge + time */}
-
-
-
+
+
+
- {/* Title */}
-
- {/* Badge row */}
-
-
-
+
+
+
+
- {/* Body lines */}
{Array.from({ length: Math.max(lines - 1, 1) }).map(
(_: unknown, index: number) => {
return (
);
},
@@ -226,94 +138,3 @@ export default function SkeletonCard({
);
}
-
-const styles: ReturnType = StyleSheet.create({
- card: {
- padding: 18,
- borderRadius: 16,
- marginBottom: 12,
- },
- topRow: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- marginBottom: 12,
- },
- numberSkeleton: {
- height: 14,
- width: 48,
- borderRadius: 4,
- },
- timeSkeleton: {
- height: 12,
- width: 36,
- borderRadius: 4,
- },
- titleLine: {
- height: 18,
- borderRadius: 4,
- width: "70%",
- marginBottom: 12,
- },
- badgeRow: {
- flexDirection: "row",
- gap: 8,
- marginBottom: 12,
- },
- badgeSkeleton: {
- height: 24,
- width: 80,
- borderRadius: 6,
- },
- line: {
- height: 12,
- borderRadius: 4,
- marginBottom: 8,
- },
- // Compact variant
- compactRow: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- marginBottom: 10,
- },
- compactBadge: {
- height: 14,
- width: 48,
- borderRadius: 4,
- },
- compactTime: {
- height: 12,
- width: 32,
- borderRadius: 4,
- },
- // Detail variant
- detailContainer: {
- padding: 20,
- },
- detailTitle: {
- height: 24,
- width: "80%",
- borderRadius: 4,
- marginBottom: 20,
- },
- detailCard: {
- borderRadius: 16,
- padding: 16,
- },
- detailRow: {
- flexDirection: "row",
- marginBottom: 12,
- },
- detailLabel: {
- height: 14,
- width: 80,
- borderRadius: 4,
- marginRight: 16,
- },
- detailValue: {
- height: 14,
- width: 120,
- borderRadius: 4,
- },
-});
diff --git a/MobileApp/src/components/StateBadge.tsx b/MobileApp/src/components/StateBadge.tsx
index bbfb609813..5878452b56 100644
--- a/MobileApp/src/components/StateBadge.tsx
+++ b/MobileApp/src/components/StateBadge.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { View, Text, StyleSheet } from "react-native";
+import { View, Text } from "react-native";
import { useTheme } from "../theme";
export type StateType =
@@ -32,39 +32,14 @@ export default function StateBadge({
const displayLabel: string = label || state;
return (
-
-
-
+
+
+
{displayLabel.charAt(0).toUpperCase() + displayLabel.slice(1)}
);
}
-
-const styles: ReturnType = StyleSheet.create({
- badge: {
- flexDirection: "row",
- alignItems: "center",
- paddingHorizontal: 8,
- paddingVertical: 4,
- borderRadius: 6,
- alignSelf: "flex-start",
- },
- dot: {
- width: 8,
- height: 8,
- borderRadius: 4,
- marginRight: 6,
- },
- text: {
- fontSize: 12,
- fontWeight: "600",
- },
-});
diff --git a/MobileApp/src/components/SwipeableCard.tsx b/MobileApp/src/components/SwipeableCard.tsx
index ff348c4223..fe90a69196 100644
--- a/MobileApp/src/components/SwipeableCard.tsx
+++ b/MobileApp/src/components/SwipeableCard.tsx
@@ -2,7 +2,6 @@ import React, { useRef } from "react";
import {
View,
Text,
- StyleSheet,
Animated,
PanResponder,
type GestureResponderEvent,
@@ -48,7 +47,6 @@ export default function SwipeableCard({
_: GestureResponderEvent,
gestureState: PanResponderGestureState,
) => {
- // Limit swipe range
const maxSwipe: number = 120;
let dx: number = gestureState.dx;
if (!rightAction && dx < 0) {
@@ -60,7 +58,6 @@ export default function SwipeableCard({
dx = Math.max(-maxSwipe, Math.min(maxSwipe, dx));
translateX.setValue(dx);
- // Haptic feedback at threshold
if (Math.abs(dx) >= SWIPE_THRESHOLD && !hasTriggeredHaptic.current) {
hasTriggeredHaptic.current = true;
mediumImpact();
@@ -97,34 +94,35 @@ export default function SwipeableCard({
).current;
return (
-
+
{/* Background actions */}
-
+
{leftAction ? (
- {leftAction.label}
+
+ {leftAction.label}
+
) : null}
{rightAction ? (
- {rightAction.label}
+
+ {rightAction.label}
+
) : null}
{/* Foreground content */}
{children}
@@ -132,40 +130,3 @@ export default function SwipeableCard({
);
}
-
-const styles: ReturnType = StyleSheet.create({
- container: {
- overflow: "hidden",
- borderRadius: 12,
- },
- actionsContainer: {
- ...StyleSheet.absoluteFillObject,
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- },
- actionLeft: {
- flex: 1,
- height: "100%",
- justifyContent: "center",
- paddingLeft: 20,
- borderRadius: 12,
- },
- actionRight: {
- flex: 1,
- height: "100%",
- justifyContent: "center",
- alignItems: "flex-end",
- paddingRight: 20,
- borderRadius: 12,
- },
- actionText: {
- color: "#FFFFFF",
- fontSize: 14,
- fontWeight: "700",
- letterSpacing: 0.3,
- },
- foreground: {
- zIndex: 1,
- },
-});
diff --git a/MobileApp/src/navigation/RootNavigator.tsx b/MobileApp/src/navigation/RootNavigator.tsx
index b29a3186f4..980eba0fa5 100644
--- a/MobileApp/src/navigation/RootNavigator.tsx
+++ b/MobileApp/src/navigation/RootNavigator.tsx
@@ -15,7 +15,7 @@ import AuthStackNavigator from "./AuthStackNavigator";
import MainTabNavigator from "./MainTabNavigator";
import ProjectSelectionScreen from "../screens/ProjectSelectionScreen";
import BiometricLockScreen from "../screens/BiometricLockScreen";
-import { View, ActivityIndicator, StyleSheet } from "react-native";
+import { View, ActivityIndicator } from "react-native";
const prefix: string = Linking.createURL("/");
@@ -96,12 +96,7 @@ export default function RootNavigator(): React.JSX.Element {
if (isLoading || !biometricChecked) {
return (
-
+
);
@@ -130,12 +125,7 @@ export default function RootNavigator(): React.JSX.Element {
if (isLoadingProjects) {
return (
-
+
);
@@ -158,11 +148,3 @@ export default function RootNavigator(): React.JSX.Element {
);
}
-
-const styles: ReturnType = StyleSheet.create({
- loading: {
- flex: 1,
- alignItems: "center",
- justifyContent: "center",
- },
-});
diff --git a/MobileApp/src/screens/AlertDetailScreen.tsx b/MobileApp/src/screens/AlertDetailScreen.tsx
index a1cef38c7a..a3449da4f2 100644
--- a/MobileApp/src/screens/AlertDetailScreen.tsx
+++ b/MobileApp/src/screens/AlertDetailScreen.tsx
@@ -7,7 +7,6 @@ import {
ActivityIndicator,
RefreshControl,
Alert,
- StyleSheet,
} from "react-native";
import type { NativeStackScreenProps } from "@react-navigation/native-stack";
import { useTheme } from "../theme";
@@ -128,9 +127,7 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
if (isLoading) {
return (
-
+
);
@@ -138,18 +135,8 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
if (!alert) {
return (
-
-
+
+
Alert not found.
@@ -180,46 +167,32 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
return (
}
>
{/* Header */}
-
-
+
+
{alert.alertNumberWithPrefix || `#${alert.alertNumber}`}
-
+
{alert.title}
{/* Badges */}
-
+
{alert.currentAlertState ? (
-
-
-
+
+
+
{alert.currentAlertState.name}
@@ -227,9 +200,13 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
{alert.alertSeverity ? (
-
+
{alert.alertSeverity.name}
@@ -238,69 +215,38 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
{/* Description */}
{alert.description ? (
-
-
+
+
Description
-
+
{alert.description}
) : null}
{/* Details */}
-
-
+
+
Details
-
-
-
+
+
+
Created
-
+
{formatDateTime(alert.createdAt)}
{alert.monitor ? (
-
-
+
+
Monitor
-
+
{alert.monitor.name}
@@ -310,20 +256,15 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
{/* State Change Actions */}
{!isResolved ? (
-
-
+
+
Actions
-
+
{!isAcknowledged && !isResolved && acknowledgeState ? (
{
return handleStateChange(
acknowledgeState._id,
@@ -340,12 +281,7 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
color={theme.colors.textInverse}
/>
) : (
-
+
Acknowledge
)}
@@ -354,11 +290,8 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
{resolveState ? (
{
return handleStateChange(resolveState._id, resolveState.name);
}}
@@ -372,12 +305,7 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
color={theme.colors.textInverse}
/>
) : (
-
+
Resolve
)}
@@ -389,10 +317,8 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
{/* State Timeline */}
{timeline && timeline.length > 0 ? (
-
-
+
+
State Timeline
{timeline.map((entry: StateTimelineItem) => {
@@ -402,32 +328,17 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
return (
-
-
+
+
{entry.alertState?.name ?? "Unknown"}
-
+
{formatDateTime(entry.createdAt)}
@@ -438,31 +349,19 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
) : null}
{/* Internal Notes */}
-
-
-
+
+
+
Internal Notes
{
return setNoteModalVisible(true);
}}
>
-
+
Add Note
@@ -473,39 +372,18 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
return (
-
+
{note.note}
-
+
{note.createdByUser ? (
-
+
{note.createdByUser.name}
) : null}
-
+
{formatDateTime(note.createdAt)}
@@ -515,12 +393,7 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
: null}
{notes && notes.length === 0 ? (
-
+
No notes yet.
) : null}
@@ -537,130 +410,3 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
);
}
-
-const styles: ReturnType = StyleSheet.create({
- centered: {
- flex: 1,
- alignItems: "center",
- justifyContent: "center",
- },
- content: {
- padding: 20,
- paddingBottom: 40,
- },
- numberBadge: {
- alignSelf: "flex-start",
- paddingHorizontal: 10,
- paddingVertical: 4,
- borderRadius: 8,
- },
- number: {
- fontSize: 14,
- fontWeight: "600",
- },
- badgeRow: {
- flexDirection: "row",
- flexWrap: "wrap",
- gap: 8,
- marginTop: 12,
- },
- badge: {
- flexDirection: "row",
- alignItems: "center",
- paddingHorizontal: 10,
- paddingVertical: 5,
- borderRadius: 6,
- },
- dot: {
- width: 8,
- height: 8,
- borderRadius: 4,
- marginRight: 6,
- },
- badgeText: {
- fontSize: 13,
- fontWeight: "600",
- },
- section: {
- marginTop: 24,
- },
- sectionHeader: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- marginBottom: 10,
- },
- sectionTitle: {
- fontSize: 13,
- fontWeight: "600",
- textTransform: "uppercase",
- letterSpacing: 0.5,
- marginBottom: 10,
- },
- detailCard: {
- borderRadius: 16,
- padding: 16,
- },
- detailRow: {
- flexDirection: "row",
- marginBottom: 10,
- },
- detailLabel: {
- fontSize: 14,
- width: 90,
- },
- detailValue: {
- fontSize: 14,
- },
- actionRow: {
- flexDirection: "row",
- gap: 12,
- },
- actionButton: {
- flex: 1,
- paddingVertical: 14,
- borderRadius: 14,
- alignItems: "center",
- justifyContent: "center",
- minHeight: 50,
- },
- actionButtonText: {
- fontSize: 15,
- fontWeight: "700",
- },
- timelineEntry: {
- flexDirection: "row",
- alignItems: "center",
- padding: 14,
- borderRadius: 12,
- marginBottom: 8,
- },
- timelineDot: {
- width: 10,
- height: 10,
- borderRadius: 5,
- marginRight: 12,
- },
- timelineInfo: {
- flex: 1,
- },
- addNoteButton: {
- paddingHorizontal: 12,
- paddingVertical: 6,
- borderRadius: 8,
- },
- addNoteButtonText: {
- fontSize: 13,
- fontWeight: "600",
- },
- noteCard: {
- borderRadius: 12,
- padding: 14,
- marginBottom: 8,
- },
- noteMeta: {
- flexDirection: "row",
- justifyContent: "space-between",
- marginTop: 8,
- },
-});
diff --git a/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx b/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx
index e4f4cd15d6..5bed44c671 100644
--- a/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx
+++ b/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx
@@ -7,7 +7,6 @@ import {
ActivityIndicator,
RefreshControl,
Alert,
- StyleSheet,
} from "react-native";
import type { NativeStackScreenProps } from "@react-navigation/native-stack";
import { useTheme } from "../theme";
@@ -135,9 +134,7 @@ export default function AlertEpisodeDetailScreen({
if (isLoading) {
return (
-
+
);
@@ -145,18 +142,8 @@ export default function AlertEpisodeDetailScreen({
if (!episode) {
return (
-
-
+
+
Episode not found.
@@ -186,46 +173,32 @@ export default function AlertEpisodeDetailScreen({
return (
}
>
{/* Header */}
-
-
+
+
{episode.episodeNumberWithPrefix || `#${episode.episodeNumber}`}
-
+
{episode.title}
{/* Badges */}
-
+
{episode.currentAlertState ? (
-
-
-
+
+
+
{episode.currentAlertState.name}
@@ -233,9 +206,13 @@ export default function AlertEpisodeDetailScreen({
{episode.alertSeverity ? (
-
+
{episode.alertSeverity.name}
@@ -244,62 +221,37 @@ export default function AlertEpisodeDetailScreen({
{/* Description */}
{episode.description ? (
-
-
+
+
Description
-
+
{episode.description}
) : null}
{/* Details */}
-
-
+
+
Details
-
-
-
+
+
+
Created
-
+
{formatDateTime(episode.createdAt)}
-
-
+
+
Alerts
-
+
{episode.alertCount ?? 0}
@@ -308,20 +260,15 @@ export default function AlertEpisodeDetailScreen({
{/* State Change Actions */}
{!isResolved ? (
-
-
+
+
Actions
-
+
{!isAcknowledged && !isResolved && acknowledgeState ? (
{
return handleStateChange(
acknowledgeState._id,
@@ -336,12 +283,7 @@ export default function AlertEpisodeDetailScreen({
color={theme.colors.textInverse}
/>
) : (
-
+
Acknowledge
)}
@@ -350,11 +292,8 @@ export default function AlertEpisodeDetailScreen({
{resolveState ? (
{
return handleStateChange(resolveState._id, resolveState.name);
}}
@@ -366,12 +305,7 @@ export default function AlertEpisodeDetailScreen({
color={theme.colors.textInverse}
/>
) : (
-
+
Resolve
)}
@@ -383,10 +317,8 @@ export default function AlertEpisodeDetailScreen({
{/* State Timeline */}
{timeline && timeline.length > 0 ? (
-
-
+
+
State Timeline
{timeline.map((entry: StateTimelineItem) => {
@@ -396,35 +328,17 @@ export default function AlertEpisodeDetailScreen({
return (
-
-
+
+
{entry.alertState?.name ?? "Unknown"}
-
+
{formatDateTime(entry.createdAt)}
@@ -435,31 +349,19 @@ export default function AlertEpisodeDetailScreen({
) : null}
{/* Internal Notes */}
-
-
-
+
+
+
Internal Notes
{
return setNoteModalVisible(true);
}}
>
-
+
Add Note
@@ -470,39 +372,18 @@ export default function AlertEpisodeDetailScreen({
return (
-
+
{note.note}
-
+
{note.createdByUser ? (
-
+
{note.createdByUser.name}
) : null}
-
+
{formatDateTime(note.createdAt)}
@@ -512,12 +393,7 @@ export default function AlertEpisodeDetailScreen({
: null}
{notes && notes.length === 0 ? (
-
+
No notes yet.
) : null}
@@ -534,130 +410,3 @@ export default function AlertEpisodeDetailScreen({
);
}
-
-const styles: ReturnType = StyleSheet.create({
- centered: {
- flex: 1,
- alignItems: "center",
- justifyContent: "center",
- },
- content: {
- padding: 20,
- paddingBottom: 40,
- },
- numberBadge: {
- alignSelf: "flex-start",
- paddingHorizontal: 10,
- paddingVertical: 4,
- borderRadius: 8,
- },
- number: {
- fontSize: 14,
- fontWeight: "600",
- },
- badgeRow: {
- flexDirection: "row",
- flexWrap: "wrap",
- gap: 8,
- marginTop: 12,
- },
- badge: {
- flexDirection: "row",
- alignItems: "center",
- paddingHorizontal: 10,
- paddingVertical: 5,
- borderRadius: 6,
- },
- dot: {
- width: 8,
- height: 8,
- borderRadius: 4,
- marginRight: 6,
- },
- badgeText: {
- fontSize: 13,
- fontWeight: "600",
- },
- section: {
- marginTop: 24,
- },
- sectionHeader: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- marginBottom: 10,
- },
- sectionTitle: {
- fontSize: 13,
- fontWeight: "600",
- textTransform: "uppercase",
- letterSpacing: 0.5,
- marginBottom: 10,
- },
- detailCard: {
- borderRadius: 16,
- padding: 16,
- },
- detailRow: {
- flexDirection: "row",
- marginBottom: 10,
- },
- detailLabel: {
- fontSize: 14,
- width: 90,
- },
- detailValue: {
- fontSize: 14,
- },
- actionRow: {
- flexDirection: "row",
- gap: 12,
- },
- actionButton: {
- flex: 1,
- paddingVertical: 14,
- borderRadius: 14,
- alignItems: "center",
- justifyContent: "center",
- minHeight: 50,
- },
- actionButtonText: {
- fontSize: 15,
- fontWeight: "700",
- },
- timelineEntry: {
- flexDirection: "row",
- alignItems: "center",
- padding: 14,
- borderRadius: 12,
- marginBottom: 8,
- },
- timelineDot: {
- width: 10,
- height: 10,
- borderRadius: 5,
- marginRight: 12,
- },
- timelineInfo: {
- flex: 1,
- },
- addNoteButton: {
- paddingHorizontal: 12,
- paddingVertical: 6,
- borderRadius: 8,
- },
- addNoteButtonText: {
- fontSize: 13,
- fontWeight: "600",
- },
- noteCard: {
- borderRadius: 12,
- padding: 14,
- marginBottom: 8,
- },
- noteMeta: {
- flexDirection: "row",
- justifyContent: "space-between",
- marginTop: 8,
- },
-});
diff --git a/MobileApp/src/screens/AlertEpisodesScreen.tsx b/MobileApp/src/screens/AlertEpisodesScreen.tsx
index 64327d3bf7..9c55bc08c8 100644
--- a/MobileApp/src/screens/AlertEpisodesScreen.tsx
+++ b/MobileApp/src/screens/AlertEpisodesScreen.tsx
@@ -5,7 +5,6 @@ import {
RefreshControl,
TouchableOpacity,
Text,
- StyleSheet,
ListRenderItemInfo,
} from "react-native";
import { useNavigation } from "@react-navigation/native";
@@ -72,13 +71,8 @@ export default function AlertEpisodesScreen(): React.JSX.Element {
if (isLoading && episodes.length === 0) {
return (
-
-
+
+
@@ -89,36 +83,18 @@ export default function AlertEpisodesScreen(): React.JSX.Element {
if (isError) {
return (
-
-
+
+
Failed to load alert episodes.
{
return refetch();
}}
>
-
+
Retry
@@ -127,19 +103,14 @@ export default function AlertEpisodesScreen(): React.JSX.Element {
}
return (
-
+
{
return item._id;
}}
contentContainerStyle={
- episodes.length === 0 ? styles.emptyContainer : styles.list
+ episodes.length === 0 ? { flex: 1 } : { padding: 16 }
}
renderItem={({ item }: ListRenderItemInfo) => {
return (
@@ -168,30 +139,3 @@ export default function AlertEpisodesScreen(): React.JSX.Element {
);
}
-
-const styles: ReturnType = StyleSheet.create({
- container: {
- flex: 1,
- },
- centered: {
- flex: 1,
- alignItems: "center",
- justifyContent: "center",
- paddingHorizontal: 32,
- },
- list: {
- padding: 16,
- },
- emptyContainer: {
- flex: 1,
- },
- skeletonList: {
- padding: 16,
- },
- retryButton: {
- marginTop: 16,
- paddingHorizontal: 24,
- paddingVertical: 12,
- borderRadius: 10,
- },
-});
diff --git a/MobileApp/src/screens/AlertsScreen.tsx b/MobileApp/src/screens/AlertsScreen.tsx
index 03ffb6529c..58d746a1ac 100644
--- a/MobileApp/src/screens/AlertsScreen.tsx
+++ b/MobileApp/src/screens/AlertsScreen.tsx
@@ -5,7 +5,6 @@ import {
RefreshControl,
TouchableOpacity,
Text,
- StyleSheet,
ListRenderItemInfo,
} from "react-native";
import { useNavigation } from "@react-navigation/native";
@@ -103,13 +102,8 @@ export default function AlertsScreen(): React.JSX.Element {
if (isLoading && alerts.length === 0) {
return (
-
-
+
+
@@ -120,36 +114,18 @@ export default function AlertsScreen(): React.JSX.Element {
if (isError) {
return (
-
-
+
+
Failed to load alerts.
{
return refetch();
}}
>
-
+
Retry
@@ -158,19 +134,14 @@ export default function AlertsScreen(): React.JSX.Element {
}
return (
-
+
{
return item._id;
}}
contentContainerStyle={
- alerts.length === 0 ? styles.emptyContainer : styles.list
+ alerts.length === 0 ? { flex: 1 } : { padding: 16 }
}
renderItem={({ item }: ListRenderItemInfo) => {
return (
@@ -213,30 +184,3 @@ export default function AlertsScreen(): React.JSX.Element {
);
}
-
-const styles: ReturnType = StyleSheet.create({
- container: {
- flex: 1,
- },
- centered: {
- flex: 1,
- alignItems: "center",
- justifyContent: "center",
- paddingHorizontal: 32,
- },
- list: {
- padding: 16,
- },
- emptyContainer: {
- flex: 1,
- },
- skeletonList: {
- padding: 16,
- },
- retryButton: {
- marginTop: 16,
- paddingHorizontal: 24,
- paddingVertical: 12,
- borderRadius: 10,
- },
-});
diff --git a/MobileApp/src/screens/BiometricLockScreen.tsx b/MobileApp/src/screens/BiometricLockScreen.tsx
index d26022d042..ec1f13320d 100644
--- a/MobileApp/src/screens/BiometricLockScreen.tsx
+++ b/MobileApp/src/screens/BiometricLockScreen.tsx
@@ -1,5 +1,5 @@
import React, { useEffect } from "react";
-import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
+import { View, Text, TouchableOpacity } from "react-native";
import { useTheme } from "../theme";
import * as LocalAuthentication from "expo-local-authentication";
@@ -32,117 +32,44 @@ export default function BiometricLockScreen({
}, []);
return (
-
+
{/* Lock icon */}
-
+
OneUptime is Locked
-
+
Use {biometricType.toLowerCase()} to unlock
-
- Unlock
-
+ Unlock
);
}
-
-const styles: ReturnType = StyleSheet.create({
- container: {
- flex: 1,
- alignItems: "center",
- justifyContent: "center",
- paddingHorizontal: 40,
- },
- iconContainer: {
- width: 80,
- height: 80,
- borderRadius: 40,
- borderWidth: 1.5,
- alignItems: "center",
- justifyContent: "center",
- },
- lockBody: {
- width: 28,
- height: 22,
- borderRadius: 4,
- marginTop: 8,
- },
- lockShackle: {
- width: 20,
- height: 16,
- borderWidth: 3,
- borderBottomWidth: 0,
- borderTopLeftRadius: 10,
- borderTopRightRadius: 10,
- position: "absolute",
- top: 16,
- },
- unlockButton: {
- marginTop: 32,
- paddingVertical: 16,
- paddingHorizontal: 48,
- borderRadius: 14,
- minWidth: 200,
- alignItems: "center",
- },
- unlockButtonText: {
- fontSize: 17,
- fontWeight: "600",
- },
-});
diff --git a/MobileApp/src/screens/HomeScreen.tsx b/MobileApp/src/screens/HomeScreen.tsx
index 5190f41040..70e909a4c9 100644
--- a/MobileApp/src/screens/HomeScreen.tsx
+++ b/MobileApp/src/screens/HomeScreen.tsx
@@ -5,7 +5,6 @@ import {
TouchableOpacity,
ScrollView,
RefreshControl,
- StyleSheet,
} from "react-native";
import { useTheme } from "../theme";
import { useProject } from "../hooks/useProject";
@@ -35,7 +34,6 @@ function StatCard({
isLoading,
onPress,
}: StatCardProps): React.JSX.Element {
- const { theme } = useTheme();
const { lightImpact } = useHaptics();
const handlePress: () => void = (): void => {
@@ -45,22 +43,19 @@ function StatCard({
return (
-
+
{isLoading ? "--" : count ?? 0}
-
+
{label}
@@ -73,27 +68,15 @@ interface QuickLinkProps {
}
function QuickLink({ label, onPress }: QuickLinkProps): React.JSX.Element {
- const { theme } = useTheme();
-
return (
-
- {label}
-
-
- ›
-
+ {label}
+ {">"}
);
}
@@ -142,8 +125,8 @@ export default function HomeScreen(): React.JSX.Element {
return (
}
>
- {/* Header */}
-
+
{selectedProject?.name ?? "OneUptime"}
-
+
Project overview
- {/* Stats Grid */}
-
+
-
+
- {/* Quick Links */}
-
-
+
+
Quick Links
);
}
-
-const styles: ReturnType = StyleSheet.create({
- content: {
- padding: 24,
- paddingBottom: 40,
- },
- cardRow: {
- flexDirection: "row",
- gap: 12,
- marginTop: 16,
- },
- summaryCard: {
- flex: 1,
- padding: 20,
- borderRadius: 16,
- alignItems: "center",
- },
- cardCount: {
- fontSize: 40,
- fontWeight: "700",
- fontVariant: ["tabular-nums"],
- },
- cardLabel: {
- fontSize: 14,
- fontWeight: "500",
- marginTop: 4,
- },
- quickLinksSection: {
- marginTop: 32,
- },
- sectionTitle: {
- fontSize: 13,
- fontWeight: "600",
- textTransform: "uppercase",
- letterSpacing: 0.8,
- marginBottom: 12,
- marginLeft: 4,
- },
- linkCard: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- padding: 18,
- borderRadius: 16,
- marginBottom: 10,
- },
- linkLabel: {
- fontSize: 16,
- fontWeight: "500",
- },
- chevron: {
- fontSize: 24,
- fontWeight: "300",
- },
-});
diff --git a/MobileApp/src/screens/IncidentDetailScreen.tsx b/MobileApp/src/screens/IncidentDetailScreen.tsx
index e26644b93b..fa2b664e23 100644
--- a/MobileApp/src/screens/IncidentDetailScreen.tsx
+++ b/MobileApp/src/screens/IncidentDetailScreen.tsx
@@ -7,10 +7,9 @@ import {
ActivityIndicator,
RefreshControl,
Alert,
- StyleSheet,
} from "react-native";
import type { NativeStackScreenProps } from "@react-navigation/native-stack";
-import { useTheme, type Theme } from "../theme";
+import { useTheme } from "../theme";
import { useProject } from "../hooks/useProject";
import {
useIncidentDetail,
@@ -41,7 +40,7 @@ export default function IncidentDetailScreen({
route,
}: Props): React.JSX.Element {
const { incidentId } = route.params;
- const { theme }: { theme: Theme } = useTheme();
+ const { theme } = useTheme();
const { selectedProject } = useProject();
const projectId: string = selectedProject?._id ?? "";
const queryClient: QueryClient = useQueryClient();
@@ -139,9 +138,7 @@ export default function IncidentDetailScreen({
if (isLoading) {
return (
-
+
);
@@ -149,18 +146,8 @@ export default function IncidentDetailScreen({
if (!incident) {
return (
-
-
+
+
Incident not found.
@@ -193,46 +180,32 @@ export default function IncidentDetailScreen({
return (
}
>
{/* Header */}
-
-
+
+
{incident.incidentNumberWithPrefix || `#${incident.incidentNumber}`}
-
+
{incident.title}
{/* Badges */}
-
+
{incident.currentIncidentState ? (
-
-
-
+
+
+
{incident.currentIncidentState.name}
@@ -240,9 +213,13 @@ export default function IncidentDetailScreen({
{incident.incidentSeverity ? (
-
+
{incident.incidentSeverity.name}
@@ -251,90 +228,49 @@ export default function IncidentDetailScreen({
{/* Description */}
{incident.description ? (
-
-
+
+
Description
-
+
{incident.description}
) : null}
{/* Details */}
-
-
+
+
Details
-
+
{incident.declaredAt ? (
-
-
+
+
Declared
-
+
{formatDateTime(incident.declaredAt)}
) : null}
-
-
+
+
Created
-
+
{formatDateTime(incident.createdAt)}
{incident.monitors?.length > 0 ? (
-
-
+
+
Monitors
-
+
{incident.monitors
.map((m: NamedEntity) => {
return m.name;
@@ -348,20 +284,15 @@ export default function IncidentDetailScreen({
{/* State Change Actions */}
{!isResolved ? (
-
-
+
+
Actions
-
+
{!isAcknowledged && !isResolved && acknowledgeState ? (
{
return handleStateChange(
acknowledgeState._id,
@@ -378,12 +309,7 @@ export default function IncidentDetailScreen({
color={theme.colors.textInverse}
/>
) : (
-
+
Acknowledge
)}
@@ -392,11 +318,8 @@ export default function IncidentDetailScreen({
{resolveState ? (
{
return handleStateChange(resolveState._id, resolveState.name);
}}
@@ -410,12 +333,7 @@ export default function IncidentDetailScreen({
color={theme.colors.textInverse}
/>
) : (
-
+
Resolve
)}
@@ -427,10 +345,8 @@ export default function IncidentDetailScreen({
{/* State Timeline */}
{timeline && timeline.length > 0 ? (
-
-
+
+
State Timeline
{timeline.map((entry: StateTimelineItem) => {
@@ -440,32 +356,17 @@ export default function IncidentDetailScreen({
return (
-
-
+
+
{entry.incidentState?.name ?? "Unknown"}
-
+
{formatDateTime(entry.createdAt)}
@@ -476,31 +377,19 @@ export default function IncidentDetailScreen({
) : null}
{/* Internal Notes */}
-
-
-
+
+
+
Internal Notes
{
return setNoteModalVisible(true);
}}
>
-
+
Add Note
@@ -511,39 +400,18 @@ export default function IncidentDetailScreen({
return (
-
+
{note.note}
-
+
{note.createdByUser ? (
-
+
{note.createdByUser.name}
) : null}
-
+
{formatDateTime(note.createdAt)}
@@ -553,12 +421,7 @@ export default function IncidentDetailScreen({
: null}
{notes && notes.length === 0 ? (
-
+
No notes yet.
) : null}
@@ -575,130 +438,3 @@ export default function IncidentDetailScreen({
);
}
-
-const styles: ReturnType = StyleSheet.create({
- centered: {
- flex: 1,
- alignItems: "center",
- justifyContent: "center",
- },
- content: {
- padding: 20,
- paddingBottom: 40,
- },
- numberBadge: {
- alignSelf: "flex-start",
- paddingHorizontal: 10,
- paddingVertical: 4,
- borderRadius: 8,
- },
- number: {
- fontSize: 14,
- fontWeight: "600",
- },
- badgeRow: {
- flexDirection: "row",
- flexWrap: "wrap",
- gap: 8,
- marginTop: 12,
- },
- badge: {
- flexDirection: "row",
- alignItems: "center",
- paddingHorizontal: 10,
- paddingVertical: 5,
- borderRadius: 6,
- },
- dot: {
- width: 8,
- height: 8,
- borderRadius: 4,
- marginRight: 6,
- },
- badgeText: {
- fontSize: 13,
- fontWeight: "600",
- },
- section: {
- marginTop: 24,
- },
- sectionHeader: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- marginBottom: 10,
- },
- sectionTitle: {
- fontSize: 13,
- fontWeight: "600",
- textTransform: "uppercase",
- letterSpacing: 0.5,
- marginBottom: 10,
- },
- detailCard: {
- borderRadius: 16,
- padding: 16,
- },
- detailRow: {
- flexDirection: "row",
- marginBottom: 10,
- },
- detailLabel: {
- fontSize: 14,
- width: 90,
- },
- detailValue: {
- fontSize: 14,
- },
- actionRow: {
- flexDirection: "row",
- gap: 12,
- },
- actionButton: {
- flex: 1,
- paddingVertical: 14,
- borderRadius: 14,
- alignItems: "center",
- justifyContent: "center",
- minHeight: 50,
- },
- actionButtonText: {
- fontSize: 15,
- fontWeight: "700",
- },
- timelineEntry: {
- flexDirection: "row",
- alignItems: "center",
- padding: 14,
- borderRadius: 12,
- marginBottom: 8,
- },
- timelineDot: {
- width: 10,
- height: 10,
- borderRadius: 5,
- marginRight: 12,
- },
- timelineInfo: {
- flex: 1,
- },
- addNoteButton: {
- paddingHorizontal: 12,
- paddingVertical: 6,
- borderRadius: 8,
- },
- addNoteButtonText: {
- fontSize: 13,
- fontWeight: "600",
- },
- noteCard: {
- borderRadius: 12,
- padding: 14,
- marginBottom: 8,
- },
- noteMeta: {
- flexDirection: "row",
- justifyContent: "space-between",
- marginTop: 8,
- },
-});
diff --git a/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx b/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx
index b0f8b3ee93..afb1ea049e 100644
--- a/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx
+++ b/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx
@@ -7,7 +7,6 @@ import {
ActivityIndicator,
RefreshControl,
Alert,
- StyleSheet,
} from "react-native";
import type { NativeStackScreenProps } from "@react-navigation/native-stack";
import { useTheme } from "../theme";
@@ -138,9 +137,7 @@ export default function IncidentEpisodeDetailScreen({
if (isLoading) {
return (
-
+
);
@@ -148,18 +145,8 @@ export default function IncidentEpisodeDetailScreen({
if (!episode) {
return (
-
-
+
+
Episode not found.
@@ -191,46 +178,32 @@ export default function IncidentEpisodeDetailScreen({
return (
}
>
{/* Header */}
-
-
+
+
{episode.episodeNumberWithPrefix || `#${episode.episodeNumber}`}
-
+
{episode.title}
{/* Badges */}
-
+
{episode.currentIncidentState ? (
-
-
-
+
+
+
{episode.currentIncidentState.name}
@@ -238,9 +211,13 @@ export default function IncidentEpisodeDetailScreen({
{episode.incidentSeverity ? (
-
+
{episode.incidentSeverity.name}
@@ -249,83 +226,48 @@ export default function IncidentEpisodeDetailScreen({
{/* Description */}
{episode.description ? (
-
-
+
+
Description
-
+
{episode.description}
) : null}
{/* Details */}
-
-
+
+
Details
-
+
{episode.declaredAt ? (
-
-
+
+
Declared
-
+
{formatDateTime(episode.declaredAt)}
) : null}
-
-
+
+
Created
-
+
{formatDateTime(episode.createdAt)}
-
-
+
+
Incidents
-
+
{episode.incidentCount ?? 0}
@@ -334,20 +276,15 @@ export default function IncidentEpisodeDetailScreen({
{/* State Change Actions */}
{!isResolved ? (
-
-
+
+
Actions
-
+
{!isAcknowledged && !isResolved && acknowledgeState ? (
{
return handleStateChange(
acknowledgeState._id,
@@ -362,12 +299,7 @@ export default function IncidentEpisodeDetailScreen({
color={theme.colors.textInverse}
/>
) : (
-
+
Acknowledge
)}
@@ -376,11 +308,8 @@ export default function IncidentEpisodeDetailScreen({
{resolveState ? (
{
return handleStateChange(resolveState._id, resolveState.name);
}}
@@ -392,12 +321,7 @@ export default function IncidentEpisodeDetailScreen({
color={theme.colors.textInverse}
/>
) : (
-
+
Resolve
)}
@@ -409,10 +333,8 @@ export default function IncidentEpisodeDetailScreen({
{/* State Timeline */}
{timeline && timeline.length > 0 ? (
-
-
+
+
State Timeline
{timeline.map((entry: StateTimelineItem) => {
@@ -422,35 +344,17 @@ export default function IncidentEpisodeDetailScreen({
return (
-
-
+