diff --git a/package-lock.json b/package-lock.json index 9607ea0..bf34c50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,8 +23,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^10.1.0", - "react-router-dom": "^7.11.0", - "stripe": "^20.1.0" + "react-router-dom": "^7.11.0" }, "devDependencies": { "@eslint/js": "^9.39.1", @@ -3666,6 +3665,14 @@ "license": "MIT", "optional": true }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -4903,6 +4910,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "devOptional": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4916,6 +4924,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5466,6 +5475,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "devOptional": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -5654,6 +5664,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5663,6 +5674,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5707,6 +5719,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "devOptional": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -7020,6 +7033,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7149,6 +7163,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "devOptional": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -7173,6 +7188,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "devOptional": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -7382,6 +7398,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7465,6 +7482,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7493,6 +7511,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "devOptional": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -8975,6 +8994,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9952,6 +9972,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10579,21 +10600,6 @@ "node": ">=6" } }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11279,6 +11285,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -11298,6 +11305,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -11314,6 +11322,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -11332,6 +11341,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -11691,26 +11701,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stripe": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-20.1.0.tgz", - "integrity": "sha512-o1VNRuMkY76ZCq92U3EH3/XHm/WHp7AerpzDs4Zyo8uE5mFL4QUcv/2SudWsSnhBSp4moO2+ZoGCZ7mT8crPmQ==", - "license": "MIT", - "dependencies": { - "qs": "^6.11.0" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@types/node": ">=16" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/strnum": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", diff --git a/src/components/dom/MonacoEditor.jsx b/src/components/dom/MonacoEditor.jsx new file mode 100644 index 0000000..70102ac --- /dev/null +++ b/src/components/dom/MonacoEditor.jsx @@ -0,0 +1,46 @@ +"use dom"; + +import { Editor } from '@monaco-editor/react'; + +export default function MonacoEditor({ + value, + onChange, + language = 'javascript', + mode = 'light', + height = '100%', + instanceKey, +}) { + return ( + onChange?.(newValue)} + theme={mode === 'dark' ? 'vs-dark' : 'vs-light'} + options={{ + fontSize: 14, + fontFamily: 'Monaco, Menlo, "Ubuntu Mono", "Consolas", monospace', + lineNumbers: 'on', + roundedSelection: false, + scrollBeyondLastLine: false, + readOnly: false, + automaticLayout: true, + minimap: { enabled: false }, + scrollbar: { + vertical: 'visible', + horizontal: 'visible', + useShadows: false, + verticalHasArrows: false, + horizontalHasArrows: false, + }, + padding: { top: 16, bottom: 16 }, + bracketPairColorization: { enabled: true }, + folding: true, + lineDecorationsWidth: 10, + lineNumbersMinChars: 3, + }} + /> + ); +} diff --git a/src/components/dom/SandpackPreview.jsx b/src/components/dom/SandpackPreview.jsx new file mode 100644 index 0000000..695268f --- /dev/null +++ b/src/components/dom/SandpackPreview.jsx @@ -0,0 +1,94 @@ +"use dom"; + +import { + SandpackProvider, + SandpackLayout, + SandpackCodeEditor, + SandpackPreview as SandpackPreviewPanel, + SandpackConsole, + useSandpackConsole, +} from "@codesandbox/sandpack-react"; +import { useEffect } from "react"; + +const ErrorListener = ({ onError }) => { + const { logs } = useSandpackConsole({ resetOnPreviewRestart: true }); + + useEffect(() => { + const errors = logs.filter((log) => log.level === "error"); + if (errors.length === 0) return; + + const latest = errors[errors.length - 1]; + const errorData = { + message: latest.data?.[0] || "Unknown error", + file: null, + line: null, + column: null, + }; + + const match = latest.data?.[0]?.match(/([^/]+\.(jsx?|tsx?)):(\d+):(\d+)/); + if (match) { + errorData.file = match[1]; + errorData.line = parseInt(match[3]); + errorData.column = parseInt(match[4]); + } + + onError?.(errorData); + }, [logs, onError]); + + return null; +}; + +export default function SandpackPreview({ + files, + theme = "dark", + layout = "horizontal", + showConsole = true, + editorHeight, + onError, +}) { + const height = editorHeight ?? (layout === "vertical" ? 400 : 800); + + return ( + + + + + + {showConsole && ( + + )} + + + ); +} diff --git a/src/components/editor/EditorPanel.jsx b/src/components/editor/EditorPanel.jsx index 33f52bb..3158555 100644 --- a/src/components/editor/EditorPanel.jsx +++ b/src/components/editor/EditorPanel.jsx @@ -1,8 +1,8 @@ -import { Editor } from '@monaco-editor/react' import { useTheme } from '../../contexts/ThemeContext' import { getTheme } from '../../styles/theme' import { useState, useEffect } from 'react' import { DocumentIcon } from '../icons' +import MonacoEditor from '../dom/MonacoEditor' export const EditorPanel = ({ files, activeFile: initialActiveFile, onFileChange }) => { const { mode } = useTheme() @@ -106,36 +106,12 @@ export const EditorPanel = ({ files, activeFile: initialActiveFile, onFileChange {/* Editor */}
- onFileChange(selectedFile, value)} - theme={mode === 'dark' ? 'vs-dark' : 'vs-light'} - options={{ - fontSize: 14, - fontFamily: 'Monaco, Menlo, "Ubuntu Mono", "Consolas", monospace', - lineNumbers: 'on', - roundedSelection: false, - scrollBeyondLastLine: false, - readOnly: false, - automaticLayout: true, - minimap: { enabled: false }, - scrollbar: { - vertical: 'visible', - horizontal: 'visible', - useShadows: false, - verticalHasArrows: false, - horizontalHasArrows: false, - }, - padding: { top: 16, bottom: 16 }, - bracketPairColorization: { enabled: true }, - folding: true, - lineDecorationsWidth: 10, - lineNumbersMinChars: 3, - }} + mode={mode} />
diff --git a/src/components/preview/LiveReactEditor.jsx b/src/components/preview/LiveReactEditor.jsx index e859f17..486eaaf 100644 --- a/src/components/preview/LiveReactEditor.jsx +++ b/src/components/preview/LiveReactEditor.jsx @@ -1,17 +1,9 @@ -import { - Sandpack, - SandpackProvider, - SandpackLayout, - SandpackCodeEditor, - SandpackPreview, - SandpackConsole, - useSandpack, - useSandpackConsole -} from "@codesandbox/sandpack-react"; +import { Sandpack } from "@codesandbox/sandpack-react"; import { useState, useEffect } from "react"; import { useTheme } from "../../contexts/ThemeContext"; import { getTheme } from "../../styles/theme"; import SandpackErrorBoundary from "./SandpackErrorBoundary"; +import SandpackPreview from "../dom/SandpackPreview"; import "../../styles/LiveReactEditor.css"; // Loading wrapper component @@ -100,40 +92,6 @@ const SandpackLoadingWrapper = ({ children }) => { ); }; -// Error listener component that must be inside SandpackProvider -const ErrorListener = ({ onError }) => { - const { logs } = useSandpackConsole({ resetOnPreviewRestart: true }); - - useEffect(() => { - const errors = logs.filter(log => log.level === 'error'); - if (errors.length > 0) { - const latestError = errors[errors.length - 1]; - - // Parse error message to extract file and line info - const errorData = { - message: latestError.data?.[0] || 'Unknown error', - file: null, - line: null, - column: null - }; - - // Try to extract file/line info from error message - const fileMatch = latestError.data?.[0]?.match(/([^/]+\.(jsx?|tsx?)):(\d+):(\d+)/); - if (fileMatch) { - errorData.file = fileMatch[1]; - errorData.line = parseInt(fileMatch[3]); - errorData.column = parseInt(fileMatch[4]); - } - - if (onError) { - onError(errorData); - } - } - }, [logs, onError]); - - return null; -}; - export const LiveReactEditor = ({ initialCode, initialFiles, @@ -309,47 +267,13 @@ button:hover {
- - - - - - {showConsolePanel && ( - - )} - - + theme={selectedTheme} + layout={selectedLayout} + showConsole={showConsolePanel} + onError={onError} + />
diff --git a/src/config/firebase.native.js b/src/config/firebase.native.js new file mode 100644 index 0000000..23fc944 --- /dev/null +++ b/src/config/firebase.native.js @@ -0,0 +1,29 @@ +/** + * Firebase Native (React Native) Configuration + * + * Metro auto-selects this over firebase.js on iOS/Android builds. + * Phase 1 will wire this up to @react-native-firebase/* packages. Until then, + * this stub exports the same surface as firebase.js so shared code that + * imports `{ app, auth, db, storage }` can compile on native without crashing + * at bundle time. Any call-site that actually uses these will throw clearly. + */ + +const notWired = (name) => { + throw new Error( + `[firebase.native] ${name} is not wired yet. Install @react-native-firebase/* in Phase 1 and implement this module.`, + ); +}; + +const pendingAccess = (name) => + new Proxy( + {}, + { + get: () => notWired(name), + apply: () => notWired(name), + }, + ); + +export const app = pendingAccess('app'); +export const auth = pendingAccess('auth'); +export const db = pendingAccess('db'); +export const storage = pendingAccess('storage'); diff --git a/src/hooks/useIsMobile.native.js b/src/hooks/useIsMobile.native.js new file mode 100644 index 0000000..05ff911 --- /dev/null +++ b/src/hooks/useIsMobile.native.js @@ -0,0 +1,5 @@ +/** + * On React Native we're always on a mobile device. + * Metro auto-selects this over useIsMobile.js for iOS/Android builds. + */ +export const useIsMobile = () => true; diff --git a/src/hooks/useVirtualKeyboard.native.js b/src/hooks/useVirtualKeyboard.native.js new file mode 100644 index 0000000..8e197f4 --- /dev/null +++ b/src/hooks/useVirtualKeyboard.native.js @@ -0,0 +1,22 @@ +/** + * useVirtualKeyboard for React Native. + * + * Metro auto-selects this over useVirtualKeyboard.js for iOS/Android builds. + * Phase 1 will replace the placeholder with react-native Keyboard events: + * + * import { Keyboard } from 'react-native'; + * Keyboard.addListener('keyboardDidShow', ...) + * + * Until `react-native` is installed (Phase 1), this returns the same shape + * as the web hook so consumers can import it without crashing at bundle time. + */ +import { useState } from 'react'; + +export const useVirtualKeyboard = () => { + const [keyboardState] = useState({ + isKeyboardVisible: false, + keyboardHeight: 0, + viewportHeight: 0, + }); + return keyboardState; +};