+
-
+ >
+ );
+}
diff --git a/components/pages/settings/account/ApiKeyDeleteForm.js b/components/pages/settings/account/ApiKeyDeleteForm.js
new file mode 100644
index 00000000..ec5abdc8
--- /dev/null
+++ b/components/pages/settings/account/ApiKeyDeleteForm.js
@@ -0,0 +1,29 @@
+import useApi from 'hooks/useApi';
+import { Button, Form, FormButtons, SubmitButton } from 'react-basics';
+
+export default function ApiKeyDeleteForm({ apiKeyId, onSave, onClose }) {
+ const { del, useMutation } = useApi();
+ const { mutate, error, isLoading } = useMutation(data => del(`/api-key/${apiKeyId}`, data));
+
+ const handleSubmit = async data => {
+ mutate(data, {
+ onSuccess: async () => {
+ onSave();
+ },
+ });
+ };
+
+ return (
+
+ );
+}
diff --git a/components/pages/settings/account/ApiKeysList.js b/components/pages/settings/account/ApiKeysList.js
new file mode 100644
index 00000000..6211a2b2
--- /dev/null
+++ b/components/pages/settings/account/ApiKeysList.js
@@ -0,0 +1,46 @@
+import { Text, Icon, useToast, Banner, LoadingButton, Loading } from 'react-basics';
+import useApi from 'hooks/useApi';
+import ApiKeysTable from 'components/pages/settings/account/ApiKeysTable';
+
+export default function ApiKeysList() {
+ const { toast, showToast } = useToast();
+ const { get, post, useQuery, useMutation } = useApi();
+ const { mutate, isLoading: isUpdating } = useMutation(data => post('/api-key', data));
+ const { data, refetch, isLoading, error } = useQuery(['api-key'], () => get(`/api-key`));
+ const hasData = data && data.length !== 0;
+
+ const handleCreate = () => {
+ mutate(
+ {},
+ {
+ onSuccess: async () => {
+ showToast({ message: 'API key saved.', variant: 'success' });
+ await handleSave();
+ },
+ },
+ );
+ };
+
+ const handleSave = async () => {
+ await refetch();
+ };
+
+ if (error) {
+ return
Something went wrong.;
+ }
+
+ if (isLoading) {
+ return
;
+ }
+
+ return (
+ <>
+ {toast}
+
+ Create key
+
+ {hasData &&
}
+ {!hasData &&
You don't have any API keys.}
+ >
+ );
+}
diff --git a/components/pages/settings/account/ApiKeysTable.js b/components/pages/settings/account/ApiKeysTable.js
new file mode 100644
index 00000000..236e76af
--- /dev/null
+++ b/components/pages/settings/account/ApiKeysTable.js
@@ -0,0 +1,100 @@
+import { formatDistance } from 'date-fns';
+import { useState } from 'react';
+import {
+ Button,
+ Icon,
+ Modal,
+ PasswordField,
+ Table,
+ TableBody,
+ TableCell,
+ TableColumn,
+ TableHeader,
+ TableRow,
+ Text,
+} from 'react-basics';
+import ApiKeyDeleteForm from 'components/pages/settings/account/ApiKeyDeleteForm';
+import Trash from 'assets/trash.svg';
+import styles from './ApiKeysTable.module.css';
+
+const columns = [
+ { name: 'apiKey', label: 'Key', style: { flex: 3 } },
+ { name: 'created', label: 'Created', style: { flex: 1 } },
+ { name: 'action', label: ' ', style: { flex: 1 } },
+];
+
+export default function ApiKeysTable({ data = [], onSave }) {
+ const [apiKeyId, setApiKeyId] = useState(null);
+
+ const handleSave = () => {
+ setApiKeyId(null);
+ onSave();
+ };
+
+ const handleClose = () => {
+ setApiKeyId(null);
+ };
+
+ const handleDelete = id => {
+ setApiKeyId(id);
+ };
+
+ return (
+ <>
+
+
+ {(column, index) => {
+ return (
+
+ {column.label}
+
+ );
+ }}
+
+
+ {(row, keys, rowIndex) => {
+ row.apiKey = ;
+
+ row.created = formatDistance(new Date(row.createdAt), new Date(), {
+ addSuffix: true,
+ });
+
+ row.action = (
+
+ );
+
+ return (
+
+ {(data, key, colIndex) => {
+ return (
+
+ {data[key]}
+
+ );
+ }}
+
+ );
+ }}
+
+
+ {apiKeyId && (
+
+ {close => }
+
+ )}
+ >
+ );
+}
diff --git a/components/pages/settings/account/ApiKeysTable.module.css b/components/pages/settings/account/ApiKeysTable.module.css
new file mode 100644
index 00000000..cc2feee5
--- /dev/null
+++ b/components/pages/settings/account/ApiKeysTable.module.css
@@ -0,0 +1,31 @@
+.table th,
+.table td {
+ flex: 2;
+}
+
+.cell {
+ display: flex;
+ align-items: center;
+}
+
+.header:first-child,
+.cell:first-child {
+ min-width: 320px;
+}
+
+.input {
+ flex: 2;
+}
+
+.cell:last-child {
+ justify-content: flex-end;
+}
+
+.actions {
+ display: flex;
+ gap: 12px;
+}
+
+.empty {
+ min-height: 300px;
+}
diff --git a/components/pages/settings/account/PasswordEditForm.js b/components/pages/settings/account/PasswordEditForm.js
new file mode 100644
index 00000000..17cc8447
--- /dev/null
+++ b/components/pages/settings/account/PasswordEditForm.js
@@ -0,0 +1,67 @@
+import { useRef } from 'react';
+import { Form, FormRow, FormInput, FormButtons, PasswordField, Button } from 'react-basics';
+import useApi from 'hooks/useApi';
+import useUser from 'hooks/useUser';
+
+export default function PasswordEditForm({ onSave, onClose }) {
+ const { post, useMutation } = useApi();
+ const { user } = useUser();
+ const { mutate, error, isLoading } = useMutation(data =>
+ post(`/accounts/${user.id}/change-password`, data),
+ );
+ const ref = useRef(null);
+
+ const handleSubmit = async data => {
+ mutate(data, {
+ onSuccess: async () => {
+ onSave();
+ },
+ });
+ };
+
+ const samePassword = value => {
+ if (value !== ref?.current?.getValues('newPassword')) {
+ return "Passwords don't match";
+ }
+ return true;
+ };
+
+ return (
+
+ );
+}
diff --git a/components/pages/settings/account/PasswordResetForm.js b/components/pages/settings/account/PasswordResetForm.js
new file mode 100644
index 00000000..ef562208
--- /dev/null
+++ b/components/pages/settings/account/PasswordResetForm.js
@@ -0,0 +1,50 @@
+import { useRef } from 'react';
+import { Form, FormRow, FormInput, FormButtons, PasswordField, SubmitButton } from 'react-basics';
+import useApi from 'hooks/useApi';
+
+export default function PasswordResetForm({ token, onSave }) {
+ const { post, useMutation } = useApi();
+ const { mutate, error } = useMutation(data =>
+ post('/accounts/reset-password', { ...data, token }),
+ );
+ const ref = useRef(null);
+
+ const handleSubmit = async data => {
+ mutate(data, {
+ onSuccess: async () => {
+ onSave();
+ },
+ });
+ };
+
+ const samePassword = value => {
+ if (value !== ref?.current?.getValues('newPassword')) {
+ return "Passwords don't match";
+ }
+ return true;
+ };
+
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/components/pages/ProfileSettings.js b/components/pages/settings/profile/ProfileSettings.js
similarity index 94%
rename from components/pages/ProfileSettings.js
rename to components/pages/settings/profile/ProfileSettings.js
index 44b93f05..03f73a8a 100644
--- a/components/pages/ProfileSettings.js
+++ b/components/pages/settings/profile/ProfileSettings.js
@@ -3,7 +3,7 @@ import PageHeader from 'components/layout/PageHeader';
import ProfileDetails from 'components/settings/ProfileDetails';
import { useState } from 'react';
import { Breadcrumbs, Icon, Item, Tabs, useToast, Modal, Button } from 'react-basics';
-import UserPasswordForm from 'components/forms/UserPasswordForm';
+import UserPasswordForm from 'components/pages/settings/users/UserPasswordForm';
import Pen from 'assets/pen.svg';
export default function ProfileSettings() {
diff --git a/components/forms/TeamAddForm.js b/components/pages/settings/teams/TeamAddForm.js
similarity index 81%
rename from components/forms/TeamAddForm.js
rename to components/pages/settings/teams/TeamAddForm.js
index ff47931d..b7d2e599 100644
--- a/components/forms/TeamAddForm.js
+++ b/components/pages/settings/teams/TeamAddForm.js
@@ -1,11 +1,9 @@
import { useRef } from 'react';
import { Form, FormRow, FormInput, FormButtons, TextField, Button } from 'react-basics';
import useApi from 'hooks/useApi';
-import styles from './Form.module.css';
-import { useMutation } from '@tanstack/react-query';
export default function TeamAddForm({ onSave, onClose }) {
- const { post } = useApi();
+ const { post, useMutation } = useApi();
const { mutate, error, isLoading } = useMutation(data => post('/teams', data));
const ref = useRef(null);
@@ -18,7 +16,7 @@ export default function TeamAddForm({ onSave, onClose }) {
};
return (
-