Skip to content
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

Refactor/settings pages shadcn #156

Merged
merged 10 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions deploy-web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion deploy-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@
"material-ui-popup-state": "^4.0.2",
"nanoid": "^3.3.4",
"next": "^14.1.0",
"next-dark-mode": "^3.0.0",
"next-nprogress-bar": "^2.1.2",
"next-pwa": "^5.6.0",
"next-qrcode": "^2.1.0",
Expand Down
118 changes: 118 additions & 0 deletions deploy-web/src/app/settings/CertificateDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"use client";
import { useState } from "react";
import { useCertificate } from "../../context/CertificateProvider";
import { ExportCertificate } from "./ExportCertificate";
import { useWallet } from "@src/context/WalletProvider";
import { BinMinusIn, Check, MoreHoriz, PlusCircle, Refresh, WarningTriangle } from "iconoir-react";
import { MdAutorenew, MdGetApp } from "react-icons/md";
import { FormPaper } from "@src/components/sdl/FormPaper";
import { CustomTooltip } from "@src/components/shared/CustomTooltip";
import { Button } from "@src/components/ui/button";
import Spinner from "@src/components/shared/Spinner";
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "@src/components/ui/dropdown-menu";
import { CustomDropdownLinkItem } from "@src/components/shared/CustomDropdownLinkItem";

export function CertificateDisplay() {
const [isExportingCert, setIsExportingCert] = useState(false);
const {
selectedCertificate,
isLocalCertMatching,
isLoadingCertificates,
loadValidCertificates,
localCert,
createCertificate,
isCreatingCert,
regenerateCertificate,
revokeCertificate
} = useCertificate();
const { address } = useWallet();

const onRegenerateCert = () => {
regenerateCertificate();
};

const onRevokeCert = () => {
if (selectedCertificate) revokeCertificate(selectedCertificate);
};

return (
<>
{address && (
<FormPaper className="mb-4" contentClassName="flex items-center">
<div className="flex items-center">
<p className="text-muted-foreground">
{selectedCertificate ? (
<span>
Current certificate:{" "}
<div className="inline-flex items-center text-xs font-bold text-primary">
{selectedCertificate.serial} <Check color="secondary" className="ml-2" />
</div>
</span>
) : (
"No local certificate."
)}
</p>

{selectedCertificate && !isLocalCertMatching && (
<CustomTooltip title="The local certificate doesn't match the one on the blockchain. You can revoke it and create a new one.">
<WarningTriangle className="ml-2 text-sm text-destructive" />
</CustomTooltip>
)}
</div>

{!selectedCertificate && (
<div className="ml-4">
<Button variant="default" color="secondary" size="sm" disabled={isCreatingCert || isLoadingCertificates} onClick={() => createCertificate()}>
{isCreatingCert ? <Spinner size="small" /> : "Create Certificate"}
</Button>
</div>
)}

<Button
onClick={() => loadValidCertificates(true)}
aria-label="refresh"
disabled={isLoadingCertificates}
size="icon"
variant="outline"
className="ml-4"
>
{isLoadingCertificates ? <Spinner size="small" /> : <Refresh />}
</Button>

{selectedCertificate && (
<div className="ml-2">
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button size="icon" variant="ghost">
<MoreHoriz />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{/** If local, regenerate else create */}
{selectedCertificate.parsed === localCert?.certPem ? (
<CustomDropdownLinkItem onClick={() => onRegenerateCert()} icon={<MdAutorenew />}>
Regenerate
</CustomDropdownLinkItem>
) : (
<CustomDropdownLinkItem onClick={() => createCertificate()} icon={<PlusCircle />}>
Create
</CustomDropdownLinkItem>
)}

<CustomDropdownLinkItem onClick={() => onRevokeCert()} icon={<BinMinusIn />}>
Revoke
</CustomDropdownLinkItem>
<CustomDropdownLinkItem onClick={() => setIsExportingCert(true)} icon={<MdGetApp />}>
Export
</CustomDropdownLinkItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
)}
</FormPaper>
)}

{isExportingCert && <ExportCertificate isOpen={isExportingCert} onClose={() => setIsExportingCert(false)} />}
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useCertificate } from "../../context/CertificateProvider";
import CheckIcon from "@mui/icons-material/Check";
"use client";
import { FormattedDate } from "react-intl";
import { Box, Button, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material";
import { CertificateDisplay } from "./CertificateDisplay";
import { CustomTableHeader, CustomTableRow } from "../shared/CustomTable";
import { useWallet } from "@src/context/WalletProvider";
import { ConnectWallet } from "../shared/ConnectWallet";
import { Check } from "iconoir-react";
import { useCertificate } from "@src/context/CertificateProvider";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@src/components/ui/table";
import { ConnectWallet } from "@src/components/shared/ConnectWallet";
import { Button } from "@src/components/ui/button";

type Props = {};

Expand All @@ -14,36 +15,36 @@ export const CertificateList: React.FunctionComponent<Props> = ({}) => {
const { address } = useWallet();

return (
<Box>
<div>
<CertificateDisplay />

{address ? (
<TableContainer>
<Table size="small">
<CustomTableHeader>
<div>
<Table>
<TableHeader>
<TableRow>
<TableCell align="center">Selected</TableCell>
<TableCell align="center">Local cert</TableCell>
<TableCell align="center">Issued on</TableCell>
<TableCell align="center">Expires</TableCell>
<TableCell align="center">Serial</TableCell>
<TableCell align="center">
<TableHead className="text-center">Selected</TableHead>
<TableHead className="text-center">Local cert</TableHead>
<TableHead className="text-center">Issued on</TableHead>
<TableHead className="text-center">Expires</TableHead>
<TableHead className="text-center">Serial</TableHead>
<TableHead className="text-center">
{validCertificates?.length > 0 && (
<Button onClick={() => revokeAllCertificates()} color="secondary" size="small" variant="outlined">
<Button onClick={() => revokeAllCertificates()} color="secondary" size="sm" variant="outline">
Revoke All
</Button>
)}
</TableCell>
</TableHead>
</TableRow>
</CustomTableHeader>
</TableHeader>

<TableBody>
{validCertificates.map(cert => {
const isCurrentCert = cert.serial === selectedCertificate?.serial;
return (
<CustomTableRow key={cert.serial}>
<TableCell align="center">{isCurrentCert && <CheckIcon color="secondary" />}</TableCell>
<TableCell align="center">{cert.parsed === localCert?.certPem && <CheckIcon color="secondary" />}</TableCell>
<TableRow key={cert.serial}>
<TableCell align="center">{isCurrentCert && <Check className="text-primary" />}</TableCell>
<TableCell align="center">{cert.parsed === localCert?.certPem && <Check className="text-primary" />}</TableCell>

<TableCell align="center">
<FormattedDate value={cert.pem.issuedOn} year="numeric" month="2-digit" day="2-digit" hour="2-digit" minute="2-digit" />
Expand All @@ -52,34 +53,33 @@ export const CertificateList: React.FunctionComponent<Props> = ({}) => {
<FormattedDate value={cert.pem.expiresOn} year="numeric" month="2-digit" day="2-digit" hour="2-digit" minute="2-digit" />
</TableCell>
<TableCell align="center">
<Typography variant="caption">{cert.serial}</Typography>
<p className="text-sm text-muted-foreground">{cert.serial}</p>
</TableCell>
<TableCell align="center">
<Button
onClick={() => revokeCertificate(cert)}
color={isCurrentCert ? "secondary" : "inherit"}
size="small"
variant={isCurrentCert ? "contained" : "text"}
size="sm"
variant={isCurrentCert ? "default" : "text"}
>
Revoke
</Button>
</TableCell>
</CustomTableRow>
</TableRow>
);
})}
</TableBody>
</Table>

{!isLoadingCertificates && validCertificates.length === 0 && (
<Box sx={{ textAlign: "center", width: "100%", marginTop: "1rem" }}>
<Typography variant="body2">No certificates.</Typography>
</Box>
<div className="mt-4 w-full text-center">
<p>No certificates.</p>
</div>
)}
</TableContainer>
</div>
) : (
<ConnectWallet text="Connect your wallet to create a certficate." />
)}
</Box>
</div>
);
};

43 changes: 43 additions & 0 deletions deploy-web/src/app/settings/ColorModeSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { useEffect, useState } from "react";
import { useTheme } from "next-themes";
import { FormItem } from "@src/components/ui/form";
import { Label } from "@src/components/ui/label";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui/select";

type Props = {};

export const ColorModeSelect: React.FunctionComponent<Props> = () => {
const { setTheme, theme } = useTheme();
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
}, []);

if (!mounted) {
return null;
}

const onThemeClick = (theme: string) => {
setTheme(theme);
document.cookie = `theme=${theme}; path=/`;
};

return (
<FormItem>
<Label>Theme</Label>
<Select value={theme} onValueChange={onThemeClick}>
<SelectTrigger>
<SelectValue placeholder="Select theme" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="system">System</SelectItem>
<SelectItem value="dark">Dark 🌑</SelectItem>
<SelectItem value="light">Light ☀️</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FormItem>
);
};
Loading
Loading