Skip to content
Snippets Groups Projects
Commit 4ad1009d authored by Eduardo Orthmann's avatar Eduardo Orthmann
Browse files

add offering && remove unixistant pages && add did selection to accept offering

parent 4c4b46c0
No related branches found
No related tags found
No related merge requests found
......@@ -93,7 +93,9 @@
"connection-details": "Verbindungsdetails",
"block": "Blockieren",
"unblock": "Entsperren",
"delete": "Löschen"
"delete": "Löschen",
"block-success": "Gerät erfolgreich blockiert!",
"delete-success": "Gerät erfolgreich gelöscht!"
},
"Table": {
"see-details": "Details anzeigen",
......@@ -125,7 +127,8 @@
"upload": "Hochladen",
"delete": "Löschen",
"add": "Backup hinzufügen",
"submit": "Einreichen"
"submit": "Einreichen",
"delete-success": "Backup erfolgreich gelöscht!"
},
"History": {
"title": "Geschichte"
......@@ -140,7 +143,8 @@
"Offering": {
"title": "Angebot",
"accept": "Akzeptieren",
"deny": "Ablehnen"
"deny": "Ablehnen",
"add": "Hinzufügen"
},
"Plugin": {
"not-found": "Plugin nicht gefunden"
......
......@@ -93,7 +93,9 @@
"connection-details": "Connection Details",
"block": "Block",
"unblock": "Unblock",
"delete": "Delete"
"delete": "Delete",
"block-success": "Device blocked successfully!",
"delete-success": "Device deleted successfully!"
},
"Table": {
"see-details": "See Details",
......@@ -125,7 +127,8 @@
"upload": "Upload",
"delete": "Delete",
"add": "Add Backup",
"submit": "Submit"
"submit": "Submit",
"delete-success": "Backup deleted successfully!"
},
"History": {
"title": "History"
......@@ -140,7 +143,8 @@
"Offering": {
"title": "Offering",
"accept": "Accept",
"deny": "Deny"
"deny": "Deny",
"add": "Add"
},
"Plugin": {
"not-found": "Plugin not found"
......
......@@ -6,11 +6,13 @@ import { useContext, useEffect, useState } from 'react';
import { AppContext } from '@/store/AppContextProvider';
import { useAuth } from 'oidc-react';
import { genericFetch, useApiData } from '@/service/apiService';
import type { BackupList, BackupQrCodeData, BackupUpload } from '@/service/types';
import type { BackupList, BackupQrCodeData } from '@/service/types';
import { useTranslations } from 'next-intl';
import QrModal from '@/components/qr-modal/QrModal';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrash } from '@fortawesome/free-solid-svg-icons';
import { toast } from 'react-toastify';
import AddBackupModal from '@/components/add-backup-modal/AddBackupModal';
interface QrCodeModalProps {
qrCode: string;
......@@ -33,6 +35,7 @@ const Backup = (): JSX.Element => {
const [rowData, setRowData] = useState<TableBodyMap>();
const t = useTranslations('Backup');
const [showQrModal, setShowQrModal] = useState(false);
const [showAddModal, setShowAddModal] = useState(false);
const [qrData, setQrData] = useState<QrCodeModalProps>();
useEffect(() => {
......@@ -42,20 +45,20 @@ const Backup = (): JSX.Element => {
useEffect(() => {
if (!data || data.backups.length <= 0) return;
console.log(data);
const backupData = data.backups;
setTableData({
head: ['bindingId', 'name', 'user_id'],
body: backupData.map(({ ...backup }) => {
return {
id: backup.bindingId,
bindingId: backup.bindingId,
name: backup.name,
user_id: backup.user_id,
};
}),
body: backupData
.filter(backup => backup.bindingId)
.map(({ ...backup }, i) => {
return {
id: backup.bindingId ?? i,
bindingId: backup.bindingId,
name: backup.name,
user_id: backup.user_id,
};
}),
});
}, [data]);
......@@ -80,19 +83,22 @@ const Backup = (): JSX.Element => {
break;
case 'delete':
setQrData({
qrCode: await genericFetch<BackupQrCodeData>(
`${process.env.API_URL_ACCOUNT_SERVICE}/credentials/backup?bindingId=${value.bindingId}`,
{
headers: {
Authorization: `Bearer ${userData?.access_token}`,
},
method: 'DELETE',
}
).then(data => data.qrCodeLink),
title: t('delete'),
await genericFetch<BackupQrCodeData>(
`${process.env.API_URL_ACCOUNT_SERVICE}/credentials/backup?bindingId=${value.bindingId}`,
{
headers: {
Authorization: `Bearer ${userData?.access_token}`,
},
method: 'DELETE',
}
).then(() => {
toast.success(t('delete-success'));
setTableData({
head: tableData!.head,
body: tableData!.body.filter(row => row.id !== value.id),
});
});
setShowQrModal(true);
break;
}
......@@ -103,37 +109,13 @@ const Backup = (): JSX.Element => {
});
}, [rowData]);
const handleShowAddModal = (): void => {
const handleAdd = async (): Promise<void> => {
const data = await genericFetch<BackupUpload>(
`${process.env.API_URL_ACCOUNT_SERVICE}/credentials/backup/link/upload`,
{
headers: {
Authorization: `Bearer ${userData?.access_token}`,
},
}
);
console.log(data);
setQrData({
qrCode: data.path,
title: t('add'),
});
setShowQrModal(true);
};
handleAdd().catch(error => setError(error));
};
return (
<>
<Container fluid>
<div className={`d-flex justify-content-between gap-2 mb-4`}>
<h1 className="mb-0">{t('title')}</h1>
<Button onClick={handleShowAddModal}>{t('add')}</Button>
<Button onClick={() => setShowAddModal(true)}>{t('add')}</Button>
</div>
<Table
......@@ -170,6 +152,11 @@ const Backup = (): JSX.Element => {
qrCodeLink={qrData?.qrCode ?? ''}
generate
/>
<AddBackupModal
show={showAddModal}
handleClose={() => setShowAddModal(false)}
/>
</>
);
};
......
@import '../../../../scss/main.scss';
'use client';
import css from './offering.module.scss';
import { Button, Col, Container, Row } from 'react-bootstrap';
import { Button, Container } from 'react-bootstrap';
import Table, { type TableBody, type TableBodyMap, type TableData } from '@/components/table/Table';
import { useContext, useEffect, useState } from 'react';
import { AppContext } from '@/store/AppContextProvider';
......@@ -10,6 +9,8 @@ import { genericFetch, useApiData } from '@/service/apiService';
import { useLocale, useTranslations } from 'next-intl';
import { getDetailedDisplay, getDisplayName } from '@/utils/objectUtils';
import type { OfferingData } from '@/service/types';
import OfferingModal, { type OfferingLinkData } from '@/components/offering-modal/OfferingModal';
import AcceptOfferingModal from '@/components/accept-offering-modal/AcceptOfferingModal';
const Offering = (): JSX.Element => {
const { userData } = useAuth();
......@@ -26,6 +27,8 @@ const Offering = (): JSX.Element => {
const { setError } = useContext(AppContext);
const t = useTranslations('Offering');
const [rowData, setRowData] = useState<TableBodyMap>();
const [showModal, setShowModal] = useState(false);
const [showAcceptModal, setShowAcceptModal] = useState(false);
const locale = useLocale();
useEffect(() => {
......@@ -35,18 +38,24 @@ const Offering = (): JSX.Element => {
useEffect(() => {
if (!data || data.length <= 0) return;
setTableData({
head: ['', 'id', 'name', 'fields'],
body: data.map(item => {
const tableBody = data.map(item => {
return Object.keys(item.metadata.credential_configurations_supported).map(key => {
const offering = item.metadata.credential_configurations_supported[key];
return {
logo: getDetailedDisplay(item.display, locale).logo.url,
id: item.id,
name: getDetailedDisplay(item.display, locale).name,
fields: Object.keys(item.credential_definition.credentialSubject)
.map(key => getDisplayName(item.credential_definition.credentialSubject, key, locale))
logo: getDetailedDisplay(offering.display, locale).logo.url,
id: key,
name: getDetailedDisplay(offering.display, locale).name,
fields: Object.keys(offering.credential_definition.credentialSubject)
.map(subKey => getDisplayName(offering.credential_definition.credentialSubject, subKey, locale))
.join(', '),
};
}),
});
});
setTableData({
head: ['', 'id', 'name', 'fields'],
body: tableBody.flat(),
});
}, [data]);
......@@ -56,12 +65,7 @@ const Offering = (): JSX.Element => {
const handleAction = async (value: TableBody, key: string): Promise<void> => {
switch (key) {
case 'accept':
await genericFetch(`${process.env.API_URL_ACCOUNT_SERVICE}/credentials/offers/${value.id}/accept`, {
method: 'POST',
headers: {
Authorization: `Bearer ${userData?.access_token}`,
},
});
setShowAcceptModal(true);
break;
case 'deny':
......@@ -70,6 +74,7 @@ const Offering = (): JSX.Element => {
headers: {
Authorization: `Bearer ${userData?.access_token}`,
},
body: JSON.stringify({}),
});
break;
}
......@@ -80,44 +85,92 @@ const Offering = (): JSX.Element => {
});
}, [rowData]);
const handleCreateOnSubmit = (data: OfferingLinkData): void => {
console.log(data);
const offerLink = async (): Promise<void> => {
await genericFetch(`${process.env.API_URL_ACCOUNT_SERVICE}/credentials/offers/create`, {
headers: {
Authorization: `Bearer ${userData?.access_token}`,
},
body: JSON.stringify({
credential_offer: data.offeringLink,
}),
method: 'PUT',
});
};
offerLink()
.then(() => window.location.reload())
.catch(error => setError(error));
};
const handleAcceptOnSubmit = (did: string): void => {
const value = rowData?.get('accept');
const acceptOffering = async (): Promise<void> => {
await genericFetch(`${process.env.API_URL_ACCOUNT_SERVICE}/credentials/offers/${did}/accept`, {
method: 'POST',
headers: {
Authorization: `Bearer ${userData?.access_token}`,
},
body: JSON.stringify({
key: value?.id,
}),
});
};
acceptOffering()
.then(() => window.location.reload())
.catch(error => setError(error));
};
return (
<Container fluid>
<Row className="mb-4">
<Col
md="6"
sm="12"
className={`${css['flex-center']} justify-content-between gap-2 mb-2`}
<>
<Container fluid>
<div className={`d-flex justify-content-between gap-2 mb-4`}>
<h1 className="mb-0">{t('title')}</h1>
<Button onClick={() => setShowModal(true)}>{t('add')}</Button>
</div>
<Table
data={tableData}
isLoading={isLoading}
handleSelectRow={data => setRowData(data)}
showId
showActions
>
<div className="d-flex gap-2 align-items-center">
<h1 className="mb-0">{t('title')}</h1>
</div>
</Col>
</Row>
<Table
data={tableData}
isLoading={isLoading}
handleSelectRow={data => setRowData(data)}
showId
showActions
>
<Table.Actions>
<Button
variant="light"
data-type="accept"
>
{t('accept')}
</Button>
<Button
variant="light"
data-type="deny"
>
{t('deny')}
</Button>
</Table.Actions>
</Table>
</Container>
<Table.Actions>
<Button
variant="light"
data-type="accept"
>
{t('accept')}
</Button>
<Button
variant="light"
data-type="deny"
>
{t('deny')}
</Button>
</Table.Actions>
</Table>
</Container>
<OfferingModal
show={showModal}
handleClose={() => setShowModal(false)}
onSubmit={handleCreateOnSubmit}
/>
<AcceptOfferingModal
show={showAcceptModal}
handleClose={() => setShowAcceptModal(false)}
onSubmit={handleAcceptOnSubmit}
/>
</>
);
};
......
......@@ -13,6 +13,7 @@ import { useContext, useEffect, useState } from 'react';
import { Button, Col, Container, Row } from 'react-bootstrap';
import css from './pairing-management.module.scss';
import DetailsModal from '@/components/details-modal/DetailsModal';
import { toast } from 'react-toastify';
const PairingManagement = (): JSX.Element => {
const t = useTranslations('SettingsPairingManagement');
......@@ -74,7 +75,7 @@ const PairingManagement = (): JSX.Element => {
method: 'POST',
})
.then(() => {
console.log('Device blocked');
toast.success(t('block-success'));
})
.catch(error => setError(error));
break;
......@@ -86,11 +87,10 @@ const PairingManagement = (): JSX.Element => {
method: 'DELETE',
})
.then(() => {
console.log('Device deleted');
toast.success(t('delete-success'));
setTableData({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
head: tableData!.head,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
body: tableData!.body.filter(row => row.id !== value.id),
});
})
......
import { type OfferingData } from '@/service/types';
export function GET(req: Request, res: Response): Response {
return new Response(
JSON.stringify([
{
id: 'did:example:123456789abcdefghi',
format: 'jwt_vc_json',
scope: 'UniversityDegree',
cryptographic_binding_methods_supported: ['did:example'],
credential_signing_alg_values_supported: ['ES256'],
credential_definition: {
type: ['VerifiableCredential', 'UniversityDegreeCredential'],
credentialSubject: {
given_name: {
display: [{ name: 'Given Name', locale: 'en-US' }],
},
family_name: {
display: [{ name: 'Surname', locale: 'en-US' }],
},
degree: {},
gpa: {
display: [{ name: 'GPA' }],
},
},
},
proof_types_supported: {
jwt: {
proof_signing_alg_values_supported: ['ES256'],
},
},
display: [
{
name: 'University Credential',
locale: 'en-US',
logo: {
url: 'https://picsum.photos/200',
alt_text: 'a square logo of a university',
},
background_color: '#12107c',
text_color: '#FFFFFF',
},
],
},
] satisfies OfferingData[]),
{
headers: {
'content-type': 'application/json;charset=UTF-8',
},
}
);
}
interface PluginsData {
id: string;
name: string;
uploaded: string;
issuer: string;
}
export function GET(req: Request, res: Response): Response {
return new Response(
JSON.stringify([
{
id: '1',
name: 'Tokens',
uploaded: '12.12.2023',
issuer: 'example',
},
{
id: '2',
name: 'Tokens',
uploaded: '12.12.2023',
issuer: 'example',
},
{
id: '3',
name: 'Tokens',
uploaded: '12.12.2023',
issuer: 'example',
},
{
id: '4',
name: 'Tokens',
uploaded: '12.12.2023',
issuer: 'example',
},
] satisfies PluginsData[]),
{
headers: {
'content-type': 'application/json;charset=UTF-8',
},
}
);
}
import { genericFetch } from '@/service/apiService';
import type { BackupUpload } from '@/service/types';
import { AppContext } from '@/store/AppContextProvider';
import { useAuth } from 'oidc-react';
import { type FormEvent, useState, useContext } from 'react';
import {
Button,
Form,
FormControl,
FormGroup,
FormLabel,
Modal,
ModalBody,
ModalHeader,
ModalTitle,
} from 'react-bootstrap';
import QRCode from 'react-qr-code';
interface BackupModalProps {
show: boolean;
handleClose: () => void;
}
export interface BackupNameData {
name: string;
}
const AddBackupModal = ({ show, handleClose }: BackupModalProps): JSX.Element => {
const { userData } = useAuth();
const [formData, setFormData] = useState<BackupNameData>({
name: '',
});
const [showQrCode, setShowQrCode] = useState(false);
const [qrCode, setQrCode] = useState<string>('');
const { setError } = useContext(AppContext);
const handleSubmit = (event: FormEvent<HTMLFormElement>, formData: BackupNameData): void => {
event.preventDefault();
const getQrCode = async (): Promise<BackupUpload> => {
return await genericFetch<BackupUpload>(
`${process.env.API_URL_ACCOUNT_SERVICE}/credentials/backup/link/upload?name=${formData.name}`,
{
headers: {
Authorization: `Bearer ${userData?.access_token}`,
},
}
);
};
getQrCode()
.then(data => {
setQrCode(data.path);
setShowQrCode(true);
})
.catch(err => setError(err));
};
return (
<Modal
show={show}
onHide={handleClose}
centered
>
<ModalHeader closeButton>
<ModalTitle>Add Backup</ModalTitle>
</ModalHeader>
<ModalBody className="d-flex flex-column">
<Form
onSubmit={event => handleSubmit(event, formData)}
className="d-flex justify-content-between gap-1 align-items-end"
>
<FormGroup
controlId="backupName"
className="flex-grow-1"
>
<FormLabel>Backup Name</FormLabel>
<FormControl
type="text"
placeholder="Enter the backup name"
value={formData.name}
onChange={e => setFormData({ name: e.target.value })}
/>
</FormGroup>
<Button type="submit">Submit</Button>
</Form>
{showQrCode && (
<QRCode
className="mt-4 align-self-center"
value={qrCode}
size={300}
/>
)}
</ModalBody>
</Modal>
);
};
export default AddBackupModal;
'use client';
import { type FormEvent, useState } from 'react';
import {
Button,
Form,
FormControl,
FormGroup,
FormLabel,
Modal,
ModalBody,
ModalHeader,
ModalTitle,
} from 'react-bootstrap';
interface OfferingModalProps {
show: boolean;
handleClose: () => void;
onSubmit: (data: OfferingLinkData) => void;
}
export interface OfferingLinkData {
offeringLink: string;
}
const OfferingModal = ({ show, handleClose, onSubmit }: OfferingModalProps): JSX.Element => {
const [formData, setFormData] = useState<OfferingLinkData>({
offeringLink: '',
});
const handleSubmit = (event: FormEvent<HTMLFormElement>, formData: OfferingLinkData): void => {
event.preventDefault();
onSubmit(formData);
setFormData({
offeringLink: '',
});
handleClose();
};
return (
<Modal
show={show}
onHide={handleClose}
centered
>
<ModalHeader closeButton>
<ModalTitle>Schema</ModalTitle>
</ModalHeader>
<ModalBody>
<Form onSubmit={event => handleSubmit(event, formData)}>
<FormGroup controlId="offeringLink">
<FormLabel>Offering Link</FormLabel>
<FormControl
type="text"
placeholder="Enter the offering link"
value={formData.offeringLink}
onChange={e => setFormData({ offeringLink: e.target.value })}
/>
</FormGroup>
<Button type="submit">Submit</Button>
</Form>
</ModalBody>
</Modal>
);
};
export default OfferingModal;
'use client';
import useScreenSize from '@/hooks/useScreenSize';
import {
faBars,
faChevronLeft,
faCircleQuestion,
faHistory,
faShield,
faWallet,
} from '@fortawesome/free-solid-svg-icons';
import { faBars, faChevronLeft, faHistory, faWallet } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
......@@ -161,32 +154,6 @@ const WalletSideMenu = (): JSX.Element => {
<span>{t('history')}</span>
</Link>
</NavItem>
<NavItem className={`${css['sidebar-item']}`}>
<Link
className={`${css['sidebar-link']}`}
href={`/${locale}/wallet/privacy`}
>
<FontAwesomeIcon
icon={faShield}
className={css.icon}
/>
<span>{t('privacy')}</span>
</Link>
</NavItem>
<NavItem className={`${css['sidebar-item']}`}>
<Link
className={`${css['sidebar-link']}`}
href={`/${locale}/wallet/help`}
>
<FontAwesomeIcon
icon={faCircleQuestion}
className={css.icon}
/>
<span>{t('help')}</span>
</Link>
</NavItem>
</div>
</Nav>
</div>
......
......@@ -214,7 +214,34 @@ export interface SchemaDataWithId extends JSONSchema7 {
}
export interface OfferingData {
id: string;
groupId: string;
requestId: string;
metadata: Metadata;
offering: Offering;
status: string;
timestamp: string;
}
export interface Metadata {
credential_issuer: string;
authorization_servers: string[];
credential_endpoint: string;
batch_credential_endpoint: any;
deferred_credential_endpoint: any;
credential_response_encryption: CredentialResponseEncryption;
display: any;
credential_configurations_supported: CredentialConfigurationsSupported;
}
export interface CredentialResponseEncryption {
alg_values_supported: any;
enc_values_supported: any;
encryption_required: boolean;
}
export type CredentialConfigurationsSupported = Record<string, CredentialConfiguration>;
export interface CredentialConfiguration {
format: string;
scope: string;
cryptographic_binding_methods_supported: string[];
......@@ -224,6 +251,26 @@ export interface OfferingData {
display: DetailedDisplay[];
}
export interface Offering {
credential_issuer: string;
credentials: string[];
grants: Grants;
}
export interface Grants {
authorization_code: AuthorizationCode;
'urn:ietf:params:oauth:grant-type:pre-authorized_code': UrnIetfParamsOauthGrantTypePreAuthorizedCode;
}
export interface AuthorizationCode {
issuer_state: string;
}
export interface UrnIetfParamsOauthGrantTypePreAuthorizedCode {
'pre-authorized_code': string;
user_pin_required: boolean;
}
export type IssuanceData = Record<string, Issuance>;
export interface Issuance {
......@@ -246,10 +293,15 @@ export interface CredentialDefinition {
export type CredentialSubjectOffering = Record<string, Display>;
export interface Display {
display?: Array<{
name: string;
locale?: string;
}>;
display?:
| Array<{
name: string;
locale?: string;
}>
| {
name: string;
locale?: string;
};
}
export interface DetailedDisplay {
......@@ -262,7 +314,7 @@ export interface DetailedDisplay {
export interface Logo {
url: string;
alt_text: string;
alternative_text: string;
}
export type ProofTypesSupported = Record<
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment