claudmax 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/claudmax-1.0.16.tgz +0 -0
  2. package/{packages/cli/index.js → index.js} +2 -0
  3. package/package.json +27 -55
  4. package/.claude/settings.local.json +0 -7
  5. package/.env.example +0 -24
  6. package/.github/workflows/publish.yml +0 -31
  7. package/README.md +0 -178
  8. package/claudmax-mcp-1.0.2.tgz +0 -0
  9. package/help +0 -0
  10. package/help-wal +0 -0
  11. package/next-env.d.ts +0 -6
  12. package/next.config.mjs +0 -43
  13. package/packages/cli/claudmax-1.0.16.tgz +0 -0
  14. package/packages/cli/package.json +0 -33
  15. package/packages/mcp/claudmax-mcp-1.0.0.tgz +0 -0
  16. package/packages/mcp/claudmax-mcp-1.0.1.tgz +0 -0
  17. package/packages/mcp/claudmax-mcp-1.0.2.tgz +0 -0
  18. package/packages/mcp/claudmax-mcp-1.0.3.tgz +0 -0
  19. package/packages/mcp/index.js +0 -129
  20. package/packages/mcp/package-lock.json +0 -1146
  21. package/packages/mcp/package.json +0 -32
  22. package/postcss.config.mjs +0 -6
  23. package/prisma/schema.prisma +0 -130
  24. package/prisma/seed.ts +0 -27
  25. package/public/favicon.svg +0 -10
  26. package/public/robots.txt +0 -10
  27. package/run_build.sh +0 -4
  28. package/scripts/migrate-plans.js +0 -98
  29. package/scripts/seed-blog.ts +0 -1014
  30. package/src/app/admin/dashboard/AdminDashboardClient.tsx +0 -1546
  31. package/src/app/admin/dashboard/page.tsx +0 -13
  32. package/src/app/admin/page.tsx +0 -132
  33. package/src/app/api/admin/auth/me/route.ts +0 -34
  34. package/src/app/api/admin/health/route.ts +0 -110
  35. package/src/app/api/admin/keys/[id]/route.ts +0 -116
  36. package/src/app/api/admin/keys/route.ts +0 -192
  37. package/src/app/api/admin/keys-list/route.ts +0 -81
  38. package/src/app/api/admin/login/route.ts +0 -72
  39. package/src/app/api/admin/logout/route.ts +0 -8
  40. package/src/app/api/admin/migrate/route.ts +0 -133
  41. package/src/app/api/admin/plans/[id]/route.ts +0 -65
  42. package/src/app/api/admin/plans/route.ts +0 -66
  43. package/src/app/api/admin/posts/[id]/route.ts +0 -81
  44. package/src/app/api/admin/posts/route.ts +0 -83
  45. package/src/app/api/admin/seed/route.ts +0 -145
  46. package/src/app/api/admin/settings/route.ts +0 -44
  47. package/src/app/api/admin/stats/route.ts +0 -74
  48. package/src/app/api/admin/users/[id]/route.ts +0 -166
  49. package/src/app/api/admin/users/plans/route.ts +0 -45
  50. package/src/app/api/admin/users/route.ts +0 -202
  51. package/src/app/api/blog/[slug]/route.ts +0 -22
  52. package/src/app/api/blog/route.ts +0 -40
  53. package/src/app/api/cron/daily-status/route.ts +0 -208
  54. package/src/app/api/support/chat/route.ts +0 -55
  55. package/src/app/api/support/chat/session/route.ts +0 -62
  56. package/src/app/api/support/chat/stream/route.ts +0 -44
  57. package/src/app/api/support/email/route.ts +0 -63
  58. package/src/app/api/tools/understand_image/route.ts +0 -113
  59. package/src/app/api/tools/upload/route.ts +0 -179
  60. package/src/app/api/tools/web_search/route.ts +0 -99
  61. package/src/app/api/v1/audio/route.ts +0 -67
  62. package/src/app/api/v1/audio/speech/route.ts +0 -73
  63. package/src/app/api/v1/chat/completions/route.ts +0 -3
  64. package/src/app/api/v1/chat/route.ts +0 -1079
  65. package/src/app/api/v1/images/generations/route.ts +0 -93
  66. package/src/app/api/v1/info/route.ts +0 -30
  67. package/src/app/api/v1/key-status/route.ts +0 -109
  68. package/src/app/api/v1/key-status/stream/route.ts +0 -135
  69. package/src/app/api/v1/messages/count_tokens/route.ts +0 -22
  70. package/src/app/api/v1/messages/route.ts +0 -807
  71. package/src/app/api/v1/models/route.ts +0 -14
  72. package/src/app/api/v1/route.ts +0 -18
  73. package/src/app/blog/BlogClient.tsx +0 -193
  74. package/src/app/blog/[slug]/page.tsx +0 -117
  75. package/src/app/blog/page.tsx +0 -20
  76. package/src/app/check-usage/CheckUsageClient.tsx +0 -186
  77. package/src/app/check-usage/layout.tsx +0 -11
  78. package/src/app/check-usage/page.tsx +0 -15
  79. package/src/app/docs/layout.tsx +0 -16
  80. package/src/app/docs/page.tsx +0 -1055
  81. package/src/app/faq/FAQClient.tsx +0 -227
  82. package/src/app/faq/page.tsx +0 -21
  83. package/src/app/globals.css +0 -75
  84. package/src/app/layout.tsx +0 -80
  85. package/src/app/page.tsx +0 -256
  86. package/src/app/reseller/ResellerClient.tsx +0 -435
  87. package/src/app/reseller/page.tsx +0 -15
  88. package/src/app/setup.ps1/route.ts +0 -79
  89. package/src/app/setup.sh/route.ts +0 -113
  90. package/src/app/sitemap.ts +0 -50
  91. package/src/app/status/StatusClient.tsx +0 -103
  92. package/src/app/status/layout.tsx +0 -11
  93. package/src/app/status/page.tsx +0 -15
  94. package/src/app/support/SupportClient.tsx +0 -411
  95. package/src/app/support/page.tsx +0 -25
  96. package/src/app/v1/chat/completions/route.ts +0 -3
  97. package/src/app/v1/chat/route.ts +0 -4
  98. package/src/app/v1/messages/route.ts +0 -3
  99. package/src/components/Footer.tsx +0 -120
  100. package/src/components/Header.tsx +0 -131
  101. package/src/components/landing/features.tsx +0 -99
  102. package/src/components/ui/badge.tsx +0 -32
  103. package/src/components/ui/button.tsx +0 -46
  104. package/src/components/ui/card.tsx +0 -50
  105. package/src/components/ui/dialog.tsx +0 -97
  106. package/src/components/ui/dropdown-menu.tsx +0 -156
  107. package/src/components/ui/input.tsx +0 -21
  108. package/src/components/ui/label.tsx +0 -15
  109. package/src/components/ui/separator.tsx +0 -22
  110. package/src/components/ui/switch.tsx +0 -27
  111. package/src/components/ui/tabs.tsx +0 -51
  112. package/src/components/ui/toast.tsx +0 -103
  113. package/src/lib/auth.ts +0 -45
  114. package/src/lib/prisma.ts +0 -20
  115. package/src/lib/providers.ts +0 -158
  116. package/src/lib/security.ts +0 -165
  117. package/src/lib/utils.ts +0 -14
  118. package/src/middleware.ts +0 -30
  119. package/tailwind.config.ts +0 -53
  120. package/tsconfig.json +0 -41
  121. package/tsconfig.tsbuildinfo +0 -1
  122. package/vercel.json +0 -8
  123. /package/{packages/cli/bin → bin}/claudmax.js +0 -0
  124. /package/{packages/cli/claudmax-1.0.17.tgz → claudmax-1.0.17.tgz} +0 -0
@@ -1,14 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
-
3
- export async function GET() {
4
- return NextResponse.json({
5
- models: [
6
- { id: 'claude-opus-4-6', name: 'Opus 4.6', provider: 'claudmax', context_length: 200000, input_types: ['text'], output_types: ['text'] },
7
- { id: 'claude-sonnet-4-6', name: 'Sonnet 4.6', provider: 'claudmax', context_length: 200000, input_types: ['text'], output_types: ['text'] },
8
- { id: 'claude-haiku-4-5-20251001', name: 'Haiku 4.5', provider: 'claudmax', context_length: 200000, input_types: ['text'], output_types: ['text'] },
9
- { id: 'claude-sonnet-4-vision', name: 'Sonnet 4 Vision', provider: 'claudmax', context_length: 200000, input_types: ['text','image'], output_types: ['text'] },
10
- { id: 'claude-image-4', name: 'Image 4', provider: 'claudmax', context_length: 200000, input_types: ['text'], output_types: ['image'] },
11
- { id: 'claude-audio-4', name: 'Audio 4', provider: 'claudmax', context_length: 200000, input_types: ['text'], output_types: ['audio'] },
12
- ],
13
- });
14
- }
@@ -1,18 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
-
3
- // API root — returns JSON 404 to signal this is an API, not a website
4
- export async function GET(req: Request) {
5
- const host = req.headers.get('host') ?? '';
6
- if (host.startsWith('api.')) {
7
- return NextResponse.json(
8
- { message: 'Cannot GET /', error: 'Not Found', statusCode: 404 },
9
- { status: 404 }
10
- );
11
- }
12
- return NextResponse.json({
13
- status: 'ok',
14
- service: 'ClaudMax API',
15
- version: '1.0.0',
16
- timestamp: new Date().toISOString(),
17
- });
18
- }
@@ -1,193 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useEffect } from 'react';
4
- import Link from 'next/link';
5
- import Header from '@/components/Header';
6
- import Footer from '@/components/Footer';
7
- import { Search, Clock, Eye, ArrowRight, BookOpen } from 'lucide-react';
8
-
9
- function fmtDate(d: Date | string | null) {
10
- if (!d) return '';
11
- return new Date(d).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
12
- }
13
-
14
- function readingTime(content: string): number {
15
- const words = content.replace(/[#*`_\[\]]/g, '').split(/\s+/).length;
16
- return Math.max(1, Math.ceil(words / 200));
17
- }
18
-
19
- interface Post {
20
- id: string; title: string; slug: string; excerpt: string | null;
21
- coverImage: string | null; tags: string | null; publishedAt: Date | null;
22
- views: number; seoTitle: string | null; seoDesc: string | null;
23
- author: { name: string } | null;
24
- }
25
-
26
- const ALL_TAGS = ['API Key', 'Anthropic', 'Tutorial', 'Security', 'Rate Limits', 'Tutorial',
27
- 'Python', 'Node.js', 'curl', 'SDK', 'Claude Opus', 'Models', 'AI', 'Deep Learning',
28
- 'Claude Sonnet', 'Review', 'Production', 'Claude Haiku', 'Speed', 'Cost Optimization',
29
- 'Proxy API', 'Architecture', 'Middleware', 'Claude Max', 'Streaming', 'SSE', 'Technical',
30
- 'Ideas', 'Projects', 'Use Cases', 'Inspiration', 'Coding', 'Claude Code', 'Development',
31
- 'VS Code', 'Cursor', 'Cost Optimization', 'Extended Thinking', 'Advanced', 'Reasoning',
32
- 'Plans', 'Features', 'Pricing', 'Chatbot', 'React', 'Python', 'Deployment', 'Comparison',
33
- 'OpenAI', 'Developer Guide', 'Tool Use', 'Function Calling', 'Agents', 'Context Window'];
34
-
35
- export default function BlogClient() {
36
- const [posts, setPosts] = useState<Post[]>([]);
37
- const [loading, setLoading] = useState(true);
38
- const [query, setQuery] = useState('');
39
- const [activeTag, setActiveTag] = useState('');
40
-
41
- useEffect(() => {
42
- fetch('/api/blog')
43
- .then(r => r.json())
44
- .then(d => { setPosts(d.posts || []); setLoading(false); })
45
- .catch(() => setLoading(false));
46
- }, []);
47
-
48
- const tags = [...new Set(ALL_TAGS)];
49
-
50
- const filtered = posts.filter(p => {
51
- const matchQuery = !query || p.title.toLowerCase().includes(query.toLowerCase()) ||
52
- (p.excerpt || '').toLowerCase().includes(query.toLowerCase()) ||
53
- (p.tags || '').toLowerCase().includes(query.toLowerCase());
54
- const matchTag = !activeTag || (p.tags || '').includes(activeTag);
55
- return matchQuery && matchTag;
56
- });
57
-
58
- const featured = filtered[0];
59
- const rest = filtered.slice(1);
60
-
61
- return (
62
- <div className="min-h-screen bg-[#F9FAFB] flex flex-col">
63
- <Header />
64
-
65
- <main className="flex-1 max-w-6xl mx-auto px-4 sm:px-6 py-10 w-full">
66
- {/* Hero */}
67
- <div className="text-center mb-10">
68
- <div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-purple-50 border border-purple-100 text-purple-600 text-xs font-semibold mb-4">
69
- <BookOpen className="w-3.5 h-3.5" />
70
- {posts.length} Articles Published
71
- </div>
72
- <h1 className="text-4xl font-black text-[#111827] mb-3 tracking-tight">ClaudMax Blog</h1>
73
- <p className="text-[#9CA3AF] text-lg max-w-xl mx-auto">
74
- Tutorials, guides, and deep dives into Claude AI integration, API development, and building with Claude Code.
75
- </p>
76
- </div>
77
-
78
- {/* Search + Filter */}
79
- <div className="mb-10 space-y-4">
80
- <div className="relative max-w-lg mx-auto">
81
- <Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
82
- <input
83
- value={query}
84
- onChange={e => setQuery(e.target.value)}
85
- placeholder="Search articles..."
86
- className="w-full pl-11 pr-4 py-3 bg-white border border-gray-200 rounded-2xl text-sm text-[#111827] placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all shadow-sm"
87
- />
88
- </div>
89
- <div className="flex gap-2 overflow-x-auto pb-2 scrollbar-hide">
90
- <button onClick={() => setActiveTag('')}
91
- className={`shrink-0 px-3.5 py-1.5 rounded-full text-xs font-semibold border transition-all ${!activeTag ? 'bg-[#5244F3] text-white border-[#5244F3]' : 'bg-white text-[#6B7280] border-gray-200 hover:border-purple-300 hover:text-purple-600'}`}>
92
- All Posts
93
- </button>
94
- {tags.slice(0, 12).map(t => (
95
- <button key={t} onClick={() => setActiveTag(activeTag === t ? '' : t)}
96
- className={`shrink-0 px-3.5 py-1.5 rounded-full text-xs font-semibold border transition-all ${activeTag === t ? 'bg-[#5244F3] text-white border-[#5244F3]' : 'bg-white text-[#6B7280] border-gray-200 hover:border-purple-300 hover:text-purple-600'}`}>
97
- {t}
98
- </button>
99
- ))}
100
- </div>
101
- </div>
102
-
103
- {loading ? (
104
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
105
- {[...Array(6)].map((_, i) => (
106
- <div key={i} className="bg-white rounded-2xl border border-gray-200 h-72 animate-pulse" />
107
- ))}
108
- </div>
109
- ) : filtered.length === 0 ? (
110
- <div className="text-center py-20 bg-white rounded-2xl border border-gray-200">
111
- <div className="text-4xl mb-4">🔍</div>
112
- <h3 className="text-lg font-bold text-[#111827] mb-2">No articles found</h3>
113
- <p className="text-[#9CA3AF] text-sm">
114
- {query ? `No results for "${query}" — try different keywords` : 'No posts in this category yet.'}
115
- </p>
116
- {query && <button onClick={() => setQuery('')} className="mt-4 text-purple-600 font-semibold text-sm hover:underline">Clear search</button>}
117
- </div>
118
- ) : (
119
- <>
120
- {/* Featured */}
121
- {featured && (
122
- <Link href={`/blog/${featured.slug}`} className="block mb-10 group">
123
- <div className="bg-white rounded-2xl border border-gray-200 overflow-hidden hover:shadow-lg hover:border-purple-200 transition-all">
124
- <div className="flex flex-col lg:flex-row">
125
- {featured.coverImage && (
126
- <div className="lg:w-5/12 shrink-0">
127
- <img src={featured.coverImage} alt={featured.title} className="w-full h-56 lg:h-full object-cover" />
128
- </div>
129
- )}
130
- <div className="flex-1 p-7 lg:p-9 flex flex-col justify-center">
131
- <div className="flex flex-wrap gap-2 mb-4">
132
- {(featured.tags || '').split(',').filter(Boolean).slice(0, 3).map(t => (
133
- <span key={t} className="text-xs font-bold px-2.5 py-1 rounded-full bg-purple-50 text-purple-600 border border-purple-100">{t.trim()}</span>
134
- ))}
135
- </div>
136
- <h2 className="text-2xl lg:text-3xl font-black text-[#111827] mb-3 group-hover:text-purple-600 transition-colors leading-tight">{featured.title}</h2>
137
- <p className="text-[#6B7280] text-sm leading-relaxed mb-5 line-clamp-2">{featured.excerpt || featured.title}</p>
138
- <div className="flex items-center gap-4 text-xs text-[#9CA3AF]">
139
- <span className="font-medium text-[#374151]">{featured.author?.name || 'ClaudMax Team'}</span>
140
- <span>·</span>
141
- <span className="flex items-center gap-1"><Clock className="w-3.5 h-3.5" />{readingTime(featured.excerpt || '')} min read</span>
142
- <span>·</span>
143
- <span className="flex items-center gap-1"><Eye className="w-3.5 h-3.5" />{featured.views.toLocaleString()} views</span>
144
- <span>·</span>
145
- <span>{fmtDate(featured.publishedAt)}</span>
146
- </div>
147
- <div className="mt-5 flex items-center gap-2 text-purple-600 font-semibold text-sm group-hover:gap-3 transition-all">
148
- Read Article <ArrowRight className="w-4 h-4" />
149
- </div>
150
- </div>
151
- </div>
152
- </div>
153
- </Link>
154
- )}
155
-
156
- {/* Grid */}
157
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
158
- {rest.map(post => (
159
- <Link key={post.id} href={`/blog/${post.slug}`} className="block group">
160
- <div className="bg-white rounded-2xl border border-gray-200 overflow-hidden hover:shadow-lg hover:border-purple-200 transition-all h-full flex flex-col">
161
- {post.coverImage && (
162
- <div className="overflow-hidden">
163
- <img src={post.coverImage} alt={post.title} className="w-full h-40 object-cover group-hover:scale-105 transition-transform duration-300" />
164
- </div>
165
- )}
166
- <div className="p-5 flex flex-col flex-1">
167
- <div className="flex gap-1.5 mb-3 flex-wrap">
168
- {(post.tags || '').split(',').filter(Boolean).slice(0, 2).map(t => (
169
- <span key={t} className="text-xs font-bold px-2.5 py-1 rounded-full bg-purple-50 text-purple-600 border border-purple-100">{t.trim()}</span>
170
- ))}
171
- </div>
172
- <h3 className="text-base font-bold text-[#111827] mb-2 group-hover:text-purple-600 transition-colors line-clamp-2 leading-snug">{post.title}</h3>
173
- <p className="text-[#9CA3AF] text-xs line-clamp-2 flex-1 leading-relaxed">{post.excerpt || post.title}</p>
174
- <div className="flex items-center gap-2 text-xs text-[#9CA3AF] mt-4 pt-3 border-t border-gray-100">
175
- <span className="flex items-center gap-1"><Clock className="w-3 h-3" />{readingTime(post.excerpt || '')} min</span>
176
- <span>·</span>
177
- <span className="flex items-center gap-1"><Eye className="w-3 h-3" />{post.views.toLocaleString()}</span>
178
- <span>·</span>
179
- <span>{fmtDate(post.publishedAt)}</span>
180
- </div>
181
- </div>
182
- </div>
183
- </Link>
184
- ))}
185
- </div>
186
- </>
187
- )}
188
- </main>
189
-
190
- <Footer />
191
- </div>
192
- );
193
- }
@@ -1,117 +0,0 @@
1
- import type { Metadata } from 'next';
2
- import { notFound } from 'next/navigation';
3
- import Header from '@/components/Header';
4
- import Footer from '@/components/Footer';
5
- import { prisma } from '@/lib/prisma';
6
-
7
- type Props = { params: Promise<{ slug: string }> };
8
-
9
- export async function generateMetadata({ params }: Props): Promise<Metadata> {
10
- const { slug } = await params;
11
- const post = await prisma.post.findUnique({ where: { slug, published: true } });
12
- if (!post) return { title: 'Post Not Found' };
13
- return {
14
- title: post.seoTitle ?? post.title,
15
- description: post.seoDesc ?? post.excerpt ?? post.title,
16
- openGraph: {
17
- title: post.seoTitle ?? post.title,
18
- description: post.seoDesc ?? post.excerpt ?? post.title,
19
- type: 'article',
20
- publishedTime: post.publishedAt?.toISOString(),
21
- images: post.coverImage ? [{ url: post.coverImage }] : [],
22
- },
23
- twitter: {
24
- card: 'summary_large_image',
25
- title: post.seoTitle ?? post.title,
26
- description: post.seoDesc ?? post.excerpt ?? post.title,
27
- images: post.coverImage ? [post.coverImage] : [],
28
- },
29
- };
30
- }
31
-
32
- function fmtDate(d: Date | string | null) {
33
- if (!d) return '';
34
- return new Date(d).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
35
- }
36
-
37
- export default async function BlogPostPage({ params }: Props) {
38
- const { slug } = await params;
39
- const post = await prisma.post.findUnique({
40
- where: { slug, published: true },
41
- include: { author: { select: { name: true } } },
42
- });
43
-
44
- if (!post) notFound();
45
-
46
- // Increment views
47
- await prisma.post.update({ where: { id: post.id }, data: { views: { increment: 1 } } });
48
-
49
- const related = await prisma.post.findMany({
50
- where: { published: true, id: { not: post.id } },
51
- orderBy: { views: 'desc' },
52
- take: 3,
53
- select: { id: true, title: true, slug: true, excerpt: true, tags: true, publishedAt: true },
54
- });
55
-
56
- return (
57
- <div className="min-h-screen bg-[#F9FAFB] flex flex-col">
58
- <Header />
59
-
60
- <article className="flex-1 max-w-3xl mx-auto px-4 sm:px-6 py-12 w-full">
61
- {/* Breadcrumb */}
62
- <div className="flex items-center gap-2 text-xs text-[#9CA3AF] mb-6">
63
- <a href="/" className="hover:text-purple-600 transition-colors">Home</a>
64
- <span>/</span>
65
- <a href="/blog" className="hover:text-purple-600 transition-colors">Blog</a>
66
- <span>/</span>
67
- <span className="text-[#6B7280] truncate max-w-48">{post.title}</span>
68
- </div>
69
-
70
- {/* Header */}
71
- {post.coverImage && (
72
- <img src={post.coverImage} alt={post.title} className="w-full h-64 object-cover rounded-2xl mb-8 card-shadow" />
73
- )}
74
-
75
- <div className="flex flex-wrap gap-2 mb-4">
76
- {post.tags.split(',').filter(Boolean).map(t => (
77
- <span key={t} className="text-xs font-bold px-2.5 py-1 rounded-full bg-purple-50 text-purple-600 border border-purple-100">{t.trim()}</span>
78
- ))}
79
- </div>
80
-
81
- <h1 className="text-3xl sm:text-4xl font-black text-[#111827] mb-4">{post.title}</h1>
82
-
83
- <div className="flex items-center gap-4 text-sm text-[#9CA3AF] mb-8 pb-8 border-b border-gray-200">
84
- {post.author?.name && <span className="font-semibold text-[#6B7280]">{post.author.name}</span>}
85
- <span>{fmtDate(post.publishedAt)}</span>
86
- <span>·</span>
87
- <span>{(post.views + 1).toLocaleString()} views</span>
88
- </div>
89
-
90
- {/* Content */}
91
- <div className="prose prose-lg max-w-none">
92
- <div
93
- className="text-[#374151] leading-relaxed"
94
- dangerouslySetInnerHTML={{ __html: post.content.replace(/\n/g, '<br/>') }}
95
- />
96
- </div>
97
-
98
- {/* Related */}
99
- {related.length > 0 && (
100
- <div className="mt-16 pt-10 border-t border-gray-200">
101
- <h3 className="text-xl font-bold text-[#111827] mb-6">More Articles</h3>
102
- <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
103
- {related.map(p => (
104
- <a key={p.id} href={`/blog/${p.slug}`} className="block bg-white rounded-xl border border-gray-200 p-4 hover:shadow-md transition-all group">
105
- <p className="text-sm font-bold text-[#374151] group-hover:text-purple-600 transition-colors line-clamp-2">{p.title}</p>
106
- <p className="text-xs text-[#9CA3AF] mt-2">{fmtDate(p.publishedAt)}</p>
107
- </a>
108
- ))}
109
- </div>
110
- </div>
111
- )}
112
- </article>
113
-
114
- <Footer />
115
- </div>
116
- );
117
- }
@@ -1,20 +0,0 @@
1
- import type { Metadata } from 'next';
2
- import BlogClient from './BlogClient';
3
-
4
- export const metadata: Metadata = {
5
- title: 'Blog | ClaudMax — Claude API Tutorials & Guides',
6
- description: 'Tutorials, guides, and deep dives into Claude AI integration, API development, Claude Code setup, and building with Anthropic models.',
7
- keywords: 'Claude API blog,Claude tutorials,Claude Code guide,Anthropic API,OpenRouter tutorials,AI development guide',
8
- openGraph: {
9
- title: 'ClaudMax Blog',
10
- description: 'Tutorials, guides, and deep dives into Claude AI integration.',
11
- type: 'website',
12
- url: 'https://claudmax.pro/blog',
13
- },
14
- twitter: { card: 'summary_large_image', title: 'ClaudMax Blog' },
15
- robots: { index: true, follow: true },
16
- };
17
-
18
- export default function BlogPage() {
19
- return <BlogClient />;
20
- }
@@ -1,186 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useEffect, useRef, useCallback } from 'react';
4
- import Link from 'next/link';
5
- import Header from '@/components/Header';
6
- import Footer from '@/components/Footer';
7
-
8
- interface KeyStatus {
9
- key: string; prefix: string; name: string; tier: string; isActive: boolean;
10
- displayMultiplier: number; windowStartAt: string; windowResetAt: string; windowResetMs: number;
11
- requestsUsed: number; requestsLimit: number; requestsLeft: number; requestsPerMinute: number;
12
- tokensUsed: number; tokensLimit: number; tokensLeft: number;
13
- lastUsedAt: string | null; createdAt: string;
14
- }
15
-
16
- function CircularGauge({ used, limit, label }: { used: number; limit: number; label: string }) {
17
- const pct = limit > 0 ? Math.min(100, (used / limit) * 100) : 0;
18
- const r = 36, circ = 2 * Math.PI * r, dash = (pct / 100) * circ;
19
- const barColor = pct >= 100 ? '#EF4444' : pct >= 80 ? '#F59E0B' : '#5244F3';
20
- return (
21
- <div className="flex flex-col items-center gap-2">
22
- <div className="relative w-24 h-24">
23
- <svg className="w-24 h-24 -rotate-90" viewBox="0 0 80 80">
24
- <circle cx="40" cy="40" r={r} fill="none" stroke="#F3F4F6" strokeWidth="6" />
25
- <circle cx="40" cy="40" r={r} fill="none" stroke={barColor} strokeWidth="6" strokeLinecap="round"
26
- strokeDasharray={`${dash} ${circ}`} style={{ transition: 'stroke-dasharray 0.5s ease' }} />
27
- </svg>
28
- <div className="absolute inset-0 flex items-center justify-center">
29
- <span className="text-lg font-black text-[#111827]">{pct.toFixed(0)}%</span>
30
- </div>
31
- </div>
32
- <p className="text-xs text-[#9CA3AF] font-medium">{label}</p>
33
- </div>
34
- );
35
- }
36
-
37
- function CountdownTimer({ windowResetMs }: { windowResetMs: number }) {
38
- const [ms, setMs] = useState(windowResetMs);
39
- useEffect(() => { setMs(windowResetMs); }, [windowResetMs]);
40
- useEffect(() => {
41
- if (ms <= 0) return;
42
- const interval = setInterval(() => { setMs(prev => { if (prev <= 1000) { clearInterval(interval); return 0; } return prev - 1000; }); }, 1000);
43
- return () => clearInterval(interval);
44
- }, [ms]);
45
- const h = Math.floor(ms / 3600000), m = Math.floor((ms % 3600000) / 60000), s = Math.floor((ms % 60000) / 1000);
46
- return <span className="text-purple-600 font-bold tabular-nums">{h > 0 ? `${String(h).padStart(2,'0')}:` : ''}{String(m).padStart(2,'0')}:{String(s).padStart(2,'0')}</span>;
47
- }
48
-
49
- function formatTokens(n: number) { return n >= 1_000_000_000 ? `${(n/1_000_000_000).toFixed(1)}B` : n >= 1_000_000 ? `${(n/1_000_000).toFixed(1)}M` : n >= 1_000 ? `${(n/1_000).toFixed(0)}K` : String(n); }
50
- function pctColor(used: number, limit: number) { return used >= limit ? 'bg-red-500' : used >= limit * 0.8 ? 'bg-amber-500' : 'bg-purple-500'; }
51
-
52
- export default function CheckUsageClient() {
53
- const [apiKey, setApiKey] = useState('');
54
- const [loading, setLoading] = useState(false);
55
- const [status, setStatus] = useState<KeyStatus | null>(null);
56
- const [error, setError] = useState('');
57
- const [live, setLive] = useState(false);
58
- const savedKeyRef = useRef('');
59
-
60
- const fetchStatus = useCallback(async (key: string) => {
61
- try {
62
- const res = await fetch('/api/v1/key-status', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey: key }) });
63
- const data = await res.json();
64
- if (res.ok) { setStatus(data); setLive(true); }
65
- else { setError(data.error ?? 'Invalid API key'); setStatus(null); setLive(false); }
66
- } catch { setError('Failed to check key status. Please try again.'); }
67
- }, []);
68
-
69
- useEffect(() => {
70
- if (!status || !savedKeyRef.current) return;
71
- const interval = setInterval(() => fetchStatus(savedKeyRef.current), 3000);
72
- return () => clearInterval(interval);
73
- }, [status, fetchStatus]);
74
-
75
- const checkStatus = async (e: React.FormEvent) => {
76
- e.preventDefault();
77
- if (!apiKey.trim()) return;
78
- setLoading(true); setError(''); setStatus(null); setLive(false);
79
- savedKeyRef.current = apiKey.trim();
80
- await fetchStatus(savedKeyRef.current);
81
- setLoading(false);
82
- };
83
-
84
- const tierPlans: Record<string, { label: string; color: string; bg: string }> = {
85
- '5x': { label: '5x Max', color: 'text-blue-600', bg: 'bg-blue-50' },
86
- '20x': { label: '20x Max', color: 'text-purple-600', bg: 'bg-purple-50' },
87
- unlimited: { label: 'Unlimited', color: 'text-amber-600', bg: 'bg-amber-50' },
88
- };
89
- const plan = status ? (tierPlans[status.tier] ?? tierPlans['5x']) : null;
90
-
91
- return (
92
- <div className="min-h-screen bg-[#F9FAFB] flex flex-col">
93
- <Header />
94
- <div className="flex-1 max-w-3xl mx-auto px-4 sm:px-6 py-12 w-full">
95
- <div className="text-center mb-8">
96
- <h1 className="text-4xl font-black text-[#111827] mb-2">API Key Status</h1>
97
- <p className="text-[#9CA3AF] text-base">Check your token usage, limits, and window timer in real time.</p>
98
- </div>
99
- <form onSubmit={checkStatus} className="flex gap-2 mb-8">
100
- <input value={apiKey} onChange={e => setApiKey(e.target.value)} placeholder="sk-cmx_your_api_key"
101
- className="flex-1 h-12 px-4 bg-white border border-gray-200 rounded-2xl text-sm font-mono focus:outline-none focus:border-purple-400 focus:ring-2 focus:ring-purple-100 transition-all" />
102
- <button type="submit" disabled={loading || !apiKey.trim()}
103
- className="h-12 px-6 bg-[#5244F3] hover:bg-[#4338CA] disabled:bg-gray-300 text-white rounded-2xl text-sm font-semibold transition-colors shadow-sm whitespace-nowrap">
104
- {loading ? 'Checking...' : 'Check'}
105
- </button>
106
- </form>
107
- {error && <div className="mb-6 p-4 rounded-2xl border border-red-200 bg-red-50 text-red-600 text-sm font-medium">{error}</div>}
108
- {status && (
109
- <div className="space-y-4">
110
- <div className="bg-white rounded-2xl border border-gray-200 p-6">
111
- <div className="flex items-center justify-between mb-4">
112
- <div><h3 className="text-[#111827] font-bold text-lg">{status.name}</h3><p className="text-[#D1D5DB] text-xs font-mono mt-0.5">{status.prefix}•••••••••••</p></div>
113
- <div className="flex items-center gap-2">
114
- {plan && <span className={`text-xs font-bold px-2.5 py-1 rounded-full ${plan.bg} ${plan.color}`}>{plan.label}</span>}
115
- <span className={`text-xs font-bold px-2.5 py-1 rounded-full flex items-center gap-1 ${status.isActive ? 'bg-emerald-50 text-emerald-700 border border-emerald-200' : 'bg-red-50 text-red-500 border border-red-100'}`}>
116
- <span className={`w-1.5 h-1.5 rounded-full ${status.isActive ? 'bg-emerald-500 animate-pulse' : 'bg-red-500'}`} />{status.isActive ? 'Active' : 'Inactive'}
117
- </span>
118
- {live && <span className="text-xs font-bold px-2 py-0.5 rounded-full bg-emerald-50 text-emerald-600 border border-emerald-100 flex items-center gap-1"><span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />Live</span>}
119
- </div>
120
- </div>
121
- <div className="flex items-center gap-6 py-4 border-t border-gray-100">
122
- <CircularGauge used={status.tokensUsed} limit={status.tokensLimit} label="Tokens" />
123
- <div className="flex-1 space-y-4">
124
- {[['Tokens', status.tokensUsed, status.tokensLimit, status.tokensLeft], ['Total Requests in 24hrs', status.requestsUsed, status.requestsLimit, status.requestsLeft]].map(([label, used, limit, left]) => (
125
- <div key={label as string}>
126
- <div className="flex justify-between text-xs mb-1.5">
127
- <span className="text-[#9CA3AF]">{label as string}</span>
128
- <span className="font-mono font-semibold text-[#374151]">{label === 'Total Requests in 24hrs' ? `${formatTokens(used as number)} / ${formatTokens(limit as number)} · 60/min` : `${formatTokens(used as number)} / ${formatTokens(limit as number)}`}</span>
129
- </div>
130
- <div className="h-2.5 bg-gray-100 rounded-full overflow-hidden">
131
- <div className={`h-full rounded-full transition-all duration-500 ${pctColor(used as number, limit as number)}`} style={{ width: `${Math.min(100, ((used as number) / (limit as number)) * 100)}%` }} />
132
- </div>
133
- <div className="flex justify-between mt-1">
134
- <span className="text-[10px] text-[#D1D5DB]">used</span>
135
- <span className="text-[10px] text-emerald-600 font-semibold">{formatTokens(left as number)} left</span>
136
- </div>
137
- </div>
138
- ))}
139
- </div>
140
- </div>
141
- </div>
142
- <div className="grid grid-cols-2 gap-4">
143
- <div className="bg-white rounded-2xl border border-gray-200 p-5">
144
- <p className="text-[#9CA3AF] text-xs font-bold uppercase tracking-widest mb-2">Window Reset</p>
145
- <p className="text-2xl font-black text-purple-600 mt-2"><CountdownTimer windowResetMs={status.windowResetMs} /></p>
146
- <p className="text-[#D1D5DB] text-xs mt-1">{new Date(status.windowResetAt).toLocaleTimeString()} · 5h rolling window</p>
147
- </div>
148
- <div className="bg-white rounded-2xl border border-gray-200 p-5">
149
- <p className="text-[#9CA3AF] text-xs font-bold uppercase tracking-widest mb-2">Tokens Left</p>
150
- <p className="text-2xl font-black text-emerald-600 mt-2">{formatTokens(status.tokensLeft)}</p>
151
- <p className="text-[#D1D5DB] text-xs mt-1">of {formatTokens(status.tokensLimit)} token budget</p>
152
- </div>
153
- </div>
154
- <div className="bg-white rounded-2xl border border-gray-200 p-5">
155
- <p className="text-[#9CA3AF] text-xs font-bold uppercase tracking-widest mb-4">Details</p>
156
- <div className="grid grid-cols-2 gap-4">
157
- {[
158
- ['Created', new Date(status.createdAt).toLocaleDateString()],
159
- ['Last Used', status.lastUsedAt ? new Date(status.lastUsedAt).toLocaleString() : 'Never'],
160
- ['Plan', (status.tier || '5x').toUpperCase()],
161
- ].map(([label, value]) => (
162
- <div key={label as string} className="flex justify-between items-center py-2 border-b border-gray-100 last:border-0">
163
- <span className="text-[#9CA3AF] text-sm">{label as string}</span>
164
- <span className="text-[#374151] text-sm font-semibold">{value as string}</span>
165
- </div>
166
- ))}
167
- </div>
168
- </div>
169
- <div className="rounded-2xl border border-purple-200 bg-purple-50 p-5 text-center">
170
- <p className="text-[#9CA3AF] text-sm mb-3">Need help integrating ClaudMax?</p>
171
- <Link href="/docs"><button className="bg-[#5244F3] hover:bg-[#4338CA] text-white font-semibold px-6 py-2.5 rounded-xl text-sm transition-colors shadow-sm">Read Documentation</button></Link>
172
- </div>
173
- </div>
174
- )}
175
- {!status && !loading && !error && (
176
- <div className="text-center py-16 bg-white rounded-2xl border border-gray-200">
177
- <h3 className="text-[#111827] font-bold text-lg mb-2">Enter your API key</h3>
178
- <p className="text-[#9CA3AF] text-sm mb-6">Enter the API key provided by your administrator to check usage and limits.</p>
179
- <Link href="/docs"><button className="bg-[#5244F3] hover:bg-[#4338CA] text-white font-semibold px-8 py-2.5 rounded-xl text-sm transition-colors shadow-sm">View Documentation</button></Link>
180
- </div>
181
- )}
182
- </div>
183
- <Footer />
184
- </div>
185
- );
186
- }
@@ -1,11 +0,0 @@
1
- import type { Metadata } from 'next';
2
-
3
- export const metadata: Metadata = {
4
- title: 'Check Usage — ClaudMax',
5
- description: 'Check your ClaudMax API key usage, token limits, and window reset timer in real time.',
6
- keywords: 'API usage, token counter, rate limit, ClaudMax usage',
7
- };
8
-
9
- export default function CheckUsageLayout({ children }: { children: React.ReactNode }) {
10
- return children;
11
- }
@@ -1,15 +0,0 @@
1
- import type { Metadata } from 'next';
2
- import CheckUsageClient from './CheckUsageClient';
3
-
4
- export const metadata: Metadata = {
5
- title: 'Check Usage | ClaudMax — API Key Status',
6
- description: 'Check your ClaudMax API key usage in real time. View token counts, request limits, and 5-hour rolling window resets for all Claude models.',
7
- keywords: 'ClaudMax usage, API key status, token usage, check API key usage, Claude API usage tracker',
8
- openGraph: { title: 'Check Usage | ClaudMax', description: 'Check your ClaudMax API key usage in real time.', type: 'website', url: 'https://claudmax.pro/check-usage', siteName: 'ClaudMax' },
9
- twitter: { card: 'summary_large_image', title: 'Check Usage | ClaudMax' },
10
- robots: { index: true, follow: true },
11
- };
12
-
13
- export default function CheckUsagePage() {
14
- return <CheckUsageClient />;
15
- }
@@ -1,16 +0,0 @@
1
- import type { Metadata } from 'next';
2
-
3
- export const metadata: Metadata = {
4
- title: 'Documentation — ClaudMax',
5
- description: 'Complete API reference, integration guides, code examples, and tutorials for ClaudMax — the fastest Claude API gateway powered by OpenRouter.',
6
- keywords: 'ClaudMax documentation, Claude API reference, OpenRouter API, API integration guide, Claude Code setup, Cursor MCP, Windsurf MCP, API authentication',
7
- openGraph: {
8
- title: 'Documentation — ClaudMax',
9
- description: 'Complete API reference and integration guides for ClaudMax.',
10
- type: 'website',
11
- },
12
- };
13
-
14
- export default function DocsLayout({ children }: { children: React.ReactNode }) {
15
- return children;
16
- }