From 54c07122f5284a55bda111baed4b015ce04f3ef3 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 10 Jul 2025 11:28:42 +0000 Subject: [PATCH] Docker fixes and data wiring for prod. Created Staging. --- .env.development | 3 +- .gitignore | 1 + Dockerfile.server | 2 +- Dockerfile.server2 | 3 +- Dockerfile.server3 | 2 +- backend/server.js | 46 ++++++++----- backend/server2.js | 5 ++ backend/server3.js | 5 +- backend/user_profile.db | 0 docker-compose.prod.yml | 41 ++++++++++++ docker-compose.staging.yml | 40 +++++++++++ docker-compose.yml | 49 ++++++++------ nginx.conf | 79 +++++++++++++++++++--- nginx.conf.bak | 89 +++++++++++++++++++++++++ postcss.config.js => postcss.config.cjs | 6 +- src/App.js | 10 ++- src/assets/botTools.json | 37 ---------- src/assets/pageToolMap.json | 5 +- tailwind.config.js | 49 ++++++++------ 19 files changed, 353 insertions(+), 119 deletions(-) delete mode 100644 backend/user_profile.db create mode 100644 docker-compose.prod.yml create mode 100644 docker-compose.staging.yml create mode 100644 nginx.conf.bak rename postcss.config.js => postcss.config.cjs (73%) diff --git a/.env.development b/.env.development index 40de1f1..fcf8a7e 100755 --- a/.env.development +++ b/.env.development @@ -16,4 +16,5 @@ APTIVA_API_BASE=https://dev1.aptivaai.com/api REACT_APP_API_URL=https://dev1.aptivaai.com/api REACT_APP_ENV=production REACT_APP_OPENAI_API_KEY=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 -OPENAI_API_KEY=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 \ No newline at end of file +OPENAI_API_KEY=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 +GCP_CLOUD_SQL_PASSWORD=q2O}1PU-R:|l57S0 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5cb4377..0fa3b3c 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ yarn-error.log* .bash_history .bashrc _logout +env/*.env diff --git a/Dockerfile.server b/Dockerfile.server index 2d005f7..f9bf4c0 100644 --- a/Dockerfile.server +++ b/Dockerfile.server @@ -6,4 +6,4 @@ RUN npm ci --omit=dev --ignore-scripts COPY . . ENV PORT=5000 EXPOSE 5000 -CMD ["npm","run","server"] +CMD ["node","backend/server.js"] diff --git a/Dockerfile.server2 b/Dockerfile.server2 index 0d34ca5..f673d04 100644 --- a/Dockerfile.server2 +++ b/Dockerfile.server2 @@ -3,7 +3,8 @@ FROM --platform=$TARGETPLATFORM node:20-slim WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev --ignore-scripts +RUN apt-get update && apt-get install -y sqlite3 && rm -rf /var/lib/apt/lists/* COPY . . ENV PORT=5001 EXPOSE 5001 -CMD ["npm","run","server2"] +CMD ["node","backend/server2.js"] diff --git a/Dockerfile.server3 b/Dockerfile.server3 index c2db890..13dac0b 100644 --- a/Dockerfile.server3 +++ b/Dockerfile.server3 @@ -10,4 +10,4 @@ RUN npm ci --omit=dev --ignore-scripts COPY . . ENV PORT=5002 EXPOSE 5002 -CMD ["npm","run","server3"] +CMD ["node","backend/server3.js"] diff --git a/backend/server.js b/backend/server.js index d3c40a0..ea67d86 100755 --- a/backend/server.js +++ b/backend/server.js @@ -22,13 +22,19 @@ const envPath = path.resolve(rootPath, `.env.${env}`); dotenv.config({ path: envPath }); // Load .env file // Grab secrets and config from ENV -const SECRET_KEY = process.env.SECRET_KEY || 'supersecurekey'; +const JWT_SECRET = process.env.JWT_SECRET; const DB_HOST = process.env.DB_HOST || '127.0.0.1'; const DB_PORT = process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 3306; const DB_USER = process.env.DB_USER || 'sqluser'; const DB_PASSWORD = process.env.DB_PASSWORD || ''; const DB_NAME = process.env.DB_NAME || 'user_profile_db'; +if (!JWT_SECRET) { + console.error('FATAL: JWT_SECRET missing – aborting startup'); + process.exit(1); // container exits, Docker marks it unhealthy +} + + // Create a MySQL pool for user_profile data const pool = mysql.createPool({ host: DB_HOST, @@ -68,6 +74,8 @@ app.use( }) ); +app.get('/healthz', (req, res) => res.sendStatus(204)); // 204 No Content + // Enable CORS with dynamic origin checking app.use( cors({ @@ -553,42 +561,46 @@ app.get('/api/user-profile', (req, res) => { /* ------------------------------------------------------------------ SALARY_INFO REMAINS IN SQLITE - ------------------------------------------------------------------ */ +------------------------------------------------------------------ */ app.get('/api/areas', (req, res) => { const { state } = req.query; if (!state) { return res.status(400).json({ error: 'State parameter is required' }); } - const salaryDbPath = path.resolve( - '/home/jcoakley/aptiva-dev1-app/salary_info.db' - ); + // Use env when present (Docker), fall back for local dev + const salaryDbPath = + process.env.SALARY_DB || '/app/data/salary_info.db'; + const salaryDb = new sqlite3.Database( salaryDbPath, sqlite3.OPEN_READONLY, (err) => { if (err) { - console.error('Error connecting to database:', err.message); - return res.status(500).json({ error: 'Failed to connect to database' }); + console.error('DB connect error:', err.message); + return res + .status(500) + .json({ error: 'Failed to connect to database' }); } } ); - const query = `SELECT DISTINCT AREA_TITLE FROM salary_data WHERE PRIM_STATE = ?`; + const query = + `SELECT DISTINCT AREA_TITLE + FROM salary_data + WHERE PRIM_STATE = ?`; + salaryDb.all(query, [state], (err, rows) => { if (err) { - console.error('Error executing query:', err.message); - return res.status(500).json({ error: 'Failed to fetch areas' }); + console.error('Query error:', err.message); + return res + .status(500) + .json({ error: 'Failed to fetch areas' }); } - const areas = rows.map((row) => row.AREA_TITLE); - res.json({ areas }); + res.json({ areas: rows.map(r => r.AREA_TITLE) }); }); - salaryDb.close((err) => { - if (err) { - console.error('Error closing the salary_info.db:', err.message); - } - }); + salaryDb.close(); }); /* ------------------------------------------------------------------ diff --git a/backend/server2.js b/backend/server2.js index 314e21f..e9695fd 100755 --- a/backend/server2.js +++ b/backend/server2.js @@ -55,9 +55,14 @@ const institutionFilePath = path.resolve(rootPath, 'public', 'Institution_data.j const app = express(); const PORT = process.env.PORT || 5001; +// at top of backend/server.js (do once per server codebase) +app.get('/healthz', (req, res) => res.sendStatus(204)); // 204 No Content + /************************************************** * DB connections **************************************************/ + + let db; const initDB = async () => { try { diff --git a/backend/server3.js b/backend/server3.js index b4384fa..0951442 100644 --- a/backend/server3.js +++ b/backend/server3.js @@ -58,7 +58,7 @@ const authenticatePremiumUser = (req, res, next) => { } try { - const SECRET_KEY = process.env.SECRET_KEY || 'supersecurekey'; + const SECRET_KEY = process.env.SECRET_KEY; const { id } = jwt.verify(token, SECRET_KEY); req.id = id; // store user ID in request next(); @@ -69,6 +69,9 @@ const authenticatePremiumUser = (req, res, next) => { const pool = db; +// at top of backend/server.js (do once per server codebase) +app.get('/healthz', (req, res) => res.sendStatus(204)); // 204 No Content + /* ======================================================================== * applyOps – executes the “milestones” array inside a fenced ```ops block * and returns an array of confirmation strings diff --git a/backend/user_profile.db b/backend/user_profile.db deleted file mode 100644 index e69de29..0000000 diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..5adb8e9 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,41 @@ +services: + # ─── server1 ─── + server1: + extends: + file: docker-compose.yml + service: server1 + env_file: [ ./env/prod.env ] + environment: + - NODE_ENV=production + - SALARY_DB=/app/data/salary_info.db # optional, if code checks env + volumes: + # SQLite wage DB needed by /api/areas + - /home/jcoakley/aptiva-dev1-app/salary_info.db:/app/data/salary_info.db:ro + # existing React build/public mount + - /home/jcoakley/aptiva-dev1-app/public:/home/jcoakley/aptiva-dev1-app/public:ro + + # ─── server2 ─── + server2: + extends: + file: docker-compose.yml + service: server2 + env_file: [ ./env/prod.env ] + environment: + - NODE_ENV=production + - SALARY_DB=/home/jcoakley/aptiva-dev1-app/salary_info.db + volumes: + - /home/jcoakley/aptiva-dev1-app/salary_info.db:/home/jcoakley/aptiva-dev1-app/salary_info.db:ro + - /home/jcoakley/aptiva-dev1-app/user_profile.db:/home/jcoakley/aptiva-dev1-app/user_profile.db:ro + - /home/jcoakley/aptiva-dev1-app/public:/home/jcoakley/aptiva-dev1-app/public:ro + + # ─── server3 ─── + server3: + extends: + file: docker-compose.yml + service: server3 + env_file: [ ./env/prod.env ] + environment: + - NODE_ENV=production + volumes: + - /home/jcoakley/aptiva-dev1-app/public:/home/jcoakley/aptiva-dev1-app/public:ro + - /home/jcoakley/aptiva-dev1-app/user_profile.db:/home/jcoakley/aptiva-dev1-app/user_profile.db:ro # keep one copy only diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml new file mode 100644 index 0000000..0a9fe1e --- /dev/null +++ b/docker-compose.staging.yml @@ -0,0 +1,40 @@ +services: + # ─── server1 ─── + server1: + extends: + file: docker-compose.yml + service: server1 + env_file: [ ./env/staging.env ] + environment: + - NODE_ENV=production + + volumes: + - /home/jcoakley/aptiva-dev1-app/salary_info.db:/home/jcoakley/aptiva-dev1-app/salary_info.db:ro + + + # ─── server2 ─── (needs DB + public data) + server2: + extends: + file: docker-compose.yml + service: server2 + env_file: [ ./env/staging.env ] + environment: + - NODE_ENV=production + - SALARY_DB=/home/jcoakley/aptiva-dev1-app/salary_info.db + volumes: + - /home/jcoakley/aptiva-dev1-app/salary_info.db:/home/jcoakley/aptiva-dev1-app/salary_info.db:ro + - /home/jcoakley/aptiva-dev1-app/public:/home/jcoakley/aptiva-dev1-app/public:ro + - /home/jcoakley/aptiva-dev1-app/user_profile.db:/home/jcoakley/aptiva-dev1-app/user_profile.db:ro + + # ─── server3 ─── (needs public data only) + server3: + extends: + file: docker-compose.yml + service: server3 + env_file: [ ./env/staging.env ] + environment: + - NODE_ENV=production + volumes: + - /home/jcoakley/aptiva-dev1-app/public:/home/jcoakley/aptiva-dev1-app/public:ro + - /home/jcoakley/aptiva-dev1-app/user_profile.db:/home/jcoakley/aptiva-dev1-app/user_profile.db:ro + - /home/jcoakley/aptiva-dev1-app/user_profile.db:/home/jcoakley/aptiva-dev1-app/user_profile.db:ro \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 3d82e56..7b54bfe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,33 +1,42 @@ -version: "3.9" - services: - server: - image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server:latest + server1: + image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server1:${TAG:-latest} restart: unless-stopped - ports: - - "5000:5000" + expose: ["5000"] + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:5000/healthz || exit 1"] + interval: 30s + timeout: 5s + retries: 3 server2: - image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server2:latest + image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server2:${TAG:-latest} restart: unless-stopped - ports: - - "5001:5001" + expose: ["5001"] + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:5001/healthz || exit 1"] + interval: 30s + timeout: 5s + retries: 3 server3: - image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server3:latest + image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server3:${TAG:-latest} restart: unless-stopped - ports: - - "5002:5002" - + expose: ["5002"] + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:5002/healthz || exit 1"] + interval: 30s + timeout: 5s + retries: 3 nginx: - image: nginx:1.27 - restart: unless-stopped - depends_on: - - server - - server2 - - server3 + image: nginx:1.25-alpine + command: ["nginx", "-g", "daemon off;"] ports: - "80:80" - "443:443" volumes: - - ./nginx.conf:/etc/nginx/nginx.conf:ro + - ./build:/usr/share/nginx/html:ro # React build + - ./nginx.conf:/etc/nginx/nginx.conf:ro # overwrite default + - /etc/letsencrypt:/etc/letsencrypt:ro # certs + - ./empty:/etc/nginx/conf.d # hide default.conf + depends_on: [server1, server2, server3] diff --git a/nginx.conf b/nginx.conf index 942976c..95a0886 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,13 +1,72 @@ events {} -http { - upstream backend5000 { server server:5000; } - upstream backend5001 { server server2:5001; } - upstream backend5002 { server server3:5002; } - server { - listen 80; - location /api1/ { proxy_pass http://backend5000/; } - location /api2/ { proxy_pass http://backend5001/; } - location /api3/ { proxy_pass http://backend5002/; } - } +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + upstream backend5000 { server server1:5000; } + upstream backend5001 { server server2:5001; } + upstream backend5002 { server server3:5002; } + + server { + listen 80; + listen [::]:80; + server_name dev1.aptivaai.com; + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl; + server_name dev1.aptivaai.com; + root /usr/share/nginx/html; + index index.html; + + ssl_certificate /etc/letsencrypt/live/dev1.aptivaai.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/dev1.aptivaai.com/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + + # ---- server1 (port 5000) ---- + location /api/register { proxy_pass http://server1:5000/api/register; } + location /api/check-username { proxy_pass http://server1:5000/api/check-username; } + location /api/signin { proxy_pass http://server1:5000/api/signin; } + location /api/login { proxy_pass http://server1:5000/api/login; } + location /api/user-profile { proxy_pass http://server1:5000/api/user-profile; } + location /api/areas { proxy_pass http://server1:5000/api/areas; } + location /api/activate-premium { proxy_pass http://server1:5000/api/activate-premium; } + + # ---- server2 (port 5001) ---- + location /api/onet/ { proxy_pass http://server2:5001; } + location /api/onet/career-description/ { proxy_pass http://server2:5001; } + location /api/job-zones { proxy_pass http://server2:5001/api/job-zones; } + location /api/salary { proxy_pass http://server2:5001/api/salary; } + location /api/cip/ { proxy_pass http://server2:5001/api/cip/; } + location /api/tuition/ { proxy_pass http://server2:5001/api/tuition/; } + location /api/projections/ { proxy_pass http://server2:5001/api/projections/; } + location /api/skills/ { proxy_pass http://server2:5001/api/skills/; } + location = /api/ai-risk { proxy_pass http://server2:5001/api/ai-risk; } + location /api/ai-risk/ { proxy_pass http://server2:5001/api/ai-risk/; } + location /api/chat/ { + proxy_pass http://server2:5001; + proxy_http_version 1.1; + proxy_buffering off; + } + location ^~ /api/maps/distance { proxy_pass http://server2:5001; } + location /api/schools { proxy_pass http://server2:5001/api/schools; } + + # ---- server3 (port 5002) ---- + location ^~ /api/premium/ { proxy_pass http://server3:5002; } + location /api/public/ { proxy_pass http://server3:5002/api/public/; } + + # ---- static React build ---- + location / { + index index.html; + try_files $uri $uri/ /index.html; + } + location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg)$ { + expires 6M; + access_log off; + } + + error_page 502 503 504 /50x.html; + location = /50x.html { root /usr/share/nginx/html; } + } } diff --git a/nginx.conf.bak b/nginx.conf.bak new file mode 100644 index 0000000..201fae2 --- /dev/null +++ b/nginx.conf.bak @@ -0,0 +1,89 @@ +events {} + +http { + upstream backend5000 { server server1:5000; } + upstream backend5001 { server server2:5001; } + upstream backend5002 { server server3:5002; } + + server { + listen 80; + listen [::]:80; + server_name dev1.aptivaai.com; + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl; + server_name dev1.aptivaai.com; + + ssl_certificate /etc/letsencrypt/live/dev1.aptivaai.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/dev1.aptivaai.com/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers off; + ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256'; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1h; + + error_log /var/log/nginx/error.log debug; + access_log /var/log/nginx/access.log; + + # ---------- server1 (5000) ---------- + location /api/register { proxy_pass http://server1:5000/api/register; } + location /api/check-username { proxy_pass http://server1:5000/api/check-username; } + location /api/signin { proxy_pass http://server1:5000/api/signin; } + location /api/login { proxy_pass http://server1:5000/api/login; } + location /api/user-profile { proxy_pass http://server1:5000/api/user-profile; } + location /api/areas { proxy_pass http://server1:5000/api/areas; } + location /api/activate-premium{ proxy_pass http://server1:5000/api/activate-premium; } + + # ---------- server2 (5001) ---------- + location /api/onet/ { proxy_pass http://server2:5001; } + location /api/onet/career-description/ { proxy_pass http://server2:5001; } + location /api/job-zones { proxy_pass http://server2:5001/api/job-zones; } + location /api/salary { proxy_pass http://server2:5001/api/salary; } + location /api/cip/ { proxy_pass http://server2:5001/api/cip/; } + location /api/tuition/ { proxy_pass http://server2:5001/api/tuition/; } + location /api/projections/ { proxy_pass http://server2:5001/api/projections/; } + location /api/skills/ { proxy_pass http://server2:5001/api/skills/; } + location = /api/ai-risk { proxy_pass http://server2:5001/api/ai-risk; } + location /api/ai-risk/ { proxy_pass http://server2:5001/api/ai-risk/; } + + location /api/chat/ { + proxy_pass http://server2:5001; + proxy_http_version 1.1; + proxy_buffering off; + } + + location ^~ /api/maps/distance { proxy_pass http://server2:5001; } + location /api/schools { proxy_pass http://server2:5001/api/schools; } + + # ---------- server3 (5002) ---------- + location ^~ /api/premium/ { proxy_pass http://server3:5002; } + location /api/public/ { proxy_pass http://server3:5002/api/public/; } + + # ---------- static React build ---------- + root /usr/share/nginx/html; + index index.html; + location / { try_files $uri /index.html; } + location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg)$ { + expires 6M; + access_log off; + add_header Cache-Control "public, max-age=31536000, immutable"; + } + + error_page 502 503 504 /50x.html; + location = /50x.html { root /usr/share/nginx/html; } + } +} +http { + upstream backend5000 { server server1:5000; } + upstream backend5001 { server server2:5001; } + upstream backend5002 { server server3:5002; } + + server { + listen 80; + location /api1/ { proxy_pass http://backend5000/; } + location /api2/ { proxy_pass http://backend5001/; } + location /api3/ { proxy_pass http://backend5002/; } + } +} diff --git a/postcss.config.js b/postcss.config.cjs similarity index 73% rename from postcss.config.js rename to postcss.config.cjs index 0551d97..12a703d 100644 --- a/postcss.config.js +++ b/postcss.config.cjs @@ -1,8 +1,6 @@ -export default { +module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, -} - - +}; diff --git a/src/App.js b/src/App.js index 9b5e2ed..66831af 100644 --- a/src/App.js +++ b/src/App.js @@ -221,9 +221,17 @@ const uiToolHandlers = useMemo(() => { { setDrawerPane('support'); setDrawerOpen(true); }, openRetire : (props) => { - setRetireProps(props); // { scenario, financialProfile, onScenarioPatch } + setRetireProps(props); setDrawerPane('retire'); setDrawerOpen(true); + + if (pageContext === 'RetirementPlanner' || pageContext === 'RetirementLanding') { + setRetireProps(props); + setDrawerPane('retire'); + setDrawerOpen(true); + } else { + console.warn('Retirement bot disabled on this page'); + } }}}>
{/* Header */} diff --git a/src/assets/botTools.json b/src/assets/botTools.json index d36f80d..114d44a 100644 --- a/src/assets/botTools.json +++ b/src/assets/botTools.json @@ -19,15 +19,6 @@ ] } }, - { - "name": "resolveCareerTitle", - "description": "Convert a free-text career label to its canonical SOC record", - "parameters": { - "type": "object", - "properties": { "title": { "type": "string" } }, - "required": ["title"] - } -}, { "name": "getSchoolsForCIPs", "description": "Return a list of schools whose CIP codes match the supplied prefixes in the given state", @@ -49,34 +40,6 @@ ] } }, - { - "name": "addCareerToComparison", - "description": "Add the career with the given SOC code to the comparison table in Career Explorer", - "parameters": { - "type": "object", - "properties": { - "socCode": { - "type": "string", - "description": "Full O*NET SOC code, e.g. \"15-2051\"" - } - }, - "required": ["socCode"] - } -}, -{ - "name": "openCareerModal", - "description": "Open the career-details modal for the given SOC code in Career Explorer", - "parameters": { - "type": "object", - "properties": { - "socCode": { - "type": "string", - "description": "Full O*NET SOC code, e.g. \"15-2051\"" - } - }, - "required": ["socCode"] - } -}, { "name": "getTuitionForCIPs", "description": "Return in-state / out-state tuition rows for schools matching CIP prefixes in a given state", diff --git a/src/assets/pageToolMap.json b/src/assets/pageToolMap.json index e716d12..61de693 100644 --- a/src/assets/pageToolMap.json +++ b/src/assets/pageToolMap.json @@ -9,10 +9,7 @@ "getTuitionForCIPs" ], "CareerExplorer": [ - "resolveCareerTitle", "getEconomicProjections", - "getSalaryData", - "addCareerToComparison", - "openCareerModal" + "getSalaryData" ] } \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index 776552c..9fa14a3 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,39 +1,46 @@ // tailwind.config.js module.exports = { - content: ['./src/**/*.{js,jsx,ts,tsx}'], - theme: { - extend: { - keyframes: { - "slide-in": { from: { transform: "translateX(100%)" }, to: { transform: "translateX(0)" } }, - }, - animation: { "slide-in": "slide-in 0.25s ease-out forwards" }, - }, -}, + content: [ + './src/**/*.{js,jsx,ts,tsx}', + './src/**/*.css', // let Tailwind parse the @apply lines + './public/index.html', + ], + theme: { extend: { - /* brand colours */ colors: { aptiva: { - DEFAULT : '#0A84FF', // primary blue - dark : '#005FCC', - light : '#3AA0FF', - accent : '#FF7C00', // accent orange - gray : '#F7FAFC', // page bg + DEFAULT: '#0A84FF', + dark : '#005FCC', + light : '#3AA0FF', + accent : '#FF7C00', + gray : '#F7FAFC', }, }, - /* fonts */ fontFamily: { sans: ['Inter', 'system-ui', 'sans-serif'], mono: ['Fira Code', 'monospace'], }, - /* radii */ - borderRadius: { - xl: '1rem', // 16 px extra-round corners everywhere + borderRadius: { xl: '1rem' }, + keyframes: { + 'slide-in': { + from: { transform: 'translateX(100%)' }, + to : { transform: 'translateX(0)' }, + }, }, + animation: { 'slide-in': 'slide-in 0.25s ease-out forwards' }, }, }, + + // OPTIONAL – only keep if you really call these classes in JSX/HTML + safelist: [ + 'bg-aptiva', 'bg-aptiva-dark', 'bg-aptiva-light', + 'text-aptiva', 'border-aptiva', + 'hover:bg-aptiva', // delete this line if unused + ], + plugins: [ - require('@tailwindcss/forms'), // prettier inputs - require('@tailwindcss/typography'), // prose-class for AI answers + require('@tailwindcss/forms'), + require('@tailwindcss/typography'), ], };