diff --git a/app/package-lock.json b/app/package-lock.json index 45b9825..9d86f91 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -23,6 +23,9 @@ "react-native": "0.81.5", "react-native-safe-area-context": "^5.8.0", "react-native-web": "^0.21.2" + }, + "devDependencies": { + "typescript": "^5.9.3" } }, "node_modules/@0no-co/graphql.web": { @@ -7871,6 +7874,20 @@ "node": ">=8" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/ua-parser-js": { "version": "1.0.41", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", diff --git a/app/package.json b/app/package.json index ee27c52..5c7522c 100644 --- a/app/package.json +++ b/app/package.json @@ -6,7 +6,8 @@ "start": "expo start", "android": "expo run:android", "ios": "expo run:ios", - "web": "expo start --web" + "web": "expo start --web", + "lint": "node scripts/check-jsx.mjs" }, "dependencies": { "@react-native-async-storage/async-storage": "2.2.0", @@ -25,5 +26,8 @@ "react-native-safe-area-context": "^5.8.0", "react-native-web": "^0.21.2" }, - "private": true + "private": true, + "devDependencies": { + "typescript": "^5.9.3" + } } diff --git a/app/scripts/check-jsx.mjs b/app/scripts/check-jsx.mjs new file mode 100644 index 0000000..9d2de5f --- /dev/null +++ b/app/scripts/check-jsx.mjs @@ -0,0 +1,35 @@ +import { parse } from '@babel/parser'; +import { readdirSync, readFileSync, statSync } from 'node:fs'; +import { join } from 'node:path'; + +const roots = ['App.jsx', 'index.js', 'src']; +const extensions = new Set(['.js', '.jsx']); +const files = []; + +function collect(path) { + const stat = statSync(path); + if (stat.isDirectory()) { + for (const entry of readdirSync(path)) { + collect(join(path, entry)); + } + return; + } + + const extension = path.slice(path.lastIndexOf('.')); + if (extensions.has(extension)) { + files.push(path); + } +} + +for (const root of roots) { + collect(root); +} + +for (const file of files) { + parse(readFileSync(file, 'utf8'), { + sourceType: 'module', + plugins: ['jsx'], + }); +} + +console.log(`Checked ${files.length} JavaScript files.`); diff --git a/app/src/screens/CalorieScreen.jsx b/app/src/screens/CalorieScreen.jsx index 439e76a..66bde58 100644 --- a/app/src/screens/CalorieScreen.jsx +++ b/app/src/screens/CalorieScreen.jsx @@ -266,7 +266,8 @@ export default function CalorieScreen({ onOpenDrawer, avatarLabel }) { Macro targets { - setMeals(baseMeals); + setMeals(baseMeals); + }}> Reset day { diff --git a/app/src/screens/HangoutScreen.jsx b/app/src/screens/HangoutScreen.jsx index a47f6c3..e10bb9d 100644 --- a/app/src/screens/HangoutScreen.jsx +++ b/app/src/screens/HangoutScreen.jsx @@ -35,6 +35,8 @@ export default function HangoutScreen({ onOpenDrawer, avatarLabel, sessionToken, const [roomNameError, setRoomNameError] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [activeFilter, setActiveFilter] = useState('All'); + const [statusMessage, setStatusMessage] = useState(''); + const [statusTone, setStatusTone] = useState('info'); const [toast, setToast] = useState({ visible: false, message: '', type: 'info' }); const [roomFetchError, setRoomFetchError] = useState(null); const [loadingAction, setLoadingAction] = useState('idle'); @@ -111,6 +113,11 @@ export default function HangoutScreen({ onOpenDrawer, avatarLabel, sessionToken, return activeRoom.ownerDisplayName.trim().toLowerCase() === userName.trim().toLowerCase(); }, [activeRoom, userName]); const canShareScreen = isHost || meetingSettings.allowGuestScreenShare; + + function showStatus(message, tone = 'info') { + setStatusTone(tone); + setStatusMessage(message); + } const filteredRooms = useMemo(() => { let result = rooms || []; @@ -200,6 +207,7 @@ export default function HangoutScreen({ onOpenDrawer, avatarLabel, sessionToken, setActiveRoom(result.room); setJoinInput(result.room.joinLink); seedMeetingRoom(result.room); + showStatus(`${result.room.roomName} is ready to join.`, 'success'); setToast({ visible: true, message: `Room ${result.room.roomCode} is live.`, type: 'success' }); await refreshRooms(); } @@ -226,6 +234,7 @@ export default function HangoutScreen({ onOpenDrawer, avatarLabel, sessionToken, setActiveRoom(result.room); setJoinInput(result.room.roomCode); seedMeetingRoom(result.room); + showStatus(`${result.room.roomName} is ready to join.`, 'success'); setToast({ visible: true, message: `Joined ${result.room.roomName}.`, type: 'success' }); await refreshRooms(); if (fromIncomingLink) { @@ -237,20 +246,17 @@ export default function HangoutScreen({ onOpenDrawer, avatarLabel, sessionToken, const result = await getRoom(roomCode); setLoadingAction('idle'); if (!result.ok) { - setStatusTone('error'); - setStatusMessage(result.message); + showStatus(result.message, 'error'); return; } setActiveRoom(result.room); setJoinInput(result.room.roomCode); seedMeetingRoom(result.room); - setStatusTone('info'); - setStatusMessage(`${result.room.roomName} is ready.`); + showStatus(`${result.room.roomName} is ready.`, 'info'); } async function handleShareRoom(friendName) { if (!activeRoom) { - setStatusTone('error'); - setStatusMessage('Create or join a room before sharing it.'); + showStatus('Create or join a room before sharing it.', 'error'); return; } const message = friendName @@ -262,21 +268,18 @@ export default function HangoutScreen({ onOpenDrawer, avatarLabel, sessionToken, message, }); setLoadingAction('idle'); - setStatusTone('success'); - setStatusMessage(friendName ? `Share sheet opened for ${friendName}.` : 'Share sheet opened.'); + showStatus(friendName ? `Share sheet opened for ${friendName}.` : 'Share sheet opened.', 'success'); } function handleEnterMeeting() { if (!activeRoom) { - setStatusTone('error'); - setStatusMessage('Open a room first.'); + showStatus('Open a room first.', 'error'); return; } if (!meetingParticipants.length) { seedMeetingRoom(activeRoom); } setMeetingOpen(true); - setStatusTone('info'); - setStatusMessage(`Inside ${activeRoom.roomName}.`); + showStatus(`Inside ${activeRoom.roomName}.`, 'info'); } function handleLeaveMeeting() { setMeetingOpen(false); @@ -284,8 +287,7 @@ export default function HangoutScreen({ onOpenDrawer, avatarLabel, sessionToken, setShareScreenOn(false); setRecordingOn(false); setFocusedParticipantId(meetingParticipants[1]?.id ?? meetingParticipants[0]?.id ?? null); - setStatusTone('info'); - setStatusMessage(activeRoom ? `Left ${activeRoom.roomName}. Room is still active.` : 'Left the meeting.'); + showStatus(activeRoom ? `Left ${activeRoom.roomName}. Room is still active.` : 'Left the meeting.', 'info'); } function updateSelfParticipant(updater) { setMeetingParticipants((current) => current.map((participant, index) => (index === 0 ? updater(participant) : participant))); @@ -446,6 +448,15 @@ export default function HangoutScreen({ onOpenDrawer, avatarLabel, sessionToken, {activeRoom.roomType} • {activeRoom.ownerDisplayName} • {activeRoom.participantCount} in room {activeRoom.joinLink} + {statusMessage ? ( + + {statusMessage} + ) : null} {getInitials(userName)} @@ -629,8 +640,8 @@ export default function HangoutScreen({ onOpenDrawer, avatarLabel, sessionToken, Prof. Deshmukh: Let's revise normalization before we solve the next DBMS problem. ) : null} - {reactionBurst ? ( - {reactionBurst.icon} + {reactionBurst ? ( + {reactionBurst.label} ) : null} @@ -661,12 +672,12 @@ export default function HangoutScreen({ onOpenDrawer, avatarLabel, sessionToken, {[ - { icon: '👏', label: 'Clap' }, - { icon: '🔥', label: 'Fire' }, - { icon: '👍', label: 'Thumbs up' }, - { icon: '🙌', label: 'Celebrate' }, + { icon: 'hand-left', label: 'Raise hand' }, + { icon: 'checkmark-circle', label: 'Agree' }, + { icon: 'help-circle', label: 'Question' }, + { icon: 'bulb', label: 'Idea' }, ].map((reaction) => ( sendReaction(reaction.icon, reaction.label)}> - {reaction.icon} + {reaction.label} ))} @@ -838,12 +849,15 @@ function MeetingPanelSheet({ visible, panel, participants, messages, activityFee ) : null} {panel === 'settings' ? ( + {!isHost ? ( + Only the host can change room-wide settings. + ) : null} onUpdateSettings({ ...settings, noiseCancellation: value })}/> onUpdateSettings({ ...settings, lowLightMode: value })}/> onUpdateSettings({ ...settings, mirrorSelfView: value })}/> - onUpdateSettings({ ...settings, allowChat: value })}/> - onUpdateSettings({ ...settings, allowGuestScreenShare: value })}/> - onUpdateSettings({ ...settings, waitingRoomEnabled: value })}/> + onUpdateSettings({ ...settings, allowChat: value })}/> + onUpdateSettings({ ...settings, allowGuestScreenShare: value })}/> + onUpdateSettings({ ...settings, waitingRoomEnabled: value })}/> Layout @@ -881,10 +895,10 @@ function MeetingPanelSheet({ visible, panel, participants, messages, activityFee ); } -function SettingsRow({ label, value, onValueChange, }) { - return ( +function SettingsRow({ label, value, disabled = false, onValueChange, }) { + return ( {label} - + ); } const panelTitleMap = { @@ -1923,6 +1937,9 @@ const styles = StyleSheet.create({ borderBottomWidth: 1, borderBottomColor: theme.colors.line, }, + settingsRowDisabled: { + opacity: 0.55, + }, settingsText: { color: theme.colors.text, fontSize: 15, diff --git a/app/tsconfig.json b/app/tsconfig.json new file mode 100644 index 0000000..05207c6 --- /dev/null +++ b/app/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "allowJs": false, + "checkJs": false, + "jsx": "react-native", + "noEmit": true, + "skipLibCheck": true, + "strict": false + }, + "files": ["types/empty.d.ts"] +} diff --git a/app/types/empty.d.ts b/app/types/empty.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/app/types/empty.d.ts @@ -0,0 +1 @@ +export {};