Added front-end ReactApp, with basic CRUD functionality for Bookmark entries
This commit is contained in:
34
yaba-web/src/components/auth0ProviderWithNavigate.jsx
Normal file
34
yaba-web/src/components/auth0ProviderWithNavigate.jsx
Normal 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>
|
||||
);
|
||||
};
|
||||
34
yaba-web/src/components/bookmark/bookmark.jsx
Normal file
34
yaba-web/src/components/bookmark/bookmark.jsx
Normal 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>
|
||||
}
|
||||
17
yaba-web/src/components/external/index.js
vendored
Normal file
17
yaba-web/src/components/external/index.js
vendored
Normal 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,
|
||||
};
|
||||
13
yaba-web/src/components/footer.jsx
Normal file
13
yaba-web/src/components/footer.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
68
yaba-web/src/components/header.jsx
Normal file
68
yaba-web/src/components/header.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
17
yaba-web/src/components/index.js
Normal file
17
yaba-web/src/components/index.js
Normal 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
|
||||
};
|
||||
19
yaba-web/src/components/protectedRoute.jsx
Normal file
19
yaba-web/src/components/protectedRoute.jsx
Normal 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 />} />;
|
||||
};
|
||||
25
yaba-web/src/components/shared/searchForm.jsx
Normal file
25
yaba-web/src/components/shared/searchForm.jsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
49
yaba-web/src/components/shared/splashScreen.jsx
Normal file
49
yaba-web/src/components/shared/splashScreen.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user