Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Org Features #950

Merged
merged 3 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -274,6 +274,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
Loading