feat: add delete account section to settings
This commit is contained in:
parent
8f58452f4c
commit
ed4d528c8d
72
client/components/Settings/SettingsDeleteAccount.tsx
Normal file
72
client/components/Settings/SettingsDeleteAccount.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import { useFormState } from "react-use-form-state";
|
||||
import React, { FC, useState } from "react";
|
||||
import Router from "next/router";
|
||||
import axios from "axios";
|
||||
|
||||
import { getAxiosConfig } from "../../utils";
|
||||
import { Col, RowCenterV } from "../Layout";
|
||||
import Text, { H2, Span } from "../Text";
|
||||
import { useMessage } from "../../hooks";
|
||||
import { TextInput } from "../Input";
|
||||
import { APIv2 } from "../../consts";
|
||||
import { Button } from "../Button";
|
||||
import Icon from "../Icon";
|
||||
|
||||
const SettingsDeleteAccount: FC = () => {
|
||||
const [message, setMessage] = useMessage(1500);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [formState, { password, label }] = useFormState(null, {
|
||||
withIds: true
|
||||
});
|
||||
|
||||
const onSubmit = async e => {
|
||||
e.preventDefault();
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
await axios.post(
|
||||
`${APIv2.Users}/delete`,
|
||||
formState.values,
|
||||
getAxiosConfig()
|
||||
);
|
||||
Router.push("/logout");
|
||||
} catch (error) {
|
||||
setMessage(error.response.data.error);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Col alignItems="flex-start" maxWidth="100%">
|
||||
<H2 mb={4} bold>
|
||||
Delete account
|
||||
</H2>
|
||||
<Text mb={4}>
|
||||
Delete your account from {process.env.SITE_NAME}. All of your data
|
||||
including your <Span bold>LINKS</Span> and <Span bold>STATS</Span> will
|
||||
be deleted.
|
||||
</Text>
|
||||
<Text
|
||||
{...label("password")}
|
||||
as="label"
|
||||
mb={[2, 3]}
|
||||
fontSize={[15, 16]}
|
||||
bold
|
||||
>
|
||||
Password
|
||||
</Text>
|
||||
<RowCenterV as="form" onSubmit={onSubmit}>
|
||||
<TextInput placeholder="Password..." {...password("password")} mr={3} />
|
||||
<Button color="red" type="submit" disabled={loading}>
|
||||
<Icon name={loading ? "spinner" : "trash"} mr={2} stroke="white" />
|
||||
{loading ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
</RowCenterV>
|
||||
<Text fontSize={15} mt={3} color={message.color}>
|
||||
{message.text}
|
||||
</Text>
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsDeleteAccount;
|
@ -1,6 +1,7 @@
|
||||
import { NextPage } from "next";
|
||||
import React from "react";
|
||||
|
||||
import SettingsDeleteAccount from "../components/Settings/SettingsDeleteAccount";
|
||||
import SettingsPassword from "../components/Settings/SettingsPassword";
|
||||
import SettingsDomain from "../components/Settings/SettingsDomain";
|
||||
import SettingsApi from "../components/Settings/SettingsApi";
|
||||
@ -30,6 +31,8 @@ const SettingsPage: NextPage = () => {
|
||||
<SettingsPassword />
|
||||
<Divider mt={4} mb={48} />
|
||||
<SettingsApi />
|
||||
<Divider mt={4} mb={48} />
|
||||
<SettingsDeleteAccount />
|
||||
</Col>
|
||||
<Footer />
|
||||
</AppWrapper>
|
||||
|
@ -12,3 +12,8 @@ export const get = async (req, res) => {
|
||||
|
||||
return res.status(200).send(data);
|
||||
};
|
||||
|
||||
export const remove = async (req, res) => {
|
||||
await query.user.remove(req.user);
|
||||
return res.status(200).send("OK");
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import { body, param } from "express-validator";
|
||||
import { isAfter, subDays, subHours } from "date-fns";
|
||||
import urlRegex from "url-regex";
|
||||
import { promisify } from "util";
|
||||
import bcrypt from "bcryptjs";
|
||||
import axios from "axios";
|
||||
import dns from "dns";
|
||||
import URL from "url";
|
||||
@ -94,7 +95,7 @@ export const createLink = [
|
||||
});
|
||||
req.body.domain = domain || null;
|
||||
|
||||
return !!domain;
|
||||
if (!domain) return Promise.reject();
|
||||
})
|
||||
.withMessage("You can't use this domain.")
|
||||
];
|
||||
@ -125,12 +126,12 @@ export const addDomain = [
|
||||
.withMessage("You can't use the default domain.")
|
||||
.custom(async (value, { req }) => {
|
||||
const domains = await query.domain.get({ user_id: req.user.id });
|
||||
return domains.length === 0;
|
||||
if (domains.length !== 0) return Promise.reject();
|
||||
})
|
||||
.withMessage("You already own a domain. Contact support if you need more.")
|
||||
.custom(async value => {
|
||||
const domain = await query.domain.find({ address: value });
|
||||
return !domain || !domain.user_id || !domain.banned;
|
||||
if (domain?.user_id || domain?.banned) return Promise.reject();
|
||||
})
|
||||
.withMessage("You can't add this domain."),
|
||||
body("homepage")
|
||||
@ -225,7 +226,7 @@ export const signup = [
|
||||
req.user = user;
|
||||
}
|
||||
|
||||
return !user || !user.verified;
|
||||
if (user?.verified) return Promise.reject();
|
||||
})
|
||||
.withMessage("You can't use this email address.")
|
||||
];
|
||||
@ -259,6 +260,16 @@ export const resetPasswordRequest = [
|
||||
.withMessage("Email length must be max 255.")
|
||||
];
|
||||
|
||||
export const deleteUser = [
|
||||
body("password", "Password is not valid.")
|
||||
.exists({ checkFalsy: true, checkNull: true })
|
||||
.isLength({ min: 8, max: 64 })
|
||||
.custom(async (password, { req }) => {
|
||||
const isMatch = await bcrypt.compare(password, req.user.password);
|
||||
if (!isMatch) return Promise.reject();
|
||||
})
|
||||
];
|
||||
|
||||
export const cooldown = (user: User) => {
|
||||
if (!env.GOOGLE_SAFE_BROWSING_KEY || !user || !user.cooldowns) return;
|
||||
|
||||
|
@ -23,19 +23,9 @@ export async function createDomainTable(knex: Knex) {
|
||||
.integer("user_id")
|
||||
.references("id")
|
||||
.inTable("users")
|
||||
.onDelete("SET NULL")
|
||||
.unique();
|
||||
table.timestamps(false, true);
|
||||
});
|
||||
}
|
||||
|
||||
const hasUUID = await knex.schema.hasColumn("domains", "uuid");
|
||||
if (!hasUUID) {
|
||||
await knex.schema.raw('create extension if not exists "uuid-ossp"');
|
||||
await knex.schema.alterTable("domains", table => {
|
||||
table
|
||||
.uuid("uuid")
|
||||
.notNullable()
|
||||
.defaultTo(knex.raw("uuid_generate_v4()"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import Raven from "raven";
|
||||
import * as helpers from "./handlers/helpers";
|
||||
import * as links from "./handlers/links";
|
||||
import * as auth from "./handlers/auth";
|
||||
import { initializeDb } from "./knex";
|
||||
import __v1Routes from "./__v1";
|
||||
import routes from "./routes";
|
||||
|
||||
@ -28,8 +27,6 @@ const app = nextApp({ dir: "./client", dev: env.isDev });
|
||||
const handle = app.getRequestHandler();
|
||||
|
||||
app.prepare().then(async () => {
|
||||
await initializeDb();
|
||||
|
||||
const server = express();
|
||||
server.set("trust proxy", true);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user