Skip to content

Commit

Permalink
Convert UserTokens
Browse files Browse the repository at this point in the history
syntax updates to AddTokenModal

simplification of tokens page, remove pagination from URL params
  • Loading branch information
mshriver committed Feb 18, 2025
1 parent 4335ae5 commit ef10aaa
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 184 deletions.
6 changes: 3 additions & 3 deletions frontend/src/components/add-token-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
import { HttpClient } from '../services/http';
import { Settings } from '../settings';

function AddTokenModal (props) {
const AddTokenModal = (props) => {
const [name, setName] = useState('');
const [expiryDate, setExpiryDate] = useState('');
const [isNameValid, setIsNameValid] = useState(true);
Expand All @@ -28,7 +28,7 @@ function AddTokenModal (props) {
onClose,
} = props;

function onSave () {
const onSave = () => {
const expiry = new Date(expiryDate);
const now = new Date();

Expand Down Expand Up @@ -60,7 +60,7 @@ function AddTokenModal (props) {

};

function localOnClose () {
const localOnClose = () => {
// call prop function
onClose();

Expand Down
281 changes: 105 additions & 176 deletions frontend/src/pages/profile/tokens.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ToastContainer, toast } from 'react-toastify';
import React, { useEffect, useState } from 'react';
import { ToastContainer } from 'react-toastify';

import {
Button,
Expand All @@ -11,8 +10,8 @@ import {
FlexItem,
PageSection,
PageSectionVariants,
Text,
TextContent,
Title,
} from '@patternfly/react-core';
import { PlusCircleIcon } from '@patternfly/react-icons';

Expand All @@ -22,62 +21,24 @@ import { getSpinnerRow } from '../../utilities';
import { FilterTable } from '../../components/filtertable';
import AddTokenModal from '../../components/add-token-modal';
import DeleteModal from '../../components/delete-modal';
import ToastWrapper from '../../components/toast-wrapper';
import { ALERT_TIMEOUT } from '../../constants';

const COLUMNS = ['Name', 'Token', 'Expires', ''];

export class UserTokens extends React.Component {
const UserTokens = () => {

static propTypes = {
location: PropTypes.object,
navigate: PropTypes.func,
eventEmitter: PropTypes.object
};
const [tokens, setTokens] = useState([]);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
const [totalItems, setTotalItems] = useState(0);
const [isError, setIsError] = useState(false);
const [isEmpty, setIsEmpty] = useState(false);
const [isAddTokenOpen, setIsAddTokenOpen] = useState(false);
const [isDeleteTokenOpen, setIsDeleteTokenOpen] = useState(false);
const [tokenToDelete, setTokenToDelete] = useState();

constructor (props) {
super(props);
this.eventEmitter = props.eventEmitter;
const params = new URLSearchParams(props.location.search);
let page = 1, pageSize = 20;
if (params.toString() !== '') {
for(let pair of params) {
if (pair[0] === 'page') {
page = parseInt(pair[1]);
}
else if (pair[0] === 'pageSize') {
pageSize = parseInt(pair[1]);
}
}
}
this.state = {
columns: ['Name', 'Token', 'Expires', ''],
rows: [getSpinnerRow(5)],
page: page,
pageSize: pageSize,
totalItems: 0,
totalPages: 0,
isError: false,
isEmpty: false,
isAddTokenOpen: false,
isDeleteTokenOpen: false,
tokenToDelete: null,
};
}


copyToClipboard = (text) => {
navigator.clipboard.writeText(text);
toast(<ToastWrapper />,
{
data: {
type: 'info',
title: 'Copied to clipboard',
message: 'Your token has been copied to the clipboard'
}});
};

tokenToRow (token) {
return {
const tokenToRow = (token) => {
const shutupEslint = {
'cells': [
{title: token.name},
{title: (
Expand All @@ -86,134 +47,102 @@ export class UserTokens extends React.Component {
</ClipboardCopy>
)},
{title: token.expires},
{title: <Button variant="danger" onClick={() => this.onDeleteTokenClick(token)}>Delete</Button>}
{title: <Button variant="danger" onClick={() => {
setTokenToDelete(token);
setIsDeleteTokenOpen(true);
}}>Delete</Button>}
]
};
}

updateUrl () {
let params = [];
params.push('page=' + this.state.page);
params.push('pageSize=' + this.state.pageSize);
this.props.navigate('/profile/tokens?' + params.join('&'));
}

setPage = (_event, pageNumber) => {
this.setState({page: pageNumber}, () => {
this.updateUrl();
this.getTokens();
});
};

setPageSize = (_event, perPage) => {
this.setState({pageSize: perPage}, () => {
this.updateUrl();
this.getTokens();
});
return(shutupEslint);
};

// TODO: useEffect on add and delete modal close
getTokens () {
useEffect(() => {
// First, show a spinner
this.setState({rows: [getSpinnerRow(4)], isEmpty: false, isError: false});
let params = {
'pageSize': this.state.pageSize,
'page': this.state.page
};
HttpClient.get([Settings.serverUrl, 'user', 'token'], params)
HttpClient.get([Settings.serverUrl, 'user', 'token'], {page: page, pageSize: pageSize})
.then(response => HttpClient.handleResponse(response))
.then(data => this.setState({
rows: data.tokens.map((token) => this.tokenToRow(token)),
page: data.pagination.page,
pageSize: data.pagination.pageSize,
totalItems: data.pagination.totalItems,
totalPages: data.pagination.totalPages,
isEmpty: data.pagination.totalItems === 0
}))
.then(data => {
if (data?.tokens) {
setIsEmpty(false);
setTokens(data.tokens);
setPage(data.pagination.page);
setPageSize(data.pagination.pageSize);
setTotalItems(data.pagination.totalItems);
} else {
setIsEmpty(true);
setTokens([]);
}

})
.catch((error) => {
console.error('Error fetching token data:', error);
this.setState({rows: [], isEmpty: false, isError: true});
setTokens([]);
setIsError(true);
});
}

onAddTokenClick = () => {
this.setState({isAddTokenOpen: true});
};

onAddTokenClose = () => {
this.setState({isAddTokenOpen: false});
};

onDeleteTokenClick = (token) => {
this.setState({tokenToDelete: token, isDeleteTokenOpen: true});
};

onDeleteTokenClose = () => {
this.setState({tokenToDelete: null, isDeleteTokenOpen: false});
};

componentDidMount () {
this.getTokens();
}

render () {
document.title = 'User Tokens | Ibutsu';
const { columns, rows } = this.state;
const pagination = {
pageSize: this.state.pageSize,
page: this.state.page,
totalItems: this.state.totalItems
};
return (
<React.Fragment>
<PageSection id="page" variant={PageSectionVariants.light}>
<Flex justifyContent={{ default: 'justifyContentSpaceBetween' }}>
<FlexItem spacer={{ default: 'spacerLg' }}>
<TextContent>
<Text className="title" component="h1">Tokens</Text>
</TextContent>
</FlexItem>
<FlexItem>
<Button
aria-label="Add token"
variant="secondary"
title="Add token"
onClick={this.onAddTokenClick}
>
<PlusCircleIcon /> Add Token
</Button>
</FlexItem>
</Flex>
</PageSection>
<PageSection>
<Card>
<CardBody className="pf-u-p-0">
<FilterTable
columns={columns}
rows={rows}
pagination={pagination}
isEmpty={this.state.isEmpty}
isError={this.state.isError}
onSetPage={this.setPage}
onSetPageSize={this.setPageSize}
/>
</CardBody>
</Card>
</PageSection>
<AddTokenModal
isOpen={this.state.isAddTokenOpen}
onClose={this.onAddTokenClose}
/>
<DeleteModal
title="Delete token"
body="Would you like to delete the selected token?"
toDeleteId={this.state.tokenToDelete?.id}
toDeletePath={['user', 'token']}
isOpen={this.state.isDeleteTokenOpen}
onClose={this.onDeleteTokenClose}
/>
<ToastContainer autoClose={ALERT_TIMEOUT} stacked/>
</React.Fragment>
);
}
}
}, [page, pageSize, isAddTokenOpen, isDeleteTokenOpen]); // extra deps to trigger fetch on modal close


document.title = 'User Tokens | Ibutsu';
return (
<React.Fragment>
<PageSection id="page" variant={PageSectionVariants.light}>
<Flex justifyContent={{ default: 'justifyContentSpaceBetween' }}>
<FlexItem spacer={{ default: 'spacerLg' }}>
<TextContent>
<Title headingLevel="h1">Tokens</Title>
</TextContent>
</FlexItem>
<FlexItem>
<Button
aria-label="Add token"
variant="secondary"
title="Add token"
onClick={() => setIsAddTokenOpen(true)}
>
<PlusCircleIcon /> Add Token
</Button>
</FlexItem>
</Flex>
</PageSection>
<PageSection>
<Card>
<CardBody className="pf-u-p-0">
<FilterTable
columns={COLUMNS}
rows={tokens ? tokens.map((t) => tokenToRow(t)) : [getSpinnerRow(4)]}
pagination={{
pageSize: pageSize,
page: page,
totalItems: totalItems
}}
isEmpty={isEmpty}
isError={isError}
onSetPage={(_event, value) => {setPage(value);}}
onSetPageSize={(_event, value) => {setPageSize(value);}}
/>
</CardBody>
</Card>
</PageSection>
<AddTokenModal
isOpen={isAddTokenOpen}
onClose={() => setIsAddTokenOpen(false)}
/>
<DeleteModal
title="Delete token"
body="Would you like to delete the selected token?"
toDeleteId={tokenToDelete?.id}
toDeletePath={['user', 'token']}
isOpen={isDeleteTokenOpen}
onClose={() => {
setTokenToDelete();
setIsDeleteTokenOpen(false);
}}
/>
<ToastContainer autoClose={ALERT_TIMEOUT} stacked/>
</React.Fragment>
);
};

UserTokens.propTypes = {};

export default UserTokens;
8 changes: 3 additions & 5 deletions frontend/src/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Navigate, Route, Routes } from 'react-router-dom';
import EventEmitter from 'wolfy87-eventemitter';

import { UserProfile } from './pages/profile/user';
import { UserTokens } from './pages/profile/tokens';
import UserTokens from './pages/profile/tokens';
import './app.css';
import ElementWrapper from './components/elementWrapper';
import ProfilePage from './components/profile-page';
Expand All @@ -18,15 +18,13 @@ const Profile = () => {
<Routes>
<Route path="" element={<ProfilePage eventEmitter={eventEmitter}/>}>
<Route path="user" element={<ElementWrapper routeElement={UserProfile} eventEmitter={eventEmitter} />} />
<Route path="tokens" element={<ElementWrapper routeElement={UserTokens} eventEmitter={eventEmitter} />} />
<Route path="tokens" element={<UserTokens />} />
<Route path="*" element={<Navigate to="user" replace />}/>
</Route>
</Routes>
);
};

Profile.propTypes = {

};
Profile.propTypes = {};

export default Profile;

0 comments on commit ef10aaa

Please sign in to comment.