diff --git a/package.json b/package.json index ddd33b62e..df675854e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "teledrive", - "version": "1.1.5", + "version": "1.1.9", "repository": "git@github.com:mgilangjanuar/teledrive.git", "author": "M Gilang Januar ", "license": "MIT", diff --git a/server/package.json b/server/package.json index 84fc158c2..7d337374a 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "1.1.5", + "version": "1.1.9", "main": "dist/index.js", "license": "MIT", "private": true, diff --git a/server/src/api/v1/Contact.ts b/server/src/api/v1/Contact.ts index 7a5ad7198..7d64af6d9 100644 --- a/server/src/api/v1/Contact.ts +++ b/server/src/api/v1/Contact.ts @@ -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 }) } diff --git a/server/src/api/v1/Users.ts b/server/src/api/v1/Users.ts index 4a4ffda2d..1700507fb 100644 --- a/server/src/api/v1/Users.ts +++ b/server/src/api/v1/Users.ts @@ -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' @@ -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 { + 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 { + 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 { // if (req.user.username !== 'mgilangjanuar') { diff --git a/server/src/model/entities/Users.ts b/server/src/model/entities/Users.ts index db09451db..48d479151 100644 --- a/server/src/model/entities/Users.ts +++ b/server/src/model/entities/Users.ts @@ -31,4 +31,9 @@ export class Users extends BaseModelWithID { @OneToMany(() => Files, files => files.user) files?: Files[] + + @Column('jsonb', { default: null }) + settings?: { + expandable_rows?: boolean + } } \ No newline at end of file diff --git a/web/package.json b/web/package.json index 95af77746..4a6be30b2 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "web", - "version": "1.1.5", + "version": "1.1.9", "private": true, "dependencies": { "@craco/craco": "^6.3.0", @@ -96,4 +96,4 @@ "workbox-strategies": "^5.1.3", "workbox-streams": "^5.1.3" } -} +} \ No newline at end of file diff --git a/web/src/App.tsx b/web/src/App.tsx index 699eee810..e792631ba 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -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') ) @@ -59,6 +62,7 @@ function App(): React.ReactElement { /> : }> + diff --git a/web/src/pages/Refund.tsx b/web/src/pages/Refund.tsx index f30aa194c..0eee9da82 100644 --- a/web/src/pages/Refund.tsx +++ b/web/src/pages/Refund.tsx @@ -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 <> - + @@ -26,4 +26,4 @@ const Terms: React.FC = () => { } -export default Terms \ No newline at end of file +export default Refund \ No newline at end of file diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx new file mode 100644 index 000000000..e2393ef4c --- /dev/null +++ b/web/src/pages/Settings.tsx @@ -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(false) + const [logoutConfirmation, setLogoutConfirmation] = useState(false) + const [removeConfirmation, setRemoveConfirmation] = useState(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 <> + + + + + + Settings + + + } title={me?.user.name} description={me?.user.username} /> + +
+ + { + setExpandableRows(val) + save({ expandable_rows: val }) + }} checked={expandableRows} /> + + Delete Account}> + + +
+ + + + + +
+ +
+
+ + + Confirmation + } + visible={logoutConfirmation} + onCancel={() => setLogoutConfirmation(false)} + onOk={logout} + okButtonProps={{ danger: true, type: 'primary' }}> + + All the files you share will not be able to download once you sign out. Continue? + + + + + This action cannot be undone + } + visible={removeConfirmation} + onCancel={() => setRemoveConfirmation(false)} + onOk={remove} + okButtonProps={{ danger: true, type: 'primary' }}> +
+ + + + Please type   permanently removed   for your confirmation} rules={[{ required: true, message: 'Please input your username' }]}> + + +
+
+ +