Created Woodpecker CI/CD deployment

- Created Dockerfile for packing up API and Web projects as Docker image
This commit is contained in:
2023-03-27 21:48:25 -05:00
parent baf38aa3cd
commit a5d5ed048f
145 changed files with 30973 additions and 18248 deletions

View File

@ -0,0 +1,36 @@
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}
useRefreshTokens={true}
cacheLocation="localstorage"
>
{children}
</Auth0Provider>
);
};

View File

@ -0,0 +1,39 @@
import React from "react";
import { Row, Col, Form, Button } from "../external";
import DateTimeHelper from "../../utils/dateTimeHelper";
import { getHighlightedText } from "../../utils";
import { useNavigate } from "react-router-dom";
import { Tag } from "../../components";
export function Bookmark(props) {
const navigate = useNavigate();
const getTruncatedDescription = () => `${props.bookmark.description.substring(0, 97)}${props.bookmark.description.length >= 97 ? "..." : ""}`;
const getHighlightedTitle = () => getHighlightedText(props.bookmark.title, props.searchString);
const getHighlightedDescription = () => getHighlightedText(getTruncatedDescription(), props.searchString)
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={props.bookmark.url} target="_blank" style={{textDecoration: "none"}}>{ getHighlightedTitle() }</a>
<div className="font-weight-normal">{ getHighlightedDescription()}</div>
<div>
{
props.bookmark.tags.map((tag) => <Tag key={props.bookmark.id-tag.id} tag={tag} searchString={props.searchString}/>)
}
</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>
}

23
Web/src/components/external/index.js vendored Normal file
View File

@ -0,0 +1,23 @@
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'
import { Table } from "react-bootstrap";
import Dropdown from 'react-bootstrap/Dropdown';
import DropdownButton from 'react-bootstrap/DropdownButton';
export {
Alert,
Col,
Container,
Row,
Form,
Button,
Modal,
Table,
Dropdown,
DropdownButton,
};

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,69 @@
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 href="/tags">Tags</Nav.Link>
<Nav.Link onClick={handleLogout}>Logout</Nav.Link>
</>
)}
</Nav>
</Container>
</Navbar>
</div>
);
}

View File

@ -0,0 +1,23 @@
import { Footer } from "./footer";
import { Header } from "./header";
import { Bookmark } from "./bookmarks/bookmark";
import { Auth0ProviderWithNavigate } from "./auth0ProviderWithNavigate";
import { ProtectedRoute } from "./protectedRoute";
import { SplashScreen } from "./shared/splashScreen";
import { SearchForm } from "./shared/searchForm";
import { UpsertTagModal } from "./tags/upsertTagModal";
import { InterceptModal } from "./shared/interceptModal";
import { Tag } from "./tags/tag";
export {
Footer,
Header,
Bookmark,
Auth0ProviderWithNavigate,
ProtectedRoute,
SplashScreen,
SearchForm,
UpsertTagModal,
InterceptModal,
Tag,
};

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,32 @@
import React from "react";
import { Button, Form, Modal } from "../external";
export function InterceptModal(props) {
return (
<Modal
show={props.show}
onHide={props.onHide}
keyboard={false}
backdrop={props.backdrop}
>
<Modal.Header closeButton>
<Modal.Title>{props.title}</Modal.Title>
</Modal.Header>
<Modal.Body>{props.message}</Modal.Body>
<Modal.Footer>
<Button
variant={props.secondaryAction.variant}
onClick={props.secondaryAction.onClick}
>
{props.secondaryAction.text}
</Button>
<Button
variant={props.primaryAction.variant}
onClick={props.primaryAction.onClick}
>
{props.primaryAction.text}
</Button>
</Modal.Footer>
</Modal>
);
};

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>
)
}

View File

@ -0,0 +1,16 @@
import React from "react";
import { Button } from "../external";
import { getHighlightedText } from "../../utils";
export function Tag(props) {
const getHighlightedTagName = () => props.searchString ? getHighlightedText(props.tag.name, props.searchString) : props.tag.name
return (
<Button
variant="link"
style={{textDecoration: "none"}} className={`ms-0 me-2 p-0 ${props.tag.isHidden ? "text-danger" : null}`}
onClick={props.onClick}>
#{getHighlightedTagName()}
</Button>
);
};

View File

@ -0,0 +1,73 @@
import React from "react";
import { Button, Form, Modal } from "../external";
import { useFormik } from "formik";
import * as Yup from "yup"
export function UpsertTagModal(props) {
const formik = useFormik({
initialValues: props.tag,
validationSchema: Yup.object({
name: Yup.string().required("Name is required").lowercase()
}),
enableReinitialize: true,
onSubmit: async(values) => props.onSave(values)
});
return (
<Modal
show={props.show}
onHide={props.onHide}
keyboard={false}
backdrop="static"
>
<Form onSubmit={formik.handleSubmit}>
<Modal.Header closeButton>
<Modal.Title>{props.tag.id > 0 ? "Edit Tag" : "New Tag"}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form.Group className="mb-3">
<Form.Label>Name:</Form.Label>
<Form.Control
id="name"
type="text"
placeholder="Enter tag name"
defaultValue={formik.values.name}
onBlur={formik.handleChange}
isInvalid={!!formik.errors.name}
/>
<Form.Control.Feedback
type="invalid"
className="d-flex justify-content-start"
>
{formik.errors.name}
</Form.Control.Feedback>
</Form.Group>
<Form.Group>
<Form.Check
id="isHidden"
type="switch"
label="Mark as hidden"
checked={formik.values.isHidden}
onChange={formik.handleChange}
/>
</Form.Group>
</Modal.Body>
<Modal.Footer>
{ props.tag.id > 0 && <Button
variant="danger"
onClick={props.onDelete}
>
Delete
</Button>
}
<Button
variant="primary"
type="submit"
>
Save
</Button>
</Modal.Footer>
</Form>
</Modal>
);
}