init commit,

This commit is contained in:
louiscklaw
2025-05-28 09:55:51 +08:00
commit efe70ceb69
8042 changed files with 951668 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
import React, { ReactNode, useState } from "react";
import {
Alert,
Box,
Button,
CircularProgress,
Container,
TextField,
Typography,
} from "@mui/material";
import axios from "axios";
import QRCode from "qrcode.react";
interface GetQRButtonProps {
name: string;
url: string;
customCredentialForm?: ReactNode;
jsonFilePath?: string;
icon?: ReactNode;
onQRGenerated?: (generated: boolean) => void;
}
const GetQRButton: React.FC<GetQRButtonProps> = ({
name,
url,
customCredentialForm,
jsonFilePath,
icon,
onQRGenerated,
}) => {
const [responseLink, setResponseLink] = useState("");
const [showQR, setShowQR] = useState(false);
const [customCredential, setCustomCredential] = useState("");
const [showCircularProgress, setShowCircularProgress] = useState(false);
const [showCustomCredential, setShowCustomCredential] = useState(false);
const [errorOnRequest, setErrorOnRequest] = useState(false);
const handleClick = async () => {
try {
setShowQR(false);
setShowCircularProgress(true);
setErrorOnRequest(false);
let response;
try {
if (customCredentialForm) {
if (customCredential) {
response = await axios.post(url, customCredential);
setShowCustomCredential(true);
} else {
setShowCircularProgress(false);
setShowQR(false);
return;
}
} else if (jsonFilePath) {
response = await axios.post(
url,
await fetch(jsonFilePath).then((response) => response.json()),
);
setResponseLink(await response.data.data);
} else {
response = await axios(url);
setResponseLink(await response.data.data);
}
setResponseLink(await response.data.data);
console.log(name, response.data.data);
} catch (e) {
console.log(e);
setShowCircularProgress(false);
setErrorOnRequest(true);
return;
}
if (onQRGenerated) {
onQRGenerated(true);
}
setShowCircularProgress(false);
setShowQR(true);
} catch (error) {
console.error("Error while trying to connect to the server.", error);
}
};
return (
<Container sx={{ py: 2 }}>
<Typography variant="h6" gutterBottom>
{icon}
{name}
</Typography>
{customCredentialForm &&
React.cloneElement(customCredentialForm as React.ReactElement<any>, {
onCustomCredentialChange: setCustomCredential,
})}
<Box sx={{ display: "flex", justifyContent: "right" }}>
<Button variant="contained" color="primary" onClick={handleClick}>
Request OOBI
</Button>
</Box>
{showCircularProgress && (
<Box sx={{ p: 2, display: "flex", justifyContent: "center" }}>
<CircularProgress />
</Box>
)}
{showQR && (
<Box sx={{ p: 2, display: "flex", justifyContent: "center" }}>
<QRCode value={responseLink} size={256} />
</Box>
)}
{showCustomCredential && (
<TextField
id="outlined-multiline-static"
label="Credential JSON"
multiline
rows={JSON.stringify(customCredential, null, 2).split("\n").length}
fullWidth
value={JSON.stringify(customCredential, null, 2)}
/>
)}
{errorOnRequest && (
<Alert severity="error">
It was not possible to connect to the server. Try again
</Alert>
)}
</Container>
);
};
export default GetQRButton;

View File

@@ -0,0 +1,126 @@
import React from "react";
import {
AppBar,
Box,
Button,
Container,
IconButton,
Menu,
MenuItem,
Toolbar,
Typography,
} from "@mui/material";
import { Link } from "react-router-dom";
import logo from "../assets/favicon.svg";
import MenuIcon from "@mui/icons-material/Menu";
import { MENU_ITEMS } from "../App";
const NavBar: React.FC = () => {
const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(
null,
);
const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElNav(event.currentTarget);
};
const handleCloseNavMenu = () => {
setAnchorElNav(null);
};
return (
<AppBar position="static">
<Container maxWidth="xl">
<Toolbar disableGutters>
<a href="/">
<img
src={logo}
alt="Logo"
style={{ width: "30px", height: "30px", margin: "10px" }}
/>
</a>
<Typography
variant="h6"
noWrap
component="a"
href="/"
sx={{
mr: 2,
display: { xs: "none", md: "flex" },
fontFamily: "monospace",
fontWeight: 700,
color: "inherit",
textDecoration: "none",
}}
>
Cardano Foundation | Identity Wallet
</Typography>
<Box sx={{ flexGrow: 1, display: { xs: "flex", md: "none" } }}>
<IconButton
size="large"
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={handleOpenNavMenu}
color="inherit"
>
<MenuIcon />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorElNav}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
keepMounted
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
open={Boolean(anchorElNav)}
onClose={handleCloseNavMenu}
sx={{
display: { xs: "block", md: "none" },
}}
>
{MENU_ITEMS.map((item) => (
<MenuItem key={item.key} onClick={handleCloseNavMenu}>
<Typography
textAlign="center"
component={Link}
to={item.path}
>
{item.label}
</Typography>
</MenuItem>
))}
</Menu>
</Box>
<Box sx={{ flexGrow: 1, display: { xs: "none", md: "flex" } }}>
{MENU_ITEMS.map((item) => (
<Button
key={item.key}
component={Link}
to={item.path}
sx={{
my: 1,
px: 2.5,
fontSize: "1.1rem",
color: "white",
display: "block",
}}
>
{item.label}
</Button>
))}
</Box>
</Toolbar>
</Container>
</AppBar>
);
};
export default NavBar;

View File

@@ -0,0 +1,132 @@
import React, { useState } from "react";
import {
Alert,
Button,
FormControl,
Grid,
InputLabel,
MenuItem,
Select,
TextField,
} from "@mui/material";
import { DatePicker } from "@mui/x-date-pickers";
import { Dayjs } from "dayjs";
interface PrescriptionFormProps {
onCustomCredentialChange?: (newJson: string) => void;
}
const PrescriptionForm: React.FC<PrescriptionFormProps> = ({
onCustomCredentialChange,
}) => {
const jsonFilePath = "credentials-json/prescription-credential.json";
const [type, setType] = useState("");
const [name, setName] = useState("");
const [expirationDate, setExpirationDate] = React.useState<Dayjs | null>(
null,
);
const [isSuccessfulValidationVisible, setIsSuccessfulValidationVisible] =
useState(false);
const [isUnsuccessfulValidationVisible, setIsUnsuccessfulValidationVisible] =
useState(false);
const isInformationCorrect = () => {
if (type === "" || name === "" || expirationDate === null) {
return false;
}
return true;
};
const generateJson = async () => {
if (!isInformationCorrect()) {
setIsSuccessfulValidationVisible(false);
setIsUnsuccessfulValidationVisible(true);
return;
}
const prescriptionCredential = await fetch(jsonFilePath).then((response) =>
response.json(),
);
prescriptionCredential.credentialSubject.prescription.type = type;
prescriptionCredential.credentialSubject.prescription.name = name;
prescriptionCredential.expirationDate = expirationDate?.toISOString();
onCustomCredentialChange?.(prescriptionCredential);
setIsSuccessfulValidationVisible(true);
setIsUnsuccessfulValidationVisible(false);
};
return (
<Grid container spacing={2} margin={2}>
<Grid item xs={11} sm={6}>
<FormControl fullWidth>
<InputLabel id="input-label-type">Class of medicine</InputLabel>
<Select
labelId="select-label-type"
id="id-select-type"
value={type}
label="Class of medicine"
onChange={(e) => setType(e.target.value)}
>
<MenuItem value={"Antipyretics"}>Antipyretics</MenuItem>
<MenuItem value={"Analgesics"}>Analgesics</MenuItem>
<MenuItem value={"Antibiotics"}>Antibiotics</MenuItem>
<MenuItem value={"Antiseptics"}>Antiseptics</MenuItem>
<MenuItem value={"Mood stabilizers"}>Mood stabilizers</MenuItem>
<MenuItem value={"Tranquilizers"}>Tranquilizers</MenuItem>
<MenuItem value={"Stimulants"}>Stimulants</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={11} sm={6}>
<TextField
required
id="input-name"
label="Name"
variant="outlined"
fullWidth
onChange={(e) => setName(e.target.value)}
/>
</Grid>
<Grid item xs={11} sm={6}>
<DatePicker
label={"Expiration Date"}
value={expirationDate}
onChange={(newExpirationDate) => setExpirationDate(newExpirationDate)}
/>
</Grid>
<Grid
item
xs={11}
sm={6}
container
alignItems="flex-end"
justifyContent="right"
>
<Button variant="contained" color="primary" onClick={generateJson}>
Validate Information
</Button>
</Grid>
<Grid container justifyContent="center" margin={2}>
<Grid item xs={12} sm={6}>
{isSuccessfulValidationVisible && (
<Alert severity="success">Successfully validated</Alert>
)}
{isUnsuccessfulValidationVisible && (
<Alert severity="error">
It was not possible to validate the information
</Alert>
)}
</Grid>
</Grid>
</Grid>
);
};
export default PrescriptionForm;

View File

@@ -0,0 +1,98 @@
import React, { useState } from "react";
import { Alert, Button, Grid, TextField } from "@mui/material";
interface RelationshipFormProps {
onCustomCredentialChange?: (newJson: string) => void;
}
const RelationshipForm: React.FC<RelationshipFormProps> = ({
onCustomCredentialChange,
}) => {
const jsonFilePath = "credentials-json/relationship-credential.json";
const [namePartner1, setNamePartner1] = useState("");
const [namePartner2, setNamePartner2] = useState("");
const [isSuccessfulValidationVisible, setIsSuccessfulValidationVisible] =
useState(false);
const [isUnsuccessfulValidationVisible, setIsUnsuccessfulValidationVisible] =
useState(false);
const isInformationCorrect = () => {
if (namePartner1 === "" || namePartner2 === "") {
return false;
}
return true;
};
const generateJson = async () => {
if (!isInformationCorrect()) {
setIsSuccessfulValidationVisible(false);
setIsUnsuccessfulValidationVisible(true);
return;
}
const prescriptionCredential = await fetch(jsonFilePath).then((response) =>
response.json(),
);
prescriptionCredential.credentialSubject[0].name = namePartner1;
prescriptionCredential.credentialSubject[1].name = namePartner2;
onCustomCredentialChange?.(prescriptionCredential);
setIsSuccessfulValidationVisible(true);
setIsUnsuccessfulValidationVisible(false);
};
return (
<Grid container spacing={2} margin={2}>
<Grid item xs={11} sm={6}>
<TextField
required
id="input-name-partner-1"
label="Name"
variant="outlined"
fullWidth
onChange={(e) => setNamePartner1(e.target.value)}
/>
</Grid>
<Grid item xs={11} sm={6}>
<TextField
required
id="input-name-partner-2"
label="Name"
variant="outlined"
fullWidth
onChange={(e) => setNamePartner2(e.target.value)}
/>
</Grid>
<Grid
item
xs={11}
sm={12}
container
alignItems="flex-end"
justifyContent="right"
>
<Button variant="contained" color="primary" onClick={generateJson}>
Validate Information
</Button>
</Grid>
<Grid container justifyContent="center" margin={2}>
<Grid item xs={12} sm={6}>
{isSuccessfulValidationVisible && (
<Alert severity="success">Successfully validated</Alert>
)}
{isUnsuccessfulValidationVisible && (
<Alert severity="error">
It was not possible to validate the information
</Alert>
)}
</Grid>
</Grid>
</Grid>
);
};
export default RelationshipForm;

View File

@@ -0,0 +1,66 @@
import React, { useState } from "react";
import { Alert, Box, Button, Container, Grid, TextField } from "@mui/material";
import { resolveOobi } from "../../services/resolve-oobi";
interface GetInputButtonProps {}
const GetInputButton: React.FC<GetInputButtonProps> = () => {
const [showInput, setShowInput] = useState(false);
const [oobi, setOobi] = useState("");
const [isAtendeeOobiEmptyVisible, setIsAtendeeOobiEmptyVisible] =
useState(false);
const [submitSuccess, setSubmitSuccess] = useState(false);
const handleSubmit = async () => {
setSubmitSuccess(false);
if (oobi === "" || !oobi.includes("oobi")) {
setIsAtendeeOobiEmptyVisible(true);
return;
} else {
setIsAtendeeOobiEmptyVisible(false);
}
await resolveOobi(oobi);
setSubmitSuccess(true);
};
return (
<Container sx={{ py: 2 }}>
<Box sx={{ display: "flex", justifyContent: "right" }} mb={2}>
<Button
variant="contained"
color="primary"
onClick={() => setShowInput(true)}
>
Input OOBI
</Button>
</Box>
{showInput && (
<>
<Grid item xs={11} mb={2}>
<TextField
required
id="input-oobi"
label="Oobi link"
variant="outlined"
fullWidth
onChange={(e) => setOobi(e.target.value)}
/>
</Grid>
<Grid item xs={12} sx={{ display: "flex", justifyContent: "right" }}>
{isAtendeeOobiEmptyVisible && (
<Alert severity="error">Please, input valid OOBI link</Alert>
)}
{submitSuccess && (
<Alert severity="info">Resolve OOBI successfully</Alert>
)}
<Button variant="contained" color="primary" onClick={handleSubmit}>
Submit
</Button>
</Grid>
</>
)}
</Container>
);
};
export default GetInputButton;

View File

@@ -0,0 +1,109 @@
import React, { useEffect, useState } from "react";
import { Box, Button, Container } from "@mui/material";
import { Html5QrcodeScanner } from "html5-qrcode";
import "./qrscanner.css";
import { resolveOobi } from "../../services/resolve-oobi";
interface GetScannerButtonProps {}
enum ContentType {
SCANNER = "scanner",
RESOLVING = "resolving",
RESOLVED = "resolved",
}
const GetScannerButton: React.FC<GetScannerButtonProps> = () => {
const [showInput, setShowInput] = useState(false);
const [restartCamera, setRestartCamera] = useState(false);
const [contentType, setContentType] = useState<ContentType>(
ContentType.SCANNER,
);
useEffect(() => {
if (showInput) {
const scanner = new Html5QrcodeScanner(
"reader",
{
qrbox: {
width: 1024,
height: 1024,
},
fps: 5,
},
false,
);
const success = (result: string) => {
scanner.clear();
handleResolveOObi(result);
};
// eslint-disable-next-line @typescript-eslint/no-empty-function
const error = (_: any) => {};
scanner.render(success, error);
}
}, [restartCamera, showInput]);
const restartScanner = async () => {
setRestartCamera(!restartCamera);
setContentType(ContentType.SCANNER);
};
const renderContent = () => {
switch (contentType) {
case ContentType.SCANNER:
return {
component: <div id="reader" />,
title: "Scan your wallet QR Code",
};
case ContentType.RESOLVING:
return {
component: <></>,
title: "Resolving wallet OOBI",
};
case ContentType.RESOLVED:
return {
component: (
<button className="resolve-button" onClick={() => restartScanner()}>
Close
</button>
),
title: "The wallet OOBI was resolved successfully",
};
}
};
const handleResolveOObi = async (oobi: string) => {
if (!(oobi.length && oobi.includes("oobi"))) {
return restartScanner();
}
setContentType(ContentType.RESOLVING);
await resolveOobi(oobi);
setContentType(ContentType.RESOLVED);
};
const content = renderContent();
return (
<Container sx={{ py: 2 }}>
<Box sx={{ display: "flex", justifyContent: "right" }} mb={2}>
<Button
variant="contained"
color="primary"
onClick={() => setShowInput(true)}
>
Get Scanner
</Button>
</Box>
{showInput && (
<>
<div className="scannerPage">
<div>
<h3 className="">{content?.title}</h3>
{content?.component}
</div>
</div>
</>
)}
</Container>
);
};
export default GetScannerButton;

View File

@@ -0,0 +1,45 @@
body {
background-color: #787a83;
color: whitesmoke;
}
#reader {
margin: 0 auto;
}
.scannerPage {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.section,
.lockMessage {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
}
.spinner-button {
border: 2px solid floralwhite;
border-top: 2px solid whitesmoke;
border-radius: 50%;
width: 15px;
height: 15px;
animation: spin 1s linear infinite;
}
.resolve-button {
width: 210px;
margin-top: 10px;
padding: 10px 20px;
font-size: 16px;
background-color: #1976d2;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}