diff --git a/Components/Repo/QuickCommands/QuickCommands.js b/Components/Repo/QuickCommands/QuickCommands.js new file mode 100644 index 0000000..2979613 --- /dev/null +++ b/Components/Repo/QuickCommands/QuickCommands.js @@ -0,0 +1,52 @@ +//Lib +import React from 'react'; +import { useState } from 'react'; +import classes from './QuickCommands.module.css'; +import { IconSettingsAutomation, IconCopy } from '@tabler/icons'; + +export default function QuickCommands(props) { + //State + const [isCopied, setIsCopied] = useState(false); + + //Functions + const handleCopy = async () => { + // Asynchronously call copy to clipboard + navigator.clipboard + .writeText( + `borg init -e repokey-blake2 ssh://${props.unixUser}@${process.env.NEXT_PUBLIC_HOSTNAME}:${process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./${props.repository}` + ) + .then(() => { + // If successful, update the isCopied state value + setIsCopied(true); + setTimeout(() => { + setIsCopied(false); + }, 1500); + }) + .catch((err) => { + console.log(err); + }); + }; + + return ( +
+ {isCopied ? ( +
Copied !
+ ) : ( +
+ borg init -e repokey-blake2 ssh://{props.unixUser}@ + {process.env.NEXT_PUBLIC_HOSTNAME}: + {process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./ + {props.repository} +
+ )} +
+ +
+ +
+
+
+ ); +} diff --git a/Components/Repo/QuickCommands/QuickCommands.module.css b/Components/Repo/QuickCommands/QuickCommands.module.css new file mode 100644 index 0000000..8dfbed6 --- /dev/null +++ b/Components/Repo/QuickCommands/QuickCommands.module.css @@ -0,0 +1,98 @@ +.container { + display: flex; + align-items: center; + align-self: flex-start; + margin: auto 47px auto auto; +} + +.icons { + position: relative; + bottom: 14px; +} + +.quickSetting { + position: absolute; + visibility: visible; + opacity: 1; +} + +.tooltip { + visibility: hidden; + opacity: 0; + width: 100%; + height: 100%; + border: 1px solid #6d4aff21; + background-color: #f5f5f5; + border-radius: 5px; + box-shadow: 0 0px 1px rgba(0, 0, 0, 0.1) inset; + color: #65748b; + font-size: 0.95rem; + padding: 5px 5px; + transition: 0.5s opacity; +} + +.copyButton { + position: absolute; + visibility: hidden; + opacity: 0; + border: none; + background-color: none; +} + +.copyValid { + margin: auto 8px auto auto; + font-size: 0.95rem; + color: #6d4aff; + animation: scale-in-center 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both; +} + +@keyframes scale-in-center { + 0% { + -webkit-transform: scale(0); + transform: scale(0); + opacity: 1; + } + 100% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1; + } +} + +/* On Hover */ + +.container:hover .tooltip { + visibility: visible; + opacity: 1; + width: 100%; + height: 100%; + border: 1px solid #6d4aff21; + background-color: #f5f5f5; + border-radius: 5px; + box-shadow: 0 0px 1px rgba(0, 0, 0, 0.1) inset; + color: #65748b; + font-size: 0.95rem; + padding: 5px 5px; + transition: 0.5s opacity; +} + +.container:hover .copyButton { + position: absolute; + visibility: visible; + opacity: 1; + border: none; + background-color: transparent; + cursor: pointer; +} + +.container:hover .quickSetting { + position: absolute; + visibility: hidden; + opacity: 0; +} + +@media all and (max-width: 1000px) { + .container { + display: none; + } +} diff --git a/Components/Repo/Repo.js b/Components/Repo/Repo.js new file mode 100644 index 0000000..ff9cb0a --- /dev/null +++ b/Components/Repo/Repo.js @@ -0,0 +1,131 @@ +//Lib +import React from 'react'; +import classes from './Repo.module.css'; +import { IconSettings, IconInfoCircle } from '@tabler/icons'; +import timestampConverter from '../../helpers/functions/timestampConverter'; +import StorageBar from '../UI/StorageBar/StorageBar'; +import QuickCommands from './QuickCommands/QuickCommands'; + +export default function Repo(props) { + return ( + <> + {props.displayDetails ? ( + <> +
+
+ {props.status ? ( +
+ ) : ( +
+ )} +
{props.alias}
+ {props.comment && ( +
+ +
+ {props.comment} +
+
+ )} + +
+ + + + + + + + + + + + + + + + + + + + + + +
Repository + Storage Size + + Storage Used + + Last change + IDEdit
{props.repository}{props.storageSize}Go + + +
+ {props.lastSave === 0 + ? '-' + : timestampConverter( + props.lastSave + )} +
+
#{props.id} +
+ + props.repoManageEditHandler() + } + /> +
+
+
+ + ) : ( + <> +
+
+ {props.status ? ( +
+ ) : ( +
+ )} +
{props.alias}
+ {props.comment && ( +
+ +
+ {props.comment} +
+
+ )} +
+
+ {props.lastSave === 0 + ? null + : timestampConverter(props.lastSave)} + + #{props.id} + +
+
+ + )} + + ); +} diff --git a/Components/Repo/Repo.module.css b/Components/Repo/Repo.module.css new file mode 100644 index 0000000..8daa501 --- /dev/null +++ b/Components/Repo/Repo.module.css @@ -0,0 +1,217 @@ +/*Repo CLOSE*/ +.RepoClose { + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); + width: auto; + max-height: 65px; + margin: 20px 0px 0px 0px; + border-radius: 5px; + overflow: visible; + /* Need to display comment on hover (which is position : absolute) */ + position: relative; +} + +.closeFlex { + display: flex; + align-items: center; + padding: 15px; +} + +.RepoClose .lastSave { + padding: 15px; +} + +/* REPO OPEN */ +.RepoOpen { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); + width: auto; + max-height: 200px; + margin: 20px 0px 0px 0px; + padding: 15px; + border-radius: 5px; + transition: max-height 0.2s linear; + overflow: visible; + /* Need to display comment on hover (which is position : absolute) */ + position: relative; +} + +.openFlex { + display: flex; + align-items: center; + align-self: flex-start; + width: 100%; +} + +.tabInfo { + width: 100%; + overflow-wrap: break-word; + border-collapse: collapse; + background: #fff; + border-radius: 10px; + overflow: hidden; + margin: 25px auto; + table-layout: fixed; +} + +.tabInfo thead tr { + height: 50px; + background: #111827; + color: #fff; +} + +.tabInfo thead th { + font-size: 1em; + color: #fff; + line-height: 1.2; + font-weight: normal; +} + +.tabInfo tbody tr { + background-color: #f3f4f6; + height: 50px; +} + +.tabInfo tbody tr th { + color: #65748b; + font-size: 0.95rem; + font-weight: 400; +} + +/*STATUS*/ + +.statusIndicatorGreen { + background: rgb(9, 255, 0); + border-radius: 50%; + margin: 10px; + height: 15px; + width: 15px; + box-shadow: 0 0 0 0 rgb(9, 255, 0); + transform: scale(1); + animation: pulseGreen 5s infinite; + animation-delay: 1s; +} + +@keyframes pulseGreen { + 0% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba(17, 255, 0, 0.7); + } + + 10% { + transform: scale(1); + box-shadow: 0 0 0 10px rgba(17, 255, 0, 0); + } + + 90% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba(17, 255, 0, 0); + } +} + +.statusIndicatorRed { + background: rgb(255, 0, 0); + border-radius: 50%; + margin: 10px; + height: 15px; + width: 15px; + + box-shadow: 0 0 0 0 rgb(255, 0, 0); + transform: scale(1); + animation: pulseRed 5s infinite; + animation-delay: 0.5s; +} + +@keyframes pulseRed { + 0% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.7); + } + + 10% { + transform: scale(1); + box-shadow: 0 0 0 10px rgba(255, 0, 0, 0); + } + + 90% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba(255, 0, 0, 0); + } +} + +/* GENERAL */ +.alias { + font-weight: bold; + color: #111827; + font-size: 1.05em; +} + +.lastSave { + color: #65748b; +} + +.editButton { + cursor: pointer; +} + +/* Comment */ +.comment { + display: flex; + flex-direction: row; + align-items: center; + margin-left: 10px; +} + +.toolTip { + visibility: hidden; + width: auto; + height: auto; + max-width: 400px; + max-height: 250px; + background-color: #fff; + color: #637381; + text-align: center; + border-radius: 6px; + padding: 5px 5px; + position: absolute; + z-index: 1; + margin: 0px 0 0 20px; + opacity: 1; + transition: 0.5s opacity; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + overflow: auto; +} + +.comment:hover .toolTip, +.comment:active .toolTip { + visibility: visible; + opacity: 1; +} + +/* MOBILE */ +@media all and (max-width: 1000px) { + .tabInfo { + display: none; + } + .toolTip { + display: none; + } + .comment { + display: none; + } + .lastSave { + display: none; + } + .closeFlex { + margin: auto; + } + .openFlex { + margin: auto; + width: auto; + } +} diff --git a/Components/UI/CopyButton/CopyButton.js b/Components/UI/CopyButton/CopyButton.js new file mode 100644 index 0000000..305d663 --- /dev/null +++ b/Components/UI/CopyButton/CopyButton.js @@ -0,0 +1,39 @@ +//Lib +import classes from './CopyButton.module.css'; +import { useState } from 'react'; +import { IconCopy } from '@tabler/icons'; + +export default function CopyButton(props) { + //State + const [isCopied, setIsCopied] = useState(false); + + //Function + const handleCopy = async (data) => { + navigator.clipboard + .writeText(data) + .then(() => { + // If successful, update the isCopied state value + setIsCopied(true); + setTimeout(() => { + setIsCopied(false); + }, 1500); + }) + .catch((err) => { + console.log(err); + }); + }; + + return ( + <> + + {isCopied ? ( + Copied ! + ) : null} + + ); +} diff --git a/Components/UI/CopyButton/CopyButton.module.css b/Components/UI/CopyButton/CopyButton.module.css new file mode 100644 index 0000000..ab59788 --- /dev/null +++ b/Components/UI/CopyButton/CopyButton.module.css @@ -0,0 +1,26 @@ +.copyButton { + visibility: visible; + opacity: 1; + border: none; + background-color: transparent; + cursor: pointer; +} + +.copyValid { + font-size: 0.95rem; + color: #6d4aff; + animation: scale-in-center 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both; +} + +@keyframes scale-in-center { + 0% { + -webkit-transform: scale(0); + transform: scale(0); + opacity: 1; + } + 100% { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1; + } +} diff --git a/Components/UI/Error/Error.js b/Components/UI/Error/Error.js new file mode 100644 index 0000000..2a3fe1f --- /dev/null +++ b/Components/UI/Error/Error.js @@ -0,0 +1,6 @@ +//Lib +import classes from './Error.module.css'; + +export default function Error(props) { + return
{props.message}
; +} diff --git a/Components/UI/Error/Error.module.css b/Components/UI/Error/Error.module.css new file mode 100644 index 0000000..19b75f6 --- /dev/null +++ b/Components/UI/Error/Error.module.css @@ -0,0 +1,18 @@ +.errorMessage { + margin: 15px 0px; + background-color: red; + color: white; + padding: 15px; + border-radius: 5px; + animation: myAnim 1s ease 0s 1 normal forwards; +} + +@keyframes myAnim { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} diff --git a/Components/UI/Layout/Footer/Footer.js b/Components/UI/Layout/Footer/Footer.js new file mode 100644 index 0000000..e5f188e --- /dev/null +++ b/Components/UI/Layout/Footer/Footer.js @@ -0,0 +1,21 @@ +//Lib +import classes from './Footer.module.css'; + +function Footer() { + return ( +
+

+ About{' '} + + BorgWarehouse + +

+
+ ); +} + +export default Footer; diff --git a/Components/UI/Layout/Footer/Footer.module.css b/Components/UI/Layout/Footer/Footer.module.css new file mode 100644 index 0000000..3a8a6fe --- /dev/null +++ b/Components/UI/Layout/Footer/Footer.module.css @@ -0,0 +1,13 @@ +.footer { + color: #494b7a; + text-align: center; + position: absolute; + bottom: 0; + width: 100%; + height: 50px; +} + +a.site { + color: #6d4aff; + text-decoration: none; +} diff --git a/Components/UI/Layout/Header/Header.js b/Components/UI/Layout/Header/Header.js new file mode 100644 index 0000000..c02dbc7 --- /dev/null +++ b/Components/UI/Layout/Header/Header.js @@ -0,0 +1,23 @@ +//Lib +import classes from "./Header.module.css"; + +//Components +import Nav from "./Nav/Nav"; + +function Header() { + return ( +
+
+
+ BorgWarehouse +
+ +
+
+ ) +} + +export default Header; \ No newline at end of file diff --git a/Components/UI/Layout/Header/Header.module.css b/Components/UI/Layout/Header/Header.module.css new file mode 100644 index 0000000..63d0dab --- /dev/null +++ b/Components/UI/Layout/Header/Header.module.css @@ -0,0 +1,29 @@ +.Header { + width: 100%; + background: #111827; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + height: 50px; + color: white; + display: flex; + align-items: center; + position: static; + top: 0; + z-index: 1000; +} + +.flex { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + max-width: 1500px; + margin: auto; +} + +.logo { + font-size: 1.5em; + font-weight: bold; + color: #6d4aff; + text-shadow: #6d4aff 0px 0px 18px; + margin-left: 20px; +} diff --git a/Components/UI/Layout/Header/Nav/Nav.js b/Components/UI/Layout/Header/Nav/Nav.js new file mode 100644 index 0000000..7a97a7f --- /dev/null +++ b/Components/UI/Layout/Header/Nav/Nav.js @@ -0,0 +1,57 @@ +//Lib +import classes from './Nav.module.css'; +import { IconUser, IconLogout } from '@tabler/icons'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { useSession, signOut } from 'next-auth/react'; + +export default function Nav() { + ////Var + //Get the current route to light the right Item + const router = useRouter(); + const currentRoute = router.pathname; + const { status, data } = useSession(); + + //Function + const onLogoutClickedHandler = async () => { + //This bug is open : https://github.com/nextauthjs/next-auth/issues/1542 + //I put redirect to false and redirect with router. + //The result on logout click is an ugly piece of page for a few milliseconds before returning to the login page. + //It's ugly if you are perfectionist but functional and invisible for most of users while waiting for a next-auth fix. + await signOut({ redirect: false }); + router.replace('/login'); + }; + + return ( + + ); +} diff --git a/Components/UI/Layout/Header/Nav/Nav.module.css b/Components/UI/Layout/Header/Nav/Nav.module.css new file mode 100644 index 0000000..675aa7e --- /dev/null +++ b/Components/UI/Layout/Header/Nav/Nav.module.css @@ -0,0 +1,50 @@ +.Nav { + list-style-type: none; + margin: 0px 15px 0px 0px; + padding: 0; + display: flex; +} + +.user { + display: flex; + align-items: center; +} + +.account { + background: none; + border: none; + cursor: pointer; + color: #494b7a; +} + +.account a { + color: #494b7a; + text-decoration: none; +} + +.account :focus, +.account .active, +.account :hover { + color: #6d4aff; + text-shadow: #6d4aff 0px 0px 18px; +} + +.logout { + background: none; + border: none; + cursor: pointer; + color: #494b7a; +} + +.logout :focus, +.logout .active, +.logout :hover { + color: #6d4aff; + text-shadow: #6d4aff 0px 0px 18px; +} + +@media all and (max-width: 1000px) { + .account { + display: none; + } +} diff --git a/Components/UI/Layout/Layout.js b/Components/UI/Layout/Layout.js new file mode 100644 index 0000000..96fcedd --- /dev/null +++ b/Components/UI/Layout/Layout.js @@ -0,0 +1,30 @@ +//Lib +import Footer from './Footer/Footer'; +import Header from './Header/Header'; +import NavSide from './NavSide/NavSide'; +import classes from './Layout.module.css'; +import { useSession } from 'next-auth/react'; + +function Layout(props) { + //Var + const { status } = useSession(); + + if (status === 'authenticated') { + return ( + <> +
+ +
{props.children}
+