-
Notifications
You must be signed in to change notification settings - Fork 4
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
Trello 19 #31
base: master
Are you sure you want to change the base?
Trello 19 #31
Changes from 1 commit
a6f822b
c1262ba
bc3ad16
0a51551
e8dc5aa
ac912e7
a3f1135
6463125
ba28515
492fb9e
99f7eca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import axios from 'axios'; | ||
|
||
// const OBTAIN_TOKEN = '/token/obtain/'; | ||
// const REFRESH_TOKEN_URL = '/token/refresh/'; | ||
|
||
export const ACCESS_TOKEN = 'access_token'; | ||
export const REFRESH_TOKEN = 'refresh_token'; | ||
|
||
// interface JWTTokenResponse { | ||
// refresh: string; | ||
// access: string; | ||
// } | ||
|
||
export const axiosInstance = axios.create({ | ||
baseURL: 'http://127.0.0.1:7424/api/', | ||
timeout: 5000, | ||
headers: { | ||
// 'Authorization': localStorage.getItem('access_token') ? 'JWT ' + localStorage.getItem('access_token') : null, | ||
'Content-Type': 'application/json', | ||
accept: 'application/json', | ||
}, | ||
}); | ||
|
||
// export const saveTokens = (jwtToken: JWTTokenResponse): JWTTokenResponse => { | ||
// localStorage.setItem(ACCESS_TOKEN, jwtToken.access); | ||
// localStorage.setItem(REFRESH_TOKEN, jwtToken.refresh); | ||
// return jwtToken; | ||
// }; | ||
|
||
const localGet = (key: string, defaultValue = ''): string => { | ||
const value = localStorage.getItem(key); | ||
if (value === null) return defaultValue; | ||
return value; | ||
}; | ||
|
||
export const getAccessToken = (): string => localGet(ACCESS_TOKEN); | ||
export const getRefreshToken = (): string => localGet(REFRESH_TOKEN); | ||
|
||
axiosInstance.interceptors.response.use( | ||
(res) => res, | ||
(err) => { | ||
// const originalRequest = err.config; | ||
// If refresh tokens is expired redirect to login page | ||
// if (err.response.status === 401 && originalRequest.url === REFRESH_TOKEN_URL) { | ||
// window.location.href = '/login/'; | ||
// return Promise.reject(err); | ||
// } | ||
|
||
// // If access token is expired update it | ||
// if (err.response.status === 401 && err.response.statusText === 'Unauthorized') { | ||
// return axiosInstance | ||
// .post<JWTTokenResponse>(REFRESH_TOKEN_URL, {refresh: localStorage.getItem(REFRESH_TOKEN)}) | ||
// .then(res => res.data) | ||
// .then(saveTokens) | ||
// .then(res => { | ||
// axiosInstance.defaults.headers['Authorization'] = 'JWT ' + res.access; | ||
// originalRequest.headers['Authorization'] = 'JWT ' + res.access; | ||
|
||
// return axiosInstance(originalRequest); | ||
// }) | ||
// } | ||
|
||
return Promise.reject(err); | ||
}, | ||
); | ||
|
||
// export const obtainTokenApi = async (email: string, password: string): Promise<JWTTokenResponse> => | ||
// axiosInstance | ||
// .post<JWTTokenResponse>(OBTAIN_TOKEN, { email, password }) | ||
// .then((res) => res.data); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { Chain } from '../interfaces'; | ||
import { axiosInstance } from './api'; | ||
|
||
// Let it be implicit here to avoid importing AxiosResponse etc. | ||
|
||
/* eslint-disable @typescript-eslint/explicit-function-return-type */ | ||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ | ||
const chainsApi = '/chains'; | ||
|
||
const getAllChains = () => axiosInstance.get<Chain[]>(`${chainsApi}`); | ||
const getChain = (chain: string) => axiosInstance.get<Chain>(`${chainsApi}/${chain}`); | ||
const setChainStatus = (chain: string) => axiosInstance.put<any>(`${chainsApi}/${chain}`); | ||
|
||
export default { getAllChains, getChain, setChainStatus }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { Contract } from '../interfaces'; | ||
import { axiosInstance } from './api'; | ||
|
||
const contractsApi = '/contracts'; | ||
|
||
/* eslint-disable @typescript-eslint/explicit-function-return-type */ | ||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ | ||
|
||
// Implementation : https://github.com/paralink-network/paralink-node/blob/master/src/api/contracts.py | ||
const getAllContracts = () => axiosInstance.get<Contract[]>(`${contractsApi}`); | ||
const getChainContracts = (chain: string) => axiosInstance.get<Contract>(`${contractsApi}/${chain}`); | ||
const createContract = (contract: Omit<Contract, 'id'>) => axiosInstance.post<any>(`${contractsApi}`, contract); | ||
const setContractStatus = (id: number, contract: Pick<Contract, 'active'>) => | ||
axiosInstance.put<any>(`${contractsApi}/${id}`, contract); | ||
const deleteContract = (id: number) => axiosInstance.delete<any>(`${contractsApi}/${id}`); | ||
|
||
export default { getAllContracts, getChainContracts, createContract, setContractStatus, deleteContract }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { Contract } from './Contract'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI @Frenkiee I moved things inside the interfaces folder, I think it might be much easier to share the interfaces across things ( apis / components / utils etc. ) avoiding some circular deps later on |
||
|
||
export interface Chain { | ||
name: string; | ||
type: string; | ||
active: boolean; | ||
contracts: Contract[]; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export interface Contract { | ||
id?: number; | ||
active: boolean; | ||
address: string; | ||
chain: string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './Chain'; | ||
export * from './Contract'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,82 @@ | ||
import React, { useEffect, useState } from 'react'; | ||
import { FaCheck, FaTimes, FaTrash } from 'react-icons/fa'; | ||
import ContractsApi from '../../api/contracts'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've done it a slightly differently from you @Frenkiee , I like that format of just importing as named import and then calling the function. The then / catch & everything else is done directly from the component. |
||
import Button from '../../components/common/Buttons'; | ||
import Toggle from '../../components/common/Toggle'; | ||
|
||
export interface TrackedContract { | ||
address?: string; | ||
enabled?: boolean; | ||
edited?: boolean; | ||
} | ||
import { Contract } from '../../interfaces'; | ||
|
||
interface TrackedContractProps { | ||
trackedContract: TrackedContract; | ||
// saved: (contract: TrackedContract) => void; // Instead do a callback as "saved to refresh the view or the array" | ||
remove: () => void; | ||
trackedContract: Contract; | ||
// saved: (contract: TrackedContract) => void; // TODO: Once we have the BE working use this function to do a callback to the parent to update with the id or the new status | ||
remove: () => void; // This helps remove the line from the array and refresh the parent view | ||
focused?: boolean; | ||
} | ||
|
||
const TrackedContractRow: React.FC<TrackedContractProps> = ({ trackedContract, remove, focused }) => { | ||
const [inputFocused, setInputFocused] = useState<boolean>(focused || false); | ||
const [edited, setEdited] = useState<boolean>(false); | ||
const [contract, setContract] = useState<TrackedContract>(trackedContract); | ||
const [contract, setContract] = useState<Contract>(trackedContract); | ||
// Keep the original contract. | ||
const initialContract = trackedContract; | ||
|
||
// Reference to the input | ||
let trackInputRef: any = null; | ||
|
||
useEffect(() => { | ||
// When adding a new line we want straight awayt to focus the new line | ||
if (focused && trackInputRef) { | ||
trackInputRef.focus(); | ||
} | ||
}, [focused, trackInputRef]); | ||
|
||
// const saved = () => { callParent to update the array view without recharging the page } | ||
// | ||
|
||
// Handle the UI state for the contrat changing | ||
const handleContractChange = (value: TrackedContract): void => { | ||
const handleContractChange = (value: Partial<Contract>): void => { | ||
setContract({ ...contract, ...value }); | ||
setEdited(true); | ||
}; | ||
|
||
// reset the state to lose focus on the row | ||
const loseFocus = (): void => { | ||
setEdited(false); | ||
trackInputRef.blur(); | ||
setInputFocused(false); | ||
console.log('trackinputRef', trackInputRef); | ||
if (trackInputRef) { | ||
trackInputRef.blur(); | ||
} | ||
}; | ||
|
||
// Save the contract | ||
const saveClick = (): void => { | ||
// save contract as it is | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before saving we would check that the address is valid or not using some utils. Ticket to come about adding a util to check input address based on the chain Good example from Jure : https://polkadot.js.org/docs/util-crypto/examples/validate-address/ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
// then set it as not edited anymore | ||
if (!contract.id) { | ||
// TODO: Should have the BE send us the response of the new object so we can update the view ( example with the id ) | ||
ContractsApi.createContract(contract).then(() => { | ||
loseFocus(); | ||
}); | ||
} else { | ||
// TODO: same as above | ||
ContractsApi.setContractStatus(contract.id, { active: contract.active }).then(() => { | ||
loseFocus(); | ||
}); | ||
} | ||
}; | ||
|
||
loseFocus(); | ||
const deleteClick = (): void => { | ||
if (contract.id) { | ||
// TODO: Handle the errors and exception | ||
// This should be done when we have our notification component | ||
// basically we'd make the notification component appear with the error | ||
// Potentially add an outline to the line as red until it's touched ( Optional ) | ||
ContractsApi.deleteContract(contract.id).then(() => { | ||
remove(); | ||
}); | ||
} else { | ||
remove(); | ||
} | ||
}; | ||
|
||
// cancel the changes on the contract | ||
// Cancel the changes on the contract and revert back to original | ||
const cancelClick = (): void => { | ||
setContract(initialContract); | ||
loseFocus(); | ||
|
@@ -62,17 +85,17 @@ const TrackedContractRow: React.FC<TrackedContractProps> = ({ trackedContract, r | |
// Templates | ||
const saveCancelButtons = ( | ||
<> | ||
<Button className="ml-1 border-none px-3.5 py-3.5" disabled={!edited} color="green" onClick={() => saveClick()}> | ||
<Button className="ml-1 border-none px-3.5 py-3.5" disabled={!edited} color="green" onClick={saveClick}> | ||
<FaCheck /> | ||
</Button> | ||
<Button className="ml-1 border-none px-3.5 py-3.5" disabled={!edited} color="gray" onClick={() => cancelClick()}> | ||
<Button className="ml-1 border-none px-3.5 py-3.5" disabled={!edited} color="gray" onClick={cancelClick}> | ||
<FaTimes /> | ||
</Button> | ||
</> | ||
); | ||
|
||
const removeButton = ( | ||
<Button className="ml-1 border-none px-3.5 py-3.5" color="red" onClick={() => remove()}> | ||
<Button className="ml-1 border-none px-3.5 py-3.5" color="red" onClick={deleteClick}> | ||
<FaTrash /> | ||
</Button> | ||
); | ||
|
@@ -95,9 +118,9 @@ const TrackedContractRow: React.FC<TrackedContractProps> = ({ trackedContract, r | |
<div className="w-48 flex justify-end items-center absolute right-0 top-1/2 transform -translate-y-1/2"> | ||
{inputFocused || edited ? <div className="flex mr-1">{saveCancelButtons}</div> : ''} | ||
<Toggle | ||
enabled={contract.enabled || false} | ||
enabled={contract.active || false} | ||
onClick={() => { | ||
handleContractChange({ enabled: !contract.enabled }); | ||
handleContractChange({ active: !contract.active }); | ||
}} | ||
/> | ||
{removeButton} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI @frisitano @jbargu I've tried some of the BE Apis and had some feedback :
https://github.com/paralink-network/paralink-node/blob/master/src/api/contracts.py#L47 POST & PUT should return the contract response instead of just OK ( we'd need the latest from the DB, especially in the POST to have the ID without refreshing the page or refetching all of the contracts )
Error when trying to update the status of an address, we might want to fix that
Should we be able to edit an address after it was created or saved ? cause we don't have a "setAddress" or a modify endpoint. Also if we were to add one, let's just do it as "updateContract" endpoint and remove the status one.
Error when deleting. It actually works, but return a 500 as there is an error after deleting :
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @ZiyedB
I have made some modifications to the API contract on this development branch - refactor_models .
This refactor modifies the API such that contracts are identified by a unique (chain, address) tuple.
Under this new API you should not need any additional information returned by the API as the contract id has been removed.
I am not experiencing the same issue as you. I suspect your
.env
file may be referencing the celery broker incorrectly. Could you ensure it containsCELERY_BROKER_URL=amqp://guest:guest@rabbitmq:5672
please?No we should not be able to update the address. A different address would represent a different contract.
Fixed.
Could you take a look at the API and let me know your feedback. I will hold fire on raising a PR to see if we need to make any modifications to the API for the front end. In any case I still need to develop some tests for the REST endpoints.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to mention that you will have to delete the docker volume associated with the paralink Postgres container before executing the dev branch above.