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 {
|
svg {
|
||||||
stroke-width: var(--icon-stroke-width, 2);
|
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 { SidebarTrigger } from "@/components/ui/sidebar";
|
||||||
import parseTraffic from "@/utils/parse-traffic";
|
import parseTraffic from "@/utils/parse-traffic";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
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 MinimalHomePage: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
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 (
|
return (
|
||||||
<div className="h-full w-full flex flex-col">
|
<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">
|
<header className="flex-shrink-0 p-5 grid grid-cols-3 items-center z-10">
|
||||||
<div className="flex justify-start">
|
<div className="flex justify-start">
|
||||||
<SidebarTrigger />
|
<SidebarTrigger />
|
||||||
@@ -244,13 +286,16 @@ const MinimalHomePage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
<div className="relative text-center">
|
<div className="relative text-center">
|
||||||
<h1
|
<h1
|
||||||
className="text-4xl mb-2 font-semibold"
|
className={cn(
|
||||||
style={{ color: isProxyEnabled ? "#22c55e" : "#ef4444" }}
|
"text-4xl mb-2 font-semibold transition-colors duration-300",
|
||||||
|
statusInfo.isAnimating && "animate-pulse"
|
||||||
|
)}
|
||||||
|
style={{ color: statusInfo.color }}
|
||||||
>
|
>
|
||||||
{isProxyEnabled ? t("Connected") : t("Disconnected")}
|
{statusInfo.text}
|
||||||
</h1>
|
</h1>
|
||||||
{isProxyEnabled && (
|
{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">
|
<div className="flex items-center gap-1">
|
||||||
<ArrowDown className="h-4 w-4 text-green-500" />
|
<ArrowDown className="h-4 w-4 text-green-500" />
|
||||||
{parseTraffic(connections.downloadTotal)}
|
{parseTraffic(connections.downloadTotal)}
|
||||||
@@ -261,23 +306,18 @@ const MinimalHomePage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="h-6 text-sm text-muted-foreground transition-opacity duration-300">
|
|
||||||
{isToggling &&
|
|
||||||
(isProxyEnabled ? t("Disconnecting...") : t("Connecting..."))}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="scale-[7] my-16">
|
<div className="relative -translate-y-6">
|
||||||
<Switch
|
<PowerButton
|
||||||
disabled={showTunAlert || isToggling || profileItems.length === 0}
|
loading={isToggling}
|
||||||
checked={!!isProxyEnabled}
|
checked={!!isProxyEnabled}
|
||||||
onCheckedChange={handleToggleProxy}
|
onClick={handleToggleProxy}
|
||||||
|
disabled={showTunAlert || isToggling || profileItems.length === 0}
|
||||||
aria-label={t("Toggle Proxy")}
|
aria-label={t("Toggle Proxy")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{showTunAlert && (
|
{showTunAlert && (
|
||||||
<div className="w-full max-w-sm">
|
<div className="w-full max-w-sm">
|
||||||
<Alert
|
<Alert
|
||||||
|
|||||||
Reference in New Issue
Block a user