Skip to content

Commit

Permalink
feat: 黑暗模式
Browse files Browse the repository at this point in the history
  • Loading branch information
LeafYeeXYZ committed Sep 18, 2024
1 parent e647eba commit 1aa7ed6
Show file tree
Hide file tree
Showing 13 changed files with 567 additions and 472 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ bun run deploy # Deploy the workers
- [x] 支持 Markdown 写邮件 (Marked)
- [x] 已发送邮件页面
- [x] 个人资料页面 (记得游客账户不能修改)
- [ ] 夜间模式
- [x] 夜间模式
- [ ] 找回密码功能 (向备用邮箱发送验证码)
- [ ] AI 总结邮件内容生成邮件摘要 (Cloudflare Workers AI)
- [ ] 附件支持
Expand Down
8 changes: 4 additions & 4 deletions app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ export default function AuthLayout({ children }: Readonly<{ children: React.Reac

return (
<div
className='flex items-center justify-center h-dvh w-dvw bg-gray-50'
className='flex items-center justify-center h-dvh w-dvw bg-gray-50 dark:bg-gray-950'
>
<div
className='w-[95%] h-[95%] max-w-[45rem] max-h-[30rem] flex flex-row gap-4 p-4 bg-white rounded-lg shadow-lg'
className='w-[95%] h-[95%] max-w-[45rem] max-h-[30rem] flex flex-row gap-4 p-4 bg-white dark:bg-gray-800 rounded-lg shadow-lg'
>
<div
className='hidden sm:block sm:w-3/5 h-full rounded-lg bg-red-50'
className='hidden sm:block sm:w-3/5 h-full rounded-lg bg-red-50 dark:bg-gray-900'
>

</div>
<div
className='w-full sm:w-2/5 h-full rounded-lg bg-white'
className='w-full sm:w-2/5 h-full rounded-lg bg-white dark:bg-gray-900'
>
{children}
</div>
Expand Down
92 changes: 53 additions & 39 deletions app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
'use client'

import { Button, Checkbox, Input, Form, Flex, message } from 'antd'
import { Button, Checkbox, Input, Form, Flex, message, ConfigProvider, ThemeConfig } from 'antd'
import { UserOutlined, KeyOutlined, ExportOutlined } from '@ant-design/icons'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useEffect, useState, useOptimistic } from 'react'
import { auth } from './action'
import sha256 from 'crypto-js/sha256'
import { clear } from 'idb-keyval'
import { darkTheme } from '@/app/config'

type FieldType = {
email: string
Expand Down Expand Up @@ -80,48 +81,61 @@ export default function Login() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

// 控制黑暗模式
const [themeConfig, setThemeConfig] = useState<ThemeConfig>({})
useEffect(() => {
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
if (isDarkMode) {
setThemeConfig(darkTheme)
}
}, [])

return (
<div className='relative w-full h-full flex flex-col items-center justify-center'>
{contextHolder}
<Form<FieldType>
name='login'
initialValues={{ remember: true }}
className='w-11/12'
onFinish={handleSubmit}
disabled={disableState}
>
<Form.Item>
<p className='mb-4 text-2xl font-bold text-center'>MailBox</p>
</Form.Item>

<Form.Item
name='email'
rules={[{ type: 'email', message: '请输入正确的邮箱地址', }, { required: true, message: '请输入邮箱地址', }]}
<ConfigProvider
theme={themeConfig}
>
<div className='relative w-full h-full flex flex-col items-center justify-center'>
{contextHolder}
<Form<FieldType>
name='login'
initialValues={{ remember: true }}
className='w-11/12'
onFinish={handleSubmit}
disabled={disableState}
>
<Input prefix={<UserOutlined />} placeholder='邮箱地址' type='email' />
</Form.Item>
<Form.Item>
<p className='mb-4 text-2xl font-bold text-center'>MailBox</p>
</Form.Item>

<Form.Item
name='password'
rules={[{ required: true, message: '请输入密码' }]}
>
<Input prefix={<KeyOutlined />} type="password" placeholder='密码' />
</Form.Item>
<Form.Item
name='email'
rules={[{ type: 'email', message: '请输入正确的邮箱地址', }, { required: true, message: '请输入邮箱地址', }]}
>
<Input prefix={<UserOutlined />} placeholder='邮箱地址' type='email' />
</Form.Item>

<Form.Item
name='password'
rules={[{ required: true, message: '请输入密码' }]}
>
<Input prefix={<KeyOutlined />} type="password" placeholder='密码' />
</Form.Item>

<Form.Item className='px-1'>
<Flex justify="space-between" align="center">
<Form.Item name="remember" valuePropName="checked" noStyle>
<Checkbox className='text-xs'>记住我</Checkbox>
</Form.Item>
<Link href="/registry" className='text-xs opacity-70'>注册<ExportOutlined className='ml-1' /></Link>
<Link href="/reset" className='text-xs opacity-70'>忘记密码<ExportOutlined className='ml-1' /></Link>
</Flex>
</Form.Item>
<Form.Item className='px-1'>
<Flex justify="space-between" align="center">
<Form.Item name="remember" valuePropName="checked" noStyle>
<Checkbox className='text-xs'>记住我</Checkbox>
</Form.Item>
<Link href="/registry" className='text-xs opacity-70'>注册<ExportOutlined className='ml-1' /></Link>
<Link href="/reset" className='text-xs opacity-70'>忘记密码<ExportOutlined className='ml-1' /></Link>
</Flex>
</Form.Item>

<Form.Item>
<Button type='primary' htmlType='submit' className='w-full'>登录</Button>
</Form.Item>
</Form>
</div>
<Form.Item>
<Button type='primary' htmlType='submit' className='w-full'>登录</Button>
</Form.Item>
</Form>
</div>
</ConfigProvider>
)
}
170 changes: 92 additions & 78 deletions app/(auth)/registry/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
'use client'

import { Button, Input, Form, Flex, message } from 'antd'
import { Button, Input, Form, Flex, message, ConfigProvider, ThemeConfig } from 'antd'
import { UserOutlined, KeyOutlined, MailOutlined, CodeOutlined } from '@ant-design/icons'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
import { useState, useEffect } from 'react'
import { flushSync } from 'react-dom'
import { registry } from './action'
import { darkTheme } from '@/app/config'

type FieldType = {
username: string
Expand Down Expand Up @@ -64,91 +65,104 @@ export default function Login() {
})
}

// 控制黑暗模式
const [themeConfig, setThemeConfig] = useState<ThemeConfig>({})
useEffect(() => {
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
if (isDarkMode) {
setThemeConfig(darkTheme)
}
}, [])

return (
<div className='relative w-full h-full flex flex-col items-center justify-center'>
{contextHolder}
<Form<FieldType>
name='registry'
className='w-11/12'
onFinish={handleSubmit}
disabled={disableForm}
>
<Form.Item>
<p className='mb-2 text-2xl font-bold text-center'>注册</p>
</Form.Item>

<Form.Item
name='username'
rules={[{ required: true, message: '请输入用户名' },
() => ({
validator(_, value) {
if (!value || value.length <= 20 && !/\s/.test(value)) {
return Promise.resolve()
}
return Promise.reject(new Error('用户名最长20个字符且不能有空格'))
},
})
]}
<ConfigProvider
theme={themeConfig}
>
<div className='relative w-full h-full flex flex-col items-center justify-center'>
{contextHolder}
<Form<FieldType>
name='registry'
className='w-11/12'
onFinish={handleSubmit}
disabled={disableForm}
>
<Input prefix={<UserOutlined />} placeholder='用户名 (支持中文)' />
</Form.Item>
<Form.Item>
<p className='mb-2 text-2xl font-bold text-center'>注册</p>
</Form.Item>

<Form.Item
name='email'
rules={[{ required: true, message: '请输入邮箱地址', },
() => ({
validator(_, value) {
if (!value || /^[a-z0-9]{1,20}$/.test(value)) {
return Promise.resolve()
}
return Promise.reject(new Error('请输入20个字符以内的小写字母或数字'))
},
})
]}
>
<Input addonAfter={`@${process.env.NEXT_PUBLIC_MAIL_SERVER}`} prefix={<MailOutlined />} placeholder='邮箱地址' type='text' />
</Form.Item>
<Form.Item
name='username'
rules={[{ required: true, message: '请输入用户名' },
() => ({
validator(_, value) {
if (!value || value.length <= 20 && !/\s/.test(value)) {
return Promise.resolve()
}
return Promise.reject(new Error('用户名最长20个字符且不能有空格'))
},
})
]}
>
<Input prefix={<UserOutlined />} placeholder='用户名 (支持中文)' />
</Form.Item>

<Form.Item
name='password'
rules={[{ required: true, message: '请输入密码' }]}
>
<Input prefix={<KeyOutlined />} type="password" placeholder='密码' />
</Form.Item>
<Form.Item
name='email'
rules={[{ required: true, message: '请输入邮箱地址', },
() => ({
validator(_, value) {
if (!value || /^[a-z0-9]{1,20}$/.test(value)) {
return Promise.resolve()
}
return Promise.reject(new Error('请输入20个字符以内的小写字母或数字'))
},
})
]}
>
<Input addonAfter={`@${process.env.NEXT_PUBLIC_MAIL_SERVER}`} prefix={<MailOutlined />} placeholder='邮箱地址' type='text' />
</Form.Item>

<Form.Item
name='confirm'
dependencies={['password']}
rules={[{ required: true, message: '请再次输入密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve()
}
return Promise.reject(new Error('两次输入的密码不一致'))
},
})]}
>
<Input prefix={<KeyOutlined />} type="password" placeholder='确认密码' />
</Form.Item>
<Form.Item
name='password'
rules={[{ required: true, message: '请输入密码' }]}
>
<Input prefix={<KeyOutlined />} type="password" placeholder='密码' />
</Form.Item>

{
process.env.NEXT_PUBLIC_REGISTRY_SET === 'true' &&
<Form.Item
name='authCode'
rules={[{ required: true, message: '请输入注册码' }]}
name='confirm'
dependencies={['password']}
rules={[{ required: true, message: '请再次输入密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve()
}
return Promise.reject(new Error('两次输入的密码不一致'))
},
})]}
>
<Input prefix={<CodeOutlined />} placeholder='注册码' />
<Input prefix={<KeyOutlined />} type="password" placeholder='确认密码' />
</Form.Item>
}

<Form.Item>
<Flex justify="space-between" align="center" className='mt-2'>
<Button type='default' htmlType='button' className='w-[48%]' onClick={() => router.push('/login')}>返回登录</Button>
<Button type='primary' htmlType='submit' className='w-[48%]'>立即注册</Button>
</Flex>
</Form.Item>
</Form>
</div>
{
process.env.NEXT_PUBLIC_REGISTRY_SET === 'true' &&
<Form.Item
name='authCode'
rules={[{ required: true, message: '请输入注册码' }]}
>
<Input prefix={<CodeOutlined />} placeholder='注册码' />
</Form.Item>
}

<Form.Item>
<Flex justify="space-between" align="center" className='mt-2'>
<Button type='default' htmlType='button' className='w-[48%]' onClick={() => router.push('/login')}>返回登录</Button>
<Button type='primary' htmlType='submit' className='w-[48%]'>立即注册</Button>
</Flex>
</Form.Item>
</Form>
</div>
</ConfigProvider>
)
}
4 changes: 2 additions & 2 deletions app/(auth)/reset/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export default function Reset() {
const router = useRouter()
return (
<div className='flex flex-col items-center justify-center h-full w-full'>
<div className="text-center text-lg font-bold">本功能开发中...</div>
<div className="text-center text-sm my-6 px-6">如需找回账号, 请联系管理员删除原账号, 再重新进行注册, 收发件箱的邮件不会丢失</div>
<div className="text-center text-lg font-bold dark:text-white">本功能开发中...</div>
<div className="text-center text-sm my-6 px-6 dark:text-white">如需找回账号, 请联系管理员删除原账号, 再重新进行注册, 收发件箱的邮件不会丢失</div>
<Button type='primary' onClick={() => router.push('/login')}>返回登录</Button>
</div>
)
Expand Down
12 changes: 6 additions & 6 deletions app/(dashboard)/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ export function Nav() {
style={{ scrollbarWidth: 'none' }}
className='relative flex flex-row sm:flex-col gap-1 sm:gap-3 w-full h-full overflow-y-auto'
>
<Link className={`bg-white text-nowrap shadow-sm w-full border text-xs sm:text-sm p-2 text-center rounded-full ${pathname === '/inbox' ? 'text-rose-400' : 'text-gray-500'}`} href='/inbox'><AppstoreOutlined /> 收件箱</Link>
<Link className={`bg-white text-nowrap shadow-sm w-full border text-xs sm:text-sm p-2 text-center rounded-full ${pathname === '/send' ? 'text-rose-400' : 'text-gray-500'}`} href='/send'><FileAddOutlined /> 新邮件</Link>
<Link className={`bg-white text-nowrap shadow-sm w-full border text-xs sm:text-sm p-2 text-center rounded-full ${pathname === '/sent' ? 'text-rose-400' : 'text-gray-500'}`} href='/sent'><FileDoneOutlined /> 已发送</Link>
<Link className={`bg-white text-nowrap shadow-sm w-full border text-xs sm:text-sm p-2 text-center rounded-full sm:absolute sm:bottom-[3.15rem] ${pathname === '/profile' ? 'text-rose-400' : 'text-gray-500'}`} href='/profile'><IdcardOutlined /> 账号</Link>
<a className='bg-white text-nowrap shadow-sm w-full border text-xs sm:text-sm p-2 text-center rounded-full sm:absolute sm:bottom-0 text-gray-500' href='#'
<Link className={`bg-white dark:bg-gray-800 dark:border-black text-nowrap shadow-sm w-full border text-xs sm:text-sm p-2 text-center rounded-full ${pathname === '/inbox' ? 'text-rose-400' : 'dark:text-white text-gray-500'}`} href='/inbox'><AppstoreOutlined /> 收件箱</Link>
<Link className={`bg-white dark:bg-gray-800 dark:border-black text-nowrap shadow-sm w-full border text-xs sm:text-sm p-2 text-center rounded-full ${pathname === '/send' ? 'text-rose-400' : 'dark:text-white text-gray-500'}`} href='/send'><FileAddOutlined /> 新邮件</Link>
<Link className={`bg-white dark:bg-gray-800 dark:border-black text-nowrap shadow-sm w-full border text-xs sm:text-sm p-2 text-center rounded-full ${pathname === '/sent' ? 'text-rose-400' : 'dark:text-white text-gray-500'}`} href='/sent'><FileDoneOutlined /> 已发送</Link>
<Link className={`bg-white dark:bg-gray-800 dark:border-black text-nowrap shadow-sm w-full border text-xs sm:text-sm p-2 text-center rounded-full sm:absolute sm:bottom-[3.15rem] ${pathname === '/profile' ? 'text-rose-400' : 'dark:text-white text-gray-500'}`} href='/profile'><IdcardOutlined /> 账号</Link>
<a className='bg-white dark:bg-gray-800 dark:border-black text-nowrap shadow-sm w-full border text-xs sm:text-sm p-2 text-center rounded-full sm:absolute sm:bottom-0 text-gray-500 dark:text-white' href='#'
onClick={event => {
event.preventDefault()
localStorage.clear()
sessionStorage.clear()
router.push('/login')
}}
><DisconnectOutlined /> 注销</a>
<p className='bg-white shadow-sm border rounded-full hidden sm:block absolute bottom-[6.5rem] overflow-hidden text-ellipsis text-nowrap w-full text-center text-xs font-bold text-gray-500 p-2'>你好, {username}</p>
<p className='bg-white dark:bg-gray-800 dark:border-black shadow-sm border rounded-full hidden sm:block absolute bottom-[6.5rem] overflow-hidden text-ellipsis text-nowrap w-full text-center text-xs font-bold text-gray-500 p-2 dark:text-white'>你好, {username}</p>
</div>
)
}
Loading

0 comments on commit 1aa7ed6

Please sign in to comment.