Skip to content

Commit b5b8b83

Browse files
committed
feat: improve ui
1 parent 3d77ac7 commit b5b8b83

28 files changed

+192
-87
lines changed

src/App.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ const App: React.FC = () => {
122122
refreshWhenOffline: true,
123123
}}
124124
>
125-
<Toaster position="bottom-right" reverseOrder={false} />
125+
<Toaster position="top-right" reverseOrder={false} />
126126

127127
<NetworkErrorModal
128128
reloadButton={isRunInSurge()}

src/components/BottomPanel.tsx

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react'
2+
3+
import { cn } from '@/utils/shadcn'
4+
5+
const BottomPanel = ({
6+
className,
7+
children,
8+
...props
9+
}: React.HTMLAttributes<HTMLDivElement>) => {
10+
return (
11+
<div
12+
className={cn('flex items-center border-t py-2 px-2', className)}
13+
{...props}
14+
>
15+
{children}
16+
</div>
17+
)
18+
}
19+
20+
export default BottomPanel

src/components/CodeContent.tsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react'
2+
import { css } from '@emotion/react'
3+
4+
import { cn } from '@/utils/shadcn'
5+
6+
type CodeContentProps = {
7+
content?: string
8+
} & React.HTMLAttributes<HTMLPreElement>
9+
10+
const CodeContent = ({ className, content, ...props }: CodeContentProps) => {
11+
return (
12+
<pre
13+
className={cn(
14+
'w-full font-mono text-muted-foreground bg-muted text-xs leading-tight p-3 whitespace-pre-wrap break-words',
15+
className,
16+
)}
17+
css={css`
18+
min-height: 7rem;
19+
`}
20+
{...props}
21+
>
22+
{content}
23+
</pre>
24+
)
25+
}
26+
27+
export default CodeContent

src/components/Data/index.tsx

-8
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,8 @@ export const DataRow = styled.div``
2727

2828
export const DataRowMain = styled.div`
2929
${tw`flex items-center justify-between px-3 py-3 md:px-5 md:py-4 leading-normal`}
30-
31-
& > div:last-of-type {
32-
${tw``}
33-
}
3430
`
3531

3632
export const DataRowSub = styled.div`
3733
${tw`flex items-center justify-between px-3 leading-normal text-xs lg:text-sm lg:leading-relaxed`}
38-
39-
& > div:last-of-type {
40-
${tw``}
41-
}
4234
`

src/components/FullLoading/index.tsx

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import React from 'react'
2-
import { ReloadIcon } from '@radix-ui/react-icons'
32
import tw from 'twin.macro'
43

54
const FullLoadingWrapper = tw.div`fixed top-0 right-0 bottom-0 left-0 flex items-center justify-center`
65

76
const FullLoading: React.FC = () => {
8-
return (
9-
<FullLoadingWrapper>
10-
<ReloadIcon className="w-6 h-6 animate-spin" />
11-
</FullLoadingWrapper>
12-
)
7+
return <FullLoadingWrapper></FullLoadingWrapper>
138
}
149

1510
export default FullLoading

src/components/ListCell.tsx

+23-1
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,26 @@ const ListCell: React.FC<ListCellProps> = ({
4141
)
4242
}
4343

44-
export default ListCell
44+
type ListFullHeightCellProps = {
45+
children: React.ReactNode
46+
} & React.HTMLAttributes<HTMLDivElement>
47+
48+
const ListFullHeightCell = ({
49+
children,
50+
className,
51+
...props
52+
}: ListFullHeightCellProps) => {
53+
return (
54+
<div
55+
className={cn(
56+
'fixed top-0 bottom-0 left-0 right-0 flex justify-center items-center text-lg font-semibold text-gray-500',
57+
className,
58+
)}
59+
{...props}
60+
>
61+
{children}
62+
</div>
63+
)
64+
}
65+
66+
export { ListCell, ListFullHeightCell }

src/components/NewVersionAlert.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ const NewVersionAlert: React.FC = () => {
2323

2424
useEffect(() => {
2525
const lastUsedVersion = store.get(LastUsedVersion)
26+
const isSWEnabled = process.env.REACT_APP_USE_SW === 'true'
27+
28+
if (!isSWEnabled) {
29+
return
30+
}
2631

2732
if (lastUsedVersion && !satisfies(currentVersion, `~${lastUsedVersion}`)) {
2833
setVersionUrl(

src/components/PageTitle/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const PageTitle: React.FC<PageTitleProps> = (props) => {
4949
<div
5050
onClick={() => setIsAutoRefresh(!isAutoRefresh)}
5151
className={cn(
52-
'relative bg-green-100 cursor-pointer w-10 h-10 rounded-full flex items-center justify-center transition-colors duration-200 ease-in-out',
52+
'relative bg-green-100 cursor-pointer w-7 h-7 rounded-full flex items-center justify-center transition-colors duration-200 ease-in-out',
5353
isAutoRefresh && 'bg-red-100',
5454
)}
5555
css={[
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React, { useCallback } from 'react'
2+
import { useTranslation } from 'react-i18next'
3+
import { InfoIcon } from 'lucide-react'
4+
5+
import { Button } from '@/components/ui/button'
6+
import { TypographyP } from '@/components/ui/typography'
7+
8+
const SWUpdateNotification = ({
9+
registration,
10+
}: {
11+
registration?: ServiceWorkerRegistration
12+
}) => {
13+
const { t } = useTranslation()
14+
15+
const onClick = useCallback(() => {
16+
if (registration) {
17+
registration.waiting?.postMessage({ type: 'SKIP_WAITING' })
18+
}
19+
20+
window.location.reload()
21+
}, [registration])
22+
23+
return (
24+
<div className="flex justify-center items-center gap-4 py-2">
25+
<InfoIcon className="stroke-black/80 h-[4rem] w-[4rem]" />
26+
<div className="space-y-3">
27+
<TypographyP>{t('common.sw_updated')}</TypographyP>
28+
<Button variant="secondary" onClick={onClick}>
29+
{t('common.refresh')}
30+
</Button>
31+
</div>
32+
</div>
33+
)
34+
}
35+
36+
export default SWUpdateNotification

src/components/ui/switch.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ const Switch = React.forwardRef<
99
>(({ className, ...props }, ref) => (
1010
<SwitchPrimitives.Root
1111
className={cn(
12-
'peer inline-flex h-[20px] w-[36px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
12+
'peer inline-flex h-[20px] w-[36px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-switch data-[state=unchecked]:bg-input',
1313
className,
1414
)}
1515
{...props}
1616
ref={ref}
1717
>
1818
<SwitchPrimitives.Thumb
1919
className={cn(
20-
'pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0',
20+
'pointer-events-none block h-4 w-4 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0',
2121
)}
2222
/>
2323
</SwitchPrimitives.Root>

src/i18n/en/translation.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"download": "Download",
1717
"dark": "Dark",
1818
"light": "Light",
19-
"system": "Follow system"
19+
"system": "Follow system",
20+
"sw_updated": "Surge Web Dashboard has been updated, refresh page to use the new version.",
21+
"refresh": "Refresh"
2022
},
2123
"tls_instruction": {
2224
"title": "Needed a certificate?",
@@ -169,9 +171,11 @@
169171
"waiting": "Waiting",
170172
"device_settings": "Device Settings",
171173
"view_requests": "View Requests",
172-
"modify": "Modify"
174+
"modify": "Modify",
175+
"empty_list": "No Device"
173176
},
174177
"modules": {
178+
"empty_list": "No Module"
175179
},
176180
"Submit": "Submit",
177181
"Clear": "Clear",

src/i18n/zh/translation.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"download": "下载",
1717
"dark": "深色",
1818
"light": "浅色",
19-
"system": "跟随系统"
19+
"system": "跟随系统",
20+
"sw_updated": "Surge Web Dashboard 已更新,刷新页面以使用新版本。",
21+
"refresh": "刷新"
2022
},
2123
"tls_instruction": {
2224
"title": "需要证书吗?",
@@ -169,9 +171,11 @@
169171
"waiting": "等待",
170172
"device_settings": "设备设定",
171173
"view_requests": "查看连接",
172-
"modify": "修改"
174+
"modify": "修改",
175+
"empty_list": "暂无设备"
173176
},
174177
"modules": {
178+
"empty_list": "暂无模块"
175179
},
176180
"Submit": "提交",
177181
"Clear": "清除",

src/index.tsx

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import React from 'react'
2+
import { toast } from 'react-hot-toast'
23
import ReactDOM from 'react-dom/client'
34

45
import './styles/shadcn.css'
56
import './styles/global.css'
7+
8+
import SWUpdateNotification from '@/components/SWUpdateNotification'
9+
610
import App from './App'
711
import AppContainer from './AppContainer'
812
import * as serviceWorkerRegistration from './serviceWorkerRegistration'
@@ -22,7 +26,13 @@ if (process.env.REACT_APP_USE_SW === 'true') {
2226
// If you want your app to work offline and load faster, you can change
2327
// unregister() to register() below. Note this comes with some pitfalls.
2428
// Learn more about service workers: https://cra.link/PWA
25-
serviceWorkerRegistration.register()
29+
serviceWorkerRegistration.register({
30+
onUpdate: (registration) => {
31+
toast(() => <SWUpdateNotification registration={registration} />, {
32+
duration: Number.POSITIVE_INFINITY,
33+
})
34+
},
35+
})
2636
}
2737

2838
if (!('scrollBehavior' in document.documentElement.style)) {

src/pages/Devices/index.tsx

+18-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useState } from 'react'
22
import { useTranslation } from 'react-i18next'
33
import useSWR from 'swr'
44

5-
import ListCell from '@/components/ListCell'
5+
import { ListCell, ListFullHeightCell } from '@/components/ListCell'
66
import PageContainer from '@/components/PageContainer'
77
import PageTitle from '@/components/PageTitle'
88
import { DevicesResult } from '@/types'
@@ -20,6 +20,16 @@ const Page = (): JSX.Element => {
2020
refreshInterval: isAutoRefresh ? 2000 : 0,
2121
})
2222

23+
const deviceList = devices?.devices.length ? (
24+
devices.devices.map((device) => (
25+
<ListCell key={device.identifier}>
26+
<DeviceItem device={device} />
27+
</ListCell>
28+
))
29+
) : (
30+
<ListFullHeightCell>{t('devices.empty_list')}</ListFullHeightCell>
31+
)
32+
2333
return (
2434
<PageContainer>
2535
<PageTitle
@@ -30,12 +40,13 @@ const Page = (): JSX.Element => {
3040
/>
3141

3242
<div className="divide-y">
33-
{devices?.devices &&
34-
devices.devices.map((device) => (
35-
<ListCell key={device.identifier}>
36-
<DeviceItem device={device} />
37-
</ListCell>
38-
))}
43+
{!devices ? (
44+
<ListFullHeightCell>
45+
{t('common.is_loading') + '...'}
46+
</ListFullHeightCell>
47+
) : (
48+
deviceList
49+
)}
3950
</div>
4051
</PageContainer>
4152
)

src/pages/Dns/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import useSWR, { mutate } from 'swr'
88
import tw from 'twin.macro'
99

1010
import FixedFullscreenContainer from '@/components/FixedFullscreenContainer'
11-
import ListCell from '@/components/ListCell'
11+
import { ListCell } from '@/components/ListCell'
1212
import PageTitle from '@/components/PageTitle'
1313
import { Button } from '@/components/ui/button'
1414
import { ButtonGroup } from '@/components/ui/button-group'

src/pages/Home/components/CapabilityTile.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ const CapabilityTile: React.FC<CapabilityTileProps> = ({
5858
description={descriptionKey ? t(`home.${descriptionKey}`) : undefined}
5959
switchElement={
6060
<Switch
61-
className="dark:border-white/20"
6261
disabled={isLoading}
6362
checked={capability?.enabled}
6463
onCheckedChange={(newVal) => toggle(newVal)}

src/pages/Home/components/MenuTile.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const MenuTile: React.FC<MenuTileProps> = (props) => {
4545
<CardContent className="px-4 md:px-6 py-4 gap-0 h-[7.6rem] lg:h-[9rem]">
4646
<div className="flex flex-col h-full justify-between gap-2 md:gap-4">
4747
{props.description ? (
48-
<CardDescription className="text-xs sm:text-sm">
48+
<CardDescription className="text-xs sm:text-sm line-clamp-3">
4949
{props.description}
5050
</CardDescription>
5151
) : (

src/pages/Home/components/SetHostModal.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Button } from '@/components/ui/button'
1111
import {
1212
Dialog,
1313
DialogContent,
14+
DialogFooter,
1415
DialogHeader,
1516
DialogTitle,
1617
DialogTrigger,
@@ -68,7 +69,7 @@ const SetHostModal: React.FC = () => {
6869
<DialogTitle>{t('landing.history')}</DialogTitle>
6970
</DialogHeader>
7071

71-
<div className="bg-gray-100 dark:bg-muted divide-y divide-gray-200 dark:divide-black/20 rounded-xl overflow-hidden">
72+
<div className="bg-gray-100 dark:bg-muted border divide-y divide-gray-200 dark:divide-black/20 rounded-xl overflow-hidden">
7273
{existingProfiles.map((profile) => {
7374
return (
7475
<div
@@ -92,11 +93,11 @@ const SetHostModal: React.FC = () => {
9293
})}
9394
</div>
9495

95-
<div>
96-
<Button onClick={() => onAddNewProfile()}>
96+
<DialogFooter>
97+
<Button className="mt-3" onClick={() => onAddNewProfile()}>
9798
{t('landing.add_new_host')}
9899
</Button>
99-
</div>
100+
</DialogFooter>
100101
</DialogContent>
101102
</Dialog>
102103
)

0 commit comments

Comments
 (0)