Skip to content

Commit 0f08b10

Browse files
committed
add team transfer option for users who want to delete account but keep team
1 parent 78d5d17 commit 0f08b10

File tree

6 files changed

+146
-19
lines changed

6 files changed

+146
-19
lines changed

ui/src/components/navbar/NavAuthLinks.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,23 @@ export default function NavAuthLinks() {
1414
const authValid = useAuthValid();
1515

1616
if (authValid) {
17-
return <NavBarDropdownLink>
17+
return <>
18+
<NavBarLink href={URLS.DASHBOARD}>Dashboard</NavBarLink>
19+
<NavBarDropdownLink>
1820
<NavBarDropdownLinkText>
1921
<FontAwesomeIcon
2022
icon={faUserCircle}/>&nbsp;{pocketbase.authStore?.model?.username}
2123
</NavBarDropdownLinkText>
2224
<NavBarDropdownItemContainer>
23-
<NavBarDropdownItem>
24-
<NavBarLink href={URLS.DASHBOARD}>Dashboard</NavBarLink>
25-
</NavBarDropdownItem>
2625
<NavBarDropdownItem>
2726
<NavBarLink href={URLS.USER_SETTINGS}>Account</NavBarLink>
2827
</NavBarDropdownItem>
2928
<NavBarDropdownItem>
3029
<NavBarLink href={URLS.LOGOUT}>Logout</NavBarLink>
3130
</NavBarDropdownItem>
3231
</NavBarDropdownItemContainer>
33-
</NavBarDropdownLink>;
32+
</NavBarDropdownLink>
33+
</>;
3434
}
3535

3636
return <>

ui/src/main.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import OAuth from "./routes/OAuth";
1818
import ForgotPassword from "./routes/ForgotPassword";
1919
import ResetPassword from "./routes/ResetPassword";
2020
import ChangePassword from "./routes/ChangePassword";
21+
import User from "./routes/Dashboards/User";
2122

2223
const router = createBrowserRouter([
2324
{
@@ -82,6 +83,10 @@ const router = createBrowserRouter([
8283
element: <Config/>,
8384
loader: configLoader,
8485
errorElement: <DashboardError/>,
86+
},
87+
{
88+
path: "user",
89+
element: <User />,
8590
}
8691
]
8792
},

ui/src/routes/ChangePassword.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ export default function ChangePassword() {
9292
{message ? message : ""}
9393
</div>
9494

95-
<p className="auth-form-text">Want to go back? <Link to={URLS.DASHBOARD}
96-
class="auth-form-link">Dashboard</Link></p>
95+
<p className="auth-form-text">Want to go back? <Link to={URLS.USER_SETTINGS}
96+
class="auth-form-link">Account Settings</Link></p>
9797

9898
</div>
9999
</form>

ui/src/routes/Dashboards/Team.tsx

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,42 @@ const userRoles: DashboardSelectItem[] = [
4444
title: "Admin",
4545
description: "Can manage, create and delete all projects, configs, environments, flags and values.",
4646
},
47+
{
48+
value: "owner",
49+
title: "Owner",
50+
description: "Owns this team and can change it in any way. Selecting this option will change the ownership of this team."
51+
}
4752
];
4853

4954
export default function Team() {
5055
const [teamData, projectsData, userData] = useLoaderData() as TeamLoaderData;
5156
const [projects, setProjects] = useState<ProjectRecord[]>(projectsData);
5257
const [newProjectName, setNewProjectName] = useState<string>("");
53-
const [userToAdd, setUserToAdd] = useState<UserRecord | null>(null);
54-
const [userRole, setUserRole] = useState<DashboardSelectItem | null>(userRoles[0]);
58+
const [userToAdd, setUserToAddInner] = useState<UserRecord | null>(null);
59+
const [userRole, setUserRoleInner] = useState<DashboardSelectItem | null>(userRoles[0]);
5560
const [error, setError] = useState<string>("");
5661
const [memberError, setMemberError] = useState<string>("");
5762
const [team, setTeam] = useState<TeamRecord>(teamData);
5863
const [reset, setReset] = useState<boolean>(false);
64+
const [terribleActionConfirm, setTerribleActionConfirm] = useState<boolean>(false);
5965

6066
const [deleteObjectType, setDeleteObjectType] = useState<string>("");
6167
const [deleteObject, setDeleteObject] = useState<ProjectRecord | UserRecord | null>(null);
6268
const [deleteObjectError, setDeleteObjectError] = useState<string>("");
6369

70+
// reset the terrible action confirmed if either input is changed.
71+
function setUserRole(item: DashboardSelectItem | null) {
72+
setTerribleActionConfirm(false);
73+
74+
setUserRoleInner(item);
75+
}
76+
77+
function setUserToAdd(item: UserRecord | null) {
78+
setTerribleActionConfirm(false);
79+
80+
setUserToAddInner(item);
81+
}
82+
6483
const navigate = useNavigate();
6584

6685
useEffect(() => {
@@ -135,8 +154,8 @@ export default function Team() {
135154
<SelectInput items={userRoles} onSelectedItemChange={setUserRole} defaultValue={userRole}/>
136155
</DialogBody>
137156
<DialogFooter>
138-
<button className="dialog-action dialog-action__save"
139-
onClick={() => addTeamMember()}>Add Member
157+
<button className={"dialog-action " + (userRole?.value === "owner" ? " dialog-action__delete" : " dialog-action__save")}
158+
onClick={() => addTeamMember()}>{userRole?.value === "owner" ? "Transfer Ownership" : "Add Member"}
140159
</button>
141160
<button className="dialog-action dialog-action__cancel"
142161
onClick={() => setUserAddDialogShowing(false)}>Cancel
@@ -148,6 +167,7 @@ export default function Team() {
148167
if (!showing) {
149168
setUserToAdd(null);
150169
setReset(true);
170+
setTerribleActionConfirm(false);
151171
} else {
152172
setReset(false);
153173
}
@@ -228,6 +248,26 @@ export default function Team() {
228248
return;
229249
}
230250

251+
if (userRole?.value === "owner") {
252+
if (!terribleActionConfirm) {
253+
setMemberError("Please click again to confirm that you want to transfer ownership");
254+
setTimeout(() => setMemberError(''), 5000);
255+
setTerribleActionConfirm(true);
256+
return;
257+
}
258+
} else {
259+
setTerribleActionConfirm(false);
260+
}
261+
262+
// check if the userRole is owner and warn them that they are about to transfer ownership
263+
if (userRole?.value === "owner") {
264+
if (team.owner === userToAdd.id) {
265+
setMemberError("User is already the owner of the team");
266+
setTimeout(() => setMemberError(''), 5000);
267+
return;
268+
}
269+
}
270+
231271
if (userRole === null) {
232272
setMemberError("Please select a role for the user");
233273
setTimeout(() => setMemberError(''), 5000);
@@ -259,22 +299,35 @@ export default function Team() {
259299
return;
260300
}
261301

262-
// add user to team
263-
pocketbase.collection('team').update(team.id, {
302+
let bodyParams: any = {
264303
[userRole.value]: [...team[userRole.value], userToAdd.id],
265-
}).then(() => {
304+
}
305+
306+
if (userRole.value === "owner") {
307+
bodyParams = {
308+
...bodyParams,
309+
owner: userToAdd.id,
310+
}
311+
}
312+
313+
// add user to team (or replace owner)
314+
pocketbase.collection('team').update(team.id, bodyParams).then(() => {
266315
// need to add it manually because otherwise we lose the extra data
267316
const userObjectClone = {...userToAdd} as UserRecord;
268317

269318
setTeam(team => {
270319
let teamClone = {...team} as TeamRecord;
271320

272-
teamClone[userRole.value].push(userObjectClone.id);
321+
if (userRole.value === "owner") {
322+
teamClone.owner = userObjectClone.id;
323+
} else {
324+
teamClone[userRole.value].push(userObjectClone.id);
273325

274-
// @ts-ignore
275-
if (teamClone.expand[userRole.value]) {
276326
// @ts-ignore
277-
teamClone.expand[userRole.value].push(userObjectClone);
327+
if (teamClone.expand[userRole.value]) {
328+
// @ts-ignore
329+
teamClone.expand[userRole.value].push(userObjectClone);
330+
}
278331
}
279332

280333
return teamClone;

ui/src/routes/Dashboards/User.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {Link, useNavigate} from "react-router-dom";
2+
import Content from "../../components/general/Content";
3+
import DashboardSpacer from "../../components/dashboard/DashboardSpacer";
4+
import SettingButtons from "../../components/dashboard/config/SettingButtons";
5+
import SettingButton from "../../components/dashboard/config/SettingButton";
6+
import DashboardNavbar from "../../components/navbar/DashboardNavbar";
7+
import {useAuthValidWithModel} from "../../hooks/useAuthValid";
8+
import useAuthRedirect from "../../hooks/useAuthRedirect";
9+
import URLS from "../../helpers/URLS";
10+
11+
export default function User() {
12+
const [, model] = useAuthValidWithModel();
13+
const navigate = useNavigate();
14+
useAuthRedirect(URLS.LOGIN, false);
15+
16+
return <>
17+
<DashboardNavbar>
18+
{model ? <div className={"navbar-links-breadcrumb"}>
19+
<Link class="breadcrumb-page"
20+
to={`/dashboard/user`}><p>Account Settings</p></Link>
21+
</div>
22+
: null}
23+
</DashboardNavbar>
24+
<Content pageName="dashboard dashboard-user-settings">
25+
26+
<h1 className="action-header">Change your password</h1>
27+
28+
<p>You can do so using the button below if you know your current password. If you have forgotten it, please
29+
log out and use the forgotten password process.</p>
30+
31+
<SettingButtons>
32+
<SettingButton onClick={() => navigate(URLS.CHANGE_PASSWORD)}
33+
type={"Change Password"}/>
34+
</SettingButtons>
35+
36+
<h1 className="action-header">Delete account</h1>
37+
38+
<p>If you want to, you can delete your ConfigDN account. This actions is irreversible! Any teams you are the owner of will also be deleted.</p>
39+
40+
<p>You currently own the following teams:</p>
41+
42+
<ul>
43+
<li>Test</li>
44+
</ul>
45+
46+
<SettingButtons>
47+
<SettingButton onClick={() => navigate(URLS.CHANGE_PASSWORD)}
48+
type={"Change Password"}/>
49+
</SettingButtons>
50+
51+
<DashboardSpacer/>
52+
</Content>
53+
</>;
54+
}

ui/src/styles/style.scss

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ nav.navbar {
262262
.navbar-link__text {
263263
margin-top: auto;
264264
margin-bottom: auto;
265+
266+
267+
font-size: 1.15em;
265268
}
266269

267270
&:hover, & {
@@ -740,7 +743,7 @@ div.page-transition {
740743
}
741744

742745
// Dashboard (Config)
743-
.page .content.dashboard.dashboard-config {
746+
.page .content.dashboard.dashboard-config, .page .content.dashboard.dashboard-user-settings {
744747
display: flex;
745748
flex-direction: column;
746749

@@ -1226,6 +1229,18 @@ div.page-transition {
12261229
}
12271230
}
12281231

1232+
.page .dashboard.dashboard-user-settings {
1233+
.action-header {
1234+
font-size: 2em;
1235+
margin-top: 50px;
1236+
margin-bottom: 10px;
1237+
1238+
&:first-of-type {
1239+
margin-top: 20px;
1240+
}
1241+
}
1242+
}
1243+
12291244
// Auth pages (login, register)
12301245
.page.auth-page {
12311246
.content.auth-content {

0 commit comments

Comments
 (0)