fix: route browser→API calls through BFF to avoid CORS
- Create /api/onboard/request-otp and /api/auth/member/login BFF routes - Update OnboardingForm and member-login page to call BFF instead of API directly - Add vercel.json with monorepo build config and rootDirectory hint - Add .vercelignore to exclude .turbo, node_modules, .next (was uploading 2.3GB) - Add apps/web/.gitignore and update root .gitignore for tsbuildinfo files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,3 +8,4 @@ coverage
|
|||||||
.env.production
|
.env.production
|
||||||
sessions/
|
sessions/
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
.vercel
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
node_modules
|
||||||
|
.turbo
|
||||||
|
.next
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
coverage
|
||||||
|
*.tsbuildinfo
|
||||||
|
.git
|
||||||
|
apps/worker/sessions
|
||||||
|
apps/api/sessions
|
||||||
|
sessions
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.vercel
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { getApiBaseUrl, jsonResponse } from '../../../../_lib/api';
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
export async function POST(req: Request): Promise<Response> {
|
||||||
|
const body = await req.json().catch(() => ({}));
|
||||||
|
const upstream = await fetch(`${getApiBaseUrl()}/public/auth/member-login`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
cache: 'no-store',
|
||||||
|
});
|
||||||
|
return jsonResponse(await upstream.json().catch(() => ({})), upstream.status);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { getApiBaseUrl, jsonResponse } from '../../../_lib/api';
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
export async function POST(req: Request): Promise<Response> {
|
||||||
|
const body = await req.json().catch(() => ({}));
|
||||||
|
const upstream = await fetch(`${getApiBaseUrl()}/public/auth/request-otp`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
cache: 'no-store',
|
||||||
|
});
|
||||||
|
return jsonResponse(await upstream.json().catch(() => ({})), upstream.status);
|
||||||
|
}
|
||||||
@@ -11,8 +11,6 @@ import { Input } from '../_components/ui/input';
|
|||||||
import { Button } from '../_components/ui/button';
|
import { Button } from '../_components/ui/button';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
const API_BASE = process.env['NEXT_PUBLIC_API_URL'] ?? 'http://localhost:3001';
|
|
||||||
|
|
||||||
const phoneSchema = z.object({ phone: z.string().min(8, 'Enter your WhatsApp number') });
|
const phoneSchema = z.object({ phone: z.string().min(8, 'Enter your WhatsApp number') });
|
||||||
const otpSchema = z.object({ code: z.string().length(6, 'Enter the 6-digit code') });
|
const otpSchema = z.object({ code: z.string().length(6, 'Enter the 6-digit code') });
|
||||||
type PhoneValues = z.infer<typeof phoneSchema>;
|
type PhoneValues = z.infer<typeof phoneSchema>;
|
||||||
@@ -28,7 +26,7 @@ export default function MemberLoginPage() {
|
|||||||
const otpForm = useForm<OtpValues>({ resolver: zodResolver(otpSchema), defaultValues: { code: '' } });
|
const otpForm = useForm<OtpValues>({ resolver: zodResolver(otpSchema), defaultValues: { code: '' } });
|
||||||
|
|
||||||
async function onPhone(values: PhoneValues) {
|
async function onPhone(values: PhoneValues) {
|
||||||
const res = await fetch(`${API_BASE}/public/auth/member-login`, {
|
const res = await fetch('/api/auth/member/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ phone: values.phone }),
|
body: JSON.stringify({ phone: values.phone }),
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ const SCOPE_META: ScopeMeta[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const API_BASE = process.env['NEXT_PUBLIC_API_URL'] ?? 'http://localhost:3001';
|
|
||||||
|
|
||||||
export function OnboardingForm({ token, groupName, tenantName, defaultScopes, defaultRetentionDays }: Props) {
|
export function OnboardingForm({ token, groupName, tenantName, defaultScopes, defaultRetentionDays }: Props) {
|
||||||
const [step, setStep] = useState<Step>('welcome');
|
const [step, setStep] = useState<Step>('welcome');
|
||||||
const [phone, setPhone] = useState('');
|
const [phone, setPhone] = useState('');
|
||||||
@@ -62,7 +60,7 @@ export function OnboardingForm({ token, groupName, tenantName, defaultScopes, de
|
|||||||
setError(null);
|
setError(null);
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_BASE}/public/auth/request-otp`, {
|
const res = await fetch('/api/onboard/request-otp', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||||
body: JSON.stringify({ onboardingToken: token, phone }),
|
body: JSON.stringify({ onboardingToken: token, phone }),
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://openapi.vercel.sh/vercel.json",
|
||||||
|
"framework": "nextjs",
|
||||||
|
"buildCommand": "cd ../.. && pnpm turbo build --filter=@tower/web",
|
||||||
|
"installCommand": "cd ../.. && pnpm install --frozen-lockfile"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user