diff --git a/.gitignore b/.gitignore index 944c89d..b22eeb6 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,4 @@ Thumbs.db # Next.js .next -*.local +*.local \ No newline at end of file diff --git a/apps/demo-nextjs-app-router/.eslintrc.json b/apps/demo-nextjs-app-router/.eslintrc.json new file mode 100644 index 0000000..17b0bc9 --- /dev/null +++ b/apps/demo-nextjs-app-router/.eslintrc.json @@ -0,0 +1,40 @@ +{ + "extends": [ + "plugin:@nx/react-typescript", + "next", + "next/core-web-vitals", + "../../.eslintrc.json" + ], + "ignorePatterns": ["!**/*", ".next/**/*"], + "overrides": [ + { + "files": ["*.*"], + "rules": { + "@next/next/no-html-link-for-pages": "off" + } + }, + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": { + "@next/next/no-html-link-for-pages": [ + "error", + "apps/demo-nextjs-app-router/pages" + ] + } + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"], + "env": { + "jest": true + } + } + ] +} diff --git a/apps/demo-nextjs-app-router/app/api/fal/proxy/route.ts b/apps/demo-nextjs-app-router/app/api/fal/proxy/route.ts new file mode 100644 index 0000000..998ab54 --- /dev/null +++ b/apps/demo-nextjs-app-router/app/api/fal/proxy/route.ts @@ -0,0 +1,3 @@ +import { route } from '@fal-ai/serverless-proxy/nextjs'; + +export const { GET, POST } = route; diff --git a/apps/demo-nextjs-app/pages/styles.css b/apps/demo-nextjs-app-router/app/global.css similarity index 100% rename from apps/demo-nextjs-app/pages/styles.css rename to apps/demo-nextjs-app-router/app/global.css diff --git a/apps/demo-nextjs-app-router/app/layout.tsx b/apps/demo-nextjs-app-router/app/layout.tsx new file mode 100644 index 0000000..836b8f4 --- /dev/null +++ b/apps/demo-nextjs-app-router/app/layout.tsx @@ -0,0 +1,18 @@ +import './global.css'; + +export const metadata = { + title: 'Welcome to demo-nextjs-app-router', + description: 'Generated by create-nx-workspace', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/apps/demo-nextjs-app/pages/index.module.css b/apps/demo-nextjs-app-router/app/page.module.css similarity index 100% rename from apps/demo-nextjs-app/pages/index.module.css rename to apps/demo-nextjs-app-router/app/page.module.css diff --git a/apps/demo-nextjs-app-router/app/page.tsx b/apps/demo-nextjs-app-router/app/page.tsx new file mode 100644 index 0000000..2313dd1 --- /dev/null +++ b/apps/demo-nextjs-app-router/app/page.tsx @@ -0,0 +1,170 @@ +'use client'; + +import * as fal from '@fal-ai/serverless-client'; +import { useMemo, useState } from 'react'; + +// @snippet:start(client.config) +fal.config({ + requestMiddleware: fal.withProxy({ + targetUrl: '/api/fal/proxy', // the built-int nextjs proxy + // targetUrl: 'http://localhost:3333/api/_fal/proxy', // or your own external proxy + }), +}); +// @snippet:end + +// @snippet:start(client.result.type) +type Image = { + url: string; + file_name: string; + file_size: number; +}; +type Result = { + images: Image[]; +}; +// @snippet:end + +type ErrorProps = { + error: any; +}; + +function Error(props: ErrorProps) { + if (!props.error) { + return null; + } + return ( +
+ Error {props.error.message} +
+ ); +} + +const DEFAULT_PROMPT = + 'a city landscape of a cyberpunk metropolis, raining, purple, pink and teal neon lights, highly detailed, uhd'; + +export default function Home() { + // @snippet:start("client.ui.state") + // Input state + const [prompt, setPrompt] = useState(DEFAULT_PROMPT); + // Result state + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [result, setResult] = useState(null); + const [logs, setLogs] = useState([]); + const [elapsedTime, setElapsedTime] = useState(0); + // @snippet:end + const image = useMemo(() => { + if (!result) { + return null; + } + return result.images[0]; + }, [result]); + + const reset = () => { + setLoading(false); + setError(null); + setResult(null); + setLogs([]); + setElapsedTime(0); + }; + + const generateImage = async () => { + reset(); + // @snippet:start("client.queue.subscribe") + setLoading(true); + const start = Date.now(); + try { + const result: Result = await fal.subscribe('110602490-lora', { + input: { + prompt, + model_name: 'stabilityai/stable-diffusion-xl-base-1.0', + image_size: 'square_hd', + }, + pollInterval: 5000, // Default is 1000 (every 1s) + logs: true, + onQueueUpdate(update) { + setElapsedTime(Date.now() - start); + if ( + update.status === 'IN_PROGRESS' || + update.status === 'COMPLETED' + ) { + setLogs((update.logs || []).map((log) => log.message)); + } + }, + }); + setResult(result); + } catch (error: any) { + setError(error); + } finally { + setLoading(false); + setElapsedTime(Date.now() - start); + } + // @snippet:end + }; + return ( +
+
+

+ Hello fal +

+
+ + setPrompt(e.target.value)} + onBlur={(e) => setPrompt(e.target.value.trim())} + /> +
+ + + + + +
+
+ {image && ( + // eslint-disable-next-line @next/next/no-img-element + + )} +
+
+

JSON Result

+

+ {`Elapsed Time (seconds): ${(elapsedTime / 1000).toFixed(2)}`} +

+
+              {result
+                ? JSON.stringify(result, null, 2)
+                : '// result pending...'}
+            
+
+ +
+

Logs

+
+              {logs.filter(Boolean).join('\n')}
+            
+
+
+
+
+ ); +} diff --git a/apps/demo-nextjs-app/index.d.ts b/apps/demo-nextjs-app-router/index.d.ts similarity index 100% rename from apps/demo-nextjs-app/index.d.ts rename to apps/demo-nextjs-app-router/index.d.ts diff --git a/apps/demo-nextjs-app/jest.config.ts b/apps/demo-nextjs-app-router/jest.config.ts similarity index 72% rename from apps/demo-nextjs-app/jest.config.ts rename to apps/demo-nextjs-app-router/jest.config.ts index f7a985d..6ff8152 100644 --- a/apps/demo-nextjs-app/jest.config.ts +++ b/apps/demo-nextjs-app-router/jest.config.ts @@ -1,11 +1,11 @@ /* eslint-disable */ export default { - displayName: 'demo-nextjs-app', + displayName: 'demo-nextjs-app-router', preset: '../../jest.preset.js', transform: { '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }], }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], - coverageDirectory: '../../coverage/apps/demo-nextjs-app', + coverageDirectory: '../../coverage/apps/demo-nextjs-app-router', }; diff --git a/apps/demo-nextjs-app/next-env.d.ts b/apps/demo-nextjs-app-router/next-env.d.ts similarity index 100% rename from apps/demo-nextjs-app/next-env.d.ts rename to apps/demo-nextjs-app-router/next-env.d.ts diff --git a/apps/demo-nextjs-app-router/next.config.js b/apps/demo-nextjs-app-router/next.config.js new file mode 100644 index 0000000..007b2aa --- /dev/null +++ b/apps/demo-nextjs-app-router/next.config.js @@ -0,0 +1,22 @@ +//@ts-check + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { composePlugins, withNx } = require('@nx/next'); + +/** + * @type {import('@nx/next/plugins/with-nx').WithNxOptions} + **/ +const nextConfig = { + nx: { + // Set this to true if you would like to use SVGR + // See: https://github.com/gregberge/svgr + svgr: false, + }, +}; + +const plugins = [ + // Add more Next.js plugins to this list if needed. + withNx, +]; + +module.exports = composePlugins(...plugins)(nextConfig); diff --git a/apps/demo-nextjs-app/postcss.config.js b/apps/demo-nextjs-app-router/postcss.config.js similarity index 100% rename from apps/demo-nextjs-app/postcss.config.js rename to apps/demo-nextjs-app-router/postcss.config.js diff --git a/apps/demo-nextjs-app-router/project.json b/apps/demo-nextjs-app-router/project.json new file mode 100644 index 0000000..5b13833 --- /dev/null +++ b/apps/demo-nextjs-app-router/project.json @@ -0,0 +1,68 @@ +{ + "name": "demo-nextjs-app-router", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/demo-nextjs-app-router", + "projectType": "application", + "targets": { + "build": { + "executor": "@nx/next:build", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "outputPath": "dist/apps/demo-nextjs-app-router" + }, + "configurations": { + "development": { + "outputPath": "apps/demo-nextjs-app-router" + }, + "production": {} + } + }, + "serve": { + "executor": "@nx/next:server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "demo-nextjs-app-router:build", + "dev": true + }, + "configurations": { + "development": { + "buildTarget": "demo-nextjs-app-router:build:development", + "dev": true + }, + "production": { + "buildTarget": "demo-nextjs-app-router:build:production", + "dev": false + } + } + }, + "export": { + "executor": "@nx/next:export", + "options": { + "buildTarget": "demo-nextjs-app-router:build:production" + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/demo-nextjs-app-router/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/demo-nextjs-app-router/**/*.{ts,tsx,js,jsx}"] + } + } + }, + "tags": [] +} diff --git a/apps/demo-nextjs-app/public/.gitkeep b/apps/demo-nextjs-app-router/public/.gitkeep similarity index 100% rename from apps/demo-nextjs-app/public/.gitkeep rename to apps/demo-nextjs-app-router/public/.gitkeep diff --git a/apps/demo-nextjs-app-router/public/favicon.ico b/apps/demo-nextjs-app-router/public/favicon.ico new file mode 100644 index 0000000..317ebcb Binary files /dev/null and b/apps/demo-nextjs-app-router/public/favicon.ico differ diff --git a/apps/demo-nextjs-app-router/tailwind.config.js b/apps/demo-nextjs-app-router/tailwind.config.js new file mode 100644 index 0000000..948d92e --- /dev/null +++ b/apps/demo-nextjs-app-router/tailwind.config.js @@ -0,0 +1,18 @@ +const { createGlobPatternsForDependencies } = require('@nx/react/tailwind'); +const { join } = require('path'); + +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + join( + __dirname, + '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}' + ), + ...createGlobPatternsForDependencies(__dirname), + ], + darkMode: 'class', + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/apps/demo-nextjs-app-router/tsconfig.json b/apps/demo-nextjs-app-router/tsconfig.json new file mode 100644 index 0000000..19a0936 --- /dev/null +++ b/apps/demo-nextjs-app-router/tsconfig.json @@ -0,0 +1,36 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "preserve", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "resolveJsonModule": true, + "isolatedModules": true, + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "types": ["jest", "node"] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "../../apps/demo-nextjs-app-router/.next/types/**/*.ts", + "../../dist/apps/demo-nextjs-app-router/.next/types/**/*.ts", + "next-env.d.ts" + ], + "exclude": [ + "node_modules", + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts" + ] +} diff --git a/apps/demo-nextjs-app-router/tsconfig.spec.json b/apps/demo-nextjs-app-router/tsconfig.spec.json new file mode 100644 index 0000000..214b2cc --- /dev/null +++ b/apps/demo-nextjs-app-router/tsconfig.spec.json @@ -0,0 +1,21 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"], + "jsx": "react" + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ] +} diff --git a/apps/demo-nextjs-app/.eslintrc.json b/apps/demo-nextjs-page-router/.eslintrc.json similarity index 100% rename from apps/demo-nextjs-app/.eslintrc.json rename to apps/demo-nextjs-page-router/.eslintrc.json diff --git a/apps/demo-nextjs-page-router/index.d.ts b/apps/demo-nextjs-page-router/index.d.ts new file mode 100644 index 0000000..7ba08fa --- /dev/null +++ b/apps/demo-nextjs-page-router/index.d.ts @@ -0,0 +1,6 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +declare module '*.svg' { + const content: any; + export const ReactComponent: any; + export default content; +} diff --git a/apps/demo-nextjs-page-router/jest.config.ts b/apps/demo-nextjs-page-router/jest.config.ts new file mode 100644 index 0000000..8599fdc --- /dev/null +++ b/apps/demo-nextjs-page-router/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'demo-nextjs-page-router', + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/apps/demo-nextjs-page-router', +}; diff --git a/apps/demo-nextjs-page-router/next-env.d.ts b/apps/demo-nextjs-page-router/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/apps/demo-nextjs-page-router/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/demo-nextjs-app/next.config.js b/apps/demo-nextjs-page-router/next.config.js similarity index 100% rename from apps/demo-nextjs-app/next.config.js rename to apps/demo-nextjs-page-router/next.config.js diff --git a/apps/demo-nextjs-app/pages/_app.tsx b/apps/demo-nextjs-page-router/pages/_app.tsx similarity index 100% rename from apps/demo-nextjs-app/pages/_app.tsx rename to apps/demo-nextjs-page-router/pages/_app.tsx diff --git a/apps/demo-nextjs-app/pages/api/_fal/proxy.ts b/apps/demo-nextjs-page-router/pages/api/_fal/proxy.ts similarity index 100% rename from apps/demo-nextjs-app/pages/api/_fal/proxy.ts rename to apps/demo-nextjs-page-router/pages/api/_fal/proxy.ts diff --git a/apps/demo-nextjs-page-router/pages/index.module.css b/apps/demo-nextjs-page-router/pages/index.module.css new file mode 100644 index 0000000..8a13e21 --- /dev/null +++ b/apps/demo-nextjs-page-router/pages/index.module.css @@ -0,0 +1,2 @@ +.page { +} diff --git a/apps/demo-nextjs-app/pages/index.tsx b/apps/demo-nextjs-page-router/pages/index.tsx similarity index 94% rename from apps/demo-nextjs-app/pages/index.tsx rename to apps/demo-nextjs-page-router/pages/index.tsx index 55a50f9..7fb7994 100644 --- a/apps/demo-nextjs-app/pages/index.tsx +++ b/apps/demo-nextjs-page-router/pages/index.tsx @@ -21,7 +21,11 @@ type Result = { }; // @snippet:end -function Error(props) { +type ErrorProps = { + error: any; +}; + +function Error(props: ErrorProps) { if (!props.error) { return null; } @@ -64,8 +68,7 @@ export function Index() { setElapsedTime(0); }; - const handleOnClick = async (e) => { - e.preventDefault(); + const generateImage = async () => { reset(); // @snippet:start("client.queue.subscribe") setLoading(true); @@ -85,12 +88,12 @@ export function Index() { update.status === 'IN_PROGRESS' || update.status === 'COMPLETED' ) { - setLogs(update.logs.map((log) => log.message)); + setLogs((update.logs || []).map((log) => log.message)); } }, }); setResult(result); - } catch (error) { + } catch (error: any) { setError(error); } finally { setLoading(false); @@ -121,7 +124,10 @@ export function Index() {