Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b7d6067
feat(wellness-dashboard): add WELLNESS_LOG and WELLNESS_WEEKLY API UR…
Yuvraj-Sarathe Jul 8, 2026
636fc69
feat(wellness-dashboard): add WellnessLog and WeeklyWellnessResponse …
Yuvraj-Sarathe Jul 8, 2026
c0e8adf
feat(wellness-dashboard): add Zod validation schema for wellness log
Yuvraj-Sarathe Jul 8, 2026
e9cbc4a
feat(wellness-dashboard): add React Query hooks for wellness log muta…
Yuvraj-Sarathe Jul 8, 2026
aa081f3
feat(wellness-dashboard): wire API integration into ManualLogCard, We…
Yuvraj-Sarathe Jul 8, 2026
9dd56ff
fix(wellness-dashboard): restore emoji in header title
Yuvraj-Sarathe Jul 8, 2026
413ce33
docs(wellness-dashboard): add plan execution summary
Yuvraj-Sarathe Jul 8, 2026
f29f4ec
fix(02): CR-01 guard sort against missing dates in WeeklyChart.tsx
Yuvraj-Sarathe Jul 8, 2026
d204421
fix(02): CR-02 make date required in wellness schema to match backend…
Yuvraj-Sarathe Jul 8, 2026
39b4d59
fix(02): CR-03 prevent prefetch race condition overwriting user input…
Yuvraj-Sarathe Jul 8, 2026
99ca67c
fix(02): CR-04 disable save button when offline in ManualLogCard
Yuvraj-Sarathe Jul 8, 2026
5441fdd
fix(02): CR-06 correct offline banner message in WellnessDashboardScreen
Yuvraj-Sarathe Jul 8, 2026
80d529b
cleanup
Yuvraj-Sarathe Jul 8, 2026
9187886
bug fixing
Yuvraj-Sarathe Jul 8, 2026
6939583
Delete REVIEW.md
Yuvraj-Sarathe Jul 8, 2026
e1820af
Delete wellness-dashboard-api-SUMMARY.md
Yuvraj-Sarathe Jul 8, 2026
20cea7c
Delete frontend/DEBUG-FINDINGS.md
Yuvraj-Sarathe Jul 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 15 additions & 27 deletions frontend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,29 @@
# These are read by app.config.js and injected into the app via expo-constants.
# Copy this file to .env and adjust values for your environment.
#
# Production defaults:
# Production defaults (no backend needed to test the UI):
PROD_URL="https://uhsocial.in/api"
SOCKET_PROD="https://uhsocial.in"
CONTENT_CHECKER_PROD="https://uhsocial.in/content-intel"

# Local development examples (uncomment and edit as needed):
# Local development (uncomment and edit as needed):
# PROD_URL="http://10.0.2.2:3000/api" # Android emulator
# PROD_URL="http://localhost:3000/api" # iOS simulator
# PROD_URL="http://localhost:3000/api" # iOS simulator / web
# SOCKET_PROD="http://10.0.2.2:3000"
# CONTENT_CHECKER_PROD="http://10.0.2.2:3000/content-intel"
FIREBASE_API_KEY = "FIREBASE_API_KEY"
FIREBASE_AUTH_DOMAIN ="FIREBASE_AUTH_DOMAIN"
FIREBASE_PROJECT_ID = "FIREBASE_PROJECT_ID"
FIREBASE_STORAGE_BUCKET = "FIREBASE_STORAGE_BUCKET"
FIREBASE_SENDER_ID = "FIREBASE_SENDER_ID"
FIREBASE_APP_ID = "FIREBASE_APP_ID"

# Gemini API Configuration (for AI-powered article summaries)
# ---------------------------------------------------------------------------
# Gemini API — for AI-powered article summaries & health chat
# ---------------------------------------------------------------------------
# Get your free API key at: https://aistudio.google.com/app/apikey
# Free tier: 1500 requests/day, no credit card required
EXPO_PUBLIC_GEMINI_API_KEY="YOUR_GEMINI_API_KEY_HERE"
FIREBASE_MEASUREMENT_ID = "FIREBASE_MEASUREMENT_ID"
FIREBASE_DATABASE_URL = "FIREBASE_DATABASE_URL"

# Sentry Configuration
EXPO_PUBLIC_SENTRY_DSN = "YOUR_SENTRY_DSN"
EXPO_PUBLIC_APP_ENV = "development"
{
"expo": {
"name": "UltimateHealth",
"extra": {
GEMINI_API_KEY=YOUR_GEMINI_API_KEY_HERE
}
}
}

# Firebase Configuration
# ---------------------------------------------------------------------------
# Firebase Configuration — for push notifications (FCM)
# ---------------------------------------------------------------------------
# These are read by app.config.js and passed via Constants.expoConfig.extra.
# Required for push notifications. Leave empty to skip Firebase locally.
FIREBASE_API_KEY_ANDROID=""
FIREBASE_API_KEY_IOS=""
FIREBASE_APP_ID_ANDROID=""
Expand All @@ -50,7 +36,9 @@ FIREBASE_SENDER_ID=""
FIREBASE_MEASUREMENT_ID=""
FIREBASE_DATABASE_URL=""


# Sentry Configuration
# ---------------------------------------------------------------------------
# Sentry Configuration — for error monitoring
# ---------------------------------------------------------------------------
# Set to empty to skip Sentry initialization in development.
EXPO_PUBLIC_SENTRY_DSN=""
EXPO_PUBLIC_APP_ENV="development"
2 changes: 1 addition & 1 deletion frontend/app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ module.exports = ({ config }) => {
// API URLs — read from environment variables with production fallbacks.
// Override these in your local .env file (see .env.example).
PROD_URL:
process.env.PROD_URL ?? "https://uhsocial.in/ap",
process.env.PROD_URL ?? "https://uhsocial.in/api",
SOCKET_PROD:
process.env.SOCKET_PROD ?? "https://uhsocial.in",
CONTENT_CHECKER_PROD:
Expand Down
13 changes: 8 additions & 5 deletions frontend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ import { logger } from './src/services/monitoring/logger';// Firebase background
// to AppContent.tsx's first useEffect so that expo-application metadata
// (nativeApplicationVersion, nativeBuildVersion) is fully available before
// Sentry initialises.
messaging().setBackgroundMessageHandler(async remoteMessage => {
// Only log notification payloads in development to avoid leaking user data
// or FCM tokens in production log aggregators.

// Guard: @react-native-firebase/messaging is undefined on web.
const firebaseMessaging = messaging();
if (firebaseMessaging) {
firebaseMessaging.setBackgroundMessageHandler(async remoteMessage => {
// Only log notification payloads in development to avoid leaking user data
// or FCM tokens in production log aggregators.
logger.log('Background notification received:', remoteMessage);
});
});
}

const AppWrapper = () => {
return (
Expand Down
16 changes: 13 additions & 3 deletions frontend/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@ const path = require('path');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);

// Alias react-native-pager-view to a mock for web
// Alias native-only modules to web mocks
const WEB_MOCKS = {
'react-native-pager-view': 'mocks/react-native-pager-view.js',
'react-native-snackbar': 'mocks/react-native-snackbar.js',
'react-native-share': 'mocks/react-native-share.js',
'react-native-fs': 'mocks/react-native-fs.js',
'react-native-html-to-pdf': 'mocks/react-native-html-to-pdf.js',
'@react-native-firebase/app': 'mocks/react-native-firebase-app.js',
'@react-native-firebase/messaging': 'mocks/react-native-firebase-messaging.js',
};

const originalResolveRequest = config.resolver.resolveRequest;

config.resolver.resolveRequest = (context, moduleName, platform) => {
if (platform === 'web' && moduleName === 'react-native-pager-view') {
if (platform === 'web' && WEB_MOCKS[moduleName]) {
return {
filePath: path.resolve(__dirname, 'mocks/react-native-pager-view.js'),
filePath: path.resolve(__dirname, WEB_MOCKS[moduleName]),
type: 'sourceFile',
};
}
Expand Down
17 changes: 17 additions & 0 deletions frontend/mocks/react-native-firebase-app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Web mock for @react-native-firebase/app

const firebase = {
apps: [],
initializeApp: () => ({
name: '[DEFAULT]',
options: {},
}),
app: () => ({
name: '[DEFAULT]',
options: {},
}),
messaging: () => null,
auth: () => null,
};

export default firebase;
18 changes: 18 additions & 0 deletions frontend/mocks/react-native-firebase-messaging.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Web mock for @react-native-firebase/messaging
// Default export is a factory function: messaging() returns an instance.

const messaging = () => ({
setBackgroundMessageHandler: () => {},
onMessage: () => () => {},
onTokenRefresh: () => () => {},
onNotificationOpenedApp: () => () => {},
getInitialNotification: async () => null,
getToken: async () => null,
requestPermission: async () => {},
registerForRemoteNotifications: async () => {},
hasPermission: async () => 1,
subscribeToTopic: () => {},
unsubscribeFromTopic: () => {},
});

export default messaging;
60 changes: 60 additions & 0 deletions frontend/mocks/react-native-fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Web mock for react-native-fs
// On web, RNFSFileTypeRegular and other native constants crash at import time.

const RNFS = {
// Constants
RNFilesystemType: 'web',
MainBundlePath: '',
CachesDirectoryPath: '',
DocumentDirectoryPath: '',
ExternalDirectoryPath: '',
ExternalStorageDirectoryPath: '',
TemporaryDirectoryPath: '',
LibraryDirectoryPath: '',
PicturesDirectoryPath: '',

// File read/write
readDir: async () => [],
readdir: async () => [],
stat: async () => ({ name: '', path: '', size: 0, mode: 0 }),
readFile: async () => '',
read: async () => '',
readFileAssets: async () => '',
exists: async () => false,
isResumable: async () => false,

// File write
writeFile: async () => {},
appendFile: async () => {},
write: async () => {},
unlink: async () => {},
unlinkIfExists: async () => {},
moveFile: async () => {},
copyFile: async () => {},
copyFileAssets: async () => {},
existsAssets: async () => false,
mkdir: async () => {},
touch: async () => {},

// Download
downloadFile: () => ({
jobId: 0,
promise: Promise.resolve({ statusCode: 200, jobId: 0 }),
}),
stopDownload: () => {},

// Upload
uploadFiles: () => ({
jobId: 0,
promise: Promise.resolve({ statusCode: 200, jobId: 0, body: '' }),
}),
stopUpload: () => {},

// Other
getFSInfo: async () => ({ totalSpace: 0, freeSpace: 0 }),
hash: async () => '',
getAllExternalFilesDirs: async () => [],
scanFile: async () => {},
};

module.exports = RNFS;
11 changes: 11 additions & 0 deletions frontend/mocks/react-native-html-to-pdf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Web mock for react-native-html-to-pdf

const RNHTMLtoPDF = {
generate: async () => ({ filePath: '' }),
convert: async () => ({ filePath: '' }),
};

const generatePDF = RNHTMLtoPDF.generate;

export { generatePDF };
export default RNHTMLtoPDF;
18 changes: 18 additions & 0 deletions frontend/mocks/react-native-share.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Web mock for react-native-share
// On web, NativeModules.RNShare is undefined, which crashes at import time.

const Share = {
open: async () => ({ success: true, message: 'Shared (mock)' }),
canShare: async () => true,
shareSingle: async () => ({ success: true }),
Social: {
FACEBOOK: 'facebook',
TWITTER: 'twitter',
WHATSAPP: 'whatsapp',
INSTAGRAM: 'instagram',
TELEGRAM: 'telegram',
EMAIL: 'email',
},
};

export default Share;
19 changes: 19 additions & 0 deletions frontend/mocks/react-native-snackbar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Web mock for react-native-snackbar
// On web, NativeModules.RNSnackbar is undefined, which crashes at import time.
// This mock provides a no-op implementation that doesn't access NativeModules.

const Snackbar = {
LENGTH_SHORT: 0,
LENGTH_LONG: 1,
LENGTH_INDEFINITE: -1,
DISMISS_EVENT_SWIPE: 'swipe',
DISMISS_EVENT_ACTION: 'action',
DISMISS_EVENT_TIMEOUT: 'timeout',
DISMISS_EVENT_MANUAL: 'manual',
DISMISS_EVENT_CONSECUTIVE: 'consecutive',
SHOW_EVENT: 'show',
show: () => {},
dismiss: () => {},
};

export default Snackbar;
7 changes: 6 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
"knip:exports": "knip --exports",
"knip:fix": "knip --fix",
"knip:check": "knip --no-progress",
"quality": "pnpm run type-check && pnpm run lint && pnpm run knip",
"quality": "yarn run type-check && yarn run lint && yarn run knip",
"preview": "expo export --platform web",
"preview:android": "expo export --platform android",
"preview:ios": "expo export --platform ios",
"export:web": "expo export --platform web",
"serve:preview": "node scripts/serve-preview.js",
"postinstall": "patch-package"
},
"dependencies": {
Expand Down
83 changes: 83 additions & 0 deletions frontend/scripts/serve-preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env node

/**
* Static preview server for exported Expo web builds.
*
* Usage:
* 1. Run `yarn export:web` (or `expo export --platform web`) to build dist/
* 2. Run `yarn serve:preview` to serve dist/ locally
*
* Serves the `dist/` directory on http://localhost:5000
*/

const http = require("http");
const fs = require("fs");
const path = require("path");

const PORT = process.env.PORT || 5000;
const DIST_DIR = path.resolve(__dirname, "..", "dist");

if (!fs.existsSync(DIST_DIR)) {
console.error(
`[serve-preview] dist/ directory not found at "${DIST_DIR}".\n` +
` Run "yarn export:web" (or "expo export --platform web") first to generate the preview build.`
);
process.exit(1);
}

const MIME_TYPES = {
".html": "text/html",
".js": "text/javascript",
".css": "text/css",
".json": "application/json",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".eot": "application/vnd.ms-fontobject",
".txt": "text/plain",
".map": "application/json",
};

const server = http.createServer((req, res) => {
let filePath = path.join(DIST_DIR, req.url === "/" ? "index.html" : req.url);
const ext = path.extname(filePath).toLowerCase();

fs.readFile(filePath, (err, data) => {
if (err) {
// Fallback to index.html for SPAs (client-side routing)
if (err.code === "ENOENT" && !ext) {
fs.readFile(path.join(DIST_DIR, "index.html"), (err2, data2) => {
if (err2) {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Not Found");
return;
}
res.writeHead(200, { "Content-Type": "text/html" });
res.end(data2);
});
return;
}

res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Not Found");
return;
}

const contentType = MIME_TYPES[ext] || "application/octet-stream";
res.writeHead(200, { "Content-Type": contentType });
res.end(data);
});
});

server.listen(PORT, () => {
const address = `http://localhost:${PORT}`;
console.log(`[serve-preview] Serving preview build at ${address}`);
console.log(`[serve-preview] Directory: ${DIST_DIR}`);
console.log(`[serve-preview] Press Ctrl+C to stop.`);
});
Loading
Loading