diff --git a/frontend/.prettierignore b/frontend/.prettierignore index 0bb2c1b..b42665b 100644 --- a/frontend/.prettierignore +++ b/frontend/.prettierignore @@ -1,3 +1,3 @@ node_modules/ dist/ -.prettierrc.js \ No newline at end of file +.prettierrc.js diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..ba08461 --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,8 @@ +{ + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "singleQuote": true, + "printWidth": 120, + "bracketSpacing": true +} diff --git a/frontend/package.json b/frontend/package.json index b8b5597..6bb21ef 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,7 +7,7 @@ "serve": "vite preview", "type-check": "tsc --noEmit", "lint": "eslint --fix --ext .js,.jsx,.ts,.tsx ./src", - "format": "prettier --write ./src" + "format": "prettier --config .prettierrc --write ./src" }, "dependencies": { "@kiltprotocol/kilt-extension-api": "0.1.0-alpha.3", diff --git a/frontend/prettierrc.js b/frontend/prettierrc.js deleted file mode 100644 index af30039..0000000 --- a/frontend/prettierrc.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - trailingComma: 'all', - tabWidth: 2, - semi: true, - singleQuote: true, - printWidth: 120, - bracketSpacing: true, -} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 25476c5..3f634f2 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,25 +4,21 @@ import { Layout, Login } from './layout' import { darkTheme, lightTheme } from './layout/themes' import { dataProvider } from './api/dataProvider' import { authProvider } from './api/authProvider' -import Dashboard from './components/Dashboard' -import { AttestationCreate } from './components/AttestationAdd' -import { AttestationList } from './components/AttestationList' +import { AttestationCreate, AttestationList, Dashboard } from './components' -export const App = () => ( - - - -) +export function App() { + return ( + + + + ) +} diff --git a/frontend/src/api/authProvider.ts b/frontend/src/api/authProvider.ts index 729c467..008f296 100644 --- a/frontend/src/api/authProvider.ts +++ b/frontend/src/api/authProvider.ts @@ -1,15 +1,15 @@ -import { AuthProvider } from "react-admin" -import jwtDecode from "jwt-decode" +import { AuthProvider } from 'react-admin' +import jwtDecode from 'jwt-decode' interface JWTPayload { - aud: string; - exp: number; - iat: number; - iss: string; - nonce: string; - pro: { [key: string]: any }; - sub: string; - w3n: string; + aud: string + exp: number + iat: number + iss: string + nonce: string + pro: { [key: string]: unknown } + sub: string + w3n: string } export const authProvider: AuthProvider = { @@ -17,12 +17,12 @@ export const authProvider: AuthProvider = { try { const decodedToken = jwtDecode(token) if (Object.keys(decodedToken.pro).length) { - localStorage.setItem("role", "admin") + localStorage.setItem('role', 'admin') } else { - localStorage.setItem("role", "user") + localStorage.setItem('role', 'user') } - localStorage.setItem("token", token) - window.location.href = "/" + localStorage.setItem('token', token) + window.location.href = '/' return Promise.resolve() } catch (Error) { return Promise.reject() @@ -30,8 +30,8 @@ export const authProvider: AuthProvider = { }, logout: () => { - localStorage.removeItem("token") - localStorage.removeItem("role") + localStorage.removeItem('token') + localStorage.removeItem('role') return Promise.resolve() }, @@ -41,23 +41,22 @@ export const authProvider: AuthProvider = { } const status = error.status if (status === 401 || status === 403) { - localStorage.removeItem("token") - localStorage.removeItem("role") + localStorage.removeItem('token') + localStorage.removeItem('role') return Promise.reject() } return Promise.resolve() }, - checkAuth: () => - localStorage.getItem("token") ? Promise.resolve() : Promise.reject(), + checkAuth: () => (localStorage.getItem('token') ? Promise.resolve() : Promise.reject()), getRole: () => { - return localStorage.getItem("role") + return localStorage.getItem('role') }, getPermissions: () => Promise.reject(undefined), - getToken: () => localStorage.getItem("token"), + getToken: () => localStorage.getItem('token'), } export default authProvider diff --git a/frontend/src/api/credential.ts b/frontend/src/api/credential.ts index 954d71c..9cd5942 100644 --- a/frontend/src/api/credential.ts +++ b/frontend/src/api/credential.ts @@ -6,29 +6,21 @@ export async function fetchCredential(extension: InjectedWindowProvider, session const client = await getAxiosClient() - const credentialUrl = apiURL + '/credential' + const credentialUrl = `${apiURL}/credential`; - const getTermsResponse = await client.post( - credentialUrl + '/terms/' + sessionId + "/" + attestationId, - sessionId - ) + const getTermsResponse = await client.post(`${credentialUrl}/terms/${sessionId}/${attestationId}`, sessionId); - const getCredentialRequestFromExtension = await new Promise( - (resolve, reject) => { - try { - extension.listen(async (credentialRequest: unknown) => { - resolve(credentialRequest) - }) - extension.send(getTermsResponse.data) - } catch (e) { - reject(e) - } - } - ) - client.post( - credentialUrl + "/" + sessionId + "/" + attestationId, - getCredentialRequestFromExtension - ) + const getCredentialRequestFromExtension = await new Promise((resolve, reject) => { + try { + extension.listen(async (credentialRequest: unknown) => { + resolve(credentialRequest) + }) + extension.send(getTermsResponse.data) + } catch (e) { + reject(e) + } + }) + client.post(`${credentialUrl}/${sessionId}/${attestationId}`, getCredentialRequestFromExtension) } diff --git a/frontend/src/api/dataProvider.ts b/frontend/src/api/dataProvider.ts index 19b5198..1dfe5e7 100644 --- a/frontend/src/api/dataProvider.ts +++ b/frontend/src/api/dataProvider.ts @@ -1,9 +1,9 @@ -import simpleRestProvider from "ra-data-simple-rest" -import authProvider from "./authProvider" -import { fetchUtils } from "react-admin" -import axios from "axios" +import simpleRestProvider from 'ra-data-simple-rest' +import authProvider from './authProvider' +import { fetchUtils } from 'react-admin' +import axios from 'axios' -export const getAxiosClient = async () => { +export async function getAxiosClient() { const token = await authProvider.getToken() const instance = axios.create({ headers: { @@ -13,13 +13,10 @@ export const getAxiosClient = async () => { return instance } -const httpClient = async ( - url: string, - options: { [key: string]: any } = {} -) => { +async function httpClient(url: string, options: { [key: string]: any } = {}) { const token = await authProvider.getToken() if (!options.headers) { - options.headers = new Headers({ Accept: "application/json" }) + options.headers = new Headers({ Accept: 'application/json' }) } options.user = { authenticated: true, @@ -28,7 +25,4 @@ const httpClient = async ( return fetchUtils.fetchJson(url, options) } -export const dataProvider = simpleRestProvider( - import.meta.env.VITE_SIMPLE_REST_URL, - httpClient -) +export const dataProvider = simpleRestProvider(import.meta.env.VITE_SIMPLE_REST_URL, httpClient) diff --git a/frontend/src/api/session.ts b/frontend/src/api/session.ts index 7d2e982..8c08e16 100644 --- a/frontend/src/api/session.ts +++ b/frontend/src/api/session.ts @@ -1,20 +1,14 @@ import { getAxiosClient } from './dataProvider' -import { - InjectedWindowProvider, - PubSubSessionV1, - PubSubSessionV2, -} from '@kiltprotocol/kilt-extension-api' - -export async function getSession( - provider: InjectedWindowProvider, -): Promise { +import { InjectedWindowProvider, PubSubSessionV1, PubSubSessionV2 } from '@kiltprotocol/kilt-extension-api' + +export async function getSession(provider: InjectedWindowProvider): Promise { if (!provider) { throw new Error('No provider') } const apiURL = import.meta.env.VITE_SIMPLE_REST_URL - const challengeUrl = apiURL + '/challenge' + const challengeUrl = `${apiURL}/challenge`; const client = await getAxiosClient() @@ -26,11 +20,7 @@ export async function getSession( const { dAppName, dAppEncryptionKeyUri, challenge } = getChallengeReponse.data - const session = await provider.startSession( - dAppName, - dAppEncryptionKeyUri, - challenge - ) + const session = await provider.startSession(dAppName, dAppEncryptionKeyUri, challenge) console.log('Here is the session', session) diff --git a/frontend/src/components/AttestationAdd.tsx b/frontend/src/components/AttestationAdd.tsx index 4bfbcd7..163b2f2 100644 --- a/frontend/src/components/AttestationAdd.tsx +++ b/frontend/src/components/AttestationAdd.tsx @@ -1,17 +1,11 @@ import { Create, SaveButton, SimpleForm, Toolbar, useNotify } from 'react-admin' import TextField from '@mui/material/TextField' -import { - ICType, - IClaimContents, - Claim, - DidUri, - Credential as KiltCredential, -} from '@kiltprotocol/sdk-js' +import { ICType, IClaimContents, Claim, DidUri, Credential as KiltCredential } from '@kiltprotocol/sdk-js' import { useState } from 'react' import ReactJson, { InteractionProps } from 'react-json-view' import { fetchCType } from '../utils/utils' -//TODO:fix +//TODO:fix "It currently only works with ctypes with a height of 1." function getDefaultEntryForType({ type }: { type: string }) { if (type === 'string') { return '' @@ -24,7 +18,7 @@ function getDefaultEntryForType({ type }: { type: string }) { } } -export const AttestationCreate = () => { +export default function AttestationCreate() { // states const [ctypeHash, setCtypeHash] = useState('') const [claimer, setClaimer] = useState('') @@ -38,36 +32,34 @@ export const AttestationCreate = () => { //callbacks const handleSelectedCtype = async (ctype: string) => { const fmtCtype = ctype.trim() - setCtypeHash(fmtCtype); + setCtypeHash(fmtCtype) try { - const ctypeDetails = await fetchCType(fmtCtype as any); - const claimContent: any = {}; + const ctypeDetails = await fetchCType(fmtCtype as unknown as `kilt:ctype:0x${string}`) + const claimContent: Record = {} Object.entries(ctypeDetails.cType.properties).map( - ([key, type]) => - (claimContent[key] = getDefaultEntryForType(type as any)) + ([key, type]) => (claimContent[key] = getDefaultEntryForType(type as { type: string })), ) setCtypeDetails(ctypeDetails.cType) setClaimContent(claimContent) } catch { - setClaimContent(undefined); - notify("CType does not exists", { type: "error" }); + setClaimContent(undefined) + notify('CType does not exists', { type: 'error' }) } } const onEdit = (data: InteractionProps) => { + const { existing_value, name, updated_src, new_value } = data - const { existing_value, name, updated_src, new_value } = data; - - let fmtSrc; + let fmtSrc if (Number.isInteger(existing_value) && new_value && name) { fmtSrc = { ...updated_src, - [name]: + new_value, + [name]: +new_value, } } - setClaimContent(fmtSrc ? fmtSrc : data.updated_src as IClaimContents) + setClaimContent(fmtSrc ? fmtSrc : (data.updated_src as IClaimContents)) } const transformData = () => { @@ -76,56 +68,44 @@ export const AttestationCreate = () => { } try { - const claim = Claim.fromCTypeAndClaimContents( - ctype, - claimContent, - claimer as DidUri - ); - return KiltCredential.fromClaim(claim); + const claim = Claim.fromCTypeAndClaimContents(ctype, claimContent, claimer as DidUri) + return KiltCredential.fromClaim(claim) } catch (e) { - console.error(e); - notify("Ctype Verification failed"); + console.error(e) + notify('Ctype Verification failed') } } //Elements - const CustomToolBar = (props: any) => { + const CustomToolBar = () => { return ( - - + + ) } return ( - + }> handleSelectedCtype(e.target.value)} required /> setClaimer(e.target.value)} required /> {claimContent && ( - + )} diff --git a/frontend/src/components/AttestationList.tsx b/frontend/src/components/AttestationList.tsx index 926e82d..d407c90 100644 --- a/frontend/src/components/AttestationList.tsx +++ b/frontend/src/components/AttestationList.tsx @@ -1,252 +1,221 @@ -import { - List, - Datagrid, - TextField, - DateField, - useRecordContext, - EditButton, - useNotify, - useTheme, - useRefresh, -} from "react-admin"; -import ReactJson from "react-json-view"; -import Fab from "@mui/material/Fab"; -import DoneIcon from "@mui/icons-material/Done"; -import RemoveIcon from "@mui/icons-material/Remove"; -import CircularProgress from "@mui/material/CircularProgress"; -import Tooltip from "@mui/material/Tooltip"; -import DownloadIcon from "@mui/icons-material/Download"; -import BookmarkAddedIcon from "@mui/icons-material/BookmarkAdded"; -import { ICType } from "@kiltprotocol/sdk-js"; -import { getExtensions } from "@kiltprotocol/kilt-extension-api"; - -import { AttestationRequest } from "../utils/types"; -import { useState } from "react"; -import { getAxiosClient } from "../api/dataProvider"; -import { getSession } from "../api/session"; -import { isUserAdmin } from "../utils/utils"; -import { InjectedWindowProvider } from "../session"; -import { fetchCredential } from "../api/credential"; - -const ExpandAttestation = () => { - const record = useRecordContext(); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [theme, _] = useTheme(); - return ( - - ); -}; - -const ApproveButton = () => { - const record = useRecordContext(); - - const isApproved = record.marked_approve; - - if (isApproved) { - return ; +import { List, Datagrid, TextField, DateField, useRecordContext, useNotify, useTheme, useRefresh } from 'react-admin' +import ReactJson from 'react-json-view' +import Fab from '@mui/material/Fab' +import DoneIcon from '@mui/icons-material/Done' +import RemoveIcon from '@mui/icons-material/Remove' +import CircularProgress from '@mui/material/CircularProgress' +import Tooltip from '@mui/material/Tooltip' +import DownloadIcon from '@mui/icons-material/Download' +import BookmarkAddedIcon from '@mui/icons-material/BookmarkAdded' +import { ICType } from '@kiltprotocol/sdk-js' +import { getExtensions } from '@kiltprotocol/kilt-extension-api' + +import { AttestationRequest } from '../utils/types' +import { useState } from 'react' +import { getAxiosClient } from '../api/dataProvider' +import { getSession } from '../api/session' +import { isUserAdmin } from '../utils/utils' +import { InjectedWindowProvider } from '@kiltprotocol/kilt-extension-api' +import { fetchCredential } from '../api/credential' + +export default function AttestationList() { + const ExpandAttestation = () => { + const record = useRecordContext() + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [theme, _] = useTheme() + return } - return ; -}; - -const ClaimButton = () => { - const record = useRecordContext(); - const apiURL = import.meta.env.VITE_SIMPLE_REST_URL; - const [isLoading, setIsLoading] = useState(false); - const notify = useNotify(); - const refresh = useRefresh(); - - const handleClick = async () => { - if (isLoading) { - return; - } - - setIsLoading(true); - const client = await getAxiosClient(); - await client.put(apiURL + "/attestation_request/" + record.id + "/approve"); - setTimeout(() => { - setIsLoading(false); - refresh(); - notify("Transaction finished"); - }, 60_000); - refresh(); - notify("Transaction for approval is fired"); - }; - return ( - - - - {isLoading ? : } - - - - ); -}; + const ApproveButton = () => { + const record = useRecordContext() -const MarkApproveButton = () => { - const record = useRecordContext(); - const apiURL = import.meta.env.VITE_SIMPLE_REST_URL; - const [isLoading, setIsLoading] = useState(false); - const notify = useNotify(); - const refresh = useRefresh(); + const isApproved = record.marked_approve - const handleClick = async () => { - if (isLoading) { - return; + if (isApproved) { + return } - setIsLoading(true); - const client = await getAxiosClient(); - await client.put( - apiURL + "/attestation_request/" + record.id + "/mark_approve" - ); - refresh(); - notify("Marked as claimable"); - setIsLoading(false); - }; - - return ( - - - - {isLoading ? ( - - ) : ( - - )} - - - - ); -}; - -const DisableEditButton = () => { - const record = useRecordContext(); - return ; -}; - -const RevokeButton = () => { - const record = useRecordContext(); - const [isLoading, setIsLoading] = useState(false); - const apiURL = import.meta.env.VITE_SIMPLE_REST_URL; - const notify = useNotify(); - const refresh = useRefresh(); + return + } - const handleClick = async () => { - if (isLoading) { - return; + const ClaimButton = () => { + const record = useRecordContext() + const apiURL = import.meta.env.VITE_SIMPLE_REST_URL + const [isLoading, setIsLoading] = useState(false) + const notify = useNotify() + const refresh = useRefresh() + + const handleClick = async () => { + if (isLoading) { + return + } + + setIsLoading(true) + const client = await getAxiosClient() + await client.put(`${apiURL}/attestation_request/${record.id}/approve`) + setTimeout(() => { + setIsLoading(false) + refresh() + notify('Transaction finished') + }, 60_000) + refresh() + notify('Transaction for approval is fired') } - setIsLoading(true); - const client = await getAxiosClient(); - - await client.put(apiURL + "/attestation_request/" + record.id + "/revoke"); - setTimeout(() => { - setIsLoading(false); - refresh(); - notify("Transaction is finished!"); - }, 60_000); - notify("Transaction for revokation is fired."); - }; - - return ( - - - - {isLoading ? : } - - - - ); -}; - -const DownloadCredential = () => { - const record = useRecordContext(); - const [isLoading, setIsLoading] = useState(false); - const notify = useNotify(); - const refresh = useRefresh(); - const extensions = getExtensions(); - const hasExtension = extensions.length > 0; + return ( + + + + {isLoading ? : } + + + + ) + } - const handleClick = async () => { - setIsLoading(true); - const extensionName = "Sporran"; - const extension: InjectedWindowProvider = extensions.find( - (val) => val.name === extensionName - ); + const MarkApproveButton = () => { + const record = useRecordContext() + const apiURL = import.meta.env.VITE_SIMPLE_REST_URL + const [isLoading, setIsLoading] = useState(false) + const notify = useNotify() + const refresh = useRefresh() + + const handleClick = async () => { + if (isLoading) { + return + } + setIsLoading(true) + const client = await getAxiosClient() + await client.put(`${apiURL}/attestation_request/${record.id}/mark_approve`) + refresh() + notify('Marked as claimable') + setIsLoading(false) + } - try { - const { session, sessionId } = await getSession(extension); + return ( + + + + {isLoading ? : } + + + + ) + } - await fetchCredential(session, sessionId, record.id); - refresh(); - notify("Claim created"); - setIsLoading(false); - } catch { - notify("Could not claim credential.", { type: "error" }); - setIsLoading(false); + const RevokeButton = () => { + const record = useRecordContext() + const [isLoading, setIsLoading] = useState(false) + const apiURL = import.meta.env.VITE_SIMPLE_REST_URL + const notify = useNotify() + const refresh = useRefresh() + + const handleClick = async () => { + if (isLoading) { + return + } + setIsLoading(true) + const client = await getAxiosClient() + + await client.put(`${apiURL}/attestation_request/${record.id}/revoke`) + + setTimeout(() => { + setIsLoading(false) + refresh() + notify('Transaction is finished!') + }, 60_000) + notify('Transaction for revokation is fired.') } - }; - return ( - <> - {hasExtension && ( - + return ( + + - {isLoading ? : } + {isLoading ? : } - - )} - - ); -}; + + + ) + } -const URLField = ({ baseURL }: { source: string; baseURL: string }) => { - const record = useRecordContext(); - let ctype = record.ctype_hash; + const DownloadCredential = () => { + const record = useRecordContext() + const [isLoading, setIsLoading] = useState(false) + const notify = useNotify() + const refresh = useRefresh() + + const extensions = getExtensions() + const hasExtension = extensions.length > 0 + + const handleClick = async () => { + setIsLoading(true) + const extensionName = 'Sporran' + const extension: InjectedWindowProvider = extensions.find((val) => val.name === extensionName) + + try { + const { session, sessionId } = await getSession(extension) + + await fetchCredential(session, sessionId, record.id) + refresh() + notify('Claim created') + setIsLoading(false) + } catch { + notify('Could not claim credential.', { type: 'error' }) + setIsLoading(false) + } + } - if (!ctype.startsWith("kilt:ctype:")) { - ctype = `kilt:ctype:${ctype}` as ICType["$id"]; + return ( + <> + {hasExtension && ( + + + {isLoading ? : } + + + )} + + ) } - return {ctype}; -}; + const URLField = ({ baseURL }: { source: string; baseURL: string }) => { + const record = useRecordContext() + let ctype = record.ctype_hash + + if (!ctype.startsWith('kilt:ctype:')) { + ctype = `kilt:ctype:${ctype}` as ICType['$id'] + } + + return {ctype} + } -export const AttestationList = () => { return ( @@ -256,15 +225,11 @@ export const AttestationList = () => { - + {isUserAdmin() && } {isUserAdmin() && } - - + {!isUserAdmin() && } - ); -}; + ) +} diff --git a/frontend/src/components/Dashboard.tsx b/frontend/src/components/Dashboard.tsx index d50af28..dfd5e6e 100644 --- a/frontend/src/components/Dashboard.tsx +++ b/frontend/src/components/Dashboard.tsx @@ -1,13 +1,5 @@ import { useEffect, useState } from 'react' -import { - LineChart, - Line, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, -} from 'recharts' +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts' import Card from '@mui/material/Card' import Typography from '@mui/material/Typography' import { getAxiosClient } from '../api/dataProvider' @@ -34,9 +26,8 @@ const Dashboard = () => { try { const client = await getAxiosClient() const apiURL = import.meta.env.VITE_SIMPLE_REST_URL - const res = await client.get( - apiURL + '/attestation_request/metric/kpis' - ) + const res = await client.get(`${apiURL}/attestation_request/metric/kpis`) + setKpi(res.data) } catch (error) { console.error('Error fetching data:', error) @@ -62,9 +53,7 @@ const Dashboard = () => { } return ( -
+
{ width: window.innerWidth * 0.9, }} > - + Total Requested Attestations - + { bottom: 5, }} > - - + + - + @@ -114,13 +99,10 @@ const Dashboard = () => { position: 'relative', }} > - + Total Attestations not Approved - + {kpi.attestationsNotApproved} @@ -133,13 +115,10 @@ const Dashboard = () => { position: 'relative', }} > - + Total Attestations revoked - + {kpi.attestationsRevoked} @@ -150,13 +129,10 @@ const Dashboard = () => { position: 'relative', }} > - + Total Claimers - + {kpi.totalClaimers} diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts new file mode 100644 index 0000000..b307d2c --- /dev/null +++ b/frontend/src/components/index.ts @@ -0,0 +1,5 @@ +import Dashboard from './Dashboard' +import AttestationList from './AttestationList' +import AttestationCreate from './AttestationAdd' + +export { Dashboard, AttestationList, AttestationCreate } diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 42abbd1..dff2a06 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,9 +1,10 @@ -import React from "react" -import ReactDOM from "react-dom/client" -import { App } from "./App" +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import React from 'react' +import ReactDOM from 'react-dom/client' +import { App } from './App' -ReactDOM.createRoot(document.getElementById("root")!).render( +ReactDOM.createRoot(document.getElementById('root')!).render( - + , ) diff --git a/frontend/src/layout/AppBar.tsx b/frontend/src/layout/AppBar.tsx index 0f92d50..f26467f 100644 --- a/frontend/src/layout/AppBar.tsx +++ b/frontend/src/layout/AppBar.tsx @@ -1,17 +1,13 @@ import { AppBar, TitlePortal } from 'react-admin' import { Box, useMediaQuery, Theme } from '@mui/material' -const CustomAppBar = () => { - const isLargeEnough = useMediaQuery((theme) => - theme.breakpoints.up('sm') - ) +export default function CustomAppBar() { + const isLargeEnough = useMediaQuery((theme) => theme.breakpoints.up('sm')) return ( - + - {isLargeEnough && } + {isLargeEnough && } ) } - -export default CustomAppBar diff --git a/frontend/src/layout/Layout.tsx b/frontend/src/layout/Layout.tsx index f1b41b7..5eeda47 100644 --- a/frontend/src/layout/Layout.tsx +++ b/frontend/src/layout/Layout.tsx @@ -1,8 +1,6 @@ -import { Layout, LayoutProps } from "react-admin" -import AppBar from "./AppBar" -import Menu from "./Menu" +import { Layout, LayoutProps } from 'react-admin' +import AppBar from './AppBar' +import Menu from './Menu' // eslint-disable-next-line react/display-name -export default (props: LayoutProps) => ( - -) +export default (props: LayoutProps) => diff --git a/frontend/src/layout/Login.tsx b/frontend/src/layout/Login.tsx index 8e93d50..6e6da63 100644 --- a/frontend/src/layout/Login.tsx +++ b/frontend/src/layout/Login.tsx @@ -5,7 +5,7 @@ import { Utils } from '@kiltprotocol/sdk-js' import Box from '@mui/material/Box' import authProvider from '../api/authProvider' -const Login = () => { +export default function Login() { const handleSubmit = useCallback((clientId: string) => { const nonce = Utils.UUID.generate() const state = Utils.UUID.generate() @@ -13,7 +13,7 @@ const Login = () => { const url = new URL(import.meta.env.VITE_AUTH_URL) url.searchParams.append('response_type', 'id_token') url.searchParams.append('client_id', clientId as string) - url.searchParams.append('redirect_uri', window.location.origin + '/#/login') + url.searchParams.append('redirect_uri', `${window.location.origin}/#/login`) url.searchParams.append('scope', 'openid') url.searchParams.append('state', state) url.searchParams.append('nonce', nonce) @@ -64,19 +64,19 @@ const Login = () => {
) } - -export default Login diff --git a/frontend/src/layout/Menu.tsx b/frontend/src/layout/Menu.tsx index d52d682..6bc0442 100644 --- a/frontend/src/layout/Menu.tsx +++ b/frontend/src/layout/Menu.tsx @@ -1,8 +1,8 @@ -import Box from "@mui/material/Box" -import AppsIcon from "@mui/icons-material/Apps" -import { DashboardMenuItem, useSidebarState, MenuItemLink } from "react-admin" +import Box from '@mui/material/Box' +import AppsIcon from '@mui/icons-material/Apps' +import { DashboardMenuItem, useSidebarState, MenuItemLink } from 'react-admin' -const Menu = () => { +export default function Menu() { const [open] = useSidebarState() return ( @@ -12,7 +12,7 @@ const Menu = () => { marginTop: 1, marginBottom: 1, transition: (theme) => - theme.transitions.create("width", { + theme.transitions.create('width', { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), @@ -22,11 +22,9 @@ const Menu = () => { } /> ) } - -export default Menu diff --git a/frontend/src/layout/SubMenu.tsx b/frontend/src/layout/SubMenu.tsx index 98c8b2f..4188e0d 100644 --- a/frontend/src/layout/SubMenu.tsx +++ b/frontend/src/layout/SubMenu.tsx @@ -1,25 +1,18 @@ -import { ReactElement, ReactNode } from "react" -import { - List, - MenuItem, - ListItemIcon, - Typography, - Collapse, - Tooltip, -} from "@mui/material" -import ExpandMore from "@mui/icons-material/ExpandMore" -import { useTranslate, useSidebarState } from "react-admin" +import { ReactElement, ReactNode } from 'react' +import { List, MenuItem, ListItemIcon, Typography, Collapse, Tooltip } from '@mui/material' +import ExpandMore from '@mui/icons-material/ExpandMore' +import { useTranslate, useSidebarState } from 'react-admin' interface Props { - dense: boolean; - handleToggle: () => void; - icon: ReactElement; - isOpen: boolean; - name: string; - children: ReactNode; + dense: boolean + handleToggle: () => void + icon: ReactElement + isOpen: boolean + name: string + children: ReactNode } -const SubMenu = (props: Props) => { +export default function SubMenu(props: Props) { const { handleToggle, isOpen, name, icon, children, dense } = props const translate = useTranslate() @@ -27,9 +20,7 @@ const SubMenu = (props: Props) => { const header = ( - - {isOpen ? : icon} - + {isOpen ? : icon} {translate(name)} @@ -51,8 +42,8 @@ const SubMenu = (props: Props) => { component="div" disablePadding sx={{ - "& a": { - transition: "padding-left 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms", + '& a': { + transition: 'padding-left 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms', paddingLeft: sidebarIsOpen ? 4 : 2, }, }} @@ -63,5 +54,3 @@ const SubMenu = (props: Props) => {
) } - -export default SubMenu diff --git a/frontend/src/layout/index.ts b/frontend/src/layout/index.ts index e39d19f..3cfd284 100644 --- a/frontend/src/layout/index.ts +++ b/frontend/src/layout/index.ts @@ -1,6 +1,6 @@ -import AppBar from "./AppBar" -import Layout from "./Layout" -import Login from "./Login" -import Menu from "./Menu" +import AppBar from './AppBar' +import Layout from './Layout' +import Login from './Login' +import Menu from './Menu' export { AppBar, Layout, Login, Menu } diff --git a/frontend/src/layout/themes.ts b/frontend/src/layout/themes.ts index 478331a..b04ad72 100644 --- a/frontend/src/layout/themes.ts +++ b/frontend/src/layout/themes.ts @@ -1,14 +1,14 @@ -import { defaultTheme } from "react-admin" +import { defaultTheme } from 'react-admin' export const darkTheme = { palette: { primary: { - main: "#90caf9", + main: '#90caf9', }, secondary: { - main: "#FBBA72", + main: '#FBBA72', }, - mode: "dark" as const, // Switching the dark mode on is a single property value change. + mode: 'dark' as const, // Switching the dark mode on is a single property value change. }, sidebar: { width: 200, @@ -18,9 +18,9 @@ export const darkTheme = { RaMenuItemLink: { styleOverrides: { root: { - borderLeft: "3px solid #000", - "&.RaMenuItemLink-active": { - borderLeft: "3px solid #90caf9", + borderLeft: '3px solid #000', + '&.RaMenuItemLink-active': { + borderLeft: '3px solid #90caf9', }, }, }, @@ -28,8 +28,8 @@ export const darkTheme = { MuiAppBar: { styleOverrides: { colorSecondary: { - color: "#ffffffb3", - backgroundColor: "#616161e6", + color: '#ffffffb3', + backgroundColor: '#616161e6', }, }, }, @@ -39,18 +39,18 @@ export const darkTheme = { export const lightTheme = { palette: { primary: { - main: "#4f3cc9", + main: '#4f3cc9', }, secondary: { - light: "#5f5fc4", - main: "#283593", - dark: "#001064", - contrastText: "#fff", + light: '#5f5fc4', + main: '#283593', + dark: '#001064', + contrastText: '#fff', }, background: { - default: "#fcfcfe", + default: '#fcfcfe', }, - mode: "light" as const, + mode: 'light' as const, }, shape: { borderRadius: 10, @@ -63,9 +63,9 @@ export const lightTheme = { RaMenuItemLink: { styleOverrides: { root: { - borderLeft: "3px solid #fff", - "&.RaMenuItemLink-active": { - borderLeft: "3px solid #4f3cc9", + borderLeft: '3px solid #fff', + '&.RaMenuItemLink-active': { + borderLeft: '3px solid #4f3cc9', }, }, }, @@ -73,36 +73,36 @@ export const lightTheme = { MuiPaper: { styleOverrides: { elevation1: { - boxShadow: "none", + boxShadow: 'none', }, root: { - border: "1px solid #e0e0e3", - backgroundClip: "padding-box", + border: '1px solid #e0e0e3', + backgroundClip: 'padding-box', }, }, }, MuiAppBar: { styleOverrides: { colorSecondary: { - color: "#808080", - backgroundColor: "#fff", + color: '#808080', + backgroundColor: '#fff', }, }, }, MuiLinearProgress: { styleOverrides: { colorPrimary: { - backgroundColor: "#f5f5f5", + backgroundColor: '#f5f5f5', }, barColorPrimary: { - backgroundColor: "#d7d7d7", + backgroundColor: '#d7d7d7', }, }, }, MuiTableRow: { styleOverrides: { root: { - "&:last-child td": { border: 0 }, + '&:last-child td': { border: 0 }, }, }, }, diff --git a/frontend/src/session.ts b/frontend/src/session.ts deleted file mode 100644 index 6e016c3..0000000 --- a/frontend/src/session.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { DidUri } from "@kiltprotocol/sdk-js"; -import { useState, useEffect } from "react"; -import { getAxiosClient } from "./api/dataProvider"; - -interface EncryptedMessage { - receiverKeyUri: string; - senderKeyUri: string; - ciphertext: string; - nonce: string; - receivedAt?: number; -} - -interface PubSubSession { - listen: ( - callback: (message: EncryptedMessage) => Promise - ) => Promise; - close: () => Promise; - send: (message: EncryptedMessage) => Promise; - encryptionKeyUri: string; - encryptedChallenge: string; - nonce: string; -} - -export interface InjectedWindowProvider { - startSession: ( - dAppName: string, - dAppEncryptionKeyUri: string, - challenge: string - ) => Promise; - name: string; - version: string; - specVersion: "3.0"; - signWithDid: ( - data: string, - didKeyUri: DidUri - ) => Promise<{ didKeyUri: string; signature: string }>; - getDidList: () => Promise>; -} - -export const apiWindow = window as unknown as { - kilt: Record; -}; - -export function useCompatibleExtensions() { - const [extensions, setExtensions] = useState(getCompatibleExtensions()); - useEffect(() => { - function handler() { - setExtensions(getCompatibleExtensions()); - } - window.dispatchEvent(new CustomEvent("kilt-dapp#initialized")); - window.addEventListener("kilt-extension#initialized", handler); - return () => - window.removeEventListener("kilt-extension#initialized", handler); - }, []); - - return { extensions }; -} - -export function getCompatibleExtensions(): Array { - return Object.entries(apiWindow.kilt) - .filter(([, provider]) => provider.specVersion.startsWith("3.")) - .map(([name]) => name); -} - -export async function requestAttestation( - provider: InjectedWindowProvider, - attestationId: string -): Promise { - if (!provider) { - throw new Error("No provider"); - } - - const apiURL = import.meta.env.VITE_SIMPLE_REST_URL; - const challengeUrl = apiURL + "/challenge"; - - - const client = await getAxiosClient(); - - const get_challenge_response = await client.get(challengeUrl); - - if (get_challenge_response.status !== 200) { - throw new Error("No valid challenge received"); - } - - const challenge = get_challenge_response.data; - const session = await provider.startSession( - challenge.dAppName, - challenge.dAppEncryptionKeyUri, - challenge.challenge - ); - - // post challenge and receive encrypted Message. - const post_session_response = await client.post(challengeUrl, session); - - if (post_session_response.status !== 200) { - throw new Error("No valid Session."); - } - - const session_reference = post_session_response.data; - - const termsRequestData = { - challenge: session_reference, - attestationId, - }; - - const credentialUrl = apiURL + "/credential"; - - const get_terms_response = await client.post( - credentialUrl + "/terms/" + session_reference + "/" + attestationId, - termsRequestData - ); - - const getCredentialRequestFromExtension = await new Promise( - async (resolve, reject) => { - try { - await session.listen(async (credentialRequest) => { - resolve(credentialRequest); - }); - await session.send(get_terms_response.data); - } catch (e) { - reject(e); - } - } - ); - - let attestation_message = await client.post( - credentialUrl + "/" + session_reference + "/" + attestationId, - getCredentialRequestFromExtension - ); - - if (attestation_message.status !== 200) { - throw new Error("No valid attestation message"); - } - -} diff --git a/frontend/src/users.json b/frontend/src/users.json deleted file mode 100644 index fc14c48..0000000 --- a/frontend/src/users.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "users": [ - { - "id": 1, - "username": "janedoe", - "password": "password", - "fullName": "Jane Doe", - "avatar": "data:image/jpeg;base64,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" - }, - { - "id": 2, - "username": "johndoe", - "password": "password", - "fullName": "John Doe", - "avatar": "data:image/jpeg;base64,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" - } - ] -} diff --git a/frontend/src/utils/types.ts b/frontend/src/utils/types.ts index 592cd11..3d8604e 100644 --- a/frontend/src/utils/types.ts +++ b/frontend/src/utils/types.ts @@ -4,7 +4,7 @@ import { UUID } from 'crypto' export interface AttestationRequest { approved: boolean revoked: boolean - marked_approve: boolean, + marked_approve: boolean claimer: DidUri createdAt: string credential: ICredential diff --git a/frontend/src/utils/utils.ts b/frontend/src/utils/utils.ts index dcabb58..292fef5 100644 --- a/frontend/src/utils/utils.ts +++ b/frontend/src/utils/utils.ts @@ -1,14 +1,12 @@ -import { ICType, CType, connect } from "@kiltprotocol/sdk-js" -import authProvider from "../api/authProvider" +import { ICType, CType, connect } from '@kiltprotocol/sdk-js' +import authProvider from '../api/authProvider' -export async function fetchCType( - ctypeId: ICType["$id"] -): Promise { +export async function fetchCType(ctypeId: ICType['$id']): Promise { await connect(import.meta.env.VITE_WSS_ENDPOINT) return CType.fetchFromChain(ctypeId) } export function isUserAdmin() { const role = authProvider.getRole() - return role === "admin" + return role === 'admin' }