diff --git a/.gitignore b/.gitignore index 0fad0cb..82c4205 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ next-env.d.ts # firebase firebase-debug.log -firestore-debug.log \ No newline at end of file +firestore-debug.log +sqlite.db diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fd391b1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +# Stage 1: Build aşaması +FROM node:18-alpine AS builder + +WORKDIR /app + +COPY package*.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +# Stage 2: Production +FROM node:18-alpine AS production + +WORKDIR /app + +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/package*.json ./ + +EXPOSE 3000 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD node -e "require('http').get('http://localhost:3000', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})" + +CMD ["npm", "start"] diff --git a/README.md b/README.md index cc730c7..82d2dd5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,80 @@ -# Firebase Studio +# SimuBourse - Financial Simulation Game -This is a NextJS starter in Firebase Studio. +Welcome to SimuBourse, an immersive financial simulation platform built with Next.js, Drizzle ORM, and Genkit for AI-powered features. Trade stocks, cryptocurrencies, create and manage your own companies, invest in others, and get investment advice from an integrated AI. -To get started, take a look at src/app/page.tsx. +## Tech Stack + +- **Framework**: [Next.js](https://nextjs.org/) (App Router) +- **Styling**: [Tailwind CSS](https://tailwindcss.com/) & [ShadCN UI](https://ui.shadcn.com/) +- **Database**: [SQLite](https://www.sqlite.org/) via `better-sqlite3` +- **ORM**: [Drizzle ORM](https://orm.drizzle.team/) +- **Generative AI**: [Firebase Genkit](https://firebase.google.com/docs/genkit) +- **Authentication**: Custom JWT-based session management + +## Getting Started + +Follow these steps to get your local development environment up and running. + +### 1. Prerequisites + +- [Node.js](https://nodejs.org/) (v20.x or higher) +- npm or another package manager + +### 2. Installation + +First, clone the repository and install the dependencies: + +```bash +git clone +cd SimuBourse-u2 # Or your project directory +npm install +``` + +### 3. Environment Variables + +Create a `.env` file in the root of your project. This file is for your secret keys. You will need a Google AI API key for the Genkit features to work. + +```env +# Get your key from Google AI Studio: https://makersuite.google.com/app/apikey +GOOGLE_API_KEY="your_google_ai_api_key" +``` + +### 4. Initialize the Database + +The project uses SQLite, and the database file (`sqlite.db`) will be created automatically. To set up the necessary tables, run the initialization script: + +```bash +npm run db:init +``` +This command only needs to be run once. It will create all the necessary tables in your `sqlite.db` file. + +### 5. Run the Development Server + +You're all set! Start the development server: + +```bash +npm run dev +``` + +The application should now be running at [http://localhost:9002](http://localhost:9002). + +## Available Scripts + +- `npm run dev`: Starts the Next.js development server with Turbopack. +- `npm run build`: Builds the application for production. +- `npm run start`: Starts a Next.js production server. +- `npm run lint`: Runs ESLint to check for code quality. +- `npm run db:init`: Initializes the SQLite database with the required schema. + +## Deployment to a VM with PM2 + +To run this application in production on a Linux VM: + +1. Clone the project and install dependencies (`npm install`). +2. Set up your `.env` file with your production `GOOGLE_API_KEY`. +3. Build the project: `npm run build`. +4. Use `pm2` to start and manage the application: + ```bash + pm2 start npm --name "simubourse" -- run start -p 3000 + ``` + This will start the app on port 3000 and ensure it restarts automatically. diff --git a/drizzle.config.ts b/drizzle.config.ts index 55789f9..319f4e8 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,16 +1,10 @@ import { defineConfig } from 'drizzle-kit'; -import * as d from 'dotenv'; -d.config({ path: '.env' }); - -if (!process.env.DATABASE_URL) { - throw new Error('DATABASE_URL is not set'); -} export default defineConfig({ - dialect: 'postgresql', + dialect: 'sqlite', schema: './src/lib/db/schema.ts', out: './drizzle', dbCredentials: { - url: process.env.DATABASE_URL, + url: './sqlite.db', }, }); diff --git a/package-lock.json b/package-lock.json index 65bb2aa..c34bbbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "@radix-ui/react-toast": "^1.2.6", "@radix-ui/react-tooltip": "^1.1.8", "bcrypt": "^5.1.1", + "better-sqlite3": "^11.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^3.6.0", @@ -43,7 +44,6 @@ "lucide-react": "^0.475.0", "next": "15.3.3", "patch-package": "^8.0.0", - "pg": "^8.12.0", "react": "^18.3.1", "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", @@ -55,14 +55,15 @@ }, "devDependencies": { "@types/bcrypt": "^5.0.2", + "@types/better-sqlite3": "^7.6.11", "@types/node": "^20", - "@types/pg": "^8.11.8", "@types/react": "^18", "@types/react-dom": "^18", "drizzle-kit": "^0.24.2", "genkit-cli": "^1.13.0", "postcss": "^8", "tailwindcss": "^3.4.1", + "tsx": "^4.19.0", "typescript": "^5" } }, @@ -3892,6 +3893,16 @@ "@types/node": "*" } }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/caseless": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", @@ -3988,8 +3999,9 @@ "version": "8.15.4", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.4.tgz", "integrity": "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -4375,6 +4387,17 @@ "node": ">= 10.0.0" } }, + "node_modules/better-sqlite3": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", + "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, "node_modules/bignumber.js": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", @@ -4394,11 +4417,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -4453,7 +4484,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -5207,6 +5237,21 @@ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deeks": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/deeks/-/deeks-3.1.0.tgz", @@ -5217,6 +5262,15 @@ "node": ">= 16" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -6028,7 +6082,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "dependencies": { "once": "^1.4.0" } @@ -6219,6 +6272,15 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -6446,6 +6508,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -6579,6 +6647,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -6910,6 +6984,12 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -7253,7 +7333,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -7307,6 +7386,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/inquirer": { "version": "8.2.6", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", @@ -7983,6 +8068,18 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -8050,6 +8147,12 @@ "node": ">=10" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/module-details-from-path": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", @@ -8096,6 +8199,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -8191,6 +8300,18 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-abi": { + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", @@ -8568,6 +8689,8 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -8595,19 +8718,24 @@ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/pg-connection-string": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/pg-int8": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", "license": "ISC", + "optional": true, + "peer": true, "engines": { "node": ">=4.0.0" } @@ -8617,6 +8745,8 @@ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", "license": "MIT", + "optional": true, + "peer": true, "peerDependencies": { "pg": ">=8.0" } @@ -8625,13 +8755,17 @@ "version": "1.10.3", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/pg-types": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", @@ -8648,6 +8782,8 @@ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "split2": "^4.1.0" } @@ -8825,6 +8961,8 @@ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -8834,6 +8972,8 @@ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -8843,6 +8983,8 @@ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -8852,6 +8994,8 @@ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "xtend": "^4.0.0" }, @@ -8859,6 +9003,32 @@ "node": ">=0.10.0" } }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -8950,7 +9120,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dev": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -9014,6 +9183,21 @@ "node": ">= 0.8" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -9740,6 +9924,51 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -9803,6 +10032,8 @@ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "license": "ISC", + "optional": true, + "peer": true, "engines": { "node": ">= 10.x" } @@ -9958,6 +10189,15 @@ "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", @@ -10100,6 +10340,40 @@ "node": ">=10" } }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tar/node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", @@ -10340,6 +10614,18 @@ "fsevents": "~2.3.3" } }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -10799,6 +11085,8 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.4" } diff --git a/package.json b/package.json index ff08aef..280ad6f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "start": "next start", "lint": "next lint", "typecheck": "tsc --noEmit", - "db:push": "drizzle-kit push" + "db:init": "tsx src/lib/db/init.ts" }, "dependencies": { "@genkit-ai/googleai": "^1.13.0", @@ -38,6 +38,7 @@ "@radix-ui/react-toast": "^1.2.6", "@radix-ui/react-tooltip": "^1.1.8", "bcrypt": "^5.1.1", + "better-sqlite3": "^11.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^3.6.0", @@ -48,7 +49,6 @@ "lucide-react": "^0.475.0", "next": "15.3.3", "patch-package": "^8.0.0", - "pg": "^8.12.0", "react": "^18.3.1", "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", @@ -60,14 +60,15 @@ }, "devDependencies": { "@types/bcrypt": "^5.0.2", + "@types/better-sqlite3": "^7.6.11", "@types/node": "^20", - "@types/pg": "^8.11.8", "@types/react": "^18", "@types/react-dom": "^18", "drizzle-kit": "^0.24.2", "genkit-cli": "^1.13.0", "postcss": "^8", "tailwindcss": "^3.4.1", + "tsx": "^4.19.0", "typescript": "^5" } } diff --git a/sqlite.db b/sqlite.db new file mode 100644 index 0000000..3f1590b Binary files /dev/null and b/sqlite.db differ diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 177b1cd..e15ab22 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -25,9 +25,9 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from ' import { Input } from '@/components/ui/input'; const addCryptoSchema = z.object({ - email: z.string().email({ message: 'Adresse e-mail invalide.' }), - ticker: z.string().min(1, 'Ticker requis.').transform(v => v.toUpperCase()), - quantity: z.coerce.number().positive('La quantité doit être positive.'), + email: z.string().email({ message: 'Invalid email address.' }), + ticker: z.string().min(1, 'Ticker required.').transform(v => v.toUpperCase()), + quantity: z.coerce.number().positive('Quantity must be positive.'), }); @@ -48,12 +48,12 @@ export default function AdminPage() { if (result.error) { toast({ variant: 'destructive', - title: 'Erreur', + title: 'Error', description: result.error, }); } else { toast({ - title: 'Succès', + title: 'Success', description: result.success, }); if (actionName === 'users') { @@ -67,9 +67,9 @@ export default function AdminPage() { setLoadingAction('grantCrypto'); const result = await addCryptoToUserByEmail(values); if (result.error) { - toast({ variant: 'destructive', title: 'Erreur', description: result.error }); + toast({ variant: 'destructive', title: 'Error', description: result.error }); } else { - toast({ title: 'Succès', description: result.success }); + toast({ title: 'Success', description: result.success }); cryptoForm.reset(); } setLoadingAction(null); @@ -79,8 +79,8 @@ export default function AdminPage() {
- Accorder des Cryptos - Ajouter directement des actifs crypto au portefeuille d'un utilisateur. + Grant Crypto + Directly add crypto assets to a user's portfolio.
@@ -91,8 +91,8 @@ export default function AdminPage() { name="email" render={({ field }) => ( - Email de l'utilisateur - + User Email + )} @@ -102,7 +102,7 @@ export default function AdminPage() { name="ticker" render={({ field }) => ( - Ticker Crypto + Crypto Ticker @@ -113,7 +113,7 @@ export default function AdminPage() { name="quantity" render={({ field }) => ( - Quantité + Quantity @@ -122,7 +122,7 @@ export default function AdminPage() {
@@ -131,38 +131,38 @@ export default function AdminPage() { - Panneau d'Administration + Administration Panel - Actions dangereuses qui affectent l'ensemble de la simulation. + Dangerous actions that affect the entire simulation.
-

Réinitialiser les Actualités de l'IA

+

Reset AI News

- Supprime toutes les actualités générées par l'IA. + Deletes all AI-generated news articles.

- Êtes-vous absolument sûr ? + Are you absolutely sure? - Cette action est irréversible. Toutes les actualités générées par l'IA seront définitivement supprimées. + This action is irreversible. All AI-generated news will be permanently deleted. - Annuler + Cancel handleAction(resetAiNews, 'news')} disabled={loadingAction !== null} className="bg-destructive hover:bg-destructive/90"> {loadingAction === 'news' && } - Confirmer la suppression + Confirm Deletion @@ -171,30 +171,30 @@ export default function AdminPage() {
-

Réinitialiser les Entreprises

+

Reset Companies

- Supprime toutes les entreprises, leurs membres, leurs actifs et leurs actions. + Deletes all companies, their members, assets, and shares.

Êtes-vous absolument sûr ? - Cette action est irréversible. Toutes les entreprises et les investissements associés seront définitivement supprimés. + This action is irreversible. All companies and associated investments will be permanently deleted. - Annuler + Cancel handleAction(resetAllCompanies, 'companies')} disabled={loadingAction !== null} className="bg-destructive hover:bg-destructive/90"> {loadingAction === 'companies' && } - Confirmer la suppression + Confirm Deletion @@ -203,30 +203,30 @@ export default function AdminPage() {
-

Réinitialiser les Utilisateurs

+

Reset Users

- Supprime tous les utilisateurs, portefeuilles et données associées. Nécessite une nouvelle inscription. + Deletes all users, portfolios, and associated data. Requires new signup.

Êtes-vous absolument sûr ? - ACTION EXTRÊMEMENT DANGEREUSE. Ceci supprimera TOUS les utilisateurs, TOUTES les entreprises, et TOUTES les données de jeu. L'application sera réinitialisée à son état initial. + EXTREMELY DANGEROUS ACTION. This will delete ALL users, ALL companies, and ALL game data. The application will be reset to its initial state. - Annuler + Cancel handleAction(resetAllUsers, 'users')} disabled={loadingAction !== null} className="bg-destructive hover:bg-destructive/90"> {loadingAction === 'users' && } - TOUT SUPPRIMER + DELETE EVERYTHING diff --git a/src/app/ai-investor/page.tsx b/src/app/ai-investor/page.tsx index 738528f..81e04ce 100644 --- a/src/app/ai-investor/page.tsx +++ b/src/app/ai-investor/page.tsx @@ -6,9 +6,9 @@ export default function AIInvestorPage() {
- Conseiller en Investissement IA + AI Investment Advisor - Analysez des articles de presse pour obtenir des recommandations d'investissement personnalisées par l'IA en fonction de votre portefeuille et de votre tolérance au risque. + Analyze news articles to get personalized AI investment recommendations based on your portfolio and risk tolerance. diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 84b78b5..9ba016c 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -15,8 +15,8 @@ import { Loader2 } from 'lucide-react'; import { login, LoginInput } from '@/lib/actions/user'; const formSchema = z.object({ - email: z.string().email({ message: 'Adresse e-mail invalide.' }), - password: z.string().min(1, { message: 'Le mot de passe est requis.' }), + email: z.string().email({ message: 'Invalid email address.' }), + password: z.string().min(1, { message: 'Password is required.' }), }); export default function LoginPage() { @@ -40,13 +40,13 @@ export default function LoginPage() { if (result?.error) { toast({ variant: 'destructive', - title: 'Échec de la connexion', + title: 'Login failed', description: result.error, }); } else { toast({ - title: 'Connexion réussie !', - description: 'Vous allez être redirigé.', + title: 'Login successful!', + description: 'You will be redirected.', }); // A full page refresh is better to re-trigger AuthProvider and other server components. window.location.href = '/'; @@ -57,8 +57,8 @@ export default function LoginPage() {
- Connexion - Entrez votre e-mail ci-dessous pour vous connecter à votre compte + Login + Enter your email below to login to your account
@@ -70,7 +70,7 @@ export default function LoginPage() { Email - + @@ -81,7 +81,7 @@ export default function LoginPage() { name="password" render={({ field }) => ( - Mot de passe + Password @@ -91,14 +91,14 @@ export default function LoginPage() { />
- Vous n'avez pas de compte ?{' '} + Don't have an account?{' '} - S'inscrire + Sign up
diff --git a/src/app/mining/page.tsx b/src/app/mining/page.tsx index 10526d0..2a6c808 100644 --- a/src/app/mining/page.tsx +++ b/src/app/mining/page.tsx @@ -65,21 +65,21 @@ export default function MiningPage() { return (
-

Minage de Crypto

-

Achetez du matériel et gagnez des récompenses en crypto en fonction de votre puissance de minage totale.

+

Crypto Mining

+

Buy hardware and earn crypto rewards based on your total mining power.

- Votre Opération de Minage + Your Mining Operation
- Puissance de Hachage Totale + Total Hash Rate {formatHashRate(totalHashRateMhs)}
- Revenu Estimé (24h) + Estimated Revenue (24h) {estimatedDailyBtc.toFixed(6)} BTC

≈ ${(estimatedDailyBtc * btcPrice).toFixed(2)} @@ -88,16 +88,16 @@ export default function MiningPage() {

- Récompenses non réclamées + Unclaimed Rewards {unclaimedRewards.toFixed(8)} BTC

- Cliquez pour ajouter les récompenses minées à votre portefeuille. + Click to add mined rewards to your portfolio.

@@ -105,14 +105,14 @@ export default function MiningPage() { {ownedRigs && ownedRigs.length > 0 && ( - Mon Matériel + My Hardware {ownedRigs.map((rig, index) => ( {rig.name} - Quantité: {rig.quantity} • Puissance: {formatHashRate(rig.hashRateMhs! * rig.quantity)} + Quantity: {rig.quantity} • Power: {formatHashRate(rig.hashRateMhs! * rig.quantity)} ))} @@ -121,7 +121,7 @@ export default function MiningPage() { )}
-

Acheter du Matériel

+

Buy Hardware

{MINING_RIGS.map((item) => ( @@ -130,22 +130,22 @@ export default function MiningPage() {
- Taux de Hashage + Hash Rate {formatHashRate(item.hashRateMhs)}
- Puissance + Power {item.power}
- Prix + Price ${item.price.toLocaleString()}
diff --git a/src/app/page.tsx b/src/app/page.tsx index e6dc8e6..8320a7c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -51,7 +51,7 @@ export default function Home() { const recentTransactions = useMemo(() => { return transactions.slice(0, 5).map(tx => { return { - description: `${tx.type === 'Buy' ? 'Achat' : 'Vente'} ${tx.quantity} ${tx.asset.ticker}`, + description: `${tx.type === 'Buy' ? 'Buy' : 'Sell'} ${tx.quantity} ${tx.asset.ticker}`, details: tx.asset.name, amount: `${tx.type === 'Buy' ? '-' : '+'}$${tx.value.toFixed(2)}` } @@ -77,54 +77,54 @@ export default function Home() { - Valeur du Portefeuille + Portfolio Value
${portfolioValue.toFixed(2)}

= 0 ? 'text-green-500' : 'text-red-500'}`}> - {portfolioChange >= 0 ? '+' : ''}{portfolioChange.toFixed(2)}% depuis le début + {portfolioChange >= 0 ? '+' : ''}{portfolioChange.toFixed(2)}% since inception

- Avoirs Crypto (BTC) + Crypto Holdings (BTC)
{btcHoldings.toFixed(4)} BTC

- Valeur: ${(btcHoldings * btcPrice).toFixed(2)} + Value: ${(btcHoldings * btcPrice).toFixed(2)}

- Transactions Totales + Total Transactions
+{transactions.length}

- Sur tous les marchés + Across all markets

- Gains (Marché des Paris) + Winnings (Betting Market)
$0.00

- Aucun pari effectué + No bets placed

@@ -133,14 +133,14 @@ export default function Home() {
- Top Mouvements + Top Movers - Les actifs avec la plus forte variation de prix des dernières 24h. + Assets with the highest price change in the last 24 hours.
@@ -149,10 +149,10 @@ export default function Home() { - Actif - Prix - Variation (24h) - Cap. Boursière + Asset + Price + Change (24h) + Market Cap @@ -175,7 +175,7 @@ export default function Home() { - Transactions Récentes + Recent Transactions {recentTransactions.length > 0 ? ( @@ -193,7 +193,7 @@ export default function Home() { )) ) : ( -

Aucune transaction récente.

+

No recent transactions.

) }
diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx index 32d63f0..99d05d7 100644 --- a/src/app/signup/page.tsx +++ b/src/app/signup/page.tsx @@ -15,9 +15,9 @@ import { Loader2 } from 'lucide-react'; import { signup, SignupInput } from '@/lib/actions/user'; const formSchema = z.object({ - displayName: z.string().min(3, { message: "Le nom d'utilisateur doit comporter au moins 3 caractères." }), - email: z.string().email({ message: 'Adresse e-mail invalide.' }), - password: z.string().min(6, { message: 'Le mot de passe doit comporter au moins 6 caractères.' }), + displayName: z.string().min(3, { message: "Username must be at least 3 characters." }), + email: z.string().email({ message: 'Invalid email address.' }), + password: z.string().min(6, { message: 'Password must be at least 6 characters.' }), }); export default function SignupPage() { @@ -42,14 +42,14 @@ export default function SignupPage() { if (result.error) { toast({ variant: 'destructive', - title: 'Échec de l\'inscription', + title: 'Signup failed', description: result.error, }); } if (result.success) { toast({ - title: 'Succès !', + title: 'Success!', description: result.success, }); router.push('/login'); @@ -60,8 +60,8 @@ export default function SignupPage() {
- Inscription - Créez un nouveau compte pour commencer votre aventure financière + Sign up + Create a new account to start your financial journey
@@ -71,9 +71,9 @@ export default function SignupPage() { name="displayName" render={({ field }) => ( - Nom d'utilisateur + Username - + @@ -86,7 +86,7 @@ export default function SignupPage() { Email - + @@ -97,7 +97,7 @@ export default function SignupPage() { name="password" render={({ field }) => ( - Mot de passe + Password @@ -107,14 +107,14 @@ export default function SignupPage() { />
- Vous avez déjà un compte ?{' '} + Already have an account?{' '} - Se connecter + Sign in
diff --git a/src/app/trading/page.tsx b/src/app/trading/page.tsx index 6cd0ca5..0636c07 100644 --- a/src/app/trading/page.tsx +++ b/src/app/trading/page.tsx @@ -69,15 +69,15 @@ export default function TradingPage() {
- Salle des Marchés - Achetez et vendez des actions et des cryptos. Les entreprises de joueurs sont dans la section "Entreprises". + Trading Floor + Buy and sell stocks and crypto. Player-owned companies are in the Companies section.
setSearchTerm(e.target.value)} @@ -85,17 +85,17 @@ export default function TradingPage() {
@@ -114,11 +114,11 @@ export default function TradingPage() {
- Actif + Asset Type - Prix - Variation (24h) - Cap. Boursière + Price + Change (24h) + Market Cap Actions @@ -141,13 +141,13 @@ export default function TradingPage() { {asset.marketCap} - + - + diff --git a/src/components/add-company-cash-dialog.tsx b/src/components/add-company-cash-dialog.tsx index 7212145..71e98f5 100644 --- a/src/components/add-company-cash-dialog.tsx +++ b/src/components/add-company-cash-dialog.tsx @@ -20,7 +20,7 @@ interface AddCompanyCashDialogProps { } const formSchema = z.object({ - amount: z.coerce.number().positive({ message: 'Le montant doit être supérieur à zéro.' }), + amount: z.coerce.number().positive({ message: 'Amount must be greater than zero.' }), }); export function AddCompanyCashDialog({ companyId, children }: AddCompanyCashDialogProps) { @@ -40,9 +40,9 @@ export function AddCompanyCashDialog({ companyId, children }: AddCompanyCashDial async function onSubmit(values: z.infer) { const result = await addCashToCompany(companyId, values.amount); if (result.error) { - toast({ variant: 'destructive', title: 'Erreur', description: result.error }); + toast({ variant: 'destructive', title: 'Error', description: result.error }); } else if (result.success) { - toast({ title: 'Succès', description: result.success }); + toast({ title: 'Success', description: result.success }); await refreshPortfolio(); router.refresh(); setOpen(false); @@ -58,10 +58,10 @@ export function AddCompanyCashDialog({ companyId, children }: AddCompanyCashDial {children} - Ajouter des fonds à la Trésorerie + Add Funds to Treasury - Transférez des fonds de votre solde personnel vers la trésorerie de l'entreprise. - Fonds disponibles : ${cash.toFixed(2)} + Transfer funds from your personal balance to the company treasury. + Available funds: ${cash.toFixed(2)}
@@ -71,14 +71,14 @@ export function AddCompanyCashDialog({ companyId, children }: AddCompanyCashDial name="amount" render={({ field }) => ( - Montant à ajouter + Amount to Add
- field.onChange(e.target.value === '' ? undefined : e.target.valueAsNumber)} /> @@ -90,12 +90,12 @@ export function AddCompanyCashDialog({ companyId, children }: AddCompanyCashDial )} /> - + - + diff --git a/src/components/ai-investor-client.tsx b/src/components/ai-investor-client.tsx index 47134f2..40d6f81 100644 --- a/src/components/ai-investor-client.tsx +++ b/src/components/ai-investor-client.tsx @@ -15,8 +15,8 @@ import { Loader2 } from 'lucide-react'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; const formSchema = z.object({ - newsArticle: z.string().min(50, { message: 'L\'article de presse doit contenir au moins 50 caractères.' }), - portfolio: z.string().min(3, { message: 'Veuillez décrire votre portefeuille (par ex., "AAPL, GOOG, cash").' }), + newsArticle: z.string().min(50, { message: 'The news article must contain at least 50 characters.' }), + portfolio: z.string().min(3, { message: 'Please describe your portfolio (e.g., "AAPL, GOOG, cash").' }), riskPreferences: z.enum(['low', 'medium', 'high']), }); @@ -43,7 +43,7 @@ export function AIInvestorClient() { setResult(response); } catch (e) { console.error(e); - setError('Une erreur est survenue lors de l\'analyse de l\'article. Veuillez réessayer.'); + setError('An error occurred while analyzing the article. Please try again.'); } setIsLoading(false); } @@ -57,12 +57,12 @@ export function AIInvestorClient() { name="newsArticle" render={({ field }) => ( - Article de Presse + News Article -