Spaces:
Running
Running
add trending apps
Browse files
frontend/src/components/LandingPage.tsx
CHANGED
|
@@ -48,10 +48,14 @@ export default function LandingPage({
|
|
| 48 |
const [showModelDropdown, setShowModelDropdown] = useState(false);
|
| 49 |
const languageDropdownRef = useRef<HTMLDivElement>(null);
|
| 50 |
const modelDropdownRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
useEffect(() => {
|
| 53 |
loadData();
|
| 54 |
handleOAuthInit();
|
|
|
|
| 55 |
// Check auth status periodically to catch OAuth redirects
|
| 56 |
const interval = setInterval(() => {
|
| 57 |
const authenticated = checkIsAuthenticated();
|
|
@@ -161,6 +165,15 @@ export default function LandingPage({
|
|
| 161 |
}
|
| 162 |
};
|
| 163 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
const handleSubmit = (e: React.FormEvent) => {
|
| 165 |
e.preventDefault();
|
| 166 |
if (prompt.trim() && isAuthenticated) {
|
|
@@ -181,9 +194,14 @@ export default function LandingPage({
|
|
| 181 |
<div className="min-h-screen flex flex-col bg-[#000000] overflow-y-auto">
|
| 182 |
{/* Header - Apple style */}
|
| 183 |
<header className="flex items-center justify-between px-6 py-4 backdrop-blur-xl bg-[#000000]/80 border-b border-[#424245]/30 flex-shrink-0">
|
| 184 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
AnyCoder
|
| 186 |
-
</
|
| 187 |
|
| 188 |
{/* Auth Section */}
|
| 189 |
<div className="flex items-center space-x-3">
|
|
@@ -428,6 +446,63 @@ export default function LandingPage({
|
|
| 428 |
</div>
|
| 429 |
)}
|
| 430 |
</form>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
</div>
|
| 432 |
</main>
|
| 433 |
</div>
|
|
|
|
| 48 |
const [showModelDropdown, setShowModelDropdown] = useState(false);
|
| 49 |
const languageDropdownRef = useRef<HTMLDivElement>(null);
|
| 50 |
const modelDropdownRef = useRef<HTMLDivElement>(null);
|
| 51 |
+
|
| 52 |
+
// Trending apps state
|
| 53 |
+
const [trendingApps, setTrendingApps] = useState<any[]>([]);
|
| 54 |
|
| 55 |
useEffect(() => {
|
| 56 |
loadData();
|
| 57 |
handleOAuthInit();
|
| 58 |
+
loadTrendingApps();
|
| 59 |
// Check auth status periodically to catch OAuth redirects
|
| 60 |
const interval = setInterval(() => {
|
| 61 |
const authenticated = checkIsAuthenticated();
|
|
|
|
| 165 |
}
|
| 166 |
};
|
| 167 |
|
| 168 |
+
const loadTrendingApps = async () => {
|
| 169 |
+
try {
|
| 170 |
+
const apps = await apiClient.getTrendingAnycoderApps();
|
| 171 |
+
setTrendingApps(apps);
|
| 172 |
+
} catch (error) {
|
| 173 |
+
console.error('Failed to load trending apps:', error);
|
| 174 |
+
}
|
| 175 |
+
};
|
| 176 |
+
|
| 177 |
const handleSubmit = (e: React.FormEvent) => {
|
| 178 |
e.preventDefault();
|
| 179 |
if (prompt.trim() && isAuthenticated) {
|
|
|
|
| 194 |
<div className="min-h-screen flex flex-col bg-[#000000] overflow-y-auto">
|
| 195 |
{/* Header - Apple style */}
|
| 196 |
<header className="flex items-center justify-between px-6 py-4 backdrop-blur-xl bg-[#000000]/80 border-b border-[#424245]/30 flex-shrink-0">
|
| 197 |
+
<a
|
| 198 |
+
href="https://huggingface.co/spaces/akhaliq/anycoder"
|
| 199 |
+
target="_blank"
|
| 200 |
+
rel="noopener noreferrer"
|
| 201 |
+
className="text-sm font-medium text-[#f5f5f7] hover:text-white transition-colors"
|
| 202 |
+
>
|
| 203 |
AnyCoder
|
| 204 |
+
</a>
|
| 205 |
|
| 206 |
{/* Auth Section */}
|
| 207 |
<div className="flex items-center space-x-3">
|
|
|
|
| 446 |
</div>
|
| 447 |
)}
|
| 448 |
</form>
|
| 449 |
+
|
| 450 |
+
{/* Trending Apps Section */}
|
| 451 |
+
{trendingApps.length > 0 && (
|
| 452 |
+
<div className="mt-16">
|
| 453 |
+
<h3 className="text-2xl font-semibold text-white mb-6 text-center">
|
| 454 |
+
Top 6 Trending Apps Built with AnyCoder
|
| 455 |
+
</h3>
|
| 456 |
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
| 457 |
+
{trendingApps.map((app) => (
|
| 458 |
+
<a
|
| 459 |
+
key={app.id}
|
| 460 |
+
href={`https://huggingface.co/spaces/${app.id}`}
|
| 461 |
+
target="_blank"
|
| 462 |
+
rel="noopener noreferrer"
|
| 463 |
+
className="group bg-[#1d1d1f] border border-[#424245] rounded-xl p-5 hover:border-white/30 transition-all hover:shadow-xl hover:scale-[1.02]"
|
| 464 |
+
>
|
| 465 |
+
<div className="flex items-start justify-between mb-3">
|
| 466 |
+
<div className="flex-1 min-w-0">
|
| 467 |
+
<h4 className="text-sm font-medium text-[#f5f5f7] truncate group-hover:text-white transition-colors">
|
| 468 |
+
{app.id.split('/')[1]}
|
| 469 |
+
</h4>
|
| 470 |
+
<p className="text-xs text-[#86868b] mt-1">
|
| 471 |
+
by {app.id.split('/')[0]}
|
| 472 |
+
</p>
|
| 473 |
+
</div>
|
| 474 |
+
<div className="flex items-center gap-2 flex-shrink-0 ml-3">
|
| 475 |
+
<div className="flex items-center gap-1">
|
| 476 |
+
<svg className="w-3.5 h-3.5 text-[#86868b]" fill="currentColor" viewBox="0 0 20 20">
|
| 477 |
+
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
| 478 |
+
</svg>
|
| 479 |
+
<span className="text-xs text-[#86868b] font-medium">{app.likes}</span>
|
| 480 |
+
</div>
|
| 481 |
+
<div className="flex items-center gap-1">
|
| 482 |
+
<svg className="w-3.5 h-3.5 text-[#86868b]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 483 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
|
| 484 |
+
</svg>
|
| 485 |
+
<span className="text-xs text-[#86868b] font-medium">{app.trendingScore}</span>
|
| 486 |
+
</div>
|
| 487 |
+
</div>
|
| 488 |
+
</div>
|
| 489 |
+
<div className="flex flex-wrap gap-1.5">
|
| 490 |
+
<span className="px-2 py-0.5 bg-[#2d2d30] text-[#86868b] text-[10px] rounded-full font-medium">
|
| 491 |
+
{app.sdk}
|
| 492 |
+
</span>
|
| 493 |
+
{app.tags?.slice(0, 2).map((tag: string) =>
|
| 494 |
+
tag !== 'anycoder' && tag !== app.sdk && tag !== 'region:us' && (
|
| 495 |
+
<span key={tag} className="px-2 py-0.5 bg-[#2d2d30] text-[#86868b] text-[10px] rounded-full font-medium">
|
| 496 |
+
{tag}
|
| 497 |
+
</span>
|
| 498 |
+
)
|
| 499 |
+
)}
|
| 500 |
+
</div>
|
| 501 |
+
</a>
|
| 502 |
+
))}
|
| 503 |
+
</div>
|
| 504 |
+
</div>
|
| 505 |
+
)}
|
| 506 |
</div>
|
| 507 |
</main>
|
| 508 |
</div>
|
frontend/src/lib/api.ts
CHANGED
|
@@ -380,6 +380,26 @@ class ApiClient {
|
|
| 380 |
logout() {
|
| 381 |
this.token = null;
|
| 382 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
}
|
| 384 |
|
| 385 |
// Export singleton instance
|
|
|
|
| 380 |
logout() {
|
| 381 |
this.token = null;
|
| 382 |
}
|
| 383 |
+
|
| 384 |
+
async getTrendingAnycoderApps(): Promise<any[]> {
|
| 385 |
+
try {
|
| 386 |
+
// Fetch from HuggingFace API directly
|
| 387 |
+
const response = await axios.get('https://huggingface.co/api/spaces', {
|
| 388 |
+
timeout: 5000,
|
| 389 |
+
});
|
| 390 |
+
|
| 391 |
+
// Filter for apps with 'anycoder' tag and sort by trendingScore
|
| 392 |
+
const anycoderApps = response.data
|
| 393 |
+
.filter((space: any) => space.tags && space.tags.includes('anycoder'))
|
| 394 |
+
.sort((a: any, b: any) => (b.trendingScore || 0) - (a.trendingScore || 0))
|
| 395 |
+
.slice(0, 6);
|
| 396 |
+
|
| 397 |
+
return anycoderApps;
|
| 398 |
+
} catch (error) {
|
| 399 |
+
console.error('Failed to fetch trending anycoder apps:', error);
|
| 400 |
+
return [];
|
| 401 |
+
}
|
| 402 |
+
}
|
| 403 |
}
|
| 404 |
|
| 405 |
// Export singleton instance
|