Skip to content
This repository has been archived by the owner on Apr 23, 2024. It is now read-only.

feature/settings and stuff #153

Merged
merged 3 commits into from
Jan 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions server/src/api/v1/Contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ export class Contact {
const { from, message } = req.body
await axios.post(`https://api.telegram.org/bot${process.env.TG_BOT_TOKEN}/sendMessage`, {
chat_id: process.env.TG_BOT_OWNER_ID,
text: `🛎 *Someone wants to contact you!*\n\n_From: @${from}_\n\n${message}`,
parse_mode: 'markdown'
text: `🛎 @${from} wants to contact you!\n\n${message}`
})
return res.send({ success: true })
}
Expand Down
28 changes: 28 additions & 0 deletions server/src/api/v1/Users.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Api } from '@mgilangjanuar/telegram'
import axios from 'axios'
import { Request, Response } from 'express'
import moment from 'moment'
import { Files } from '../../model/entities/Files'
import { Usages } from '../../model/entities/Usages'
import { Users as Model } from '../../model/entities/Users'
import { Midtrans, TransactionDetails } from '../../service/Midtrans'
Expand Down Expand Up @@ -132,6 +134,32 @@ export class Users {
return res.send({ users, length })
}

@Endpoint.PATCH('/me/settings', { middlewares: [Auth] })
public async settings(req: Request, res: Response): Promise<any> {
const { settings } = req.body
req.user.settings = settings
await req.user.save()
return res.send({ settings: req.user?.settings })
}

@Endpoint.POST('/me/delete', { middlewares: [Auth] })
public async remove(req: Request, res: Response): Promise<any> {
const { reason, agreement } = req.body
if (agreement !== 'permanently removed') {
throw { status: 400, body: { error: 'Invalid agreement' } }
}
if (reason) {
await axios.post(`https://api.telegram.org/bot${process.env.TG_BOT_TOKEN}/sendMessage`, {
chat_id: process.env.TG_BOT_OWNER_ID,
text: `😭 ${req.user.name} (@${req.user.username}) removed their account.\n\nReason: ${reason}`
})
}
await Files.delete({ user_id: req.user.id })
await req.user.remove()
const success = await req.tg.invoke(new Api.auth.LogOut())
return res.clearCookie('authorization').clearCookie('refreshToken').send({ success })
}

// @Endpoint.USE('/upgradePlans', { middlewares: [Auth] })
// public async upgradePlans(req: Request, res: Response): Promise<any> {
// if (req.user.username !== 'mgilangjanuar') {
Expand Down
5 changes: 5 additions & 0 deletions server/src/model/entities/Users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ export class Users extends BaseModelWithID {

@OneToMany(() => Files, files => files.user)
files?: Files[]

@Column('jsonb', { default: null })
settings?: {
expandable_rows?: boolean
}
}
4 changes: 4 additions & 0 deletions web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import 'antd-country-phone-input/dist/index.css'
const Dashboard = lazy(
() => import(/* webpackChunkName: 'DashboardPage' */ './pages/dashboard')
)
const Settings = lazy(
() => import(/* webpackChunkName: 'SettingsPage' */ './pages/Settings')
)
const Home = lazy(
() => import(/* webpackChunkName: 'HomePage' */ './pages/Home')
)
Expand Down Expand Up @@ -59,6 +62,7 @@ function App(): React.ReactElement {
/> : <Suspense fallback={<></>}>
<Switch>
<Route path="/dashboard/:type?" exact component={Dashboard} />
<Route path="/settings" exact component={Settings} />
<Route path="/view/:id" exact component={View} />
<Route path="/login" exact component={Login} />
<Route path="/terms" exact component={Terms} />
Expand Down
6 changes: 3 additions & 3 deletions web/src/pages/Refund.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { fetcher } from '../utils/Fetcher'
import Footer from './components/Footer'
import Navbar from './components/Navbar'

const Terms: React.FC = () => {
const Refund: React.FC = () => {
const { data } = useSWRImmutable('/documents/refund', fetcher)
const { data: me } = useSWRImmutable('/users/me', fetcher)

return <>
<Navbar page="terms" user={me} />
<Navbar page="refund" user={me} />
<Layout.Content className="container">
<Row>
<Col lg={{ span: 18, offset: 3 }} md={{ span: 20, offset: 2 }} span={24}>
Expand All @@ -26,4 +26,4 @@ const Terms: React.FC = () => {
</>
}

export default Terms
export default Refund
112 changes: 112 additions & 0 deletions web/src/pages/Settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { DeleteOutlined, LogoutOutlined, WarningOutlined } from '@ant-design/icons'
import { Avatar, Button, Card, Col, Divider, Form, Input, Layout, Modal, notification, Row, Switch, Typography } from 'antd'
import { useForm } from 'antd/es/form/Form'
import React, { useState } from 'react'
import { useHistory } from 'react-router-dom'
import useSWRImmutable from 'swr/immutable'
import { apiUrl, fetcher, req } from '../utils/Fetcher'
import Footer from './components/Footer'
import Navbar from './components/Navbar'

const Settings: React.FC = () => {
const history = useHistory()
const [expandableRows, setExpandableRows] = useState<boolean>(false)
const [logoutConfirmation, setLogoutConfirmation] = useState<boolean>(false)
const [removeConfirmation, setRemoveConfirmation] = useState<boolean>(false)
const [formRemoval] = useForm()
const { data: me } = useSWRImmutable('/users/me', fetcher, {
onError: () => history.push('/login'),
onSuccess: ({ user }) => {
setExpandableRows(user?.settings?.expandable_rows)
}
})

const save = (settings: any) => {
req.patch('/users/me/settings', { settings })
.then(() => notification.success({ message: 'Settings saved' }))
.catch(() => notification.error({ message: 'Something error. Please try again.' }))
}

const logout = async () => {
await req.post('/auth/logout')
return window.location.replace('/')
}

const remove = async () => {
const { agreement, reason } = formRemoval.getFieldsValue()
try {
await req.post('/users/me/delete', { agreement, reason })
return window.location.replace('/')
} catch (error: any) {
return notification.error({ message: 'Error', description: error.response?.data.error })
}
}

return <>
<Navbar page="settings" user={me} />
<Layout.Content className="container">
<Row style={{ marginTop: '30px' }}>
<Col lg={{ span: 10, offset: 7 }} md={{ span: 14, offset: 5 }} span={20} offset={2}>
<Typography.Title level={2}>
Settings
</Typography.Title>
<Card>
<Card.Meta avatar={<Avatar size="large" src={`${apiUrl}/users/me/photo`} />} title={me?.user.name} description={me?.user.username} />
<Divider />
<Form layout="horizontal" labelAlign="left" labelCol={{ span: 12 }} wrapperCol={{ span: 12 }}>
<Form.Item label="Expandable Rows" name="expandable_rows">
<Switch onChange={val => {
setExpandableRows(val)
save({ expandable_rows: val })
}} checked={expandableRows} />
</Form.Item>
<Form.Item label={<Typography.Text type="danger">Delete Account</Typography.Text>}>
<Button shape="round" danger type="primary" icon={<DeleteOutlined />} onClick={() => setRemoveConfirmation(true)}>Permanently Removed</Button>
</Form.Item>
</Form>
<Row>
<Col span={24} md={{ span: 12, offset: 12 }} lg={{ span: 12, offset: 12 }}>
<Button icon={<LogoutOutlined />} danger shape="round" onClick={() => setLogoutConfirmation(true)}>
Logout
</Button>
</Col>
</Row>
</Card>
</Col>
</Row>
</Layout.Content>

<Modal title={<Typography.Text>
<Typography.Text type="warning"><WarningOutlined /></Typography.Text> Confirmation
</Typography.Text>}
visible={logoutConfirmation}
onCancel={() => setLogoutConfirmation(false)}
onOk={logout}
okButtonProps={{ danger: true, type: 'primary' }}>
<Typography.Paragraph>
All the files you share will not be able to download once you sign out. Continue?
</Typography.Paragraph>
</Modal>

<Modal title={<Typography.Text>
<Typography.Text type="warning"><WarningOutlined /></Typography.Text> This action cannot be undone
</Typography.Text>}
visible={removeConfirmation}
onCancel={() => setRemoveConfirmation(false)}
onOk={remove}
okButtonProps={{ danger: true, type: 'primary' }}>
<Form form={formRemoval} onFinish={remove} layout="vertical">
<Form.Item name="reason" label="Reason" rules={[{ required: true, message: 'Please input your username' }]}>
<Input.TextArea />
</Form.Item>
<Form.Item name="agreement" label={<>Please type &nbsp; <Typography.Text type="danger">permanently removed</Typography.Text> &nbsp; for your confirmation</>} rules={[{ required: true, message: 'Please input your username' }]}>
<Input />
</Form.Item>
</Form>
</Modal>

<Footer />
</>
}

export default Settings
5 changes: 3 additions & 2 deletions web/src/pages/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CrownOutlined, DashboardOutlined, LoginOutlined, LogoutOutlined, MenuOutlined, UserOutlined, WarningOutlined } from '@ant-design/icons'
import { CrownOutlined, DashboardOutlined, LoginOutlined, LogoutOutlined, MenuOutlined, SettingOutlined, UserOutlined, WarningOutlined } from '@ant-design/icons'
import { Button, Layout, Menu, Modal, Popover, Progress, Tag, Tooltip, Typography } from 'antd'
import moment from 'moment'
import prettyBytes from 'pretty-bytes'
Expand Down Expand Up @@ -44,11 +44,12 @@ const Navbar: React.FC<Props> = ({ user, page }) => {
<div style={{ padding: '10px' }}>
Bandwidth usage: { }
{(user?.user?.plan || user?.plan) === 'premium' ? <Tag color="green">Unlimited</Tag> : <Tooltip placement="left" title={<>You can download up to {prettyBytes(Math.max(0, 1_500_000_000 - Number(usage?.usage.usage)))} until {moment(usage?.usage.expire).local().format('lll')}</>}>
<Progress status="exception" percent={Number((Number(usage?.usage.usage) / 1_500_000_000 * 100).toFixed(1))} />
<Progress status="exception" percent={Number((Number(usage?.usage.usage || 0) / 1_500_000_000 * 100).toFixed(1))} />
</Tooltip>}
</div>
<Menu>
<Menu.Item key="dashboard" icon={<DashboardOutlined />} onClick={() => history.push('/dashboard')}>Dashboard</Menu.Item>
<Menu.Item key="settings" icon={<SettingOutlined />} onClick={() => history.push('/settings')}>Settings</Menu.Item>
<Menu.Item danger key="logout" icon={<LogoutOutlined />} onClick={() => setLogoutConfirmation(true)}>Logout</Menu.Item>
</Menu>
</div>}>
Expand Down
4 changes: 3 additions & 1 deletion web/src/pages/dashboard/components/TableFiles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { apiUrl } from '../../../utils/Fetcher'
interface Props {
files?: any,
tab: string,
me?: any,
onChange: (...args: any[]) => void,
onDelete: (row: any) => void,
onRename: (row: any) => void,
Expand All @@ -48,6 +49,7 @@ interface Props {
const TableFiles: React.FC<Props> = ({
files,
tab,
me,
onChange,
onDelete,
onRename,
Expand Down Expand Up @@ -307,7 +309,7 @@ const TableFiles: React.FC<Props> = ({
})
}
})}
expandable={window.innerWidth < 752 ? {
expandable={me?.settings?.expandable_rows && window.innerWidth < 752 ? {
expandedRowRender: (row: any) => <Descriptions labelStyle={{ fontWeight: 'bold' }} column={1}>
<Descriptions.Item label="Size">{row.size ? prettyBytes(Number(row.size)) : '-'}</Descriptions.Item>
<Descriptions.Item label="Uploaded At">{row.upload_progress !== null ? <>Uploading {Number((row.upload_progress * 100).toFixed(2))}%</> : moment(row.uploaded_at).local().format('lll')}</Descriptions.Item>
Expand Down
10 changes: 9 additions & 1 deletion web/src/pages/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { FolderAddOutlined, HomeOutlined, PlusOutlined, SyncOutlined, UploadOutlined, WarningOutlined } from '@ant-design/icons'
import {
FolderAddOutlined,
HomeOutlined,
PlusOutlined,
SyncOutlined,
UploadOutlined,
WarningOutlined
} from '@ant-design/icons'
import {
Alert,
Button,
Expand Down Expand Up @@ -344,6 +351,7 @@ const Dashboard: React.FC<PageProps> = ({ match }) => {
<TableFiles
files={files}
tab={tab}
me={me?.user}
onChange={change}
onDelete={row => {
if (!selected?.find(select => select.id === row.id)) {
Expand Down