Skip to content

Commit e36beca

Browse files
authored
Merge pull request #214 from Resgrid/develop
Develop
2 parents 476af83 + bb602ce commit e36beca

File tree

167 files changed

+5177
-2042
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

167 files changed

+5177
-2042
lines changed

.eslintignore

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.eslintignorenode_modules
1+
node_modules
22
__tests__/
33
.vscode/
44
android/
@@ -7,4 +7,9 @@ ios/
77
.expo
88
.expo-shared
99
docs/
10-
cli/
10+
cli/
11+
electron/
12+
fastlane/
13+
patches/
14+
public/
15+
scripts/

.eslintrc.js

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
const path = require('path');
22

33
module.exports = {
4-
extends: ['expo', 'plugin:tailwindcss/recommended', 'prettier'],
5-
plugins: ['prettier', 'unicorn', '@typescript-eslint', 'unused-imports', 'tailwindcss', 'simple-import-sort', 'eslint-plugin-react-compiler'],
6-
parserOptions: {
7-
project: './tsconfig.json',
8-
},
4+
extends: ['expo', 'prettier'],
5+
plugins: ['prettier', 'unicorn', '@typescript-eslint', 'unused-imports', 'simple-import-sort', 'eslint-plugin-react-compiler'],
6+
// parserOptions: {
7+
// project: './tsconfig.json',
8+
// },
99
rules: {
1010
'prettier/prettier': 'warn',
1111
'max-params': ['error', 10], // Limit the number of parameters in a function to use object instead
@@ -24,17 +24,10 @@ module.exports = {
2424
},
2525
], // Ensure `import type` is used when it's necessary
2626
'import/prefer-default-export': 'off', // Named export is easier to refactor automatically
27-
'import/no-cycle': ['error', { maxDepth: '∞' }],
28-
'tailwindcss/classnames-order': [
29-
'warn',
30-
{
31-
officialSorting: true,
32-
},
33-
], // Follow the same ordering as the official plugin `prettier-plugin-tailwindcss`
27+
'import/no-cycle': 'off', // Disabled due to performance issues
3428
'simple-import-sort/imports': 'error', // Import configuration for `eslint-plugin-simple-import-sort`
3529
'simple-import-sort/exports': 'error', // Export configuration for `eslint-plugin-simple-import-sort`
3630
'@typescript-eslint/no-unused-vars': 'off',
37-
'tailwindcss/no-custom-classname': 'off',
3831
'unused-imports/no-unused-imports': 'off',
3932
'unused-imports/no-unused-vars': [
4033
'off',

Dockerfile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ RUN yarn install --frozen-lockfile
1515
# Copy source files
1616
COPY . .
1717

18-
# Build the web application without environment variables
19-
# Environment variables will be injected at runtime via docker-entrypoint.sh
20-
RUN yarn web:build
18+
# Build the web application with production defaults
19+
# Runtime environment variables will be injected at startup via docker-entrypoint.sh
20+
# APP_ENV=production ensures the build uses production defaults and no .env suffix on IDs
21+
RUN APP_ENV=production yarn web:build
2122

2223
### STAGE 2: Run ###
2324
FROM nginx:1.25-alpine
@@ -42,6 +43,8 @@ EXPOSE 80
4243
ENV APP_ENV=production \
4344
UNIT_NAME="Resgrid Unit" \
4445
UNIT_SCHEME="ResgridUnit" \
46+
UNIT_BUNDLE_ID="com.resgrid.unit" \
47+
UNIT_PACKAGE="com.resgrid.unit" \
4548
UNIT_VERSION="0.0.1" \
4649
UNIT_BASE_API_URL="https://api.resgrid.com" \
4750
UNIT_API_VERSION="v4" \

__mocks__/react-native-svg.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
3+
const createMockComponent = (name: string) => {
4+
const Component = React.forwardRef((props: any, ref: any) =>
5+
React.createElement(name, { ...props, ref })
6+
);
7+
Component.displayName = name;
8+
return Component;
9+
};
10+
11+
export const Svg = createMockComponent('Svg');
12+
export const Circle = createMockComponent('Circle');
13+
export const Ellipse = createMockComponent('Ellipse');
14+
export const G = createMockComponent('G');
15+
export const Text = createMockComponent('SvgText');
16+
export const TSpan = createMockComponent('TSpan');
17+
export const TextPath = createMockComponent('TextPath');
18+
export const Path = createMockComponent('Path');
19+
export const Polygon = createMockComponent('Polygon');
20+
export const Polyline = createMockComponent('Polyline');
21+
export const Line = createMockComponent('Line');
22+
export const Rect = createMockComponent('Rect');
23+
export const Use = createMockComponent('Use');
24+
export const Image = createMockComponent('SvgImage');
25+
export const Symbol = createMockComponent('SvgSymbol');
26+
export const Defs = createMockComponent('Defs');
27+
export const LinearGradient = createMockComponent('LinearGradient');
28+
export const RadialGradient = createMockComponent('RadialGradient');
29+
export const Stop = createMockComponent('Stop');
30+
export const ClipPath = createMockComponent('ClipPath');
31+
export const Pattern = createMockComponent('Pattern');
32+
export const Mask = createMockComponent('Mask');
33+
export const ForeignObject = createMockComponent('ForeignObject');
34+
export const Marker = createMockComponent('Marker');
35+
export const SvgFromUri = createMockComponent('SvgFromUri');
36+
export const SvgFromXml = createMockComponent('SvgFromXml');
37+
export const SvgXml = createMockComponent('SvgXml');
38+
export const SvgUri = createMockComponent('SvgUri');
39+
export const SvgCss = createMockComponent('SvgCss');
40+
export const SvgCssUri = createMockComponent('SvgCssUri');
41+
export const parse = jest.fn();
42+
43+
export default Svg;

app.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,11 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
7878
'android.permission.POST_NOTIFICATIONS',
7979
'android.permission.FOREGROUND_SERVICE',
8080
'android.permission.FOREGROUND_SERVICE_MICROPHONE',
81+
'android.permission.FOREGROUND_SERVICE_PHONE_CALL',
8182
'android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE',
8283
'android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK',
8384
'android.permission.READ_PHONE_STATE',
85+
'android.permission.READ_PHONE_NUMBERS',
8486
'android.permission.MANAGE_OWN_CALLS',
8587
],
8688
},

babel.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ module.exports = function (api) {
2525
extensions: ['.ios.ts', '.android.ts', '.ts', '.ios.tsx', '.android.tsx', '.tsx', '.jsx', '.js', '.json'],
2626
},
2727
],
28+
'babel-plugin-transform-import-meta',
2829
'react-native-reanimated/plugin',
2930
],
3031
};

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ services:
1212
- APP_ENV=production
1313
- UNIT_NAME=Resgrid Unit
1414
- UNIT_SCHEME=ResgridUnit
15+
- UNIT_BUNDLE_ID=com.resgrid.unit
16+
- UNIT_PACKAGE=com.resgrid.unit
1517
- UNIT_VERSION=0.0.1
1618
- UNIT_BASE_API_URL=https://api.resgrid.com
1719
- UNIT_API_VERSION=v4

docker/docker-entrypoint.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@ js_escape() {
1111
}
1212

1313
# Create the env-config.js file with environment variables
14+
# Includes ALL fields expected by the client env schema in env.js
1415
cat > "${HTML_DIR}/env-config.js" << EOF
1516
// Runtime environment configuration - generated by docker-entrypoint.sh
1617
// This file is generated at container startup and injects environment variables
1718
window.__ENV__ = {
1819
APP_ENV: "$(js_escape "${APP_ENV:-production}")",
1920
NAME: "$(js_escape "${UNIT_NAME:-Resgrid Unit}")",
2021
SCHEME: "$(js_escape "${UNIT_SCHEME:-ResgridUnit}")",
22+
BUNDLE_ID: "$(js_escape "${UNIT_BUNDLE_ID:-com.resgrid.unit}")",
23+
PACKAGE: "$(js_escape "${UNIT_PACKAGE:-com.resgrid.unit}")",
2124
VERSION: "$(js_escape "${UNIT_VERSION:-0.0.1}")",
25+
ANDROID_VERSION_CODE: 1,
2226
BASE_API_URL: "$(js_escape "${UNIT_BASE_API_URL:-https://api.resgrid.com}")",
2327
API_VERSION: "$(js_escape "${UNIT_API_VERSION:-v4}")",
2428
RESGRID_API_URL: "$(js_escape "${UNIT_RESGRID_API_URL:-/api/v4}")",

electron/main.js

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
/* eslint-disable no-undef */
2-
const { app, BrowserWindow, ipcMain, Notification, nativeTheme, Menu } = require('electron');
2+
const { app, BrowserWindow, ipcMain, Notification, nativeTheme, Menu, protocol, net } = require('electron');
33
const path = require('path');
4+
const fs = require('fs');
5+
const { pathToFileURL } = require('url');
6+
7+
// Register custom protocol scheme before app is ready
8+
// This allows serving the Expo web export with absolute paths (/_expo/static/...)
9+
// via a custom protocol instead of file://, which breaks absolute path resolution.
10+
protocol.registerSchemesAsPrivileged([
11+
{
12+
scheme: 'app',
13+
privileges: {
14+
standard: true,
15+
secure: true,
16+
supportFetchAPI: true,
17+
corsEnabled: true,
18+
stream: true,
19+
},
20+
},
21+
]);
422

523
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
624
if (require('electron-squirrel-startup')) {
@@ -33,9 +51,14 @@ function createWindow() {
3351
});
3452

3553
// Load the app
36-
const startUrl = isDev ? 'http://localhost:8081' : `file://${path.join(__dirname, '../dist/index.html')}`;
37-
38-
mainWindow.loadURL(startUrl);
54+
if (isDev) {
55+
// In development, load from the Expo dev server
56+
mainWindow.loadURL('http://localhost:8081');
57+
} else {
58+
// In production, load via the custom app:// protocol
59+
// which correctly resolves absolute paths (/_expo/static/...) from the dist directory
60+
mainWindow.loadURL('app://bundle/index.html');
61+
}
3962

4063
// Show window when ready
4164
mainWindow.once('ready-to-show', () => {
@@ -154,6 +177,41 @@ ipcMain.handle('get-platform', () => {
154177

155178
// Handle app ready
156179
app.whenReady().then(() => {
180+
// Register custom protocol handler for serving the Expo web export
181+
// This resolves absolute paths like /_expo/static/js/... from the dist directory
182+
const distPath = path.join(__dirname, '..', 'dist');
183+
const resolvedDist = path.resolve(distPath);
184+
185+
protocol.handle('app', (request) => {
186+
const url = new URL(request.url);
187+
// Decode the pathname, join with base path, then canonicalize to prevent directory traversal
188+
const joinedPath = path.join(distPath, decodeURIComponent(url.pathname));
189+
const resolvedPath = path.resolve(joinedPath);
190+
191+
// Security check: ensure resolved path is within distPath to prevent directory traversal
192+
let filePath;
193+
if (!resolvedPath.startsWith(resolvedDist + path.sep) && resolvedPath !== resolvedDist) {
194+
// Path escapes distPath - fall back to index.html
195+
filePath = path.join(resolvedDist, 'index.html');
196+
} else {
197+
filePath = resolvedPath;
198+
199+
// If the path points to a directory or file doesn't exist, fall back to index.html
200+
// This supports SPA client-side routing
201+
try {
202+
const stat = fs.statSync(filePath);
203+
if (stat.isDirectory()) {
204+
filePath = path.join(resolvedDist, 'index.html');
205+
}
206+
} catch {
207+
// File not found - serve index.html for client-side routing
208+
filePath = path.join(resolvedDist, 'index.html');
209+
}
210+
}
211+
212+
return net.fetch(pathToFileURL(filePath).toString());
213+
});
214+
157215
createMenu();
158216
createWindow();
159217

global.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
11
@tailwind base;
22
@tailwind components;
33
@tailwind utilities;
4+
5+
/* Web-only pulse animation for user location marker on map */
6+
@keyframes pulse-ring {
7+
0%, 100% {
8+
transform: scale(1);
9+
opacity: 0.3;
10+
}
11+
50% {
12+
transform: scale(1.2);
13+
opacity: 0.15;
14+
}
15+
}
16+
17+
/* Web-only skeleton loading animation */
18+
@keyframes skeleton-pulse {
19+
0%, 100% {
20+
opacity: 1;
21+
}
22+
50% {
23+
opacity: 0.75;
24+
}
25+
}

0 commit comments

Comments
 (0)