Added front-end ReactApp, with basic CRUD functionality for Bookmark entries

This commit is contained in:
2023-01-28 20:48:32 -06:00
parent 0413abf587
commit 1f3adf0932
61 changed files with 19643 additions and 21 deletions

View File

@ -0,0 +1,34 @@
import { Auth0Provider } from "@auth0/auth0-react";
import React from "react";
import { useNavigate } from "react-router-dom";
export const Auth0ProviderWithNavigate = ({ children }) => {
const navigate = useNavigate();
const domain = process.env.REACT_APP_AUTH0_DOMAIN;
const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID;
const redirectUri = process.env.REACT_APP_AUTH0_CALLBACK_URL;
const audience = process.env.REACT_APP_AUTH0_AUDIENCE;
const onRedirectCallback = (appState) => {
navigate(appState?.returnTo || window.location.pathname);
};
if (!(domain && clientId && redirectUri)) {
return null;
}
return (
<Auth0Provider
domain={domain}
clientId={clientId}
authorizationParams={{
redirect_uri: redirectUri,
audience: audience
}}
onRedirectCallback={onRedirectCallback}
>
{children}
</Auth0Provider>
);
};

View File

@ -0,0 +1,34 @@
import React from "react";
import { Row, Col, Form, Button } from "../external";
import DateTimeHelper from "../../utils/dateTimeHelper";
import "../../styles/component/bookmark.css";
import { useNavigate } from "react-router-dom";
export function Bookmark(props) {
const navigate = useNavigate();
return <div className="mb-3">
<Row>
<Col xs={1} className="d-flex justify-content-center align-items-center">
<Form.Check onChange={(e) => {props.onBookmarkSelected(e.target.checked)}} checked={props.bookmark.isSelected} />
</Col>
<Col xs={11}>
<a href="#" style={{textDecoration: "none"}}>{props.bookmark.title}</a>
<div className="font-weight-normal">{props.bookmark.description.substring(0, 100)}</div>
<div>
{props.bookmark.tags.map((tag) => {
return <Button variant="link" key={props.bookmark.id-tag.id} style={{textDecoration: "none"}} className="p-0 me-2">#{tag.name}</Button>
})}
</div>
<div>
<span>{DateTimeHelper.getFriendlyDate(props.bookmark.createdOn)} | </span>
<span>
<Button variant="link" className="p-0 me-2" style={{textDecoration: "none"}} onClick={() => navigate(`/bookmarks/${props.bookmark.id}`)}>Edit</Button>
<Button variant="link" className="p-0 me-2" style={{textDecoration: "none"}} onClick={props.onHideClicked}>{props.bookmark.isHidden ? "Unhide" : "Hide"}</Button>
<Button variant="link" className="text-danger p-0 me-2" style={{textDecoration: "none"}} onClick={props.onDeleteClicked}>Delete</Button>
</span>
</div>
</Col>
</Row>
</div>
}

View File

@ -0,0 +1,17 @@
import Alert from "react-bootstrap/Alert";
import Col from "react-bootstrap/Col";
import Container from "react-bootstrap/Container";
import Row from "react-bootstrap/Row";
import Form from 'react-bootstrap/Form';
import Button from "react-bootstrap/Button";
import Modal from 'react-bootstrap/Modal'
export {
Alert,
Col,
Container,
Row,
Form,
Button,
Modal,
};

View File

@ -0,0 +1,13 @@
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import Container from 'react-bootstrap/Container';
export function Footer(props) {
return (
<footer className="py-4 bg-light mt-auto">
<Container fluid>
<div className="text-center align-middle">YABA: Yet Another Bookmark App</div>
</Container>
</footer>
);
}

View File

@ -0,0 +1,68 @@
import React from 'react';
import Navbar from 'react-bootstrap/Navbar';
import NavDropdown from 'react-bootstrap/NavDropdown';
import Container from 'react-bootstrap/Container';
import Nav from 'react-bootstrap/Nav';
import 'bootstrap/dist/css/bootstrap.min.css';
import { useAuth0 } from "@auth0/auth0-react";
export function Header(props) {
const { isAuthenticated, loginWithRedirect, logout } = useAuth0();
const handleLogin = async () => {
await loginWithRedirect({
appState: {
returnTo: "/bookmarks",
},
});
};
const handleSignUp = async () => {
await loginWithRedirect({
appState: {
returnTo: "/bookmarks",
},
authorizationParams: {
screen_hint: "signup",
},
});
};
const handleLogout = () => {
logout({
logoutParams: {
returnTo: window.location.origin,
},
});
};
return (
<div>
<Navbar bg="dark" variant="dark" expand="lg">
<Container>
<Navbar.Brand href={!isAuthenticated ? "/" : "/bookmarks"}>YABA: Yet Another Bookmark App</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav" />
<Nav className="ms-auto">
{!isAuthenticated && (
<>
<Nav.Link onClick={handleLogin}>Login</Nav.Link>
<Nav.Link onClick={handleSignUp}>Register</Nav.Link>
</>
)}
{ isAuthenticated && (
<>
<NavDropdown title="Bookmarks">
<NavDropdown.Item href="/bookmarks">All</NavDropdown.Item>
<NavDropdown.Item href="/bookmarks/hidden">Hidden</NavDropdown.Item>
<NavDropdown.Divider />
<NavDropdown.Item href="/bookmarks/new">New</NavDropdown.Item>
</NavDropdown>
<Nav.Link onClick={handleLogout}>Logout</Nav.Link>
</>
)}
</Nav>
</Container>
</Navbar>
</div>
);
}

View File

@ -0,0 +1,17 @@
import { Footer } from "./footer";
import { Header } from "./header";
import { Bookmark } from "./bookmark/bookmark";
import { Auth0ProviderWithNavigate } from "./auth0ProviderWithNavigate";
import { ProtectedRoute } from "./protectedRoute";
import { SplashScreen } from "./shared/splashScreen";
import { SearchForm } from "./shared/searchForm";
export {
Footer,
Header,
Bookmark,
Auth0ProviderWithNavigate,
ProtectedRoute,
SplashScreen,
SearchForm
};

View File

@ -0,0 +1,19 @@
import { withAuthenticationRequired } from "@auth0/auth0-react";
import React from "react";
import { SplashScreen } from "./shared/splashScreen";
export const ProtectedRoute = ({ layout, header, footer, view, viewProps }) => {
const ProtectedView = withAuthenticationRequired(layout, {
// onRedirecting: () => (
// <div className="page-layout">
// <SplashScreen message="You are being redirected to the authentication page" />
// </div>
// ),
});
const Header = () => React.createElement(header);
const Footer = () => React.createElement(footer);
const View = () => React.createElement(view, viewProps);
return <ProtectedView header={<Header/>} footer={<Footer />} content={<View />} />;
};

View File

@ -0,0 +1,25 @@
import React from "react";
import { Button, Form } from "../external";
export function SearchForm(props) {
return (
<>
<Form
onSubmit={props.onHandleSearch}
className="d-flex">
<Form.Control
type="text"
className="me-2"
defaultValue={props.searchString}
onChange={props.onSearchStringChange}
/>
<Button
variant="primary"
type="submit"
>
{ props.searchButtonText ?? "Search" }
</Button>
</Form>
</>
);
}

View File

@ -0,0 +1,49 @@
import React from "react";
import Spinner from 'react-bootstrap/Spinner';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Button from "react-bootstrap/Button";
export function SplashScreen(props) {
return(
<div style={{
position: "absolute",
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
opacity: "0.6",
backgroundColor: "grey"
}}
>
<Row style={{
width: "100%"
}}>
<Col xs="12" style={{
display: "flex",
alignItems: "center",
justifyContent: "center"
}}>
<Spinner animation="border" />
</Col>
<Col xs="12"style={{
display: "flex",
alignItems: "center",
justifyContent: "center"
}}>
<div>{props.message}</div>
</Col>
<Col xs="12"style={{
display: "flex",
alignItems: "center",
justifyContent: "center"
}}>
{props.showCloseSplashScreenButton && (
<Button variant="link" onClick={props.onCloseSpashScreenClick}>Close this window</Button>
)}
</Col>
</Row>
</div>
)
}