new homepage
This commit is contained in:
422
src/assets/image/map.svg
Normal file
422
src/assets/image/map.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 453 KiB |
56
src/components/home/power-button.tsx
Normal file
56
src/components/home/power-button.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
import { cn } from '@root/lib/utils';
|
||||
import { Power } from 'lucide-react';
|
||||
|
||||
export interface PowerButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
checked?: boolean;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export const PowerButton = React.forwardRef<HTMLButtonElement, PowerButtonProps>(
|
||||
({ className, checked = false, loading = false, ...props }, ref) => {
|
||||
const state = checked ? 'on' : 'off';
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center justify-center h-44 w-44">
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'absolute h-28 w-28 rounded-full blur-3xl transition-all duration-500',
|
||||
state === 'on' ? 'bg-green-400/60' : 'bg-red-500/40'
|
||||
)}
|
||||
/>
|
||||
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
disabled={loading || props.disabled}
|
||||
data-state={state}
|
||||
className={cn(
|
||||
'relative z-10 flex items-center justify-center h-36 w-36 rounded-full border-2',
|
||||
'backdrop-blur-sm bg-white/10 border-white/20',
|
||||
'text-red-500 shadow-[0_0_30px_rgba(239,68,68,0.6)]',
|
||||
'data-[state=on]:text-green-500 dark:data-[state=on]:text-white',
|
||||
'data-[state=on]:shadow-[0_0_50px_rgba(34,197,94,1)]',
|
||||
'transition-all duration-300 hover:scale-105 active:scale-95 focus:outline-none',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<Power className="h-20 w-20 transition-transform duration-300 active:scale-90" />
|
||||
</button>
|
||||
|
||||
{loading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center z-20">
|
||||
<div className={cn(
|
||||
'h-full w-full animate-spin rounded-full border-4',
|
||||
'border-transparent',
|
||||
checked ? 'border-t-green-500' : 'border-t-red-500',
|
||||
'blur-xs'
|
||||
)} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -131,3 +131,21 @@
|
||||
svg {
|
||||
stroke-width: var(--icon-stroke-width, 2);
|
||||
}
|
||||
|
||||
|
||||
@keyframes gradient-wave {
|
||||
0% {
|
||||
background-position: -200% center;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% center;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-gradient-wave {
|
||||
background-size: 200% auto;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
animation: gradient-wave 2s linear infinite;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ import { updateProfile } from "@/services/cmds";
|
||||
import { SidebarTrigger } from "@/components/ui/sidebar";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
import { useAppData } from "@/providers/app-data-provider";
|
||||
import {PowerButton} from "@/components/home/power-button";
|
||||
import {cn} from "@root/lib/utils";
|
||||
|
||||
const MinimalHomePage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -154,8 +156,48 @@ const MinimalHomePage: React.FC = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const statusInfo = useMemo(() => {
|
||||
if (isToggling) {
|
||||
return {
|
||||
text: isProxyEnabled ? t('Disconnecting...') : t('Connecting...'),
|
||||
color: isProxyEnabled ? '#f59e0b' : '#84cc16',
|
||||
isAnimating: true,
|
||||
};
|
||||
}
|
||||
if (isProxyEnabled) {
|
||||
return {
|
||||
text: t('Connected'),
|
||||
color: '#22c55e',
|
||||
isAnimating: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
text: t('Disconnected'),
|
||||
color: '#ef4444',
|
||||
isAnimating: false,
|
||||
};
|
||||
}, [isToggling, isProxyEnabled, t]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full flex flex-col">
|
||||
<div className="absolute inset-0 opacity-20 pointer-events-none z-0 [transform:translateZ(0)]">
|
||||
<img
|
||||
src="../assets/image/map.svg"
|
||||
alt="World map"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isProxyEnabled && (
|
||||
<div
|
||||
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 h-[500px] w-[500px] rounded-full pointer-events-none z-0 transition-opacity duration-500"
|
||||
style={{
|
||||
background: 'radial-gradient(circle, rgba(34,197,94,0.3) 0%, transparent 70%)',
|
||||
filter: 'blur(100px)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<header className="flex-shrink-0 p-5 grid grid-cols-3 items-center z-10">
|
||||
<div className="flex justify-start">
|
||||
<SidebarTrigger />
|
||||
@@ -242,15 +284,18 @@ const MinimalHomePage: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="relative text-center">
|
||||
<h1
|
||||
className="text-4xl mb-2 font-semibold"
|
||||
style={{ color: isProxyEnabled ? "#22c55e" : "#ef4444" }}
|
||||
>
|
||||
{isProxyEnabled ? t("Connected") : t("Disconnected")}
|
||||
</h1>
|
||||
<div className="relative text-center">
|
||||
<h1
|
||||
className={cn(
|
||||
"text-4xl mb-2 font-semibold transition-colors duration-300",
|
||||
statusInfo.isAnimating && "animate-pulse"
|
||||
)}
|
||||
style={{ color: statusInfo.color }}
|
||||
>
|
||||
{statusInfo.text}
|
||||
</h1>
|
||||
{isProxyEnabled && (
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-48 flex justify-center items-center text-sm text-muted-foreground gap-6">
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-52 flex justify-center items-center text-sm text-muted-foreground gap-6">
|
||||
<div className="flex items-center gap-1">
|
||||
<ArrowDown className="h-4 w-4 text-green-500" />
|
||||
{parseTraffic(connections.downloadTotal)}
|
||||
@@ -261,23 +306,18 @@ const MinimalHomePage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="h-6 text-sm text-muted-foreground transition-opacity duration-300">
|
||||
{isToggling &&
|
||||
(isProxyEnabled ? t("Disconnecting...") : t("Connecting..."))}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="scale-[7] my-16">
|
||||
<Switch
|
||||
disabled={showTunAlert || isToggling || profileItems.length === 0}
|
||||
checked={!!isProxyEnabled}
|
||||
onCheckedChange={handleToggleProxy}
|
||||
aria-label={t("Toggle Proxy")}
|
||||
<div className="relative -translate-y-6">
|
||||
<PowerButton
|
||||
loading={isToggling}
|
||||
checked={!!isProxyEnabled}
|
||||
onClick={handleToggleProxy}
|
||||
disabled={showTunAlert || isToggling || profileItems.length === 0}
|
||||
aria-label={t("Toggle Proxy")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{showTunAlert && (
|
||||
<div className="w-full max-w-sm">
|
||||
<Alert
|
||||
|
||||
Reference in New Issue
Block a user