feat(web): Excalidraw 스케치 스타일 리디자인 + 문의 폼
- 웹페이지를 Excalidraw 스타일 손그림 디자인으로 전면 리디자인 - 라이트 모드 + 크림색 배경 + 격자 패턴 - 손글씨 폰트 (제목: Caveat, 본문: Noto Sans KR) - 스케치 스타일 카드, 버튼, 스티커 노트 컴포넌트 - 문의 폼 추가 (이메일 + 메시지) - /api/contact 엔드포인트 추가 (텔레그램 알림 연동) - 이메일 실시간 유효성 검사 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
80
src/index.ts
80
src/index.ts
@@ -465,6 +465,86 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 문의 폼 API (웹사이트용)
|
||||||
|
if (url.pathname === '/api/contact' && request.method === 'POST') {
|
||||||
|
// CORS preflight는 OPTIONS에서 처리
|
||||||
|
const corsHeaders = {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||||
|
'Access-Control-Allow-Headers': 'Content-Type',
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = await request.json() as {
|
||||||
|
email: string;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 필수 필드 검증
|
||||||
|
if (!body.email || !body.message) {
|
||||||
|
return Response.json(
|
||||||
|
{ error: '이메일과 메시지는 필수 항목입니다.' },
|
||||||
|
{ status: 400, headers: corsHeaders }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 이메일 형식 검증
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (!emailRegex.test(body.email)) {
|
||||||
|
return Response.json(
|
||||||
|
{ error: '올바른 이메일 형식이 아닙니다.' },
|
||||||
|
{ status: 400, headers: corsHeaders }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 메시지 길이 제한
|
||||||
|
if (body.message.length > 2000) {
|
||||||
|
return Response.json(
|
||||||
|
{ error: '메시지는 2000자 이내로 작성해주세요.' },
|
||||||
|
{ status: 400, headers: corsHeaders }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 관리자에게 텔레그램 알림
|
||||||
|
const adminId = env.DEPOSIT_ADMIN_ID || env.DOMAIN_OWNER_ID;
|
||||||
|
if (env.BOT_TOKEN && adminId) {
|
||||||
|
const timestamp = new Date().toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' });
|
||||||
|
await sendMessage(
|
||||||
|
env.BOT_TOKEN,
|
||||||
|
parseInt(adminId),
|
||||||
|
`📬 <b>웹사이트 문의</b>\n\n` +
|
||||||
|
`📧 이메일: <code>${body.email}</code>\n` +
|
||||||
|
`🕐 시간: ${timestamp}\n\n` +
|
||||||
|
`💬 내용:\n${body.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[Contact] 문의 수신: ${body.email}`);
|
||||||
|
|
||||||
|
return Response.json(
|
||||||
|
{ success: true, message: '문의가 성공적으로 전송되었습니다.' },
|
||||||
|
{ headers: corsHeaders }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Contact] 오류:', error);
|
||||||
|
return Response.json(
|
||||||
|
{ error: '문의 전송 중 오류가 발생했습니다.' },
|
||||||
|
{ status: 500, headers: corsHeaders }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORS preflight for contact API
|
||||||
|
if (url.pathname === '/api/contact' && request.method === 'OPTIONS') {
|
||||||
|
return new Response(null, {
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||||
|
'Access-Control-Allow-Headers': 'Content-Type',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Telegram Webhook 처리
|
// Telegram Webhook 처리
|
||||||
if (url.pathname === '/webhook') {
|
if (url.pathname === '/webhook') {
|
||||||
// 보안 검증
|
// 보안 검증
|
||||||
|
|||||||
822
web/index.html
822
web/index.html
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user