Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ const nextConfig: NextConfig = {
},
// X-Powered-Byヘッダーを無効化
poweredByHeader: false,
async redirects() {
return [
{
source: '/general',
destination: '/',
permanent: true,
},
{
source: '/general/:id',
destination: '/',
permanent: true,
},
]
},
async headers() {
// CSPヘッダーを設定(proxy.tsと統一させる)
// Google Analytics: script-src に googletagmanager.com + unsafe-eval、connect-src / img-src に GA ドメインを許可
Expand Down
17 changes: 16 additions & 1 deletion src/app/api/contact/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { createCorsResponse, createCorsOptionsResponse } from '@/lib/api/cors'

// 環境変数から取得(.env.localのAPPS_SCRIPT_URLと一致させる)
const scriptUrl = process.env.APPS_SCRIPT_URL
const RESEND_FROM = process.env.RESEND_FROM ?? 'onboarding@resend.dev'
const RESEND_FROM =
process.env.RESEND_FROM ?? '医者と歯医者の交換日記 <info@ishatohaisha.com>'
const CONTACT_NOTIFY_TO =
process.env.CONTACT_NOTIFY_TO ?? 'info@ishatohaisha.com'

interface ContactPayload {
name: string
Expand Down Expand Up @@ -126,6 +129,18 @@ export async function POST(request: Request) {
if (sendError) {
console.warn('Contact confirmation email failed:', sendError.message)
}

const adminHtml = `<div style="font-family:sans-serif;max-width:640px;color:#212529;"><h2 style="font-size:18px;margin:0 0 12px;">新しいお問い合わせが届きました</h2><table style="width:100%;border-collapse:collapse;font-size:14px;"><tr><th align="left" style="padding:8px;border:1px solid #dee2e6;background:#f8f9fa;">お名前</th><td style="padding:8px;border:1px solid #dee2e6;">${escapeHtml(body.name)}</td></tr><tr><th align="left" style="padding:8px;border:1px solid #dee2e6;background:#f8f9fa;">メールアドレス</th><td style="padding:8px;border:1px solid #dee2e6;">${escapeHtml(body.email)}</td></tr><tr><th align="left" style="padding:8px;border:1px solid #dee2e6;background:#f8f9fa;">電話番号</th><td style="padding:8px;border:1px solid #dee2e6;">${escapeHtml(body.phone || '')}</td></tr><tr><th align="left" style="padding:8px;border:1px solid #dee2e6;background:#f8f9fa;">所属・団体</th><td style="padding:8px;border:1px solid #dee2e6;">${escapeHtml(body.organization || '')}</td></tr><tr><th align="left" style="padding:8px;border:1px solid #dee2e6;background:#f8f9fa;">職種</th><td style="padding:8px;border:1px solid #dee2e6;">${escapeHtml(body.profession || '')}</td></tr><tr><th align="left" style="padding:8px;border:1px solid #dee2e6;background:#f8f9fa;">件名</th><td style="padding:8px;border:1px solid #dee2e6;">${escapeHtml(body.subject)}</td></tr></table><p style="margin:16px 0 8px;font-size:14px;color:#495057;"><strong>お問い合わせ内容</strong></p><div style="white-space:pre-wrap;border:1px solid #dee2e6;border-radius:6px;padding:12px;font-size:14px;color:#212529;">${escapeHtml(body.message)}</div></div>`
const { error: notifyError } = await resend.emails.send({
from: RESEND_FROM,
to: CONTACT_NOTIFY_TO,
subject: `【お問い合わせ通知】${body.subject}`,
html: adminHtml,
replyTo: body.email,
})
if (notifyError) {
console.warn('Contact notify email failed:', notifyError.message)
}
} catch (confirmErr) {
console.warn('Contact confirmation email error:', confirmErr)
}
Expand Down
3 changes: 2 additions & 1 deletion src/app/api/subscribe/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { createCorsResponse, createCorsOptionsResponse } from '@/lib/api/cors'
// Segment: RESEND_SEGMENT_GENERAL(一般読者), RESEND_SEGMENT_MEDICAL(医療従事者)
const WELCOME_PDF_URL = 'https://www.ishatohaisha.com/pdf_test.pdf'

const FROM_EMAIL = process.env.RESEND_FROM ?? 'onboarding@resend.dev'
const FROM_EMAIL =
process.env.RESEND_FROM ?? '医者と歯医者の交換日記 <info@ishatohaisha.com>'

const SEGMENT_IDS = {
general: process.env.RESEND_SEGMENT_GENERAL,
Expand Down
21 changes: 11 additions & 10 deletions src/app/general/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import { redirect } from 'next/navigation'

// generalページ廃止のためリダイレクト対応
// 元のコードは以下の通り:

/*
import {
getArticleById,
getSidebarData,
Expand Down Expand Up @@ -81,7 +87,6 @@ export default async function ArticlePage({ params }: Props) {
return (
<div className="min-h-screen bg-white">
<div className="mx-auto max-w-7xl py-8">
{/* 記事ヘッダー(画像より上) */}
<div className="mb-8">
<div className="mb-4 flex flex-wrap items-center gap-2">
{article.category && (
Expand Down Expand Up @@ -114,7 +119,6 @@ export default async function ArticlePage({ params }: Props) {
</time>
</div>

{/* ヒーロー画像セクション */}
{article.eyecatch && (
<div className="mb-8">
<div className="relative aspect-video w-full overflow-hidden rounded-3xl">
Expand All @@ -130,28 +134,22 @@ export default async function ArticlePage({ params }: Props) {
</div>
)}

{/* 目次 */}
<div className="mb-8">
<TableOfContents html={article.content} />
</div>

{/* メインコンテンツとサイドバー */}
<div className="grid grid-cols-1 gap-8 lg:grid-cols-10">
{/* メインコンテンツ */}
<article className="lg:col-span-7">
{/* 記事本文 */}
<div className="prose prose-lg max-w-none">
<SafeHTML html={article.content} />
</div>

{/* 著者情報セクション */}
<AuthorInfo author={article.author} />

<ShareButtons
url={`https://www.ishatohaisha.com/general/${article.id}`}
title={article.title}
/>
{/* 関連記事 */}
{relatedArticlesWithEndpoint.length > 0 && (
<div className="mt-12">
<h2 className="mb-6 text-2xl font-bold text-[color:var(--foreground)]">
Expand All @@ -171,7 +169,6 @@ export default async function ArticlePage({ params }: Props) {
</div>
)}

{/* CTA セクション */}
<div className="mt-12 rounded-2xl bg-gradient-to-r from-[color:var(--accent)] to-[color:var(--accent)]/90 p-8 text-center text-white">
<h2 className="mb-4 text-2xl font-bold">
医療の未来を一緒に創りませんか?
Expand Down Expand Up @@ -209,7 +206,6 @@ export default async function ArticlePage({ params }: Props) {
</div>
</article>

{/* サイドバー */}
<ArticleSidebar sidebarData={sidebarData} basePath="/general" />
</div>
</div>
Expand All @@ -220,3 +216,8 @@ export default async function ArticlePage({ params }: Props) {
notFound()
}
}
*/

export default function GeneralArticlePage() {
redirect('/')
}
15 changes: 11 additions & 4 deletions src/app/general/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import { redirect } from 'next/navigation'

// generalページ廃止のためリダイレクト対応
// 元のコードは以下の通り:

/*
import { Suspense } from 'react'
import { getArticles, getCategories, getTags } from '@/lib/microCMS/microcms'
import { ArticleCard } from '@/components/ArticleCard'
Expand Down Expand Up @@ -69,9 +75,7 @@ export default async function ArticlesPage({ searchParams }: Props) {
</h1>

<div className="grid grid-cols-1 gap-8 lg:grid-cols-4">
{/* メインコンテンツ */}
<div className="lg:col-span-3">
{/* SP版: 絞り込みフィルター(上部) */}
<div className="mb-8 lg:hidden">
<Suspense
fallback={
Expand All @@ -88,7 +92,6 @@ export default async function ArticlesPage({ searchParams }: Props) {
</Suspense>
</div>

{/* 記事一覧 */}
<div className="mb-4 flex items-center justify-between">
<p className="text-sm text-gray-600">
{sorted.length}件の記事が見つかりました
Expand Down Expand Up @@ -119,7 +122,6 @@ export default async function ArticlesPage({ searchParams }: Props) {
)}
</div>

{/* PC版: サイドバー */}
<aside className="hidden lg:col-span-1 lg:block">
<div className="sticky top-24">
<Suspense
Expand All @@ -142,3 +144,8 @@ export default async function ArticlesPage({ searchParams }: Props) {
</div>
)
}
*/

export default function GeneralPage() {
redirect('/')
}
59 changes: 42 additions & 17 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,12 @@ export default async function Home() {
{/* Latest Articles - 新着記事 */}
<section className="bg-white py-16">
<div className="container mx-auto px-4">
<h2 className="mb-8 text-2xl font-bold text-[color:var(--foreground)]">
新着記事
</h2>
<div className="mb-8 flex items-center gap-3">
<span className="h-6 w-1 rounded-full bg-[color:var(--accent)]" />
<h2 className="text-2xl font-bold text-[color:var(--foreground)]">
新着記事
</h2>
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{newestArticlesWithEndpoint.map(({ article, endpoint }) => (
<ArticleCard
Expand All @@ -159,22 +162,38 @@ export default async function Home() {
</div>
<div className="mt-8 text-center">
<Link
href="/articles"
className="inline-flex items-center gap-2 rounded-lg bg-[color:var(--accent)] px-6 py-3 text-white transition-colors hover:bg-[color:var(--accent)]/90"
href="/medical-articles"
className="inline-flex items-center gap-2 rounded-lg border border-[color:var(--accent)]/20 bg-white px-6 py-3 text-sm font-medium text-[color:var(--accent)] transition-all hover:border-[color:var(--accent)] hover:shadow-sm"
>
もっと見る
<svg
className="h-3.5 w-3.5"
fill="none"
stroke="currentColor"
strokeWidth={2}
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9 5l7 7-7 7"
/>
</svg>
</Link>
</div>
</div>
</section>

{/* Featured Keywords - 話題のキーワード */}
{tags.length > 0 && (
<section className="bg-white py-12">
<section className="bg-[#f8f9fb] py-12">
<div className="container mx-auto px-4">
<h2 className="mb-6 text-2xl font-bold text-[color:var(--foreground)]">
話題のキーワード
</h2>
<div className="mb-6 flex items-center gap-3">
<span className="h-6 w-1 rounded-full bg-[color:var(--accent)]" />
<h2 className="text-2xl font-bold text-[color:var(--foreground)]">
話題のキーワード
</h2>
</div>
<div className="flex flex-wrap gap-3">
{tags.map((tag) => (
<Link
Expand All @@ -194,9 +213,12 @@ export default async function Home() {
{popularArticles.length > 0 && (
<section className="bg-white py-16">
<div className="container mx-auto px-4">
<h2 className="mb-8 text-2xl font-bold text-[color:var(--foreground)]">
人気の記事
</h2>
<div className="mb-8 flex items-center gap-3">
<span className="h-6 w-1 rounded-full bg-[color:var(--accent)]" />
<h2 className="text-2xl font-bold text-[color:var(--foreground)]">
人気の記事
</h2>
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{popularArticlesWithEndpoint.map(({ article, endpoint }) => (
<ArticleCard
Expand All @@ -208,7 +230,7 @@ export default async function Home() {
</div>
<div className="mt-8 text-center">
<Link
href="/articles"
href="/medical-articles"
className="inline-flex items-center gap-2 rounded-lg bg-[color:var(--accent)] px-6 py-3 text-white transition-colors hover:bg-[color:var(--accent)]/90"
>
もっと見る
Expand All @@ -220,11 +242,14 @@ export default async function Home() {

{/* Featured Articles - おすすめ記事 */}
{(featuredArticles.length > 0 || medicalFeaturedArticles.length > 0) && (
<section className="bg-white py-16">
<section className="bg-[#f8f9fb] py-16">
<div className="container mx-auto px-4">
<h2 className="mb-8 text-2xl font-bold text-[color:var(--foreground)]">
おすすめ記事
</h2>
<div className="mb-8 flex items-center gap-3">
<span className="h-6 w-1 rounded-full bg-[color:var(--accent)]" />
<h2 className="text-2xl font-bold text-[color:var(--foreground)]">
おすすめ記事
</h2>
</div>

{/* 一般向け */}
{featuredArticles.length > 0 && (
Expand Down
4 changes: 2 additions & 2 deletions src/app/privacy/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,10 @@ export default function PrivacyPage() {
<p>
Email:{' '}
<a
href="mailto:info@tadashiiiryou.or.jp"
href="mailto:info@ishatohaisha.com"
className="text-[color:var(--accent)] underline"
>
info@tadashiiiryou.or.jp
info@ishatohaisha.com
</a>
</p>
</address>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ArticleSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const ArticleSidebar = ({
医科歯科連携マニュアル
</h3>
<p className="mb-4 text-[0.8125rem] leading-relaxed text-white/75 lg:text-sm">
現場で使える実践ガイドを無料でお届け
現場で使える医科歯科連携マニュアルを無料でお届け
</p>
<span className="inline-flex items-center gap-2 rounded-lg bg-white px-4 py-2 text-[0.8125rem] font-semibold text-[color:var(--accent)] transition-all group-hover:gap-3 group-hover:shadow-md">
無料ダウンロード
Expand Down
77 changes: 51 additions & 26 deletions src/components/MedicalArticlesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,57 @@ export default function MedicalArticlesModal() {

return (
showModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
<div className="w-full max-w-md rounded-xl bg-white p-8 shadow-2xl">
<h2 className="mb-4 text-2xl font-bold text-[color:var(--foreground)]">
医療従事者向け情報
</h2>
<p className="mb-6 leading-relaxed text-gray-600">
本ページのコンテンツは医療従事者の方を対象としており、一般の方への情報提供を目的としたものではありません。
<br />
あなたは医療従事者ですか?
</p>
<div className="flex flex-col gap-3 sm:flex-row sm:justify-end">
<button
className="inline-flex items-center justify-center rounded-lg bg-[color:var(--accent)] px-6 py-3 font-semibold text-white transition-colors hover:bg-[color:var(--accent)]/90"
onClick={() => {
sessionStorage.setItem('isMedical', 'true')
setShowModal(false)
}}
>
はい
</button>
<Link
href="/general"
className="inline-flex items-center justify-center rounded-lg border-2 border-gray-300 bg-white px-6 py-3 font-semibold text-[color:var(--foreground)] transition-colors hover:bg-gray-50"
>
一般向けページに移動
</Link>
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm">
<div className="w-full max-w-md overflow-hidden rounded-2xl bg-white shadow-2xl">
{/* ヘッダーバー */}
<div className="bg-[color:var(--accent)] px-6 py-4">
<div className="flex items-center gap-3">
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-white/15">
<svg
className="h-5 w-5 text-white"
fill="none"
stroke="currentColor"
strokeWidth={1.8}
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
/>
</svg>
</div>
<h2 className="text-lg font-bold text-white">
医療従事者向け情報
</h2>
</div>
</div>

{/* コンテンツ */}
<div className="px-6 py-6">
<p className="mb-6 text-[0.9375rem] leading-relaxed text-gray-600">
本ページのコンテンツは医療従事者の方を対象としており、一般の方への情報提供を目的としたものではありません。
</p>
<p className="mb-6 text-base font-semibold text-[color:var(--foreground)]">
あなたは医療従事者ですか?
</p>
<div className="flex gap-3">
<button
className="flex flex-1 items-center justify-center gap-2 rounded-xl bg-[color:var(--accent)] px-6 py-3.5 font-bold text-white transition-all hover:shadow-lg hover:shadow-[color:var(--accent)]/25"
onClick={() => {
sessionStorage.setItem('isMedical', 'true')
setShowModal(false)
}}
>
はい
</button>
<Link
href="/"
className="flex flex-1 items-center justify-center rounded-xl border border-gray-200 bg-gray-50 px-6 py-3.5 font-bold text-gray-500 transition-all hover:bg-gray-100"
>
いいえ
</Link>
</div>
</div>
</div>
</div>
Expand Down
Loading
Loading