Skip to content

Commit

Permalink
Merge pull request #950 from stakwork/fix/org_manage
Browse files Browse the repository at this point in the history
Org Features
  • Loading branch information
elraphty authored Nov 16, 2023
2 parents b27a40d + c51b46e commit d68bdbd
Show file tree
Hide file tree
Showing 16 changed files with 386 additions and 182 deletions.
12 changes: 10 additions & 2 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -1005,7 +1005,7 @@ func (db database) GetOrganizations(r *http.Request) []Organization {
offset, limit, sortBy, direction, search := utils.GetPaginationParams(r)

// return if like owner_alias, unique_name, or equals pubkey
db.db.Offset(offset).Limit(limit).Order(sortBy+" "+direction+" ").Where("LOWER(name) LIKE ?", "%"+search+"%").Find(&ms)
db.db.Offset(offset).Limit(limit).Order(sortBy+" "+direction+" ").Where("LOWER(name) LIKE ?", "%"+search+"%").Where("deleted != ?", false).Find(&ms)
return ms
}

Expand Down Expand Up @@ -1105,7 +1105,7 @@ func (db database) GetUserRoles(uuid string, pubkey string) []UserRoles {

func (db database) GetUserCreatedOrganizations(pubkey string) []Organization {
ms := []Organization{}
db.db.Where("owner_pub_key = ?", pubkey).Find(&ms)
db.db.Where("owner_pub_key = ?", pubkey).Where("deleted != ?", true).Find(&ms)
return ms
}

Expand Down Expand Up @@ -1300,3 +1300,11 @@ func (db database) DeleteUserInvoiceData(payment_request string) UserInvoiceData
db.db.Where("payment_request = ?", payment_request).Delete(&ms)
return ms
}

func (db database) ChangeOrganizationDeleteStatus(org_uuid string, status bool) Organization {
ms := Organization{}
db.db.Model(&ms).Where("uuid", org_uuid).Updates(map[string]interface{}{
"deleted": status,
})
return ms
}
1 change: 1 addition & 0 deletions db/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ type Organization struct {
Created *time.Time `json:"created"`
Updated *time.Time `json:"updated"`
Show bool `json:"show"`
Deleted bool `gorm:"default:false" json:"deleted"`
BountyCount int64 `json:"bounty_count,omitempty"`
Budget uint `json:"budget,omitempty"`
}
Expand Down
12 changes: 11 additions & 1 deletion frontend/app/src/helpers/__test__/helpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
randomString,
calculateTimeLeft,
toCapitalize,
userHasRole
userHasRole,
spliceOutPubkey
} from '../helpers-extended';
import { uiStore } from '../../store/ui';
import crypto from 'crypto';
Expand Down Expand Up @@ -139,4 +140,13 @@ describe('testing helpers', () => {
expect(capitalizeString).toBe('Hello Test Sphinx');
});
});
describe('spliceOutPubkey', () => {
test('test that it returns pubkey from a pubkey:route_hint string', () => {
const pubkey = '12344444444444444';
const routeHint = '899900000000000000:88888888';
const userAddress = `${pubkey}:${routeHint}`;
const pub = spliceOutPubkey(userAddress);
expect(pub).toBe(pubkey);
});
});
});
11 changes: 11 additions & 0 deletions frontend/app/src/helpers/helpers-extended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,14 @@ export const isInvoiceExpired = (paymentRequest: string): boolean => {
}
return true;
};

export const spliceOutPubkey = (userAddress: string): string => {
if (userAddress.includes(':')) {
const addArray = userAddress.split(':');
const pubkey = addArray[0];

return pubkey;
}

return userAddress;
};
120 changes: 23 additions & 97 deletions frontend/app/src/people/widgetViews/OrganizationDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Button } from 'components/common';
import { BountyRoles, Organization, PaymentHistory, Person } from 'store/main';
import MaterialIcon from '@material/react-material-icon';
import { Route, Router, Switch, useRouteMatch } from 'react-router-dom';
import { satToUsd, userHasRole, Roles } from 'helpers';
import { satToUsd, userHasRole } from 'helpers';
import { BountyModal } from 'people/main/bountyModal';
import history from '../../config/history';
import avatarIcon from '../../public/static/profile_avatar.svg';
Expand All @@ -16,6 +16,7 @@ import AddUserModal from './organization/AddUserModal';
import AddBudgetModal from './organization/AddBudgetModal';
import WithdrawBudgetModal from './organization/WithdrawBudgetModal';
import EditOrgModal from './organization/EditOrgModal';
import Users from './organization/UsersList';

import {
ActionWrap,
Expand All @@ -29,21 +30,13 @@ import {
HeadButtonWrap,
HeadNameWrap,
HeadWrap,
IconWrap,
NoBudgetText,
NoBudgetWrap,
OrgImg,
OrgName,
User,
UserAction,
UserDetails,
UserImage,
UserName,
UserPubkey,
UserWrap,
UsersHeadWrap,
UsersHeader,
UsersList,
ViewBudgetWrap,
ViewBudgetTextWrap
} from './organization/style';
Expand All @@ -54,12 +47,9 @@ const OrganizationDetails = (props: {
close: () => void;
org: Organization | undefined;
resetOrg: (Organization) => void;
getOrganizations: () => Promise<void>;
}) => {
const { main, ui } = useStores();
const roleData = main.bountyRoles.map((role: any) => ({
name: role.name,
status: false
}));

const [loading, setIsLoading] = useState<boolean>(false);
const [isOpenAddUser, setIsOpenAddUser] = useState<boolean>(false);
Expand All @@ -75,7 +65,6 @@ const OrganizationDetails = (props: {
const [users, setUsers] = useState<Person[]>([]);
const [user, setUser] = useState<Person>();
const [userRoles, setUserRoles] = useState<any[]>([]);
const [bountyRolesData, setBountyRolesData] = useState<BountyRoles[]>(roleData);
const [toasts, setToasts]: any = useState([]);
const [invoiceStatus, setInvoiceStatus] = useState(false);
const { path, url } = useRouteMatch();
Expand All @@ -88,14 +77,10 @@ const OrganizationDetails = (props: {
!isOrganizationAdmin && !userHasRole(main.bountyRoles, userRoles, 'VIEW REPORT');
const addBudgetDisabled =
!isOrganizationAdmin && !userHasRole(main.bountyRoles, userRoles, 'ADD BUDGET');
const deleteUserDisabled =
!isOrganizationAdmin && !userHasRole(main.bountyRoles, userRoles, 'DELETE USER');
const addRolesDisabled =
!isOrganizationAdmin && !userHasRole(main.bountyRoles, userRoles, 'ADD ROLES');
const addWithdrawDisabled =
!isOrganizationAdmin && !userHasRole(main.bountyRoles, userRoles, 'WITHDRAW BUDGET');

const { org } = props;
const { org, close, getOrganizations } = props;
const uuid = org?.uuid || '';

function addToast(title: string, color: 'danger' | 'success') {
Expand Down Expand Up @@ -146,24 +131,10 @@ const OrganizationDetails = (props: {

const getUserRoles = useCallback(
async (user: any) => {
if (uuid && user.owner_pubkey) {
const userRoles = await main.getUserRoles(uuid, user.owner_pubkey);
const pubkey = user.owner_pubkey;
if (uuid && pubkey) {
const userRoles = await await main.getUserRoles(uuid, pubkey);
setUserRoles(userRoles);

if (userRoles.length) {
// set all values to false, so every user data will be fresh
const rolesData = bountyRolesData.map((data: any) => ({
name: data.name,
status: false
}));
userRoles.forEach((userRole: any) => {
const index = rolesData.findIndex((role: any) => role.name === userRole.role);
if (index !== -1) {
rolesData[index]['status'] = true;
}
});
setBountyRolesData(rolesData);
}
}
},
[uuid, main]
Expand Down Expand Up @@ -267,26 +238,20 @@ const OrganizationDetails = (props: {
};

const onDeleteOrg = async () => {
const res = { status: 200 };
const res = await main.organizationDelete(uuid);
if (res.status === 200) {
addToast('Still need to implement this', 'danger');
addToast('Deleted organization', 'success');
if (ui.meInfo) {
getOrganizations();
close();
}
} else {
addToast('Error: could not create organization', 'danger');
addToast('Error: could not delete organization', 'danger');
}
};

const roleChange = (e: Roles, s: any) => {
const rolesData = bountyRolesData.map((role: any) => {
if (role.name === e) {
role.status = s.target.checked;
}
return role;
});
setBountyRolesData(rolesData);
};

const submitRoles = async () => {
const roleData = bountyRolesData
const submitRoles = async (bountyRoles: BountyRoles[]) => {
const roleData = bountyRoles
.filter((r: any) => r.status)
.map((role: any) => ({
owner_pubkey: user?.owner_pubkey,
Expand Down Expand Up @@ -478,49 +443,13 @@ const OrganizationDetails = (props: {
/>
</HeadButtonWrap>
</UsersHeadWrap>
<UsersList>
{users.map((user: Person, i: number) => {
const isUser = user.owner_pubkey === ui.meInfo?.owner_pubkey;
return (
<User key={i}>
<UserImage src={user.img || avatarIcon} />
<UserDetails>
<UserName>{user.unique_name}</UserName>
<UserPubkey>{user.owner_pubkey}</UserPubkey>
</UserDetails>
<UserAction>
<IconWrap>
<MaterialIcon
disabled={isUser || addRolesDisabled}
icon={'settings'}
style={{
fontSize: 24,
cursor: 'pointer',
color: '#ccc'
}}
onClick={() => handleSettingsClick(user)}
/>
</IconWrap>
<IconWrap>
<MaterialIcon
icon={'delete'}
disabled={isUser || deleteUserDisabled}
style={{
fontSize: 24,
cursor: 'pointer',
color: '#ccc'
}}
onClick={() => {
setUser(user);
handleDeleteClick(user);
}}
/>
</IconWrap>
</UserAction>
</User>
);
})}
</UsersList>
<Users
org={org}
handleDeleteClick={handleDeleteClick}
handleSettingsClick={handleSettingsClick}
userRoles={userRoles}
users={users}
/>
</UserWrap>
<DetailsWrap>
{isOpenEditOrg && (
Expand Down Expand Up @@ -553,14 +482,11 @@ const OrganizationDetails = (props: {
)}
{isOpenRoles && (
<RolesModal
userRoles={userRoles}
bountyRolesData={bountyRolesData}
uuid={uuid}
user={user}
addToast={addToast}
close={closeRolesHandler}
isOpen={isOpenRoles}
roleChange={roleChange}
submitRoles={submitRoles}
/>
)}
Expand Down
1 change: 1 addition & 0 deletions frontend/app/src/people/widgetViews/OrganizationView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ const Organizations = (props: { person: Person }) => {
close={closeDetails}
org={organization}
resetOrg={(newOrg: Organization) => setOrganization(newOrg)}
getOrganizations={getUserOrganizations}
/>
)}
{!detailsOpen && (
Expand Down
21 changes: 18 additions & 3 deletions frontend/app/src/people/widgetViews/organization/AddUserModal.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import React, { useRef } from 'react';
import React, { useRef, useState } from 'react';
import { Wrap } from 'components/form/style';
import { useIsMobile } from 'hooks/uiHooks';
import { nonWidgetConfigs } from 'people/utils/Constants';
import { Formik } from 'formik';
import { FormField, validator } from 'components/form/utils';
import { spliceOutPubkey } from 'helpers';
import { Button, Modal } from '../../../components/common';
import Input from '../../../components/form/inputs';
import { colors } from '../../../config/colors';
import { ModalTitle } from './style';
import { ModalTitle, RouteHintText } from './style';
import { AddUserModalProps } from './interface';

const color = colors['light'];

const AddUserModal = (props: AddUserModalProps) => {
const isMobile = useIsMobile();
const { isOpen, close, onSubmit, loading, disableFormButtons, setDisableFormButtons } = props;
const [displayHint, setDisplayHint] = useState(false);

const hintText = 'Route hint detected and removed';

const checkDisplayHint = (address: string) => {
if (address.includes(':')) {
setDisplayHint(true);
} else {
setDisplayHint(false);
}
};

const config = nonWidgetConfigs['organizationusers'];

Expand Down Expand Up @@ -59,6 +71,7 @@ const AddUserModal = (props: AddUserModalProps) => {
{({ setFieldTouched, handleSubmit, values, setFieldValue, errors, initialValues }: any) => (
<Wrap newDesign={true}>
<ModalTitle>Add new user</ModalTitle>
{displayHint && <RouteHintText>{hintText}</RouteHintText>}
<div className="SchemaInnerContainer">
{schema.map((item: FormField) => (
<Input
Expand All @@ -73,7 +86,9 @@ const AddUserModal = (props: AddUserModalProps) => {
if (errors[item.name]) delete errors[item.name];
}}
handleChange={(e: any) => {
setFieldValue(item.name, e);
checkDisplayHint(e);
const pubkey = spliceOutPubkey(e);
setFieldValue(item.name, pubkey);
}}
setFieldValue={(e: any, f: any) => {
setFieldValue(e, f);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Formik } from 'formik';
import { validator } from 'components/form/utils';
import { widgetConfigs } from 'people/utils/Constants';
import { FormField } from 'components/form/utils';
import { useStores } from 'store';
import Input from '../../../components/form/inputs';
import { Button, Modal } from '../../../components/common';
import { colors } from '../../../config/colors';
Expand Down Expand Up @@ -138,6 +139,8 @@ const HLine = styled.div`
`;

const EditOrgModal = (props: EditOrgModalProps) => {
const { ui } = useStores();

const isMobile = useIsMobile();
const { isOpen, close, onSubmit, onDelete, org } = props;
const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false);
Expand All @@ -154,6 +157,8 @@ const EditOrgModal = (props: EditOrgModalProps) => {
const [selectedImage, setSelectedImage] = useState<string>(org?.img || '');
const fileInputRef = useRef<HTMLInputElement | null>(null);

const isOrganizationAdmin = props.org?.owner_pubkey === ui.meInfo?.owner_pubkey;

const handleFileInputChange = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files && e.target.files[0];
if (file) {
Expand Down Expand Up @@ -312,7 +317,7 @@ const EditOrgModal = (props: EditOrgModalProps) => {
</EditOrgColumns>
<HLine style={{ width: '551px', transform: 'translate(-48px, 0px' }} />
<Button
disabled={false}
disabled={!isOrganizationAdmin}
onClick={() => {
setShowDeleteModal(true);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ const HistoryModal = (props: PaymentHistoryModalProps) => {
setCurrentPaymentHistory(
paymentsHistory.filter((history: PaymentHistory) => filter[history.payment_type])
);
}, [filter]);
}, [filter, props.paymentsHistory]);

return (
<Modal
Expand Down
Loading

0 comments on commit d68bdbd

Please sign in to comment.