Update dashboard community links to use env vars, add deploy configs
- Dashboard community links now use .env values like community page - Removed hardcoded social media URLs from dashboard - Added deploy folder with nginx and systemd service configs - Moved linktree page to public route - Various backend and auth context updates
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx watch src/index.ts",
|
"dev": "tsx watch src/index.ts",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "node dist/index.js",
|
"start": "NODE_ENV=production node dist/index.js",
|
||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:migrate": "tsx src/db/migrate.ts",
|
"db:migrate": "tsx src/db/migrate.ts",
|
||||||
"db:studio": "drizzle-kit studio"
|
"db:studio": "drizzle-kit studio"
|
||||||
|
|||||||
@@ -25,10 +25,14 @@ const app = new Hono();
|
|||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
app.use('*', logger());
|
app.use('*', logger());
|
||||||
app.use('*', cors({
|
|
||||||
|
// CORS: Only enable in development. In production, nginx handles CORS.
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
app.use('*', cors({
|
||||||
origin: process.env.FRONTEND_URL || 'http://localhost:3002',
|
origin: process.env.FRONTEND_URL || 'http://localhost:3002',
|
||||||
credentials: true,
|
credentials: true,
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// OpenAPI specification
|
// OpenAPI specification
|
||||||
const openApiSpec = {
|
const openApiSpec = {
|
||||||
|
|||||||
81
deploy/back-end_nginx.conf
Normal file
81
deploy/back-end_nginx.conf
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# ============================================================
|
||||||
|
# Spanglish Community - Backend API
|
||||||
|
# api.spanglishcommunity.com
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name api.spanglishcommunity.com;
|
||||||
|
|
||||||
|
# ACME
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Force HTTPS
|
||||||
|
location / {
|
||||||
|
return 301 https://api.spanglishcommunity.com$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
http2 on;
|
||||||
|
|
||||||
|
server_name api.spanglishcommunity.com;
|
||||||
|
|
||||||
|
# SSL
|
||||||
|
ssl_certificate /etc/letsencrypt/live/spanglishcommunity.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/spanglishcommunity.com/privkey.pem;
|
||||||
|
|
||||||
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||||
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||||
|
|
||||||
|
# Security (API)
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
access_log /var/log/nginx/spanglish_api_access.log;
|
||||||
|
error_log /var/log/nginx/spanglish_api_error.log;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
limit_req zone=spanglish_api_limit burst=50 nodelay;
|
||||||
|
|
||||||
|
# CORS Configuration
|
||||||
|
set $cors_origin "";
|
||||||
|
if ($http_origin ~* "^https://(www\.)?spanglishcommunity\.com$") {
|
||||||
|
set $cors_origin $http_origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle preflight OPTIONS requests
|
||||||
|
if ($request_method = 'OPTIONS') {
|
||||||
|
add_header 'Access-Control-Allow-Origin' $cors_origin always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
|
||||||
|
add_header 'Access-Control-Allow-Credentials' 'true' always;
|
||||||
|
add_header 'Access-Control-Max-Age' 86400 always;
|
||||||
|
add_header 'Content-Type' 'text/plain; charset=utf-8';
|
||||||
|
add_header 'Content-Length' 0;
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add CORS headers to all responses
|
||||||
|
add_header 'Access-Control-Allow-Origin' $cors_origin always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
|
||||||
|
add_header 'Access-Control-Allow-Credentials' 'true' always;
|
||||||
|
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
|
||||||
|
|
||||||
|
proxy_pass http://spanglish_backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_connect_timeout 300s;
|
||||||
|
}
|
||||||
|
}
|
||||||
90
deploy/front-end_nginx.conf
Normal file
90
deploy/front-end_nginx.conf
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# ============================================================
|
||||||
|
# Spanglish Community - Frontend
|
||||||
|
# spanglishcommunity.com / www
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name spanglishcommunity.com www.spanglishcommunity.com;
|
||||||
|
|
||||||
|
# ACME
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Force HTTPS
|
||||||
|
location / {
|
||||||
|
return 301 https://spanglishcommunity.com$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
http2 on;
|
||||||
|
|
||||||
|
server_name spanglishcommunity.com www.spanglishcommunity.com;
|
||||||
|
|
||||||
|
# SSL
|
||||||
|
ssl_certificate /etc/letsencrypt/live/spanglishcommunity.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/spanglishcommunity.com/privkey.pem;
|
||||||
|
|
||||||
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||||
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||||
|
|
||||||
|
# Security
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
access_log /var/log/nginx/spanglish_frontend_access.log;
|
||||||
|
error_log /var/log/nginx/spanglish_frontend_error.log;
|
||||||
|
|
||||||
|
# Proxy /api to backend
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://spanglish_backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_connect_timeout 300s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy /uploads to backend
|
||||||
|
location /uploads {
|
||||||
|
proxy_pass http://spanglish_backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Cache static files
|
||||||
|
proxy_cache_valid 200 1d;
|
||||||
|
expires 1d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Frontend App
|
||||||
|
location / {
|
||||||
|
proxy_pass http://spanglish_frontend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# WebSocket / HMR
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
deploy/spanglish-backend.service
Normal file
29
deploy/spanglish-backend.service
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Spanglish Backend API
|
||||||
|
Documentation=https://github.com/spanglish/Spanglish
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=spanglish
|
||||||
|
Group=spanglish
|
||||||
|
WorkingDirectory=/home/spanglish/Spanglish/backend
|
||||||
|
Environment=NODE_ENV=production
|
||||||
|
Environment=PORT=3018
|
||||||
|
EnvironmentFile=/home/spanglish/Spanglish/backend/.env
|
||||||
|
ExecStart=/usr/bin/node dist/index.js
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
StandardOutput=syslog
|
||||||
|
StandardError=syslog
|
||||||
|
SyslogIdentifier=spanglish-backend
|
||||||
|
|
||||||
|
# Security hardening
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=read-only
|
||||||
|
ReadWritePaths=/home/spanglish/Spanglish/backend/uploads /home/spanglish/Spanglish/backend/data
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
28
deploy/spanglish-frontend.service
Normal file
28
deploy/spanglish-frontend.service
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Spanglish Frontend (Next.js)
|
||||||
|
Documentation=https://github.com/spanglish/Spanglish
|
||||||
|
After=network.target spanglish-backend.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=spanglish
|
||||||
|
Group=spanglish
|
||||||
|
WorkingDirectory=/home/spanglish/Spanglish/frontend
|
||||||
|
Environment=NODE_ENV=production
|
||||||
|
Environment=PORT=3019
|
||||||
|
EnvironmentFile=/home/spanglish/Spanglish/frontend/.env
|
||||||
|
ExecStart=/usr/bin/npx next start -p 3019
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
StandardOutput=syslog
|
||||||
|
StandardError=syslog
|
||||||
|
SyslogIdentifier=spanglish-frontend
|
||||||
|
|
||||||
|
# Security hardening
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=read-only
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
7
deploy/spanglish_upstreams.conf
Normal file
7
deploy/spanglish_upstreams.conf
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
upstream spanglish_frontend {
|
||||||
|
server 127.0.0.1:3019;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream spanglish_backend {
|
||||||
|
server 127.0.0.1:3018;
|
||||||
|
}
|
||||||
@@ -9,6 +9,12 @@ import Button from '@/components/ui/Button';
|
|||||||
import { dashboardApi, DashboardSummary, NextEventInfo, UserTicket, UserPayment } from '@/lib/api';
|
import { dashboardApi, DashboardSummary, NextEventInfo, UserTicket, UserPayment } from '@/lib/api';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import {
|
||||||
|
socialConfig,
|
||||||
|
getWhatsAppUrl,
|
||||||
|
getInstagramUrl,
|
||||||
|
getTelegramUrl
|
||||||
|
} from '@/lib/socialLinks';
|
||||||
|
|
||||||
// Tab components
|
// Tab components
|
||||||
import TicketsTab from './components/TicketsTab';
|
import TicketsTab from './components/TicketsTab';
|
||||||
@@ -364,8 +370,9 @@ function OverviewTab({
|
|||||||
{language === 'es' ? 'Comunidad' : 'Community'}
|
{language === 'es' ? 'Comunidad' : 'Community'}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
{getWhatsAppUrl(socialConfig.whatsapp) && (
|
||||||
<a
|
<a
|
||||||
href="https://wa.me/your-number"
|
href={getWhatsAppUrl(socialConfig.whatsapp)!}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-3 p-3 bg-green-50 rounded-lg hover:bg-green-100 transition-colors"
|
className="flex items-center gap-3 p-3 bg-green-50 rounded-lg hover:bg-green-100 transition-colors"
|
||||||
@@ -375,10 +382,12 @@ function OverviewTab({
|
|||||||
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/>
|
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-medium">WhatsApp Group</span>
|
<span className="font-medium">WhatsApp</span>
|
||||||
</a>
|
</a>
|
||||||
|
)}
|
||||||
|
{getInstagramUrl(socialConfig.instagram) && (
|
||||||
<a
|
<a
|
||||||
href="https://instagram.com/spanglish"
|
href={getInstagramUrl(socialConfig.instagram)!}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-3 p-3 bg-pink-50 rounded-lg hover:bg-pink-100 transition-colors"
|
className="flex items-center gap-3 p-3 bg-pink-50 rounded-lg hover:bg-pink-100 transition-colors"
|
||||||
@@ -390,19 +399,22 @@ function OverviewTab({
|
|||||||
</div>
|
</div>
|
||||||
<span className="font-medium">Instagram</span>
|
<span className="font-medium">Instagram</span>
|
||||||
</a>
|
</a>
|
||||||
<Link
|
)}
|
||||||
href="/community"
|
{getTelegramUrl(socialConfig.telegram) && (
|
||||||
|
<a
|
||||||
|
href={getTelegramUrl(socialConfig.telegram)!}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-3 p-3 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors"
|
className="flex items-center gap-3 p-3 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors"
|
||||||
>
|
>
|
||||||
<div className="w-10 h-10 bg-secondary-blue rounded-full flex items-center justify-center">
|
<div className="w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center">
|
||||||
<svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-medium">
|
<span className="font-medium">Telegram</span>
|
||||||
{language === 'es' ? 'Comunidad' : 'Community Page'}
|
</a>
|
||||||
</span>
|
)}
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export default function HomePage() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="relative rounded-card h-32 flex items-center justify-center overflow-hidden">
|
<div className="relative rounded-card h-32 flex items-center justify-center overflow-hidden">
|
||||||
<Image
|
<Image
|
||||||
src="/images/2026-01-29 13.10.26.jpg"
|
src="/images/2026-01-29 13.10.08.jpg"
|
||||||
alt="Language exchange event"
|
alt="Language exchange event"
|
||||||
fill
|
fill
|
||||||
sizes="(max-width: 1024px) 50vw, 25vw"
|
sizes="(max-width: 1024px) 50vw, 25vw"
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback } from 'react';
|
import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback } from 'react';
|
||||||
|
|
||||||
|
const API_BASE = process.env.NEXT_PUBLIC_API_URL || '';
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
@@ -66,7 +68,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const login = async (email: string, password: string) => {
|
const login = async (email: string, password: string) => {
|
||||||
const res = await fetch('/api/auth/login', {
|
const res = await fetch(`${API_BASE}/api/auth/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ email, password }),
|
body: JSON.stringify({ email, password }),
|
||||||
@@ -82,7 +84,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loginWithGoogle = async (credential: string) => {
|
const loginWithGoogle = async (credential: string) => {
|
||||||
const res = await fetch('/api/auth/google', {
|
const res = await fetch(`${API_BASE}/api/auth/google`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ credential }),
|
body: JSON.stringify({ credential }),
|
||||||
@@ -98,7 +100,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loginWithMagicLink = async (magicToken: string) => {
|
const loginWithMagicLink = async (magicToken: string) => {
|
||||||
const res = await fetch('/api/auth/magic-link/verify', {
|
const res = await fetch(`${API_BASE}/api/auth/magic-link/verify`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ token: magicToken }),
|
body: JSON.stringify({ token: magicToken }),
|
||||||
@@ -114,7 +116,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const register = async (registerData: RegisterData) => {
|
const register = async (registerData: RegisterData) => {
|
||||||
const res = await fetch('/api/auth/register', {
|
const res = await fetch(`${API_BASE}/api/auth/register`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(registerData),
|
body: JSON.stringify(registerData),
|
||||||
|
|||||||
Reference in New Issue
Block a user