mirror of
https://github.com/AeThex-Corporation/AeThex-OS.git
synced 2026-04-17 22:27:19 +00:00
modified: client/index.html
This commit is contained in:
parent
293d3c0d02
commit
4642d7a76a
18 changed files with 2103 additions and 64 deletions
|
|
@ -58,22 +58,29 @@ public class MainActivity extends BridgeActivity {
|
|||
|
||||
private void enableImmersiveMode() {
|
||||
View decorView = getWindow().getDecorView();
|
||||
|
||||
// Full immersive mode - hide everything
|
||||
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
||||
|
||||
WindowInsetsControllerCompat controller = WindowCompat.getInsetsController(getWindow(), decorView);
|
||||
if (controller != null) {
|
||||
// Hide both status and navigation bars
|
||||
// Hide BOTH status bar and navigation bar completely
|
||||
controller.hide(WindowInsetsCompat.Type.systemBars());
|
||||
// Make them sticky so they stay hidden
|
||||
// Swipe from edge to temporarily show bars
|
||||
controller.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||
}
|
||||
|
||||
// Additional flags for fullscreen
|
||||
// Set bars to transparent when they do show
|
||||
getWindow().setStatusBarColor(android.graphics.Color.TRANSPARENT);
|
||||
getWindow().setNavigationBarColor(android.graphics.Color.TRANSPARENT);
|
||||
|
||||
// Keep screen on + extend into cutout areas
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
getWindow().setFlags(
|
||||
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
|
||||
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
|
||||
);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
getWindow().getAttributes().layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<!-- Base application theme with Edge-to-Edge support -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="android:windowBackground">@color/splash_background</item>
|
||||
<item name="android:statusBarColor">@color/status_bar</item>
|
||||
<item name="android:navigationBarColor">@color/navigation_bar</item>
|
||||
<!-- Transparent system bars for edge-to-edge -->
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowTranslucentStatus">false</item>
|
||||
<item name="android:windowTranslucentNavigation">false</item>
|
||||
<item name="android:windowLightStatusBar">false</item>
|
||||
<item name="android:windowLightNavigationBar">false</item>
|
||||
<item name="android:enforceNavigationBarContrast">false</item>
|
||||
<item name="android:enforceStatusBarContrast">false</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:background">@null</item>
|
||||
<item name="android:statusBarColor">@color/status_bar</item>
|
||||
<item name="android:navigationBarColor">@color/navigation_bar</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowLightStatusBar">false</item>
|
||||
<item name="android:windowLightNavigationBar">false</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ const config: CapacitorConfig = {
|
|||
},
|
||||
StatusBar: {
|
||||
style: 'DARK',
|
||||
backgroundColor: '#0a0a0a',
|
||||
overlaysWebView: false
|
||||
backgroundColor: '#00000000',
|
||||
overlaysWebView: true
|
||||
},
|
||||
App: {
|
||||
backButtonEnabled: true
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
|
||||
|
||||
<!-- Samsung OneUI / Android Integration -->
|
||||
<meta name="theme-color" content="#0a0a0a" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
|
||||
<title>AeThex OS - Operating System for the Metaverse</title>
|
||||
<meta name="description" content="AeThex is the Operating System for the Metaverse. Join the network, earn credentials, and build the future with Axiom, Codex, and Aegis." />
|
||||
<meta name="keywords" content="metaverse, web3, architects, game development, digital ecosystem, AeThex, Aegis, Codex, Axiom" />
|
||||
|
|
|
|||
|
|
@ -164,4 +164,43 @@
|
|||
background-size: 100% 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Safe Area Utilities for Edge-to-Edge */
|
||||
.safe-top {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
}
|
||||
.safe-bottom {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
.safe-left {
|
||||
padding-left: env(safe-area-inset-left);
|
||||
}
|
||||
.safe-right {
|
||||
padding-right: env(safe-area-inset-right);
|
||||
}
|
||||
.safe-all {
|
||||
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
||||
}
|
||||
}
|
||||
|
||||
/* Edge-to-Edge support for Capacitor apps */
|
||||
html {
|
||||
/* Ensure we use viewport-fit=cover for edge-to-edge */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
/* Let content extend behind system bars */
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Ensure WebView fills entire viewport including notch/punch-hole areas */
|
||||
@supports (padding: env(safe-area-inset-top)) {
|
||||
html, body {
|
||||
min-height: 100vh;
|
||||
min-height: -webkit-fill-available;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,3 +10,62 @@ export const isEmbedded = (): boolean => {
|
|||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect if running on mobile device
|
||||
*/
|
||||
export const isMobileDevice = (): boolean => {
|
||||
return typeof window !== 'undefined' && window.innerWidth < 768;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get mobile theme based on Foundation/Corp mode
|
||||
* Retrieves theme from localStorage (set by OS) or defaults to Foundation
|
||||
*/
|
||||
export const getMobileTheme = () => {
|
||||
const clearanceMode = typeof localStorage !== 'undefined'
|
||||
? localStorage.getItem('aethex-clearance') || 'foundation'
|
||||
: 'foundation';
|
||||
|
||||
const isFoundation = clearanceMode === 'foundation';
|
||||
|
||||
return {
|
||||
mode: clearanceMode as 'foundation' | 'corp',
|
||||
isFoundation,
|
||||
// Colors
|
||||
primary: isFoundation ? 'rgb(220, 38, 38)' : 'rgb(59, 130, 246)',
|
||||
secondary: isFoundation ? 'rgb(212, 175, 55)' : 'rgb(148, 163, 184)',
|
||||
// Tailwind classes
|
||||
primaryClass: isFoundation ? 'text-red-500' : 'text-blue-500',
|
||||
secondaryClass: isFoundation ? 'text-amber-400' : 'text-slate-300',
|
||||
borderClass: isFoundation ? 'border-red-900/50' : 'border-blue-900/50',
|
||||
bgAccent: isFoundation ? 'bg-red-900/20' : 'bg-blue-900/20',
|
||||
iconClass: isFoundation ? 'text-red-400' : 'text-blue-400',
|
||||
activeBorder: isFoundation ? 'border-red-500' : 'border-blue-500',
|
||||
activeBtn: isFoundation ? 'bg-red-600' : 'bg-blue-600',
|
||||
hoverBtn: isFoundation ? 'hover:bg-red-700' : 'hover:bg-blue-700',
|
||||
gradientBg: isFoundation
|
||||
? 'linear-gradient(135deg, #0a0a0a 0%, #1a0505 50%, #0a0a0a 100%)'
|
||||
: 'linear-gradient(135deg, #0a0a0a 0%, #050a14 50%, #0a0a0a 100%)',
|
||||
cardBg: 'bg-zinc-900/80',
|
||||
inputBg: 'bg-zinc-800/80',
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook-like function to get mobile-aware styling
|
||||
* Returns desktop styling when not on mobile, mobile styling otherwise
|
||||
*/
|
||||
export const getResponsiveStyles = () => {
|
||||
const embedded = isEmbedded();
|
||||
const mobile = isMobileDevice();
|
||||
const theme = getMobileTheme();
|
||||
|
||||
return {
|
||||
embedded,
|
||||
mobile,
|
||||
theme,
|
||||
// Use mobile styles when embedded (in OS) or on mobile device
|
||||
useMobileStyles: embedded || mobile,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
Download,
|
||||
Loader2
|
||||
} from "lucide-react";
|
||||
import { isEmbedded, getResponsiveStyles } from "@/lib/embed-utils";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
|
||||
|
|
@ -128,6 +129,135 @@ export default function Analytics() {
|
|||
...activityData.map(d => Math.max(d.projects, d.messages, d.earnings / 50, d.achievements))
|
||||
);
|
||||
|
||||
const embedded = isEmbedded();
|
||||
const { useMobileStyles, theme } = getResponsiveStyles();
|
||||
|
||||
// Mobile-optimized layout when embedded or on mobile device
|
||||
if (useMobileStyles) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ background: theme.gradientBg }}>
|
||||
<div className="p-4 pb-20">
|
||||
{/* Mobile Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-xl ${theme.bgAccent} border ${theme.borderClass} flex items-center justify-center`}>
|
||||
<BarChart3 className={`w-5 h-5 ${theme.iconClass}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className={`${theme.primaryClass} font-bold text-lg`}>Analytics</h1>
|
||||
<p className="text-zinc-500 text-xs">Track your growth</p>
|
||||
</div>
|
||||
</div>
|
||||
<select
|
||||
value={timeRange}
|
||||
onChange={(e) => setTimeRange(e.target.value)}
|
||||
className={`px-3 py-1.5 ${theme.inputBg} border border-zinc-700 rounded-lg text-xs text-zinc-300`}
|
||||
>
|
||||
<option value="7d">7 days</option>
|
||||
<option value="30d">30 days</option>
|
||||
<option value="90d">90 days</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className={`w-6 h-6 ${theme.iconClass} animate-spin`} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stats Grid */}
|
||||
{!loading && (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-2 mb-6">
|
||||
{stats.slice(0, 4).map((stat, idx) => (
|
||||
<div key={idx} className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-3`}>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className={`p-2 bg-zinc-800 rounded-lg ${stat.color}`}>
|
||||
{stat.icon}
|
||||
</div>
|
||||
<span className="text-[10px] font-semibold text-green-400 bg-green-500/10 px-1.5 py-0.5 rounded">
|
||||
+{stat.change}%
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-[10px] text-zinc-500 mb-1">{stat.label}</p>
|
||||
<p className="text-lg font-bold text-white">{stat.value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Activity Chart */}
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-4 mb-4`}>
|
||||
<h3 className="text-white font-bold text-sm mb-4 flex items-center gap-2">
|
||||
<TrendingUp className={`w-4 h-4 ${theme.iconClass}`} />
|
||||
Weekly Activity
|
||||
</h3>
|
||||
<div className="flex items-end justify-between gap-1 h-20 mb-2">
|
||||
{activityData.map((data, idx) => (
|
||||
<div key={idx} className="flex-1 flex flex-col items-center">
|
||||
<div
|
||||
className={`w-full ${theme.isFoundation ? 'bg-gradient-to-t from-red-500/40 to-red-500' : 'bg-gradient-to-t from-blue-500/40 to-blue-500'} rounded-t`}
|
||||
style={{ height: `${(data.projects / maxValue) * 60}px` }}
|
||||
/>
|
||||
<p className="text-[8px] text-zinc-500 mt-1">{data.date}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Top Activities */}
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-4 mb-4`}>
|
||||
<h3 className="text-white font-bold text-sm mb-3 flex items-center gap-2">
|
||||
<Target className={`w-4 h-4 ${theme.iconClass}`} />
|
||||
Top Activities
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{topActivities.slice(0, 3).map((activity, idx) => (
|
||||
<div key={idx} className="bg-zinc-800/50 p-3 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-zinc-300">{activity.name}</span>
|
||||
<span className="text-[10px] text-green-400 font-semibold">{activity.growth}</span>
|
||||
</div>
|
||||
<div className="text-sm font-bold text-white mt-1">{activity.count}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Goals Progress */}
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-4`}>
|
||||
<h3 className="text-white font-bold text-sm mb-3 flex items-center gap-2">
|
||||
<Zap className={`w-4 h-4 ${theme.iconClass}`} />
|
||||
Goals
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
{ goal: "Complete 15 Projects", current: 12, target: 15 },
|
||||
{ goal: "Earn 5,000 LP", current: 2450, target: 5000 },
|
||||
{ goal: "Unlock 30 Achievements", current: 23, target: 30 }
|
||||
].map((item, idx) => (
|
||||
<div key={idx}>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-xs text-zinc-400">{item.goal}</span>
|
||||
<span className="text-[10px] text-zinc-500">{item.current}/{item.target}</span>
|
||||
</div>
|
||||
<div className="w-full bg-zinc-800 rounded-full h-1.5">
|
||||
<div
|
||||
className={`h-1.5 rounded-full ${theme.isFoundation ? 'bg-red-500' : 'bg-blue-500'}`}
|
||||
style={{ width: `${(item.current / item.target) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-slate-900 to-slate-950 text-slate-50 p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Link } from "wouter";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { ArrowLeft, TrendingUp, Code, Star, Eye, Heart, Share2, Loader2 } from "lucide-react";
|
||||
import { isEmbedded } from "@/lib/embed-utils";
|
||||
import { isEmbedded, getResponsiveStyles } from "@/lib/embed-utils";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
|
||||
|
|
@ -55,6 +55,120 @@ export default function CodeGallery() {
|
|||
};
|
||||
|
||||
const embedded = isEmbedded();
|
||||
const { useMobileStyles, theme } = getResponsiveStyles();
|
||||
|
||||
// Mobile-optimized layout when embedded or on mobile device
|
||||
if (useMobileStyles) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ background: theme.gradientBg }}>
|
||||
<div className="p-4 pb-20">
|
||||
{/* Mobile Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-xl ${theme.bgAccent} border ${theme.borderClass} flex items-center justify-center`}>
|
||||
<Code className={`w-5 h-5 ${theme.iconClass}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className={`${theme.primaryClass} font-bold text-lg`}>Code Gallery</h1>
|
||||
<p className="text-zinc-500 text-xs">{snippets.length} snippets</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button className={`${theme.activeBtn} ${theme.hoverBtn} gap-1`} size="sm">
|
||||
Share
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className={`w-6 h-6 ${theme.iconClass} animate-spin`} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Snippets List */}
|
||||
{!loading && (
|
||||
<div className="space-y-3">
|
||||
{snippets.length === 0 ? (
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-8 text-center`}>
|
||||
<Code className={`w-12 h-12 ${theme.iconClass} mx-auto mb-3 opacity-50`} />
|
||||
<p className="text-zinc-500 text-sm">No snippets yet</p>
|
||||
<p className="text-zinc-600 text-xs mt-1">Share your first code snippet</p>
|
||||
</div>
|
||||
) : (
|
||||
snippets.map((snippet) => (
|
||||
<div
|
||||
key={snippet.id}
|
||||
onClick={() => setSelectedSnippet(snippet)}
|
||||
className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-4 active:scale-[0.98] transition-all ${
|
||||
selectedSnippet?.id === snippet.id ? `border-2 ${theme.isFoundation ? 'border-red-500' : 'border-blue-500'}` : ''
|
||||
}`}
|
||||
>
|
||||
{/* Language & Category */}
|
||||
<div className="flex gap-2 mb-2">
|
||||
<span className={`${theme.isFoundation ? 'bg-red-500' : 'bg-blue-500'} text-white text-[10px] px-2 py-0.5 rounded`}>
|
||||
{snippet.language.toUpperCase()}
|
||||
</span>
|
||||
<span className="bg-purple-500 text-white text-[10px] px-2 py-0.5 rounded capitalize">
|
||||
{snippet.category}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Title & Author */}
|
||||
<h3 className="text-white font-bold text-sm mb-1">{snippet.title}</h3>
|
||||
<p className="text-zinc-400 text-xs mb-2">by {snippet.creator}</p>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="flex gap-4 text-xs text-zinc-500">
|
||||
<span className="flex items-center gap-1">
|
||||
<Eye className="w-3 h-3" /> {snippet.views}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Heart className="w-3 h-3" /> {snippet.likes}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Selected Snippet Preview */}
|
||||
{selectedSnippet && (
|
||||
<div className={`mt-4 ${theme.cardBg} border ${theme.borderClass} rounded-xl overflow-hidden`}>
|
||||
<div className={`px-4 py-3 border-b ${theme.borderClass} flex items-center justify-between`}>
|
||||
<span className="text-white font-medium text-sm">{selectedSnippet.title}</span>
|
||||
<button onClick={() => setSelectedSnippet(null)} className="text-zinc-400">✕</button>
|
||||
</div>
|
||||
|
||||
{/* Code Preview */}
|
||||
<div className="bg-zinc-950 p-4 font-mono text-xs text-zinc-300 overflow-x-auto max-h-40">
|
||||
{selectedSnippet.code}
|
||||
</div>
|
||||
|
||||
{/* Tags */}
|
||||
<div className="px-4 py-3 flex flex-wrap gap-1">
|
||||
{selectedSnippet.tags.map((tag) => (
|
||||
<span key={tag} className={`${theme.bgAccent} ${theme.primaryClass} text-[10px] px-2 py-0.5 rounded`}>
|
||||
#{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className={`px-4 py-3 border-t ${theme.borderClass} flex gap-2`}>
|
||||
<Button className={`flex-1 ${theme.activeBtn} ${theme.hoverBtn} gap-1 text-xs`} size="sm">
|
||||
<Heart className="w-3 h-3" /> Like
|
||||
</Button>
|
||||
<Button variant="outline" className="flex-1 border-zinc-700 text-zinc-300 gap-1 text-xs" size="sm">
|
||||
<Share2 className="w-3 h-3" /> Share
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 to-slate-800">
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { useState, useEffect } from "react";
|
|||
import { Link } from "wouter";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { ArrowLeft, FileText, Folder, Plus, Trash2, Download, Copy, Loader2 } from "lucide-react";
|
||||
import { isEmbedded } from "@/lib/embed-utils";
|
||||
import { ArrowLeft, FileText, Folder, Plus, Trash2, Download, Copy, Loader2, HardDrive } from "lucide-react";
|
||||
import { isEmbedded, getResponsiveStyles } from "@/lib/embed-utils";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import { nanoid } from "nanoid";
|
||||
|
|
@ -72,6 +72,115 @@ export default function FileManager() {
|
|||
};
|
||||
|
||||
const embedded = isEmbedded();
|
||||
const { useMobileStyles, theme } = getResponsiveStyles();
|
||||
|
||||
// Mobile-optimized layout when embedded or on mobile device
|
||||
if (useMobileStyles) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ background: theme.gradientBg }}>
|
||||
<div className="p-4 pb-20">
|
||||
{/* Mobile Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-xl ${theme.bgAccent} border ${theme.borderClass} flex items-center justify-center`}>
|
||||
<HardDrive className={`w-5 h-5 ${theme.iconClass}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className={`${theme.primaryClass} font-bold text-lg`}>File Manager</h1>
|
||||
<p className="text-zinc-500 text-xs">{files.length} files</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button className={`${theme.activeBtn} ${theme.hoverBtn} gap-1`} size="sm">
|
||||
<Plus className="w-4 h-4" /> New
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Breadcrumb */}
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl px-4 py-2 mb-4 text-xs text-zinc-400`}>
|
||||
<span className={theme.primaryClass}>root</span>
|
||||
{currentPath.split("/").filter(Boolean).map((part, idx) => (
|
||||
<span key={idx}> / <span className="text-zinc-400">{part}</span></span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className={`w-6 h-6 ${theme.iconClass} animate-spin`} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* File List */}
|
||||
{!loading && (
|
||||
<div className="space-y-2">
|
||||
{files.length === 0 ? (
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-8 text-center`}>
|
||||
<Folder className={`w-12 h-12 ${theme.iconClass} mx-auto mb-3 opacity-50`} />
|
||||
<p className="text-zinc-500 text-sm">No files yet</p>
|
||||
<p className="text-zinc-600 text-xs mt-1">Upload or create a new file</p>
|
||||
</div>
|
||||
) : (
|
||||
files.map((file) => (
|
||||
<div
|
||||
key={file.id}
|
||||
onClick={() => setSelectedFile(file)}
|
||||
className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-4 active:scale-[0.98] transition-all ${
|
||||
selectedFile?.id === file.id ? `border-2 ${theme.isFoundation ? 'border-red-500' : 'border-blue-500'}` : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
{file.type === "folder" ? (
|
||||
<Folder className={`w-8 h-8 ${theme.isFoundation ? 'text-red-400' : 'text-blue-400'}`} />
|
||||
) : (
|
||||
<FileText className="w-8 h-8 text-zinc-400" />
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-white font-medium text-sm truncate">{file.name}</p>
|
||||
<p className="text-[10px] text-zinc-500">
|
||||
{file.type === "file" && file.language && `${file.language.toUpperCase()} • `}
|
||||
{formatFileSize(file.size)} • {file.modified}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteFile(file.id);
|
||||
}}
|
||||
className="text-red-400 p-2"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Selected File Actions */}
|
||||
{selectedFile && selectedFile.type === "file" && (
|
||||
<div className={`fixed bottom-20 left-4 right-4 ${theme.cardBg} border ${theme.borderClass} rounded-xl p-4`}>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<p className="text-white font-medium text-sm">{selectedFile.name}</p>
|
||||
<p className="text-[10px] text-zinc-500">{formatFileSize(selectedFile.size)}</p>
|
||||
</div>
|
||||
<button onClick={() => setSelectedFile(null)} className="text-zinc-400">✕</button>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button className={`flex-1 ${theme.activeBtn} ${theme.hoverBtn} gap-1 text-xs`} size="sm">
|
||||
<Copy className="w-3 h-3" /> Copy
|
||||
</Button>
|
||||
<Button variant="outline" className="flex-1 border-zinc-700 text-zinc-300 gap-1 text-xs" size="sm">
|
||||
<Download className="w-3 h-3" /> Download
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col bg-slate-900">
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
ArrowLeft, ShoppingCart, Star, Plus, Loader2, Gamepad2,
|
||||
Zap, Trophy, Users, DollarSign, TrendingUp, Filter, Search
|
||||
} from "lucide-react";
|
||||
import { isEmbedded } from "@/lib/embed-utils";
|
||||
import { isEmbedded, getResponsiveStyles } from "@/lib/embed-utils";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
|
||||
|
|
@ -198,6 +198,155 @@ export default function GameMarketplace() {
|
|||
);
|
||||
|
||||
const embedded = isEmbedded();
|
||||
const { useMobileStyles, theme } = getResponsiveStyles();
|
||||
|
||||
// Mobile-optimized layout when embedded or on mobile device
|
||||
if (useMobileStyles) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ background: theme.gradientBg }}>
|
||||
<div className="p-4 pb-20">
|
||||
{/* Mobile Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-xl ${theme.bgAccent} border ${theme.borderClass} flex items-center justify-center`}>
|
||||
<Gamepad2 className={`w-5 h-5 ${theme.iconClass}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className={`${theme.primaryClass} font-bold text-lg`}>Game Shop</h1>
|
||||
<p className="text-zinc-500 text-xs">{items.length} items</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${theme.cardBg} px-3 py-1.5 rounded-lg border ${theme.borderClass} flex items-center gap-1`}>
|
||||
<DollarSign className="w-3 h-3 text-yellow-400" />
|
||||
<span className={`text-sm font-bold ${theme.primaryClass}`}>{wallet.balance} LP</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className="relative mb-4">
|
||||
<Search className="absolute left-3 top-2.5 w-4 h-4 text-zinc-500" />
|
||||
<input
|
||||
placeholder="Search games, assets..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className={`w-full pl-10 pr-4 py-2 ${theme.inputBg} border border-zinc-700 rounded-xl text-white text-sm`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Category Pills */}
|
||||
<div className="flex gap-2 overflow-x-auto pb-2 mb-4 scrollbar-hide">
|
||||
{["all", "game", "cosmetic", "pass", "asset"].map((cat) => (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => setSelectedCategory(cat)}
|
||||
className={`px-3 py-1.5 rounded-lg text-xs font-medium whitespace-nowrap transition-colors ${
|
||||
selectedCategory === cat
|
||||
? `${theme.activeBtn} text-white`
|
||||
: `${theme.cardBg} text-zinc-400 border ${theme.borderClass}`
|
||||
}`}
|
||||
>
|
||||
{cat === "all" ? "All" : cat.charAt(0).toUpperCase() + cat.slice(1) + "s"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Platform Pills */}
|
||||
<div className="flex gap-2 overflow-x-auto pb-2 mb-4 scrollbar-hide">
|
||||
{["all", "minecraft", "roblox", "steam", "meta"].map((platform) => (
|
||||
<button
|
||||
key={platform}
|
||||
onClick={() => setSelectedPlatform(platform)}
|
||||
className={`px-3 py-1 rounded-lg text-[10px] font-medium capitalize whitespace-nowrap ${
|
||||
selectedPlatform === platform
|
||||
? `${theme.bgAccent} ${theme.primaryClass}`
|
||||
: 'bg-zinc-800 text-zinc-500'
|
||||
}`}
|
||||
>
|
||||
{platform === "all" ? "All" : platform}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className={`w-6 h-6 ${theme.iconClass} animate-spin`} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Items Grid */}
|
||||
{!loading && (
|
||||
<div className="space-y-3">
|
||||
{filteredItems.length === 0 ? (
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-8 text-center`}>
|
||||
<Gamepad2 className={`w-12 h-12 ${theme.iconClass} mx-auto mb-3 opacity-50`} />
|
||||
<p className="text-zinc-500 text-sm">No items found</p>
|
||||
</div>
|
||||
) : (
|
||||
filteredItems.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`${theme.cardBg} border ${theme.borderClass} rounded-xl overflow-hidden active:scale-[0.98] transition-transform`}
|
||||
>
|
||||
{/* Image */}
|
||||
<div className="h-24 bg-gradient-to-br from-zinc-700 to-zinc-900 flex items-center justify-center text-4xl">
|
||||
{item.image}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-4">
|
||||
{/* Badges */}
|
||||
<div className="flex gap-2 mb-2">
|
||||
<span className={`text-[10px] font-bold px-2 py-0.5 rounded capitalize ${
|
||||
item.type === "game" ? "bg-purple-500/20 text-purple-400" :
|
||||
item.type === "cosmetic" ? "bg-pink-500/20 text-pink-400" :
|
||||
item.type === "pass" ? `${theme.bgAccent} ${theme.primaryClass}` :
|
||||
"bg-green-500/20 text-green-400"
|
||||
}`}>
|
||||
{item.type}
|
||||
</span>
|
||||
<span className="text-[10px] text-zinc-500 bg-zinc-800 px-2 py-0.5 rounded capitalize">
|
||||
{item.platform}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Name */}
|
||||
<h3 className="text-white font-bold text-sm mb-1 line-clamp-1">{item.name}</h3>
|
||||
<p className="text-[10px] text-zinc-500 mb-2 line-clamp-1">{item.description}</p>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="flex items-center gap-3 mb-3 text-[10px] text-zinc-500">
|
||||
<span className="flex items-center gap-1">
|
||||
<Star className="w-3 h-3 text-yellow-400 fill-yellow-400" /> {item.rating}
|
||||
</span>
|
||||
<span>{item.purchases.toLocaleString()} sold</span>
|
||||
</div>
|
||||
|
||||
{/* Price & Buy */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className={`text-lg font-bold ${theme.primaryClass}`}>
|
||||
{item.price}
|
||||
<span className="text-xs text-zinc-500 ml-1">LP</span>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => handlePurchase(item)}
|
||||
className={`${theme.activeBtn} ${theme.hoverBtn} gap-1 text-xs`}
|
||||
size="sm"
|
||||
disabled={wallet.balance < item.price}
|
||||
>
|
||||
<ShoppingCart className="w-3 h-3" /> Buy
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 to-slate-950 text-white">
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import { Card } from "@/components/ui/card";
|
|||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
ArrowLeft, Radio, Eye, Heart, MessageCircle, Share2,
|
||||
Twitch, Youtube, Play, Clock, Users, TrendingUp, Filter, Search
|
||||
Twitch, Youtube, Play, Clock, Users, TrendingUp, Filter, Search, Loader2
|
||||
} from "lucide-react";
|
||||
import { isEmbedded } from "@/lib/embed-utils";
|
||||
import { isEmbedded, getResponsiveStyles } from "@/lib/embed-utils";
|
||||
|
||||
interface Stream {
|
||||
id: string;
|
||||
|
|
@ -182,6 +182,156 @@ export default function GameStreaming() {
|
|||
const recordedStreams = filteredStreams.filter(s => !s.isLive);
|
||||
|
||||
const embedded = isEmbedded();
|
||||
const { useMobileStyles, theme } = getResponsiveStyles();
|
||||
|
||||
// Mobile-optimized layout when embedded or on mobile device
|
||||
if (useMobileStyles) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ background: theme.gradientBg }}>
|
||||
<div className="p-4 pb-20">
|
||||
{/* Mobile Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-xl ${theme.bgAccent} border ${theme.borderClass} flex items-center justify-center`}>
|
||||
<Radio className={`w-5 h-5 ${theme.iconClass}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className={`${theme.primaryClass} font-bold text-lg`}>Streaming</h1>
|
||||
<p className="text-zinc-500 text-xs">{liveStreams.length} live now</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className="relative mb-4">
|
||||
<Search className="absolute left-3 top-2.5 w-4 h-4 text-zinc-500" />
|
||||
<input
|
||||
placeholder="Search streams..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className={`w-full pl-10 pr-4 py-2 ${theme.inputBg} border border-zinc-700 rounded-xl text-white text-sm`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Platform Pills */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
{(["all", "twitch", "youtube"] as const).map((platform) => (
|
||||
<button
|
||||
key={platform}
|
||||
onClick={() => setSelectedPlatform(platform)}
|
||||
className={`px-3 py-1.5 rounded-lg text-xs font-medium capitalize ${
|
||||
selectedPlatform === platform
|
||||
? `${theme.activeBtn} text-white`
|
||||
: `${theme.cardBg} text-zinc-400 border ${theme.borderClass}`
|
||||
}`}
|
||||
>
|
||||
{platform === "all" ? "All" : platform}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-2 gap-2 mb-6">
|
||||
{mockStats.slice(0, 2).map((stat, idx) => (
|
||||
<div key={idx} className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-3`}>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-[10px] text-zinc-500 uppercase">{stat.label}</span>
|
||||
{stat.icon}
|
||||
</div>
|
||||
<div className="text-lg font-bold text-white">{stat.value}</div>
|
||||
{stat.change && <span className="text-[10px] text-green-400">{stat.change}</span>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className={`w-6 h-6 ${theme.iconClass} animate-spin`} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Live Streams */}
|
||||
{!loading && liveStreams.length > 0 && (
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Radio className="w-4 h-4 text-red-500 animate-pulse" />
|
||||
<h2 className="text-white font-bold text-sm">Live Now ({liveStreams.length})</h2>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{liveStreams.map((stream) => (
|
||||
<div
|
||||
key={stream.id}
|
||||
className={`${theme.cardBg} border-2 border-red-500/30 rounded-xl overflow-hidden active:scale-[0.98] transition-transform`}
|
||||
>
|
||||
{/* Thumbnail */}
|
||||
<div className="relative h-32 bg-gradient-to-br from-zinc-700 to-zinc-900 flex items-center justify-center text-3xl">
|
||||
{stream.thumbnail}
|
||||
<div className="absolute top-2 left-2 bg-red-500 text-white text-[10px] px-2 py-0.5 rounded font-bold flex items-center gap-1">
|
||||
<Radio className="w-2 h-2 animate-pulse" /> LIVE
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
<div className="p-3">
|
||||
<h3 className="text-white font-bold text-sm mb-1 line-clamp-2">{stream.title}</h3>
|
||||
<p className="text-[10px] text-zinc-400 mb-2">{stream.channel} • {stream.game}</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="flex items-center gap-1 text-[10px] text-zinc-500">
|
||||
<Eye className="w-3 h-3" /> {stream.viewers.toLocaleString()}
|
||||
</span>
|
||||
<Button className={`${theme.activeBtn} ${theme.hoverBtn} gap-1 text-xs`} size="sm">
|
||||
<Play className="w-3 h-3" /> Watch
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Recorded Streams */}
|
||||
{!loading && recordedStreams.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-white font-bold text-sm mb-3">Recorded</h2>
|
||||
<div className="space-y-2">
|
||||
{recordedStreams.map((stream) => (
|
||||
<div
|
||||
key={stream.id}
|
||||
className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-3 active:scale-[0.98] transition-transform`}
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
<div className="w-20 h-14 bg-gradient-to-br from-zinc-700 to-zinc-900 rounded flex items-center justify-center text-xl flex-shrink-0 relative">
|
||||
{stream.thumbnail}
|
||||
{stream.duration && (
|
||||
<div className="absolute bottom-1 right-1 bg-black/80 text-white text-[8px] px-1 rounded">
|
||||
{stream.duration}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-white font-medium text-xs mb-1 line-clamp-2">{stream.title}</h3>
|
||||
<p className="text-[10px] text-zinc-500">{stream.channel}</p>
|
||||
<div className="flex items-center gap-2 mt-1 text-[10px] text-zinc-500">
|
||||
<span className="flex items-center gap-1">
|
||||
<Eye className="w-2 h-2" /> {stream.viewers.toLocaleString()}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Heart className="w-2 h-2" /> {stream.likes.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 to-slate-950 text-white">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
Trash2, Award, User, Calendar, Search, Filter, Plus, Loader2,
|
||||
Package, AlertCircle, CheckCircle
|
||||
} from "lucide-react";
|
||||
import { isEmbedded } from "@/lib/embed-utils";
|
||||
import { isEmbedded, getResponsiveStyles } from "@/lib/embed-utils";
|
||||
|
||||
interface Mod {
|
||||
id: string;
|
||||
|
|
@ -201,6 +201,233 @@ export default function ModWorkshop() {
|
|||
const games = ["all", "Minecraft", "Roblox", "Steam Games", "All Games"];
|
||||
|
||||
const embedded = isEmbedded();
|
||||
const { useMobileStyles, theme } = getResponsiveStyles();
|
||||
|
||||
// Mobile-optimized layout when embedded or on mobile device
|
||||
if (useMobileStyles) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ background: theme.gradientBg }}>
|
||||
<div className="p-4 pb-20">
|
||||
{/* Mobile Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-xl ${theme.bgAccent} border ${theme.borderClass} flex items-center justify-center`}>
|
||||
<Package className={`w-5 h-5 ${theme.iconClass}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className={`${theme.primaryClass} font-bold text-lg`}>Mod Workshop</h1>
|
||||
<p className="text-zinc-500 text-xs">{sortedMods.length} mods</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setShowUploadModal(true)}
|
||||
className={`${theme.activeBtn} ${theme.hoverBtn} gap-1`}
|
||||
size="sm"
|
||||
>
|
||||
<Upload className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className="relative mb-4">
|
||||
<Search className="absolute left-3 top-2.5 w-4 h-4 text-zinc-500" />
|
||||
<input
|
||||
placeholder="Search mods..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className={`w-full pl-10 pr-4 py-2 ${theme.inputBg} border border-zinc-700 rounded-xl text-white text-sm`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Category Pills */}
|
||||
<div className="flex gap-2 overflow-x-auto pb-2 mb-4 scrollbar-hide">
|
||||
{(["all", "gameplay", "cosmetic", "utility", "enhancement"] as const).map((cat) => (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => setSelectedCategory(cat)}
|
||||
className={`px-3 py-1.5 rounded-lg text-xs font-medium whitespace-nowrap transition-colors capitalize ${
|
||||
selectedCategory === cat
|
||||
? `${theme.activeBtn} text-white`
|
||||
: `${theme.cardBg} text-zinc-400 border ${theme.borderClass}`
|
||||
}`}
|
||||
>
|
||||
{cat === "all" ? "All" : cat}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Sort & Game Filter */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(e.target.value as any)}
|
||||
className={`flex-1 px-3 py-2 ${theme.inputBg} border border-zinc-700 rounded-lg text-white text-xs`}
|
||||
>
|
||||
<option value="trending">Trending</option>
|
||||
<option value="newest">Newest</option>
|
||||
<option value="popular">Most Downloaded</option>
|
||||
<option value="rating">Highest Rated</option>
|
||||
</select>
|
||||
<select
|
||||
value={selectedGame}
|
||||
onChange={(e) => setSelectedGame(e.target.value)}
|
||||
className={`flex-1 px-3 py-2 ${theme.inputBg} border border-zinc-700 rounded-lg text-white text-xs`}
|
||||
>
|
||||
{games.map(game => (
|
||||
<option key={game} value={game}>
|
||||
{game === "all" ? "All Games" : game}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Mods Grid */}
|
||||
<div className="space-y-3">
|
||||
{sortedMods.length === 0 ? (
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-8 text-center`}>
|
||||
<Package className={`w-12 h-12 ${theme.iconClass} mx-auto mb-3 opacity-50`} />
|
||||
<p className="text-zinc-500 text-sm">No mods found</p>
|
||||
</div>
|
||||
) : (
|
||||
sortedMods.map((mod) => (
|
||||
<div
|
||||
key={mod.id}
|
||||
className={`${theme.cardBg} border ${theme.borderClass} rounded-xl overflow-hidden active:scale-[0.98] transition-transform`}
|
||||
>
|
||||
{/* Image */}
|
||||
<div className="h-24 bg-gradient-to-br from-zinc-700 to-zinc-900 flex items-center justify-center text-4xl relative">
|
||||
{mod.image}
|
||||
<div className={`absolute top-2 right-2 px-2 py-0.5 rounded text-[10px] font-bold flex items-center gap-1 ${
|
||||
mod.status === "approved" ? "bg-green-500/20 text-green-400" :
|
||||
mod.status === "reviewing" ? "bg-yellow-500/20 text-yellow-400" :
|
||||
"bg-red-500/20 text-red-400"
|
||||
}`}>
|
||||
{mod.status === "approved" ? <CheckCircle className="w-2 h-2" /> : <AlertCircle className="w-2 h-2" />}
|
||||
{mod.status}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-4">
|
||||
{/* Badges */}
|
||||
<div className="flex gap-2 mb-2">
|
||||
<span className={`text-[10px] font-bold px-2 py-0.5 rounded capitalize ${
|
||||
mod.category === "gameplay" ? "bg-purple-500/20 text-purple-400" :
|
||||
mod.category === "cosmetic" ? "bg-pink-500/20 text-pink-400" :
|
||||
mod.category === "utility" ? `${theme.bgAccent} ${theme.primaryClass}` :
|
||||
"bg-green-500/20 text-green-400"
|
||||
}`}>
|
||||
{mod.category}
|
||||
</span>
|
||||
<span className="text-[10px] text-zinc-500 bg-zinc-800 px-2 py-0.5 rounded">
|
||||
v{mod.version}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Name & Author */}
|
||||
<h3 className="text-white font-bold text-sm mb-1 line-clamp-1">{mod.name}</h3>
|
||||
<p className="text-[10px] text-zinc-400 flex items-center gap-1 mb-2">
|
||||
<User className="w-2 h-2" /> {mod.author}
|
||||
</p>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-2 gap-2 mb-3 text-[10px]">
|
||||
<div className="bg-zinc-800/50 p-2 rounded">
|
||||
<div className="text-zinc-500">Rating</div>
|
||||
<div className="font-bold text-yellow-400">{mod.rating} ⭐</div>
|
||||
</div>
|
||||
<div className="bg-zinc-800/50 p-2 rounded">
|
||||
<div className="text-zinc-500">Downloads</div>
|
||||
<div className={`font-bold ${theme.primaryClass}`}>{(mod.downloads / 1000).toFixed(0)}K</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Button */}
|
||||
<Button className={`w-full ${theme.activeBtn} ${theme.hoverBtn} gap-1 text-xs`} size="sm">
|
||||
<Download className="w-3 h-3" /> Download ({mod.fileSize})
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upload Modal */}
|
||||
{showUploadModal && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl w-full max-w-sm p-4`}>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-white font-bold text-sm">Upload Mod</h2>
|
||||
<button onClick={() => setShowUploadModal(false)} className="text-zinc-400">✕</button>
|
||||
</div>
|
||||
|
||||
{uploadStatus === "idle" && (
|
||||
<div className="space-y-3">
|
||||
<div
|
||||
className={`border-2 border-dashed ${theme.borderClass} rounded-xl p-6 text-center cursor-pointer`}
|
||||
onClick={handleUploadClick}
|
||||
>
|
||||
<Upload className={`w-6 h-6 mx-auto mb-2 ${theme.iconClass}`} />
|
||||
<p className="text-xs text-zinc-400">Tap to select mod file</p>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".zip,.rar"
|
||||
onChange={handleFileChange}
|
||||
className="hidden"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Mod Title"
|
||||
className={`w-full px-3 py-2 ${theme.inputBg} border border-zinc-700 rounded-lg text-white text-xs`}
|
||||
/>
|
||||
<textarea
|
||||
placeholder="Description..."
|
||||
rows={2}
|
||||
className={`w-full px-3 py-2 ${theme.inputBg} border border-zinc-700 rounded-lg text-white text-xs resize-none`}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={() => setShowUploadModal(false)} variant="outline" className="flex-1 border-zinc-700 text-xs" size="sm">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={() => setUploadStatus("uploading")} className={`flex-1 ${theme.activeBtn} text-xs`} size="sm">
|
||||
Upload
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{uploadStatus === "uploading" && (
|
||||
<div className="text-center py-6">
|
||||
<Loader2 className={`w-6 h-6 mx-auto mb-3 animate-spin ${theme.iconClass}`} />
|
||||
<p className="text-zinc-400 text-xs">Uploading...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{uploadStatus === "success" && (
|
||||
<div className="text-center py-6">
|
||||
<CheckCircle className="w-6 h-6 mx-auto mb-3 text-green-400" />
|
||||
<p className="text-white text-xs font-bold">Upload Complete!</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{uploadStatus === "error" && (
|
||||
<div className="text-center py-6">
|
||||
<AlertCircle className="w-6 h-6 mx-auto mb-3 text-red-400" />
|
||||
<p className="text-white text-xs font-bold">Upload Failed</p>
|
||||
<Button onClick={() => setUploadStatus("idle")} className={`mt-3 ${theme.activeBtn} text-xs`} size="sm">
|
||||
Try Again
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 to-slate-950 text-white">
|
||||
|
|
@ -290,6 +517,7 @@ export default function ModWorkshop() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mods Grid */}
|
||||
<div className="max-w-7xl mx-auto p-4 md:p-6">
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Input } from "@/components/ui/input";
|
|||
import { Card } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { ArrowLeft, ShoppingCart, Star, Plus, Loader2 } from "lucide-react";
|
||||
import { isEmbedded } from "@/lib/embed-utils";
|
||||
import { isEmbedded, getResponsiveStyles } from "@/lib/embed-utils";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
|
||||
|
|
@ -77,6 +77,106 @@ export default function Marketplace() {
|
|||
};
|
||||
|
||||
const embedded = isEmbedded();
|
||||
const { useMobileStyles, theme } = getResponsiveStyles();
|
||||
|
||||
// Mobile-optimized layout when embedded or on mobile device
|
||||
if (useMobileStyles) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ background: theme.gradientBg }}>
|
||||
<div className="p-4 pb-20">
|
||||
{/* Mobile Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-xl ${theme.bgAccent} border ${theme.borderClass} flex items-center justify-center`}>
|
||||
<ShoppingCart className={`w-5 h-5 ${theme.iconClass}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className={`${theme.primaryClass} font-bold text-lg`}>Marketplace</h1>
|
||||
<p className="text-zinc-500 text-xs">{listings.length} items</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${theme.cardBg} px-3 py-1.5 rounded-lg border ${theme.borderClass}`}>
|
||||
<p className={`text-sm font-bold ${theme.primaryClass}`}>{balance} LP</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Category Pills */}
|
||||
<div className="flex gap-2 overflow-x-auto pb-2 mb-4 scrollbar-hide">
|
||||
{["all", "code", "achievement", "service", "credential"].map((cat) => (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => setSelectedCategory(cat)}
|
||||
className={`px-3 py-1.5 rounded-lg text-xs font-medium whitespace-nowrap transition-colors ${
|
||||
selectedCategory === cat
|
||||
? `${theme.activeBtn} text-white`
|
||||
: `${theme.cardBg} text-zinc-400 border ${theme.borderClass}`
|
||||
}`}
|
||||
>
|
||||
{cat === "all" ? "All" : cat.charAt(0).toUpperCase() + cat.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className={`w-6 h-6 ${theme.iconClass} animate-spin`} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Listings Grid */}
|
||||
{!loading && (
|
||||
<div className="space-y-3">
|
||||
{filteredListings.length === 0 ? (
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-8 text-center`}>
|
||||
<ShoppingCart className={`w-12 h-12 ${theme.iconClass} mx-auto mb-3 opacity-50`} />
|
||||
<p className="text-zinc-500 text-sm">No items found</p>
|
||||
</div>
|
||||
) : (
|
||||
filteredListings.map((listing) => (
|
||||
<div
|
||||
key={listing.id}
|
||||
className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-4 active:scale-[0.98] transition-transform`}
|
||||
>
|
||||
{/* Category Badge */}
|
||||
<div className="mb-2">
|
||||
<span className={`${getCategoryColor(listing.category)} text-white text-[10px] font-bold px-2 py-0.5 rounded capitalize`}>
|
||||
{listing.category}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="text-white font-bold text-sm mb-1">{listing.title}</h3>
|
||||
<p className="text-zinc-400 text-xs mb-2">by {listing.seller}</p>
|
||||
|
||||
{/* Rating & Stats */}
|
||||
<div className="flex items-center gap-3 mb-3 text-xs text-zinc-500">
|
||||
<span className="flex items-center gap-1">
|
||||
<Star className="w-3 h-3 text-yellow-400 fill-yellow-400" />
|
||||
{listing.rating}
|
||||
</span>
|
||||
<span>{listing.purchases} sold</span>
|
||||
</div>
|
||||
|
||||
{/* Price & Buy */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className={`text-lg font-bold ${theme.primaryClass}`}>
|
||||
{listing.price}
|
||||
<span className="text-xs text-zinc-500 ml-1">LP</span>
|
||||
</div>
|
||||
<Button className={`${theme.activeBtn} ${theme.hoverBtn} gap-1 text-xs`} size="sm">
|
||||
<ShoppingCart className="w-3 h-3" /> Buy
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 to-slate-800">
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import { Link, useLocation } from "wouter";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { ArrowLeft, Send, Search, Loader2 } from "lucide-react";
|
||||
import { ArrowLeft, Send, Search, Loader2, MessageCircle } from "lucide-react";
|
||||
import { MobileHeader } from "@/components/mobile/MobileHeader";
|
||||
import { isEmbedded } from "@/lib/embed-utils";
|
||||
import { isEmbedded, getResponsiveStyles } from "@/lib/embed-utils";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import { nanoid } from "nanoid";
|
||||
|
|
@ -98,6 +98,127 @@ export default function Messaging() {
|
|||
);
|
||||
|
||||
const embedded = isEmbedded();
|
||||
const { useMobileStyles, theme } = getResponsiveStyles();
|
||||
|
||||
// Mobile-optimized layout when embedded or on mobile device
|
||||
if (useMobileStyles) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ background: theme.gradientBg }}>
|
||||
<div className="p-4 pb-20">
|
||||
{/* Mobile Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-xl ${theme.bgAccent} border ${theme.borderClass} flex items-center justify-center`}>
|
||||
<MessageCircle className={`w-5 h-5 ${theme.iconClass}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className={`${theme.primaryClass} font-bold text-lg`}>Messages</h1>
|
||||
<p className="text-zinc-500 text-xs">{chats.length} conversations</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className="relative mb-4">
|
||||
<Search className="absolute left-3 top-2.5 w-4 h-4 text-zinc-500" />
|
||||
<Input
|
||||
placeholder="Search conversations..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className={`${theme.inputBg} border-zinc-700 text-white pl-10 text-sm`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className={`w-6 h-6 ${theme.iconClass} animate-spin`} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Chat List */}
|
||||
{!loading && (
|
||||
<div className="space-y-2">
|
||||
{filteredChats.length === 0 ? (
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-8 text-center`}>
|
||||
<MessageCircle className={`w-12 h-12 ${theme.iconClass} mx-auto mb-3 opacity-50`} />
|
||||
<p className="text-zinc-500 text-sm">No conversations yet</p>
|
||||
<p className="text-zinc-600 text-xs mt-1">Start a new conversation</p>
|
||||
</div>
|
||||
) : (
|
||||
filteredChats.map((chat) => (
|
||||
<button
|
||||
key={chat.id}
|
||||
onClick={() => setSelectedChatId(chat.id)}
|
||||
className={`w-full text-left ${theme.cardBg} border ${theme.borderClass} rounded-xl p-4 active:scale-[0.98] transition-all ${
|
||||
selectedChatId === chat.id ? `border-2 ${theme.isFoundation ? 'border-red-500' : 'border-blue-500'}` : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-full ${theme.bgAccent} flex items-center justify-center`}>
|
||||
<span className="text-white font-bold text-sm">{chat.username[0]}</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-white font-medium text-sm">{chat.username}</span>
|
||||
<span className="text-xs text-zinc-500">{chat.timestamp}</span>
|
||||
</div>
|
||||
<p className={`text-xs truncate ${chat.unread ? 'text-white font-semibold' : 'text-zinc-400'}`}>
|
||||
{chat.lastMessage}
|
||||
</p>
|
||||
</div>
|
||||
{chat.unread && (
|
||||
<div className={`w-2 h-2 rounded-full ${theme.isFoundation ? 'bg-red-500' : 'bg-blue-500'}`} />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Selected Chat Messages */}
|
||||
{selectedChat && (
|
||||
<div className={`mt-4 ${theme.cardBg} border ${theme.borderClass} rounded-xl overflow-hidden`}>
|
||||
<div className={`px-4 py-3 border-b ${theme.borderClass} flex items-center justify-between`}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-white font-medium text-sm">{selectedChat.username}</span>
|
||||
<span className="text-xs text-green-400">Online</span>
|
||||
</div>
|
||||
<button onClick={() => setSelectedChatId("")} className="text-zinc-400">✕</button>
|
||||
</div>
|
||||
<div className="h-48 overflow-y-auto p-4 space-y-3">
|
||||
{messages.map((msg) => (
|
||||
<div key={msg.id} className={`flex ${msg.isOwn ? 'justify-end' : 'justify-start'}`}>
|
||||
<div className={`max-w-[80%] px-3 py-2 rounded-lg text-xs ${
|
||||
msg.isOwn
|
||||
? `${theme.isFoundation ? 'bg-red-600' : 'bg-blue-600'} text-white`
|
||||
: 'bg-zinc-700 text-zinc-100'
|
||||
}`}>
|
||||
<p>{msg.content}</p>
|
||||
<span className="text-[10px] opacity-70 mt-1 block">{msg.timestamp}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className={`p-3 border-t ${theme.borderClass} flex gap-2`}>
|
||||
<Input
|
||||
placeholder="Type a message..."
|
||||
value={messageInput}
|
||||
onChange={(e) => setMessageInput(e.target.value)}
|
||||
onKeyPress={(e) => e.key === "Enter" && handleSendMessage()}
|
||||
className={`${theme.inputBg} border-zinc-700 text-white text-sm flex-1`}
|
||||
/>
|
||||
<Button onClick={handleSendMessage} className={`${theme.activeBtn} ${theme.hoverBtn} px-3`} size="sm">
|
||||
<Send className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col bg-slate-900">
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Button } from "@/components/ui/button";
|
|||
import { Card } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Bell, Check, Trash2, Filter, CheckCircle, Loader2 } from "lucide-react";
|
||||
import { isEmbedded, getResponsiveStyles } from "@/lib/embed-utils";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
|
||||
|
|
@ -122,6 +123,148 @@ export default function Notifications() {
|
|||
return date.toLocaleDateString();
|
||||
};
|
||||
|
||||
const embedded = isEmbedded();
|
||||
const { useMobileStyles, theme } = getResponsiveStyles();
|
||||
|
||||
// Mobile-optimized layout when embedded or on mobile device
|
||||
if (useMobileStyles) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ background: theme.gradientBg }}>
|
||||
<div className="p-4 pb-20">
|
||||
{/* Mobile Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-xl ${theme.bgAccent} border ${theme.borderClass} flex items-center justify-center`}>
|
||||
<Bell className={`w-5 h-5 ${theme.iconClass}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className={`${theme.primaryClass} font-bold text-lg`}>Notifications</h1>
|
||||
<p className="text-zinc-500 text-xs">
|
||||
{unreadCount > 0 ? `${unreadCount} unread` : "All caught up!"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{unreadCount > 0 && (
|
||||
<Button
|
||||
onClick={handleMarkAllAsRead}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className={`border ${theme.borderClass} text-xs gap-1`}
|
||||
>
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
All
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Filter Pills */}
|
||||
<div className="flex gap-2 overflow-x-auto pb-2 mb-4 scrollbar-hide">
|
||||
{[
|
||||
{ key: null, label: "All" },
|
||||
{ key: "achievement", label: "🏆" },
|
||||
{ key: "message", label: "💬" },
|
||||
{ key: "event", label: "📅" },
|
||||
{ key: "marketplace", label: "🛍️" }
|
||||
].map((filter) => (
|
||||
<button
|
||||
key={filter.key || "all"}
|
||||
onClick={() => setFilterType(filter.key)}
|
||||
className={`px-3 py-1.5 rounded-lg text-xs font-medium whitespace-nowrap transition-colors ${
|
||||
filterType === filter.key
|
||||
? `${theme.activeBtn} text-white`
|
||||
: `${theme.cardBg} text-zinc-400 border ${theme.borderClass}`
|
||||
}`}
|
||||
>
|
||||
{filter.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className={`w-6 h-6 ${theme.iconClass} animate-spin`} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Notifications List */}
|
||||
{!loading && (
|
||||
<div className="space-y-2">
|
||||
{filteredNotifications.length === 0 ? (
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-8 text-center`}>
|
||||
<Bell className={`w-12 h-12 ${theme.iconClass} mx-auto mb-3 opacity-50`} />
|
||||
<p className="text-zinc-500 text-sm">No notifications</p>
|
||||
</div>
|
||||
) : (
|
||||
filteredNotifications.map((notification) => (
|
||||
<div
|
||||
key={notification.id}
|
||||
className={`${theme.cardBg} border-l-4 ${
|
||||
notification.read
|
||||
? `border ${theme.borderClass} border-l-zinc-600`
|
||||
: `border ${theme.borderClass} ${theme.isFoundation ? 'border-l-red-400' : 'border-l-blue-400'}`
|
||||
} rounded-xl p-4 active:scale-[0.98] transition-all`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="text-xl mt-0.5">{getTypeIcon(notification.type)}</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="text-white font-medium text-sm">{notification.title}</h3>
|
||||
{!notification.read && (
|
||||
<span className={`w-2 h-2 rounded-full ${theme.isFoundation ? 'bg-red-400' : 'bg-blue-400'}`} />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-zinc-400 text-xs mb-2 line-clamp-2">{notification.description}</p>
|
||||
<p className="text-zinc-600 text-[10px]">{formatTime(notification.timestamp)}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{!notification.read && (
|
||||
<button
|
||||
onClick={() => handleMarkAsRead(notification.id)}
|
||||
className={`p-1.5 rounded ${theme.bgAccent}`}
|
||||
>
|
||||
<Check className={`w-3 h-3 ${theme.iconClass}`} />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleDelete(notification.id)}
|
||||
className="p-1.5 rounded bg-red-500/10"
|
||||
>
|
||||
<Trash2 className="w-3 h-3 text-red-400" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Notification Preferences */}
|
||||
<div className={`mt-6 ${theme.cardBg} border ${theme.borderClass} rounded-xl p-4`}>
|
||||
<h3 className="text-white font-bold text-sm mb-3 flex items-center gap-2">
|
||||
<Filter className={`w-4 h-4 ${theme.iconClass}`} />
|
||||
Preferences
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
{ label: "Achievement Notifications", enabled: true },
|
||||
{ label: "Message Alerts", enabled: true },
|
||||
{ label: "Event Reminders", enabled: true },
|
||||
{ label: "Marketplace Updates", enabled: false }
|
||||
].map((pref, idx) => (
|
||||
<div key={idx} className="flex items-center justify-between">
|
||||
<label className="text-xs text-zinc-400">{pref.label}</label>
|
||||
<div className={`w-8 h-4 rounded-full ${pref.enabled ? (theme.isFoundation ? 'bg-red-600' : 'bg-blue-600') : 'bg-zinc-700'}`} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-slate-900 to-slate-950 text-slate-50 p-6">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import { Button } from "@/components/ui/button";
|
|||
import { Input } from "@/components/ui/input";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { ArrowLeft, Plus, Trash2, ExternalLink, Github, Globe, Loader2 } from "lucide-react";
|
||||
import { ArrowLeft, Plus, Trash2, ExternalLink, Github, Globe, Loader2, FolderKanban } from "lucide-react";
|
||||
import { MobileHeader } from "@/components/mobile/MobileHeader";
|
||||
import { isEmbedded } from "@/lib/embed-utils";
|
||||
import { isEmbedded, getResponsiveStyles } from "@/lib/embed-utils";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import { nanoid } from "nanoid";
|
||||
|
|
@ -104,7 +104,168 @@ export default function Projects() {
|
|||
};
|
||||
|
||||
const embedded = isEmbedded();
|
||||
const { useMobileStyles, theme } = getResponsiveStyles();
|
||||
|
||||
// Mobile-optimized layout when embedded or on mobile device
|
||||
if (useMobileStyles) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ background: theme.gradientBg }}>
|
||||
<div className="p-4 pb-20">
|
||||
{/* Mobile Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-xl ${theme.bgAccent} border ${theme.borderClass} flex items-center justify-center`}>
|
||||
<FolderKanban className={`w-5 h-5 ${theme.iconClass}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className={`${theme.primaryClass} font-bold text-lg`}>Projects</h1>
|
||||
<p className="text-zinc-500 text-xs">{projects.length} total</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setShowForm(!showForm)}
|
||||
className={`${theme.activeBtn} ${theme.hoverBtn} gap-2`}
|
||||
size="sm"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
New
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Add Project Form */}
|
||||
{showForm && (
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-4 mb-6`}>
|
||||
<h2 className={`text-sm font-bold ${theme.secondaryClass} mb-4`}>Create New Project</h2>
|
||||
<div className="space-y-3">
|
||||
<Input
|
||||
placeholder="Project Title"
|
||||
value={newProject.title}
|
||||
onChange={(e) => setNewProject({ ...newProject, title: e.target.value })}
|
||||
className={`${theme.inputBg} border-zinc-700 text-white text-sm`}
|
||||
/>
|
||||
<textarea
|
||||
placeholder="Description..."
|
||||
value={newProject.description}
|
||||
onChange={(e) => setNewProject({ ...newProject, description: e.target.value })}
|
||||
className={`w-full ${theme.inputBg} border border-zinc-700 text-white rounded-lg px-3 py-2 text-sm focus:outline-none focus:${theme.activeBorder}`}
|
||||
rows={2}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Technologies (comma-separated)"
|
||||
value={newProject.technologies}
|
||||
onChange={(e) => setNewProject({ ...newProject, technologies: e.target.value })}
|
||||
className={`${theme.inputBg} border-zinc-700 text-white text-sm`}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={handleAddProject} className={`flex-1 ${theme.activeBtn} ${theme.hoverBtn}`} size="sm">
|
||||
Create
|
||||
</Button>
|
||||
<Button onClick={() => setShowForm(false)} variant="outline" className="border-zinc-700 text-zinc-400" size="sm">
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className={`w-6 h-6 ${theme.iconClass} animate-spin`} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Projects Grid */}
|
||||
{!loading && (
|
||||
<div className="space-y-3">
|
||||
{projects.length === 0 ? (
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-8 text-center`}>
|
||||
<FolderKanban className={`w-12 h-12 ${theme.iconClass} mx-auto mb-3 opacity-50`} />
|
||||
<p className="text-zinc-500 text-sm">No projects yet</p>
|
||||
<p className="text-zinc-600 text-xs mt-1">Create your first project to get started</p>
|
||||
</div>
|
||||
) : (
|
||||
projects.map((project) => (
|
||||
<div
|
||||
key={project.id}
|
||||
className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-4 active:scale-[0.98] transition-transform`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className={`${getStatusColor(project.status)} text-white text-[10px] font-bold px-2 py-0.5 rounded capitalize`}>
|
||||
{project.status}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-white font-bold text-sm">{project.title}</h3>
|
||||
</div>
|
||||
<button onClick={() => deleteProject(project.id)} className="text-red-400 p-2 -m-2">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
{project.description && (
|
||||
<p className="text-zinc-400 text-xs mb-3 line-clamp-2">{project.description}</p>
|
||||
)}
|
||||
|
||||
{/* Progress */}
|
||||
<div className="mb-3">
|
||||
<div className="flex justify-between mb-1">
|
||||
<span className="text-xs text-zinc-500">Progress</span>
|
||||
<span className={`text-xs ${theme.primaryClass}`}>{project.progress}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-zinc-800 rounded-full h-1.5">
|
||||
<div
|
||||
className={`h-1.5 rounded-full ${theme.isFoundation ? 'bg-red-500' : 'bg-blue-500'}`}
|
||||
style={{ width: `${project.progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Technologies */}
|
||||
{Array.isArray(project.tech_stack) && project.tech_stack.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5 mb-3">
|
||||
{project.tech_stack.slice(0, 4).map((tech) => (
|
||||
<span key={tech} className={`${theme.bgAccent} ${theme.primaryClass} text-[10px] px-2 py-0.5 rounded`}>
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
{project.tech_stack.length > 4 && (
|
||||
<span className="text-zinc-500 text-[10px] px-2 py-0.5">+{project.tech_stack.length - 4}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Links */}
|
||||
{(project.live_url || project.github_url) && (
|
||||
<div className="flex gap-2">
|
||||
{project.live_url && (
|
||||
<a href={project.live_url} target="_blank" rel="noopener noreferrer"
|
||||
className={`flex-1 flex items-center justify-center gap-1.5 ${theme.activeBtn} text-white text-xs font-medium py-2 rounded-lg`}>
|
||||
<Globe className="w-3.5 h-3.5" /> Live
|
||||
</a>
|
||||
)}
|
||||
{project.github_url && (
|
||||
<a href={project.github_url} target="_blank" rel="noopener noreferrer"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 bg-zinc-800 text-white text-xs font-medium py-2 rounded-lg">
|
||||
<Github className="w-3.5 h-3.5" /> Code
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Desktop layout (original)
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 to-slate-800">
|
||||
{/* Headers - hidden when embedded in OS iframe */}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Link } from "wouter";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { ArrowLeft, Settings, Bell, Lock, Palette, HardDrive, User, Loader2 } from "lucide-react";
|
||||
import { isEmbedded } from "@/lib/embed-utils";
|
||||
import { isEmbedded, getResponsiveStyles } from "@/lib/embed-utils";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import { nanoid } from "nanoid";
|
||||
|
|
@ -90,6 +90,189 @@ export default function SettingsWorkspace() {
|
|||
};
|
||||
|
||||
const embedded = isEmbedded();
|
||||
const { useMobileStyles, theme } = getResponsiveStyles();
|
||||
|
||||
// Mobile-optimized layout when embedded or on mobile device
|
||||
if (useMobileStyles) {
|
||||
return (
|
||||
<div className="min-h-screen" style={{ background: theme.gradientBg }}>
|
||||
<div className="p-4 pb-20">
|
||||
{/* Mobile Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-xl ${theme.bgAccent} border ${theme.borderClass} flex items-center justify-center`}>
|
||||
<Settings className={`w-5 h-5 ${theme.iconClass}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className={`${theme.primaryClass} font-bold text-lg`}>Settings</h1>
|
||||
<p className="text-zinc-500 text-xs">Workspace preferences</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Loading State */}
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className={`w-6 h-6 ${theme.iconClass} animate-spin`} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Settings Sections */}
|
||||
{!loading && (
|
||||
<div className="space-y-4">
|
||||
{/* Appearance */}
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-4`}>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Palette className={`w-4 h-4 ${theme.iconClass}`} />
|
||||
<h2 className="text-white font-bold text-sm">Appearance</h2>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-xs text-zinc-400 mb-1.5">Theme</label>
|
||||
<select
|
||||
value={settings.theme}
|
||||
onChange={(e) => handleChange("theme", e.target.value)}
|
||||
className={`w-full ${theme.inputBg} border border-zinc-700 text-white text-sm rounded-lg px-3 py-2`}
|
||||
>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="auto">Auto (System)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-zinc-400 mb-1.5">Font Size</label>
|
||||
<select
|
||||
value={settings.fontSize}
|
||||
onChange={(e) => handleChange("fontSize", e.target.value)}
|
||||
className={`w-full ${theme.inputBg} border border-zinc-700 text-white text-sm rounded-lg px-3 py-2`}
|
||||
>
|
||||
<option value="small">Small</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="large">Large</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
<span className="text-xs text-zinc-400">Collapse Sidebar</span>
|
||||
<button
|
||||
onClick={() => handleToggle("sidebarCollapsed")}
|
||||
className={`w-10 h-5 rounded-full transition-colors ${
|
||||
settings.sidebarCollapsed ? (theme.isFoundation ? 'bg-red-600' : 'bg-blue-600') : 'bg-zinc-700'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Notifications */}
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-4`}>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Bell className={`w-4 h-4 ${theme.iconClass}`} />
|
||||
<h2 className="text-white font-bold text-sm">Notifications</h2>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-zinc-300">Push Notifications</p>
|
||||
<p className="text-[10px] text-zinc-500">Get alerts for events</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleToggle("notificationsEnabled")}
|
||||
className={`w-10 h-5 rounded-full transition-colors ${
|
||||
settings.notificationsEnabled ? (theme.isFoundation ? 'bg-red-600' : 'bg-blue-600') : 'bg-zinc-700'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-zinc-300">Email Notifications</p>
|
||||
<p className="text-[10px] text-zinc-500">Receive email digests</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleToggle("emailNotifications")}
|
||||
className={`w-10 h-5 rounded-full transition-colors ${
|
||||
settings.emailNotifications ? (theme.isFoundation ? 'bg-red-600' : 'bg-blue-600') : 'bg-zinc-700'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-zinc-300">Sound Effects</p>
|
||||
<p className="text-[10px] text-zinc-500">Play notification sounds</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleToggle("soundEnabled")}
|
||||
className={`w-10 h-5 rounded-full transition-colors ${
|
||||
settings.soundEnabled ? (theme.isFoundation ? 'bg-red-600' : 'bg-blue-600') : 'bg-zinc-700'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Editor */}
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-4`}>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<HardDrive className={`w-4 h-4 ${theme.iconClass}`} />
|
||||
<h2 className="text-white font-bold text-sm">Editor</h2>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-zinc-300">Auto-save</p>
|
||||
<p className="text-[10px] text-zinc-500">Automatically save work</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleToggle("autoSave")}
|
||||
className={`w-10 h-5 rounded-full transition-colors ${
|
||||
settings.autoSave ? (theme.isFoundation ? 'bg-red-600' : 'bg-blue-600') : 'bg-zinc-700'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Privacy */}
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-4`}>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Lock className={`w-4 h-4 ${theme.iconClass}`} />
|
||||
<h2 className="text-white font-bold text-sm">Privacy</h2>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-xs text-zinc-400 mb-1.5">Profile Privacy</label>
|
||||
<select
|
||||
value={settings.privacyLevel}
|
||||
onChange={(e) => handleChange("privacyLevel", e.target.value)}
|
||||
className={`w-full ${theme.inputBg} border border-zinc-700 text-white text-sm rounded-lg px-3 py-2`}
|
||||
>
|
||||
<option value="private">Private (Only you)</option>
|
||||
<option value="friends">Friends Only</option>
|
||||
<option value="public">Public</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Account */}
|
||||
<div className={`${theme.cardBg} border ${theme.borderClass} rounded-xl p-4`}>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<User className={`w-4 h-4 ${theme.iconClass}`} />
|
||||
<h2 className="text-white font-bold text-sm">Account</h2>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className={`${theme.inputBg} p-3 rounded-lg`}>
|
||||
<p className="text-[10px] text-zinc-500">Email</p>
|
||||
<p className="text-white text-xs font-medium">user@example.com</p>
|
||||
</div>
|
||||
<Button variant="outline" className={`w-full border-red-600 text-red-400 text-xs`} size="sm">
|
||||
Log Out
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 to-slate-800">
|
||||
|
|
|
|||
|
|
@ -171,8 +171,8 @@ export default function AeThexOS() {
|
|||
const native = useNativeFeatures();
|
||||
const biometric = useBiometricAuth();
|
||||
|
||||
// Skip boot sequence on mobile
|
||||
const [isBooting, setIsBooting] = useState(!layout.isMobile);
|
||||
// Mobile also gets a quick boot sequence
|
||||
const [isBooting, setIsBooting] = useState(true);
|
||||
const [bootProgress, setBootProgress] = useState(0);
|
||||
const [bootStep, setBootStep] = useState('');
|
||||
const [windows, setWindows] = useState<WindowState[]>([]);
|
||||
|
|
@ -869,6 +869,8 @@ export default function AeThexOS() {
|
|||
theme={theme}
|
||||
setTheme={setTheme}
|
||||
savedLayouts={savedLayouts}
|
||||
clearanceMode={clearanceMode}
|
||||
setClearanceMode={setClearanceMode}
|
||||
onSaveLayout={(name) => {
|
||||
const layout: DesktopLayout = {
|
||||
name,
|
||||
|
|
@ -959,6 +961,122 @@ export default function AeThexOS() {
|
|||
high: 'bg-red-500/20 border-red-500/50'
|
||||
};
|
||||
|
||||
// Mobile boot screen - simplified and themed
|
||||
if (layout.isMobile) {
|
||||
const isFoundation = clearanceMode === 'foundation';
|
||||
const bootTheme = {
|
||||
primary: isFoundation ? 'text-red-500' : 'text-blue-500',
|
||||
secondary: isFoundation ? 'text-amber-400' : 'text-slate-300',
|
||||
glow: isFoundation ? 'from-red-600 to-amber-500' : 'from-blue-600 to-cyan-400',
|
||||
bar: isFoundation ? 'from-red-600 via-red-500 to-amber-500' : 'from-blue-600 via-blue-500 to-cyan-400',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-screen w-screen bg-black flex flex-col items-center justify-center relative overflow-hidden">
|
||||
{/* Animated background grid */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="absolute inset-0" style={{
|
||||
backgroundImage: `linear-gradient(${isFoundation ? 'rgba(220,38,38,0.3)' : 'rgba(59,130,246,0.3)'} 1px, transparent 1px),
|
||||
linear-gradient(90deg, ${isFoundation ? 'rgba(220,38,38,0.3)' : 'rgba(59,130,246,0.3)'} 1px, transparent 1px)`,
|
||||
backgroundSize: '50px 50px',
|
||||
animation: 'pulse 2s ease-in-out infinite'
|
||||
}} />
|
||||
</div>
|
||||
|
||||
{/* Logo with animated glow */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="relative mb-8"
|
||||
>
|
||||
<div className={`absolute inset-0 bg-gradient-to-br ${bootTheme.glow} rounded-3xl blur-2xl opacity-50 animate-pulse`} />
|
||||
<div className={`relative w-24 h-24 bg-gradient-to-br ${bootTheme.glow} rounded-3xl flex items-center justify-center`}>
|
||||
<div className="absolute inset-1 bg-black rounded-2xl" />
|
||||
<span className="relative text-4xl font-display font-bold text-white z-10">Æ</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Brand name */}
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className={`text-3xl font-display font-bold ${bootTheme.primary} mb-2`}
|
||||
>
|
||||
AETHEX OS
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className={`text-sm font-mono ${bootTheme.secondary} opacity-60 uppercase tracking-widest mb-12`}
|
||||
>
|
||||
{isFoundation ? 'FOUNDATION' : 'CORPORATION'}
|
||||
</motion.p>
|
||||
|
||||
{/* Boot step with cursor */}
|
||||
<motion.div
|
||||
key={bootStep}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className={`${bootTheme.primary} font-mono text-xs mb-6 h-5 text-center px-8`}
|
||||
>
|
||||
{bootStep}
|
||||
<motion.span
|
||||
animate={{ opacity: [1, 0] }}
|
||||
transition={{ duration: 0.5, repeat: Infinity }}
|
||||
>
|
||||
_
|
||||
</motion.span>
|
||||
</motion.div>
|
||||
|
||||
{/* Progress bar */}
|
||||
<div className="w-64 px-4">
|
||||
<div className="flex justify-between text-[10px] font-mono text-white/40 mb-2">
|
||||
<span>INITIALIZING</span>
|
||||
<span>{bootProgress}%</span>
|
||||
</div>
|
||||
<div className="h-1.5 bg-zinc-900 rounded-full overflow-hidden border border-white/10">
|
||||
<motion.div
|
||||
className={`h-full bg-gradient-to-r ${bootTheme.bar} relative`}
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${bootProgress}%` }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent animate-pulse" />
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Continue button when ready */}
|
||||
<AnimatePresence>
|
||||
{showLoginPrompt && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="mt-12"
|
||||
>
|
||||
<motion.button
|
||||
onClick={handleGuestContinue}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className={`px-8 py-4 bg-gradient-to-r ${bootTheme.bar} rounded-2xl font-mono font-bold uppercase tracking-wider text-black shadow-lg`}
|
||||
>
|
||||
Enter System
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Version info */}
|
||||
<div className="absolute bottom-8 text-white/20 text-[10px] font-mono">
|
||||
v4.2.1 • {isFoundation ? 'FOUNDATION' : 'CORP'} BUILD
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Desktop boot screen
|
||||
return (
|
||||
<div className="h-screen w-screen bg-black relative overflow-hidden">
|
||||
{/* Scan lines overlay */}
|
||||
|
|
@ -1221,7 +1339,10 @@ export default function AeThexOS() {
|
|||
useEffect(() => {
|
||||
if (!layout.isMobile || !isMobile()) return;
|
||||
|
||||
const backHandler = CapacitorApp.addListener('backButton', () => {
|
||||
let backHandler: any = null;
|
||||
|
||||
const setupBackHandler = async () => {
|
||||
backHandler = await CapacitorApp.addListener('backButton', () => {
|
||||
// Get current active windows (non-minimized)
|
||||
const activeWindows = windows.filter(w => !w.minimized);
|
||||
|
||||
|
|
@ -1235,9 +1356,14 @@ export default function AeThexOS() {
|
|||
CapacitorApp.minimizeApp();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
setupBackHandler();
|
||||
|
||||
return () => {
|
||||
if (backHandler && typeof backHandler.remove === 'function') {
|
||||
backHandler.remove();
|
||||
}
|
||||
};
|
||||
}, [layout.isMobile, windows, closeWindow, impact]);
|
||||
|
||||
|
|
@ -1262,9 +1388,16 @@ export default function AeThexOS() {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="h-screen w-screen overflow-hidden flex flex-col" style={{ background: mobileTheme.gradientBg }}>
|
||||
<div
|
||||
className="h-screen w-screen overflow-hidden flex flex-col"
|
||||
style={{
|
||||
background: mobileTheme.gradientBg,
|
||||
paddingTop: 'env(safe-area-inset-top)',
|
||||
paddingBottom: 'env(safe-area-inset-bottom)',
|
||||
}}
|
||||
>
|
||||
{/* AeThex Mobile Status Bar */}
|
||||
<div className={`relative h-10 bg-black/90 ${mobileTheme.borderClass} border-b shrink-0`} style={{ paddingTop: 'env(safe-area-inset-top)' }}>
|
||||
<div className={`relative h-10 bg-black/80 backdrop-blur-md ${mobileTheme.borderClass} border-b shrink-0`}>
|
||||
<div className="relative flex items-center justify-between px-4 h-full">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className={`${mobileTheme.primaryClass} font-bold text-sm font-mono`}>AeThex</span>
|
||||
|
|
@ -1425,10 +1558,9 @@ export default function AeThexOS() {
|
|||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Bottom Navigation */}
|
||||
{/* Bottom Navigation - transparent for gesture nav integration */}
|
||||
<div
|
||||
className={`bg-black/95 border-t ${mobileTheme.borderClass} shrink-0`}
|
||||
style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}
|
||||
className={`bg-black/80 backdrop-blur-md border-t ${mobileTheme.borderClass} shrink-0`}
|
||||
>
|
||||
<div className="flex items-center justify-around py-2 px-4">
|
||||
<button
|
||||
|
|
@ -4951,7 +5083,7 @@ function PitchApp({ onNavigate }: { onNavigate: () => void }) {
|
|||
);
|
||||
}
|
||||
|
||||
function SettingsApp({ wallpaper, setWallpaper, soundEnabled, setSoundEnabled, secretsUnlocked, theme, setTheme, savedLayouts, onSaveLayout, onLoadLayout, onDeleteLayout }: {
|
||||
function SettingsApp({ wallpaper, setWallpaper, soundEnabled, setSoundEnabled, secretsUnlocked, theme, setTheme, savedLayouts, onSaveLayout, onLoadLayout, onDeleteLayout, clearanceMode, setClearanceMode }: {
|
||||
wallpaper: typeof WALLPAPERS[0];
|
||||
setWallpaper: (w: typeof WALLPAPERS[0]) => void;
|
||||
soundEnabled: boolean;
|
||||
|
|
@ -4963,11 +5095,210 @@ function SettingsApp({ wallpaper, setWallpaper, soundEnabled, setSoundEnabled, s
|
|||
onSaveLayout: (name: string) => void;
|
||||
onLoadLayout: (layout: DesktopLayout) => void;
|
||||
onDeleteLayout: (name: string) => void;
|
||||
clearanceMode?: 'foundation' | 'corp';
|
||||
setClearanceMode?: (mode: 'foundation' | 'corp') => void;
|
||||
}) {
|
||||
const [layoutName, setLayoutName] = useState('');
|
||||
const [activeTab, setActiveTab] = useState<'appearance' | 'layouts' | 'system'>('appearance');
|
||||
const visibleWallpapers = WALLPAPERS.filter(wp => !wp.secret || secretsUnlocked);
|
||||
const isMobileView = typeof window !== 'undefined' && window.innerWidth < 768;
|
||||
|
||||
// Mobile-specific theme colors based on clearance mode
|
||||
const isFoundation = clearanceMode === 'foundation';
|
||||
const mobileTheme = {
|
||||
primary: isFoundation ? 'rgb(220, 38, 38)' : 'rgb(59, 130, 246)',
|
||||
primaryClass: isFoundation ? 'text-red-500' : 'text-blue-500',
|
||||
secondaryClass: isFoundation ? 'text-amber-400' : 'text-slate-300',
|
||||
borderClass: isFoundation ? 'border-red-900/50' : 'border-blue-900/50',
|
||||
bgAccent: isFoundation ? 'bg-red-900/20' : 'bg-blue-900/20',
|
||||
iconClass: isFoundation ? 'text-red-400' : 'text-blue-400',
|
||||
activeBtn: isFoundation ? 'bg-red-600' : 'bg-blue-500',
|
||||
activeBorder: isFoundation ? 'border-red-500' : 'border-blue-500',
|
||||
};
|
||||
|
||||
// Mobile Settings UI
|
||||
if (isMobileView) {
|
||||
return (
|
||||
<div className="h-full bg-black flex flex-col overflow-hidden">
|
||||
{/* Mobile Tab Bar */}
|
||||
<div className={`flex border-b ${mobileTheme.borderClass} bg-black/90 shrink-0`}>
|
||||
{(['appearance', 'system'] as const).map(tab => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setActiveTab(tab as any)}
|
||||
className={`flex-1 px-4 py-4 text-xs font-mono uppercase tracking-wider transition-colors ${
|
||||
activeTab === tab
|
||||
? `${mobileTheme.primaryClass} border-b-2 ${mobileTheme.activeBorder}`
|
||||
: 'text-zinc-500'
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Mobile Content */}
|
||||
<div className="flex-1 overflow-auto p-4 pb-20">
|
||||
{activeTab === 'appearance' && (
|
||||
<div className="space-y-6">
|
||||
{/* Theme Toggle */}
|
||||
{setClearanceMode && (
|
||||
<div className={`p-4 rounded-xl bg-zinc-900/80 border ${mobileTheme.borderClass}`}>
|
||||
<div className={`text-xs ${mobileTheme.secondaryClass} uppercase tracking-wider mb-3 font-bold`}>
|
||||
Identity Mode
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
onClick={() => setClearanceMode('foundation')}
|
||||
className={`p-4 rounded-xl border transition-all ${
|
||||
clearanceMode === 'foundation'
|
||||
? 'border-red-500 bg-red-900/30'
|
||||
: 'border-zinc-800 bg-zinc-900/50'
|
||||
}`}
|
||||
>
|
||||
<div className="text-red-500 font-bold text-sm mb-1">Foundation</div>
|
||||
<div className="text-zinc-500 text-xs">Red & Gold</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setClearanceMode('corp')}
|
||||
className={`p-4 rounded-xl border transition-all ${
|
||||
clearanceMode === 'corp'
|
||||
? 'border-blue-500 bg-blue-900/30'
|
||||
: 'border-zinc-800 bg-zinc-900/50'
|
||||
}`}
|
||||
>
|
||||
<div className="text-blue-500 font-bold text-sm mb-1">Corporation</div>
|
||||
<div className="text-zinc-500 text-xs">Blue & Silver</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Accent Color */}
|
||||
<div className={`p-4 rounded-xl bg-zinc-900/80 border ${mobileTheme.borderClass}`}>
|
||||
<div className={`text-xs ${mobileTheme.secondaryClass} uppercase tracking-wider mb-3 font-bold`}>
|
||||
Accent Color
|
||||
</div>
|
||||
<div className="grid grid-cols-6 gap-3">
|
||||
{ACCENT_COLORS.map(color => (
|
||||
<button
|
||||
key={color.id}
|
||||
onClick={() => setTheme({ ...theme, accentColor: color.id })}
|
||||
className={`w-12 h-12 rounded-xl transition-all ${color.bg} ${
|
||||
theme.accentColor === color.id
|
||||
? 'ring-2 ring-white ring-offset-2 ring-offset-black scale-110'
|
||||
: 'active:scale-95'
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Wallpaper Selection */}
|
||||
<div className={`p-4 rounded-xl bg-zinc-900/80 border ${mobileTheme.borderClass}`}>
|
||||
<div className={`text-xs ${mobileTheme.secondaryClass} uppercase tracking-wider mb-3 font-bold`}>
|
||||
Wallpaper {secretsUnlocked && <span className="text-yellow-400 ml-2">✨ UNLOCKED</span>}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{visibleWallpapers.map(wp => (
|
||||
<button
|
||||
key={wp.id}
|
||||
onClick={() => setWallpaper(wp)}
|
||||
className={`p-3 rounded-xl border transition-all active:scale-95 ${
|
||||
wallpaper.id === wp.id
|
||||
? `${mobileTheme.activeBorder} ${mobileTheme.bgAccent}`
|
||||
: wp.secret
|
||||
? 'border-yellow-500/30'
|
||||
: 'border-zinc-800'
|
||||
}`}
|
||||
>
|
||||
<div className="w-full h-16 rounded-lg mb-2" style={{ background: wp.bg }} />
|
||||
<div className="text-xs text-zinc-300 font-medium">{wp.name}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Transparency */}
|
||||
<div className={`p-4 rounded-xl bg-zinc-900/80 border ${mobileTheme.borderClass}`}>
|
||||
<div className={`text-xs ${mobileTheme.secondaryClass} uppercase tracking-wider mb-3 font-bold`}>
|
||||
Transparency
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="50"
|
||||
max="100"
|
||||
value={theme.transparency}
|
||||
onChange={e => setTheme({ ...theme, transparency: parseInt(e.target.value) })}
|
||||
className={`w-full h-2 rounded-full appearance-none bg-zinc-800 ${isFoundation ? 'accent-red-500' : 'accent-blue-500'}`}
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-zinc-500 mt-2">
|
||||
<span>Glass</span>
|
||||
<span className={mobileTheme.primaryClass}>{theme.transparency}%</span>
|
||||
<span>Solid</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'system' && (
|
||||
<div className="space-y-4">
|
||||
{/* Sound Toggle */}
|
||||
<div className={`flex items-center justify-between p-4 rounded-xl bg-zinc-900/80 border ${mobileTheme.borderClass}`}>
|
||||
<div>
|
||||
<div className="text-white text-sm font-medium">Sound Effects</div>
|
||||
<div className="text-zinc-500 text-xs">UI interaction feedback</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSoundEnabled(!soundEnabled)}
|
||||
className={`w-14 h-8 rounded-full relative transition-all ${soundEnabled ? mobileTheme.activeBtn : 'bg-zinc-700'}`}
|
||||
>
|
||||
<div className={`absolute top-1 w-6 h-6 bg-white rounded-full transition-all shadow-lg ${soundEnabled ? 'right-1' : 'left-1'}`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Version Info */}
|
||||
<div className={`p-4 rounded-xl ${mobileTheme.bgAccent} border ${mobileTheme.borderClass}`}>
|
||||
<div className={`${mobileTheme.primaryClass} text-sm font-mono font-bold`}>AeThex OS v3.0.0</div>
|
||||
<div className="text-zinc-500 text-xs mt-1">Build 2025.12.17 • Mobile</div>
|
||||
</div>
|
||||
|
||||
{/* Secrets Hint */}
|
||||
{!secretsUnlocked && (
|
||||
<div className={`p-4 rounded-xl bg-zinc-900/80 border ${mobileTheme.borderClass} text-center`}>
|
||||
<div className="text-zinc-500 text-xs font-mono">🔒 Hidden features available...</div>
|
||||
<div className="text-zinc-600 text-[10px] mt-1">Try the Konami Code</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Device Info */}
|
||||
<div className={`p-4 rounded-xl bg-zinc-900/80 border ${mobileTheme.borderClass}`}>
|
||||
<div className={`text-xs ${mobileTheme.secondaryClass} uppercase tracking-wider mb-3 font-bold`}>
|
||||
Device
|
||||
</div>
|
||||
<div className="space-y-2 text-xs">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-zinc-500">Platform</span>
|
||||
<span className="text-zinc-300">Android (Capacitor)</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-zinc-500">Mode</span>
|
||||
<span className={mobileTheme.primaryClass}>{isFoundation ? 'Foundation' : 'Corporation'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-zinc-500">Session</span>
|
||||
<span className="text-zinc-300">Active</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Desktop Settings UI (original)
|
||||
return (
|
||||
<div className="h-full bg-slate-950 flex flex-col">
|
||||
<div className="flex border-b border-white/10">
|
||||
|
|
|
|||
Loading…
Reference in a new issue