proper ts types
Diff
chartered-frontend/.parcelrc | 3 +++
chartered-frontend/package.json | 2 ++
chartered-frontend/tsconfig.json | 7 ++++---
chartered-frontend/yarn.lock | 40 ++++++++++++++++++++++++++++++++++++++++
chartered-frontend/src/index.tsx | 8 ++++----
chartered-frontend/src/overscrollColourFixer.ts | 2 +-
chartered-frontend/src/useAuth.tsx | 14 +++++++-------
chartered-frontend/src/util.tsx | 11 +++++++----
chartered-frontend/src/pages/Dashboard.tsx | 15 ++++++++++++++-
chartered-frontend/src/pages/Login.tsx | 23 ++++++++++++-----------
chartered-frontend/src/pages/Search.tsx | 23 +++++++++++++++++++----
chartered-frontend/src/pages/User.tsx | 13 ++++++++++---
chartered-frontend/src/sections/Nav.tsx | 16 ++++++++--------
chartered-frontend/src/pages/crate/CrateView.tsx | 42 +++++++++++++++++++++++++++++++-----------
chartered-frontend/src/pages/crate/Members.tsx | 56 ++++++++++++++++++++++++++++++++++++--------------------
chartered-frontend/src/pages/crate/OrganisationView.tsx | 49 +++++++++++++++++++++++++++++--------------------
chartered-frontend/src/pages/organisations/CreateOrganisation.tsx | 14 +++++++-------
chartered-frontend/src/pages/organisations/ListOrganisations.tsx | 6 +++++-
chartered-frontend/src/pages/ssh-keys/AddSshKeys.tsx | 16 +++++++++-------
chartered-frontend/src/pages/ssh-keys/ListSshKeys.tsx | 22 +++++++++++++++-------
20 files changed, 252 insertions(+), 130 deletions(-)
@@ -1,0 +1,3 @@
{
"extends": "@parcel/config-default"
}
@@ -14,6 +14,7 @@
"devDependencies": {
"@parcel/reporter-bundle-analyzer": "^2.0.0",
"@parcel/transformer-sass": "^2.0.0",
"@parcel/validator-typescript": "^2.0.0",
"@types/react": "^17.0.20",
"@types/react-dom": "^17.0.9",
"@types/react-syntax-highlighter": "^13.5.2",
@@ -29,6 +30,7 @@
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.1.15",
"@types/node": "^16.10.1",
"@types/react-router-dom": "^5.3.1",
"bootstrap": "^5.1.1",
"react": "^17.0.2",
"react-bootstrap": "^2.0.0-beta.6",
@@ -1,7 +1,8 @@
{
"compilerOptions": {
"jsx": "react",
"allowSyntheticDefaultImports": true
"jsx": "react-jsx",
"allowSyntheticDefaultImports": true,
"strict": true
},
"lib": ["ES2015"]
"lib": ["ES2021"]
}
@@ -909,6 +909,13 @@
posthtml-render "^3.0.0"
semver "^5.4.1"
"@parcel/ts-utils@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@parcel/ts-utils/-/ts-utils-2.0.0.tgz#89954d341a8942cb6ddcda217f5f7b38914bef12"
integrity sha512-+jMwsBu5+gyp8iw+h6CN1MR8hYDALdO1ccphHPD2WMzQHpvvp+hhZ/gUfN70ioPz9DOG/d6BMlfm/HVaZLePsA==
dependencies:
nullthrows "^1.1.1"
"@parcel/types@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.0.0.tgz#31f6eb7c24b2d9ae752ce49adaf01469a9e9067a"
@@ -948,6 +955,17 @@
nullthrows "^1.1.1"
open "^7.0.3"
terminal-link "^2.1.1"
"@parcel/validator-typescript@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@parcel/validator-typescript/-/validator-typescript-2.0.0.tgz#2894cdd06db9e156a3b6e67d4f8e73a94c221a5f"
integrity sha512-qv3SuwuKwtLU/EC9NzqYoJAkCicQp/t4vdM1dms19Cre1od8ZhSHDjEi+oDvUnwPZ1yZrLLBY/zWxQh/yUsP+A==
dependencies:
"@parcel/diagnostic" "^2.0.0"
"@parcel/plugin" "^2.0.0"
"@parcel/ts-utils" "^2.0.0"
"@parcel/types" "^2.0.0"
"@parcel/utils" "^2.0.0"
"@parcel/watcher@^2.0.0":
version "2.0.0"
@@ -1039,6 +1057,11 @@
integrity sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==
dependencies:
"@types/unist" "*"
"@types/history@*":
version "4.7.9"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.9.tgz#1cfb6d60ef3822c589f18e70f8b12f9a28ce8724"
integrity sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==
"@types/http-proxy@^1.17.5":
version "1.17.7"
@@ -1088,7 +1111,24 @@
version "17.0.9"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add"
integrity sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==
dependencies:
"@types/react" "*"
"@types/react-router-dom@^5.3.1":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.1.tgz#76700ccce6529413ec723024b71f01fc77a4a980"
integrity sha512-UvyRy73318QI83haXlaMwmklHHzV9hjl3u71MmM6wYNu0hOVk9NLTa0vGukf8zXUqnwz4O06ig876YSPpeK28A==
dependencies:
"@types/history" "*"
"@types/react" "*"
"@types/react-router" "*"
"@types/react-router@*":
version "5.1.17"
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.17.tgz#087091006213b11042f39570e5cd414863693968"
integrity sha512-RNSXOyb3VyRs/EOGmjBhhGKTbnN6fHWvy5FNLzWfOWOGjgVUKqJZXfpKzLmgoU8h6Hj8mpALj/mbXQASOb92wQ==
dependencies:
"@types/history" "*"
"@types/react" "*"
"@types/react-syntax-highlighter@^13.5.2":
@@ -122,7 +122,7 @@
return (
<Route
{...rest}
render={(props) => {
render={(props: any) => {
if (!unauthedOnly || !auth || !auth?.getAuthKey()) {
return <Component {...props} />;
@@ -137,7 +137,7 @@
);
}
}}
></Route>
/>
);
}
@@ -158,7 +158,7 @@
const isAuthenticated = auth?.getAuthKey();
useEffect(() => {
if (!isAuthenticated) {
auth.logout();
auth?.logout();
}
}, [isAuthenticated]);
@@ -177,6 +177,6 @@
);
}
}}
></Route>
/>
);
}
@@ -1,10 +1,10 @@
window.addEventListener("load", () => {
let ticking;
let ticking = false;
const backgroundFix = () => {
if (!ticking) {
@@ -44,8 +44,8 @@
);
let json = await result.json();
auth.handleLoginResponse(json);
} catch (err) {
auth?.handleLoginResponse(json);
} catch (err: any) {
setResult(
<Redirect
to={{
@@ -65,7 +65,7 @@
};
function useProvideAuth(): AuthContext {
const [auth, setAuth] = useState(() => {
const [auth, setAuth] = useState<any[] | null>(() => {
let authStorage = getAuthStorage();
return [
authStorage.userUuid,
@@ -141,7 +141,7 @@
const getAuthKey = () => {
if (auth?.[2] > new Date()) {
return auth[1];
return auth?.[1];
} else if (auth) {
return null;
}
@@ -149,7 +149,7 @@
const getUserUuid = () => {
if (auth?.[2] > new Date()) {
return auth[0];
return auth?.[0];
} else if (auth) {
return null;
}
@@ -157,7 +157,7 @@
const getPictureUrl = () => {
if (auth?.[2] > new Date()) {
return auth[3];
return auth?.[3];
} else if (auth) {
return null;
}
@@ -176,7 +176,7 @@
function getAuthStorage() {
const saved = localStorage.getItem("charteredAuthentication");
const initial = JSON.parse(saved);
const initial = saved ? JSON.parse(saved) : {};
return {
userUuid: initial?.userUuid || null,
authKey: initial?.authKey || null,
@@ -18,7 +18,7 @@
export function useAuthenticatedRequest<S>(
{ auth, endpoint }: { auth: AuthContext; endpoint: string },
reloadOn = []
reloadOn: any[] = []
): { response: S | null; error: string | null } {
const [error, setError] = useState(null);
const [response, setResponse] = useState(null);
@@ -40,7 +40,7 @@
} else {
setResponse(jsonRes);
}
} catch (e) {
} catch (e: any) {
setError(e.message);
}
}, reloadOn);
@@ -65,7 +65,7 @@
} else {
setResponse(jsonRes);
}
} catch (e) {
} catch (e: any) {
setError(e.message);
}
}, reloadOn);
@@ -79,7 +79,7 @@
width,
className,
}: {
src: string;
src?: string | null;
height: string;
width: string;
className?: string;
@@ -113,11 +113,13 @@
export function RoundedPicture({
src,
alt,
height,
width,
className,
}: {
src: string;
alt?: string;
height: string;
width: string;
className?: string;
@@ -143,6 +145,7 @@
height,
width,
}}
alt={alt}
src={src}
onLoad={() => setImageLoaded(true)}
className="rounded-circle"
@@ -1,6 +1,6 @@
import { PropsWithChildren } from "react";
import { Link } from "react-router-dom";
import {Link, Redirect} from "react-router-dom";
import { useAuth } from "../useAuth";
import Nav from "../sections/Nav";
import { Calendar3, ChevronRight, Download } from "react-bootstrap-icons";
@@ -41,6 +41,10 @@
export default function Dashboard() {
const auth = useAuth();
if (!auth) {
return <Redirect to="/login" />;
}
const { response: recentlyCreated, error: recentlyCreatedError } =
useAuthenticatedRequest<RecentlyCreatedResponse>({
auth,
@@ -83,6 +87,9 @@
<div className="row">
<div className="col-12 col-md-4">
<h4>Newly Created</h4>
{recentlyCreatedError ? <div className="alert alert-danger" role="alert">
{recentlyCreatedError}
</div> : <></>}
{(recentlyCreated?.crates || []).map((v) => (
<CrateCard
key={v.name}
@@ -107,6 +114,9 @@
<div className="col-12 col-md-4">
<h4>Recently Updated</h4>
{recentlyUpdatedError ? <div className="alert alert-danger" role="alert">
{recentlyUpdatedError}
</div> : <></>}
{(recentlyUpdated?.versions || []).map((v) => (
<CrateCard
key={v.name}
@@ -120,6 +130,9 @@
<div className="col-12 col-md-4">
<h4>Most Downloaded</h4>
{mostDownloadedError ? <div className="alert alert-danger" role="alert">
{mostDownloadedError}
</div> : <></>}
{(mostDownloaded?.crates || []).map((v) => (
<CrateCard
key={v.name}
@@ -1,10 +1,9 @@
import { useState, useEffect, useRef } from "react";
import {useState, useEffect, useRef, SyntheticEvent, MouseEventHandler} from "react";
import { useLocation } from "react-router-dom";
import { useAuth } from "../useAuth";
import { useUnauthenticatedRequest } from "../util";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconName } from "@fortawesome/fontawesome-svg-core";
import {
faGithub,
faGitlab,
@@ -38,18 +37,18 @@
}
isMountedRef.current = true;
return () => (isMountedRef.current = false);
return () => { isMountedRef.current = false };
});
const handleSubmit = async (evt) => {
const handleSubmit = async (evt: SyntheticEvent) => {
evt.preventDefault();
setError("");
setLoading("password");
try {
await auth.login(username, password);
} catch (e) {
await auth?.login(username, password);
} catch (e: any) {
setError(e.message);
} finally {
if (isMountedRef.current) {
@@ -58,13 +57,13 @@
}
};
const handleOAuthLogin = async (provider) => {
const handleOAuthLogin = async (provider: string) => {
setError("");
setLoading(provider);
try {
await auth.oauthLogin(provider);
} catch (e) {
await auth?.oauthLogin(provider);
} catch (e: any) {
setError(e.message);
}
};
@@ -180,7 +179,7 @@
};
function getIconForProvider(provider: string): [IconDefinition, string] {
return BRANDS[provider] || BRANDS["default"];
return BRANDS[provider] || BRANDS.default;
}
function ButtonOrSpinner({
@@ -200,7 +199,7 @@
text: string;
icon?: IconDefinition;
background?: string;
onClick: (evt) => any;
onClick: MouseEventHandler<HTMLButtonElement>;
}) {
if (showSpinner) {
return (
@@ -227,4 +226,6 @@
</button>
);
}
return <></>;
}
@@ -1,19 +1,16 @@
import { useState, useEffect } from "react";
import { Link, useHistory, useLocation } from "react-router-dom";
import { Link, useLocation } from "react-router-dom";
import Nav from "../sections/Nav";
import { useAuth } from "../useAuth";
import {
authenticatedEndpoint,
ProfilePicture,
useAuthenticatedRequest,
} from "../util";
import { BoxSeam, Plus } from "react-bootstrap-icons";
import { BoxSeam } from "react-bootstrap-icons";
import { LoadingSpinner } from "./Loading";
export default function Search() {
const auth = useAuth();
const location = useLocation();
const query =
@@ -47,6 +44,10 @@
function UsersResults({ query }: { query: string }) {
const auth = useAuth();
if (!auth) {
return <></>;
}
const { response: results, error } =
useAuthenticatedRequest<UsersSearchResponse>(
@@ -56,6 +57,10 @@
},
[query]
);
if (error) {
return <div className="alert alert-danger">{error}</div>;
}
if (!results) {
return (
@@ -118,6 +123,10 @@
className?: string;
}) {
const auth = useAuth();
if (!auth) {
return <></>;
}
const { response: results, error } =
useAuthenticatedRequest<CrateSearchResponse>(
@@ -127,6 +136,10 @@
},
[query]
);
if (error) {
return <div className="alert alert-danger">{error}</div>
}
if (!results) {
return (
@@ -1,8 +1,7 @@
import { useParams } from "react-router-dom";
import {Redirect, useParams} from "react-router-dom";
import { useAuth } from "../useAuth";
import {
ProfilePicture,
RoundedPicture,
useAuthenticatedRequest,
} from "../util";
import Nav from "../sections/Nav";
@@ -20,9 +19,17 @@
picture_url?: string;
}
interface UrlParams {
uuid: string;
}
export default function User() {
const auth = useAuth();
const { uuid } = useParams();
const { uuid } = useParams<UrlParams>();
if (!auth) {
return <Redirect to="/login" />;
}
const { response: user, error } = useAuthenticatedRequest<Response>({
auth,
@@ -1,10 +1,10 @@
import { useState } from "react";
import {SyntheticEvent, useState} from "react";
import { useHistory, useLocation } from "react-router-dom";
import { NavLink, Link } from "react-router-dom";
import { BoxArrowRight, CaretDownFill, Search } from "react-bootstrap-icons";
import { BoxArrowRight, Search } from "react-bootstrap-icons";
import { useAuth } from "../useAuth";
import { Dropdown, Navbar, NavDropdown, NavItem } from "react-bootstrap";
import { Dropdown, Navbar } from "react-bootstrap";
import { ProfilePicture } from "../util";
export default function Nav() {
@@ -12,9 +12,9 @@
const history = useHistory();
const location = useLocation();
const logout = async (e) => {
const logout = async (e: SyntheticEvent) => {
e.preventDefault();
await auth.logout();
await auth?.logout();
};
const [search, setSearch] = useState(
@@ -22,7 +22,7 @@
? new URLSearchParams(location.search).get("q") || ""
: ""
);
const submitSearchForm = (e) => {
const submitSearchForm = (e: SyntheticEvent) => {
e.preventDefault();
if (search != "") {
@@ -85,7 +85,7 @@
className="d-inline-flex align-items-center"
>
<ProfilePicture
src={auth.getPictureUrl()}
src={auth?.getPictureUrl()}
height="2rem"
width="2rem"
/>
@@ -95,7 +95,7 @@
align={{ md: "end" }}
style={{ marginTop: "5px" }}
>
<Dropdown.Item as={Link} to={`/users/${auth.getUserUuid()}`}>
<Dropdown.Item as={Link} to={`/users/${auth?.getUserUuid()}`}>
Your profile
</Dropdown.Item>
@@ -53,7 +53,7 @@
export interface CrateInfoVersion {
vers: string;
deps: CrateInfoVersionDependency[];
features: any[];
features: { [key: string]: any };
size: number;
uploader: CrateInfoVersionUploader;
created_at: string;
@@ -65,11 +65,20 @@
registry?: string;
}
interface UrlParameters {
organisation: string;
crate: string;
subview: Tab | undefined;
}
export default function SingleCrate() {
const auth = useAuth();
const { organisation, crate, subview } = useParams();
const currentTab: Tab | undefined = subview;
const { organisation, crate, subview: currentTab } = useParams<UrlParameters>();
if (!auth) {
return <Redirect to="/login" />;
}
if (!currentTab) {
return <Redirect to={`/crates/${organisation}/${crate}/readme`} />;
}
@@ -274,7 +283,7 @@
}) {
let link = <>{dep.name}</>;
if (dep.registry === null) {
if (dep.registry === null || dep.registry === undefined) {
link = (
<a target="_blank" href={`/crates/${organisation}/${dep.name}`}>
{link}
@@ -299,16 +308,21 @@
{link} = "<strong>{dep.req}</strong>"
</li>
);
}
interface MembersProps {
organisation: string;
crate: string;
}
function Members({
organisation,
crate,
}: {
organisation: string;
crate: string;
}) {
}: MembersProps) {
const auth = useAuth();
if (!auth) { return <></>; }
const [reload, setReload] = useState(0);
const { response, error } = useAuthenticatedRequest<CratesMembersResponse>(
{
@@ -331,9 +345,9 @@
}
const saveMemberPermissions = async (
prospectiveMember,
uuid,
selectedPermissions
prospectiveMember: boolean,
uuid: string,
selectedPermissions: string[],
) => {
let res = await fetch(
authenticatedEndpoint(auth, `crates/${organisation}/${crate}/members`),
@@ -358,7 +372,7 @@
setReload(reload + 1);
};
const deleteMember = async (uuid) => {
const deleteMember = async (uuid: string) => {
let res = await fetch(
authenticatedEndpoint(auth, `crates/${organisation}/${crate}/members`),
{
@@ -392,8 +406,8 @@
}
function Versions(props: { crate: CrateInfo }) {
const humanFileSize = (size) => {
var i = Math.floor(Math.log(size) / Math.log(1024));
const humanFileSize = (size: number) => {
const i = Math.floor(Math.log(size) / Math.log(1024));
return (
Number((size / Math.pow(1024, i)).toFixed(2)) +
" " +
@@ -1,26 +1,22 @@
import { useState, useEffect, useRef } from "react";
import { Link } from "react-router-dom";
import {
PersonPlus,
Trash,
CheckLg,
Save,
PlusLg,
} from "react-bootstrap-icons";
import {
authenticatedEndpoint,
ProfilePicture,
RoundedPicture,
useAuthenticatedRequest,
} from "../../util";
import { useAuth } from "../../useAuth";
import { Button, Modal } from "react-bootstrap";
import { AsyncTypeahead } from "react-bootstrap-typeahead";
import ReactPlaceholder from "react-placeholder";
interface Member {
uuid: string;
permissions?: string[];
permissions: string[];
display_name: string;
picture_url?: string;
}
@@ -40,7 +36,7 @@
) => Promise<any>;
deleteMember: (uuid: string) => Promise<any>;
}) {
const [prospectiveMembers, setProspectiveMembers] = useState([]);
const [prospectiveMembers, setProspectiveMembers] = useState<Member[]>([]);
useEffect(() => {
setProspectiveMembers(
@@ -122,12 +118,12 @@
deleteMember: (uuid: string) => Promise<any>;
}) {
const [selectedPermissions, setSelectedPermissions] = useState(
member.permissions
member.permissions || []
);
const auth = useAuth();
const [deleting, setDeleting] = useState(false);
const [saving, setSaving] = useState(false);
const [error, setError] = useState(null);
const [error, setError] = useState(undefined);
let itemAction = <></>;
@@ -151,7 +147,7 @@
setSaving(true);
try {
deleteMember(member.uuid);
await deleteMember(member.uuid);
} catch (e) {
setError(error);
} finally {
@@ -203,13 +199,13 @@
return (
<>
<DeleteModal
show={deleting === true}
show={deleting}
onCancel={() => setDeleting(false)}
onConfirm={() => doDelete()}
username={member.display_name}
/>
<ErrorModal error={error} onClose={() => setError(null)} />
<ErrorModal error={error} onClose={() => setError(undefined)} />
<tr>
<td className="align-middle fit">
@@ -222,7 +218,7 @@
{member.display_name}
</Link>
</strong>
{auth.getUserUuid() === member.uuid ? (
{auth?.getUserUuid() === member.uuid ? (
<>
<br />
<em>(that's you!)</em>
@@ -253,20 +249,32 @@
);
}
interface MemberListInserterProps {
existingMembers: Member[];
onInsert: (username: string, user_uuid: string, picture_url: string | null) => any;
}
interface SearchOption {
user_uuid: string;
display_name: string;
picture_url: string | null;
}
function MemberListInserter({
onInsert,
existingMembers,
}: {
existingMembers: Member[];
onInsert: (username, picture_url, user_uuid) => any;
}) {
}: MemberListInserterProps) {
const auth = useAuth();
const searchRef = useRef(null);
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState([]);
const [error, setError] = useState("");
const handleSearch = async (query) => {
if (!auth) {
return <></>;
}
const handleSearch = async (query: string) => {
setLoading(true);
setError("");
@@ -284,20 +292,20 @@
}
setOptions(json.users || []);
} catch (e) {
} catch (e: any) {
setError(e.message);
} finally {
setLoading(false);
}
};
const handleChange = (selected) => {
const handleChange = (selected: SearchOption[]) => {
onInsert(
selected[0].display_name,
selected[0].picture_url,
selected[0].user_uuid
);
searchRef.current.clear();
searchRef.current?.clear();
};
return (
@@ -320,7 +328,7 @@
<AsyncTypeahead
id="search-new-user"
onSearch={handleSearch}
filterBy={(option) => {
filterBy={(option: SearchOption) => {
for (const existing of existingMembers) {
if (option.user_uuid === existing.uuid) {
return false;
@@ -335,7 +343,7 @@
placeholder="Search for User"
onChange={handleChange}
ref={searchRef}
renderMenuItemChildren={(option, props) => (
renderMenuItemChildren={(option: SearchOption) => (
<>
<ProfilePicture
src={option.picture_url}
@@ -351,7 +359,7 @@
<div className="text-danger">{error}</div>
</td>
<td className="align-middle"></td>
<td className="align-middle" />
<td className="align-middle">
<button type="button" className="btn text-dark pe-none">
@@ -371,7 +379,7 @@
possiblePermissions: string[];
selectedPermissions: string[];
userUuid: string;
onChange: (permissions) => any;
onChange: (permissions: string[]) => any;
}) {
return (
<div className="grid" style={{ "--bs-gap": 0 }}>
@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { Link, useParams } from "react-router-dom";
import { useState } from "react";
import {Link, Redirect, useParams} from "react-router-dom";
import Nav from "../../sections/Nav";
import { useAuth } from "../../useAuth";
@@ -9,18 +9,8 @@
RoundedPicture,
} from "../../util";
import { BoxSeam, Plus, Trash } from "react-bootstrap-icons";
import {
Button,
Dropdown,
Modal,
NavLink,
OverlayTrigger,
Tooltip,
} from "react-bootstrap";
import HumanTime from "react-human-time";
import { BoxSeam } from "react-bootstrap-icons";
import ErrorPage from "../ErrorPage";
import Loading from "../Loading";
import Members from "./Members";
import ReactPlaceholder from "react-placeholder";
@@ -38,8 +28,12 @@
interface Member {
uuid: string;
username: string;
permissions?: string[];
display_name: string;
permissions: string[];
}
interface UrlParams {
organisation: string;
}
export default function ShowOrganisation() {
@@ -48,9 +42,13 @@
members: "Members",
};
const { organisation } = useParams();
const { organisation } = useParams<UrlParams>();
const auth = useAuth();
const [activeTab, setActiveTab] = useState(Object.keys(tabs)[0]);
if (!auth) {
return <Redirect to="/login" />;
}
const [reload, setReload] = useState(0);
const { response: organisationDetails, error } =
@@ -67,7 +65,6 @@
}
const ready = !!organisationDetails;
const [imageLoaded, setImageLoaded] = useState(false);
return (
<div className="text-white">
@@ -216,6 +213,13 @@
</table>
</div>
);
}
interface ListMemberParams {
organisation: string;
members: Member[];
possiblePermissions?: string[];
reload: () => any;
}
function ListMembers({
@@ -223,13 +227,12 @@
members,
possiblePermissions,
reload,
}: {
organisation: string;
members: Member[];
possiblePermissions?: string[];
reload: () => any;
}) {
}: ListMemberParams) {
const auth = useAuth();
if (!auth) {
return <></>;
}
const saveMemberPermissions = async (
prospectiveMember: boolean,
@@ -1,26 +1,26 @@
import { useState, useEffect } from "react";
import {SyntheticEvent, useState} from "react";
import { Link, useHistory } from "react-router-dom";
import Nav from "../../sections/Nav";
import { useAuth } from "../../useAuth";
import { authenticatedEndpoint } from "../../util";
import { Plus } from "react-bootstrap-icons";
export default function CreateOrganisation() {
const auth = useAuth();
const router = useHistory();
if (!auth) {
return <></>;
}
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [publicOrg, setPublicOrg] = useState(false);
console.log(publicOrg);
const createOrganisation = async (evt) => {
const createOrganisation = async (evt: SyntheticEvent) => {
evt.preventDefault();
setError("");
@@ -43,7 +43,7 @@
setName("");
setDescription("");
router.push(`/crates/${name}`);
} catch (e) {
} catch (e: any) {
setError(e.message);
} finally {
setLoading(false);
@@ -1,5 +1,5 @@
import { Plus } from "react-bootstrap-icons";
import { Link } from "react-router-dom";
import {Link, Redirect} from "react-router-dom";
import Nav from "../../sections/Nav";
import { useAuth } from "../../useAuth";
@@ -18,6 +18,10 @@
export default function ListOrganisations() {
const auth = useAuth();
if (!auth) {
return <Redirect to="/login" />;
}
const { response: list, error } = useAuthenticatedRequest<Response>({
auth,
@@ -1,21 +1,23 @@
import { useState, useEffect } from "react";
import {SyntheticEvent, useState} from "react";
import { Link, useHistory } from "react-router-dom";
import Nav from "../../sections/Nav";
import { useAuth } from "../../useAuth";
import { authenticatedEndpoint } from "../../util";
import { Plus } from "react-bootstrap-icons";
export default function ListSshKeys() {
const auth = useAuth();
const router = useHistory();
if (!auth) {
return <></>;
}
const [sshKey, setSshKey] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const submitSshKey = async (evt) => {
const submitSshKey = async (evt: SyntheticEvent) => {
evt.preventDefault();
setError("");
@@ -37,7 +39,7 @@
setSshKey("");
router.push("/ssh-keys/list");
} catch (e) {
} catch (e: any) {
setError(e.message);
} finally {
setLoading(false);
@@ -63,7 +65,7 @@
className="btn-close"
aria-label="Close"
onClick={() => setError("")}
></button>
/>
</div>
<div className="card border-0 shadow-sm text-black">
@@ -77,7 +79,7 @@
value={sshKey}
/>
<div className="clearfix"></div>
<div className="clearfix" />
<button
type="submit"
@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { useState } from "react";
import {Link, Redirect} from "react-router-dom";
import Nav from "../../sections/Nav";
import { useAuth } from "../../useAuth";
@@ -9,7 +9,7 @@
import { Button, Modal, OverlayTrigger, Tooltip } from "react-bootstrap";
import HumanTime from "react-human-time";
import ErrorPage from "../ErrorPage";
import Loading, { LoadingSpinner } from "../Loading";
import { LoadingSpinner } from "../Loading";
interface SshKeysResponse {
keys: SshKeysResponseKey[];
@@ -26,8 +26,12 @@
export default function ListSshKeys() {
const auth = useAuth();
if (!auth) {
return <Redirect to="/login" />;
}
const [error, setError] = useState("");
const [deleting, setDeleting] = useState(null);
const [deleting, setDeleting] = useState<SshKeysResponseKey | null>(null);
const [reloadSshKeys, setReloadSshKeys] = useState(0);
const { response: sshKeys, error: loadError } =
@@ -46,6 +50,10 @@
const deleteKey = async () => {
setError("");
if (!deleting) {
return;
}
try {
let res = await fetch(
authenticatedEndpoint(auth, `ssh-key/${deleting.uuid}`),
@@ -63,7 +71,7 @@
}
setReloadSshKeys(reloadSshKeys + 1);
} catch (e) {
} catch (e: any) {
setError(e.message);
} finally {
setDeleting(null);
@@ -92,7 +100,7 @@
className="btn-close"
aria-label="Close"
onClick={() => setError("")}
></button>
/>
</div>
<div className="card border-0 shadow-sm text-black">
@@ -211,7 +219,7 @@
show: boolean;
onCancel: () => void;
onConfirm: () => void;
fingerprint: string;
fingerprint?: string;
}) {
return (
<Modal