Skip to content

Commit

Permalink
DASH-130 Add Admin Partner profile creation page
Browse files Browse the repository at this point in the history
  • Loading branch information
Vadym Karpenko authored and vmkarpenko committed Nov 10, 2022
1 parent 8c93468 commit f4013d3
Show file tree
Hide file tree
Showing 27 changed files with 1,096 additions and 8 deletions.
14 changes: 14 additions & 0 deletions client/src/api/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import {
AdminGeneralCreateResponse,
AdminOrderItemResponse,
AdminOrdersListResponse,
AdminPartnersListResponse,
AdminSearchResponse,
AdminUsersListResponse,
AuthLoginResponse,
RemoveAdminResponse,
SendResetAdminPasswordResponse,
} from './responses';
import { RefProfile } from '../types';

export default class Admin extends Base {
adminList(limit: number, offset: number): Promise<AdminUsersListResponse> {
Expand Down Expand Up @@ -89,4 +91,16 @@ export default class Admin extends Base {
hash,
});
}
partnersList(
limit: number,
offset: number,
): Promise<AdminPartnersListResponse> {
return this.apiClient.get('admin/partners/list', { limit, offset });
}
createPartner(data: RefProfile): Promise<AdminGeneralCreateResponse> {
return this.apiClient.post(`admin/partners`, data);
}
editPartner(data: RefProfile): Promise<AdminGeneralCreateResponse> {
return this.apiClient.post(`admin/partners/${data.id}`, data);
}
}
1 change: 1 addition & 0 deletions client/src/api/responses.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export type UserOrdersListResponse = {
};

export type AdminFioAccountsProfilesListResponse = FioAccountProfile[];
export type AdminPartnersListResponse = RefProfile[];
export type AdminUsersListResponse = AdminUser[];
export type AdminOrdersListResponse = AdminUser[];
export type AdminOrderItemResponse = AdminUser;
Expand Down
6 changes: 4 additions & 2 deletions client/src/components/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Props = {
};
hasSmallText?: boolean;
hasThinText?: boolean;
showPreview?: boolean;
};

const Input: React.FC<Props & FieldRenderProps<Props>> = props => {
Expand Down Expand Up @@ -71,6 +72,7 @@ const Input: React.FC<Props & FieldRenderProps<Props>> = props => {
isSimple,
hasSmallText,
hasThinText,
showPreview = true,
...rest
} = props;
const {
Expand Down Expand Up @@ -262,7 +264,7 @@ const Input: React.FC<Props & FieldRenderProps<Props>> = props => {
type={type}
className={classes.fileInput}
/>
{value && previewUrl ? (
{showPreview && value && previewUrl ? (
<img className={classes.image} src={previewUrl} alt="preview" />
) : (
<>
Expand Down Expand Up @@ -295,7 +297,7 @@ const Input: React.FC<Props & FieldRenderProps<Props>> = props => {
if (type === 'checkbox')
return (
<label className={classes.checkboxContainer}>
<input {...rest} {...input} />
<input disabled={disabled} {...rest} {...input} />
<FontAwesomeIcon
icon="check-square"
className={classnames(
Expand Down
2 changes: 2 additions & 0 deletions client/src/components/Navigation/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const adminNavItems: string[] = [
LINKS.ADMIN_HOME,
LINKS.ADMIN_ORDERS,
LINKS.ADMIN_ACCOUNTS,
LINKS.ADMIN_PARTNERS,
LINKS.ADMIN_REGULAR_USERS,
LINKS.ADMIN_USERS,
LINKS.ADMIN_PROFILE,
Expand All @@ -34,6 +35,7 @@ const superAdminNavItems: string[] = [
LINKS.ADMIN_HOME,
LINKS.ADMIN_ORDERS,
LINKS.ADMIN_ACCOUNTS,
LINKS.ADMIN_PARTNERS,
LINKS.ADMIN_REGULAR_USERS,
LINKS.ADMIN_USERS,
LINKS.ADMIN_PROFILE,
Expand Down
2 changes: 2 additions & 0 deletions client/src/constants/labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const LINKS: { [linkKey: string]: string } = {
ADMIN_REGULAR_USERS: 'ADMIN_REGULAR_USERS',
ADMIN_PROFILE: 'ADMIN_PROFILE',
ADMIN_ACCOUNTS: 'ADMIN_ACCOUNTS',
ADMIN_PARTNERS: 'ADMIN_PARTNERS',
IS_NEW_USER: 'IS_NEW_USER',
CART: 'CART',
CHECKOUT: 'CHECKOUT',
Expand Down Expand Up @@ -103,6 +104,7 @@ export const LINK_LABELS: { [linkKey: string]: string } = {
[LINKS.ADMIN_REGULAR_USERS]: 'Users',
[LINKS.ADMIN_PROFILE]: 'Profile',
[LINKS.ADMIN_ACCOUNTS]: 'Accounts',
[LINKS.ADMIN_PARTNERS]: 'Partners',
};

export const APP_TITLE = 'FIO Dashboard';
Expand Down
1 change: 1 addition & 0 deletions client/src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const ROUTES: { [route: string]: string } = {
ADMIN_REGULAR_USERS: '/admin/regular-users',
ADMIN_PROFILE: '/admin/profile',
ADMIN_ACCOUNTS: '/admin/accounts',
ADMIN_PARTNERS: '/admin/partners',
ADMIN_SEARCH_RESULT: '/admin/search-result',

CONFIRM_EMAIL: '/confirm-email', // ?hash=
Expand Down
1 change: 1 addition & 0 deletions client/src/hooks/usePagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import MathOp from '../util/math';
import { AnyObject } from '../types';

export const DEFAULT_LIMIT = 25;
export const DEFAULT_OFFSET = 0;

const LIMIT_QUERY_PARAMETER_NAME = 'limit';
const OFFSET_QUERY_PARAMETER_NAME = 'offset';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@import '../../assets/styles/colors.scss';
@import '../../assets/styles/fontMixins.scss';

.tableContainer {
width: auto;
margin-top: 30px;

th {
text-align: center;
vertical-align: middle;
}

.userItem { cursor: pointer }
}

.label {
margin-bottom: 1rem;
font-size: 0.875em;
color: $dark-jungle-green;
@include sf-pro-display-medium;
}

.button {
line-height: 1;
}

.imageContainer {
> div {
padding: 0 20px;
}
.previewImage {
max-width: 380px;
max-height: 115px;
width: 100%;
}
}

.landingTextsContainer {
padding: 10px;
background-color: $gallery;
border-radius: 7px;
box-shadow: 0 0 8px 0 rgba($black, 0.11);
}
135 changes: 135 additions & 0 deletions client/src/pages/AdminPartnersListPage/AdminPartnersListPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React, { useState, useCallback } from 'react';
import { Button, Table } from 'react-bootstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

import Loader from '../../components/Loader/Loader';
import { PartnerModal } from './components/updatePartner/PartnerModal';

import { formatDateToLocale } from '../../helpers/stringFormatters';
import usePagination from '../../hooks/usePagination';
import apis from '../../api';
import { log } from '../../util/general';

import { Props } from './types';
import { RefProfile } from '../../types';

import classes from './AdminPartnersListPage.module.scss';

const AdminPartnersListPage: React.FC<Props> = props => {
const { loading, partnersList, getPartnersList } = props;
const [showPartnerModal, setShowPartnerModal] = useState<boolean>(false);
const [selectedPartner, setSelectedPartner] = useState<RefProfile>(null);
const [partnerActionLoading, setPartnerActionLoading] = useState<boolean>(
false,
);

const { paginationComponent, refresh } = usePagination(getPartnersList);

const onAddPartner = useCallback(() => {
setShowPartnerModal(true);
}, []);
const onEditPartner = useCallback((partner: RefProfile) => {
if (partner.settings?.actions?.SIGNNFT) {
partner.settings.actions.SIGNNFT.enabled = true;
}
if (partner.settings?.actions?.REG) {
partner.settings.actions.REG.enabled = true;
}
if (
partner.settings?.domains.length &&
!partner.settings.domains.includes(partner.settings.preselectedDomain)
) {
partner.settings.preselectedDomain = partner.settings.domains[0];
} else {
partner.settings.preselectedDomain = null;
}
setSelectedPartner(partner);
setShowPartnerModal(true);
}, []);
const closeModal = useCallback(() => {
setShowPartnerModal(false);
setSelectedPartner(null);
}, []);
const savePartner = useCallback(
async (partner: RefProfile) => {
setPartnerActionLoading(true);
try {
if (partner.settings?.actions) {
if (!partner.settings.actions.SIGNNFT?.enabled) {
delete partner.settings.actions.SIGNNFT;
}
if (!partner.settings.actions.REG?.enabled) {
delete partner.settings.actions.REG;
}
}
if (partner.id) {
await apis.admin.editPartner(partner);
} else {
await apis.admin.createPartner(partner);
}
await refresh();
closeModal();
} catch (err) {
log.error(err);
} finally {
setPartnerActionLoading(false);
}
},
[refresh, closeModal],
);

return (
<>
<div className={classes.tableContainer}>
<Button className="mb-4" onClick={onAddPartner}>
<FontAwesomeIcon icon="plus-square" className="mr-2" /> Add New
Partner
</Button>
<Table className="table" striped={true}>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Referral Code</th>
<th scope="col">Created</th>
</tr>
</thead>
<tbody>
{partnersList?.length
? partnersList.map((partner, i) => (
<tr
key={partner.id}
className={classes.userItem}
onClick={() => onEditPartner(partner)}
>
<th>{i + 1}</th>
<th>{partner.label}</th>
<th>{partner.code}</th>
<th>
{partner.createdAt
? formatDateToLocale(partner.createdAt)
: null}
</th>
</tr>
))
: null}
</tbody>
</Table>

{paginationComponent}

{loading && <Loader />}
</div>

<PartnerModal
initialValues={selectedPartner}
show={showPartnerModal}
onSubmit={savePartner}
loading={partnerActionLoading}
onClose={closeModal}
/>
</>
);
};

export default AdminPartnersListPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { Form } from 'react-final-form';
import arrayMutators from 'final-form-arrays';

import { PartnerFormComponent } from './PartnerFormComponent';

import { validate } from './validation';

import { RefProfile } from '../../../../types';

type Props = {
onSubmit: (values: RefProfile) => Promise<void>;
loading: boolean;
initialValues?: RefProfile;
};

export const PartnerForm: React.FC<Props> = props => {
const { onSubmit, loading, initialValues } = props;

return (
<Form
onSubmit={onSubmit}
initialValues={initialValues}
component={PartnerFormComponent}
loading={loading}
validate={validate}
mutators={{
...arrayMutators,
}}
/>
);
};
Loading

0 comments on commit f4013d3

Please sign in to comment.