From f0acd9ebc1bb57773c3876ad364f47bc621e9c0d Mon Sep 17 00:00:00 2001 From: MedzikUser Date: Mon, 9 May 2022 23:04:46 +0200 Subject: [PATCH] feat: add website --- .github/workflows/build-release-binaries.yml | 3 + .github/workflows/rust.yml | 4 + .github/workflows/website.yml | 67 + server/src/fs/download.rs | 12 +- website/.editorconfig | 12 + website/.eslintrc.json | 3 + website/.gitignore | 34 + website/api/axios.ts | 8 + website/api/index.ts | 4 + website/api/list.ts | 34 + website/api/login.ts | 32 + website/components/container.tsx | 15 + website/components/footer.tsx | 33 + website/components/header.tsx | 42 + website/components/main.tsx | 11 + website/config.ts | 8 + website/next-env.d.ts | 5 + website/next.config.js | 10 + website/package.json | 40 + website/pages/404.tsx | 59 + website/pages/_app.tsx | 132 + website/pages/_document.tsx | 32 + website/pages/index.tsx | 169 ++ website/pages/login.tsx | 135 + website/pages/user/files.tsx | 135 + website/pnpm-lock.yaml | 2843 ++++++++++++++++++ website/tsconfig.json | 20 + website/types/theme.ts | 23 + 28 files changed, 3917 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/website.yml create mode 100644 website/.editorconfig create mode 100644 website/.eslintrc.json create mode 100644 website/.gitignore create mode 100644 website/api/axios.ts create mode 100644 website/api/index.ts create mode 100644 website/api/list.ts create mode 100644 website/api/login.ts create mode 100644 website/components/container.tsx create mode 100644 website/components/footer.tsx create mode 100644 website/components/header.tsx create mode 100644 website/components/main.tsx create mode 100644 website/config.ts create mode 100644 website/next-env.d.ts create mode 100644 website/next.config.js create mode 100644 website/package.json create mode 100644 website/pages/404.tsx create mode 100644 website/pages/_app.tsx create mode 100644 website/pages/_document.tsx create mode 100644 website/pages/index.tsx create mode 100644 website/pages/login.tsx create mode 100644 website/pages/user/files.tsx create mode 100644 website/pnpm-lock.yaml create mode 100644 website/tsconfig.json create mode 100644 website/types/theme.ts diff --git a/.github/workflows/build-release-binaries.yml b/.github/workflows/build-release-binaries.yml index 5e1fbda..a256b22 100644 --- a/.github/workflows/build-release-binaries.yml +++ b/.github/workflows/build-release-binaries.yml @@ -4,8 +4,11 @@ on: push: paths-ignore: - '*.md' + - website/** pull_request: + paths-ignore: + - website/** workflow_dispatch: diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 068f64c..008cb1f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -4,8 +4,12 @@ on: push: branches: - main + paths-ignore: + - website/** pull_request: + paths-ignore: + - website/** workflow_dispatch: diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml new file mode 100644 index 0000000..9eab20d --- /dev/null +++ b/.github/workflows/website.yml @@ -0,0 +1,67 @@ +name: Website + +on: + push: + branches: + - main + paths: + - website/** + + pull_request: + paths: + - website/** + + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + name: Next.js Build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + + - name: Install pnpm + uses: pnpm/action-setup@v2.0.1 + id: pnpm-install + with: + version: 7 + run_install: false + + - name: Get pnpm store directory + id: pnpm-cache + run: | + echo "::set-output name=pnpm_cache_dir::$(pnpm store path)" + + - name: Cache pnpm + uses: actions/cache@v3 + with: + path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('website/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Cache next.js + uses: actions/cache@v3 + with: + path: | + ${{ github.workspace }}/.next/cache + key: ${{ runner.os }}-nextjs-${{ hashFiles('website/pnpm-lock.yaml') }}-${{ hashFiles('website/**.[jt]s', 'website/**.[jt]sx') }} + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('website/pnpm-lock.yaml') }}- + + - name: Install dependencies + run: pnpm install + + - name: Build page + run: pnpm run build + + - name: Export page + run: pnpm run export diff --git a/server/src/fs/download.rs b/server/src/fs/download.rs index 6c1f682..24b1366 100644 --- a/server/src/fs/download.rs +++ b/server/src/fs/download.rs @@ -1,21 +1,17 @@ -use std::path::PathBuf; -use std::{fs, io}; +use std::fs; use crate::fs::validate_path; use axum::extract::Query; -use axum::{extract::rejection::JsonRejection, Extension, Json}; +use axum::Extension; use axum_auth::AuthBearer; -use byte_unit::Byte; use homedisk_database::Database; -use homedisk_types::fs::list::DirInfo; use homedisk_types::fs::upload::Pagination; use homedisk_types::{ config::types::Config, - errors::{FsError, ServerError}, - fs::list::{FileInfo, Request, Response}, + errors::ServerError, }; -use crate::middleware::{find_user, validate_json, validate_jwt}; +use crate::middleware::{find_user, validate_jwt}; pub async fn handle( Extension(db): Extension, diff --git a/website/.editorconfig b/website/.editorconfig new file mode 100644 index 0000000..ad4c311 --- /dev/null +++ b/website/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = false diff --git a/website/.eslintrc.json b/website/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/website/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..1437c53 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,34 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel diff --git a/website/api/axios.ts b/website/api/axios.ts new file mode 100644 index 0000000..ce1b808 --- /dev/null +++ b/website/api/axios.ts @@ -0,0 +1,8 @@ +import axios from "axios" +import config from "../config" + +const instance = axios.create({ + baseURL: config.apiUrl, +}) + +export default instance diff --git a/website/api/index.ts b/website/api/index.ts new file mode 100644 index 0000000..c31f03e --- /dev/null +++ b/website/api/index.ts @@ -0,0 +1,4 @@ +import list from "./list" +import login from "./login" + +export default { list, login } diff --git a/website/api/list.ts b/website/api/list.ts new file mode 100644 index 0000000..aa5f216 --- /dev/null +++ b/website/api/list.ts @@ -0,0 +1,34 @@ +import axios from './axios' + +export default async function list(path: string, token: string): Promise { + const request = axios.post("/fs/list", { + path + }, { + headers: { + Authorization: `Bearer ${token}`, + } + }) + + const response = request + .then(response => { + const { data } = response + return data + }) + .catch(err => { + if (err.response?.data?.error_message) { + const error = err.response.data.error_message + + if (error.toString() == "[object Object]") { + Object.keys(error).forEach(key => { + throw new Error(key) + }) + } + + throw new Error(error) + } + + throw new Error(err) + }) + + return response +} diff --git a/website/api/login.ts b/website/api/login.ts new file mode 100644 index 0000000..be4e356 --- /dev/null +++ b/website/api/login.ts @@ -0,0 +1,32 @@ +import axios from './axios' + +export default async function login(username: string, password: string): Promise { + const request = axios.post("/auth/login", { + username, + password, + }) + + const response = request + .then(response => { + const { data } = response + + return data.LoggedIn.access_token + }) + .catch(err => { + if (err.response?.data?.error_message) { + const error = err.response.data.error_message + + if (error.toString() == "[object Object]") { + Object.keys(error).forEach(key => { + throw new Error(key) + }) + } + + throw new Error(error) + } + + throw new Error(err) + }) + + return response +} diff --git a/website/components/container.tsx b/website/components/container.tsx new file mode 100644 index 0000000..4a6bef9 --- /dev/null +++ b/website/components/container.tsx @@ -0,0 +1,15 @@ +import styled from 'styled-components' + +const Container = styled.div` + height: 100%; + min-height: 100vh; + padding: 0 0.5rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: ${({ theme }) => theme.colors.background}; + color: ${({ theme }) => theme.colors.color}; +` + +export default Container diff --git a/website/components/footer.tsx b/website/components/footer.tsx new file mode 100644 index 0000000..eecdc06 --- /dev/null +++ b/website/components/footer.tsx @@ -0,0 +1,33 @@ +import { GitHub } from "@mui/icons-material" +import { IconButton } from "@mui/material" +import styled from "styled-components" +import { links } from "../config" + +const StyledFooter = styled.footer` + width: 100%; + height: 100px; + border-top: 1px solid ${({ theme }) => theme.footer.borderTop}; + display: flex; + justify-content: center; + align-items: center; + background-color: ${({ theme }) => theme.colors.background}; + color: ${({ theme }) => theme.colors.color}; + + a { + display: flex; + justify-content: center; + align-items: cente; + } +` + +export default function Footer() { + return ( + + + + + + + + ) +} diff --git a/website/components/header.tsx b/website/components/header.tsx new file mode 100644 index 0000000..76fa1a9 --- /dev/null +++ b/website/components/header.tsx @@ -0,0 +1,42 @@ +import { faM, faMoon, faSun } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { RocketLaunch } from "@mui/icons-material"; +import { AppBar, IconButton, Link, Stack, Toolbar, Typography } from "@mui/material"; +import { useState } from "react"; + +export default function Footer({ toggleTheme, theme}: Props) { + return ( + + + + + + + + + + HomeDisk + + + +
toggleTheme()}> + +
+
+
+
+ ) +} + +type Props = { + toggleTheme: () => any, + theme: string +} diff --git a/website/components/main.tsx b/website/components/main.tsx new file mode 100644 index 0000000..7bc3b77 --- /dev/null +++ b/website/components/main.tsx @@ -0,0 +1,11 @@ +import styled from 'styled-components' + +const Main = styled.main` + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +` + +export default Main diff --git a/website/config.ts b/website/config.ts new file mode 100644 index 0000000..32a2eff --- /dev/null +++ b/website/config.ts @@ -0,0 +1,8 @@ +export const links = { + github: "https://github.com/HomeDisk/cloud" +} + +export default { + apiUrl: "/api", + links, +} diff --git a/website/next-env.d.ts b/website/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/website/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/website/next.config.js b/website/next.config.js new file mode 100644 index 0000000..b398b1b --- /dev/null +++ b/website/next.config.js @@ -0,0 +1,10 @@ +module.exports = { + async rewrites() { + return [ + { + source: '/api/:slug*', + destination: 'http://127.0.0.1:8080/:slug*' + } + ] + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..e96b030 --- /dev/null +++ b/website/package.json @@ -0,0 +1,40 @@ +{ + "name": "@homedisk/website", + "description": "Fast and lightweight local cloud for your data written in Rust", + "version": "0.0.0", + "author": "MedzikUser ", + "license": "GPL-3.0", + "scripts": { + "dev": "next dev", + "build": "next build", + "export": "next export", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@babel/core": "7.17.10", + "@emotion/react": "11.9.0", + "@emotion/styled": "11.8.1", + "@fortawesome/fontawesome-svg-core": "6.1.1", + "@fortawesome/free-solid-svg-icons": "6.1.1", + "@fortawesome/react-fontawesome": "^0.1.18", + "@mui/icons-material": "5.6.2", + "@mui/material": "5.6.4", + "@mui/styled-engine-sc": "5.6.1", + "axios": "0.27.2", + "next": "12.1.6", + "react": "18.1.0", + "react-cookie": "4.1.1", + "react-dom": "18.1.0", + "react-is": "18.1.0", + "styled-components": "5.3.5" + }, + "devDependencies": { + "@types/node": "17.0.31", + "@types/react": "18.0.9", + "@types/styled-components": "5.1.25", + "eslint": "8.15.0", + "eslint-config-next": "12.1.6", + "typescript": "4.6.4" + } +} diff --git a/website/pages/404.tsx b/website/pages/404.tsx new file mode 100644 index 0000000..4e48ffd --- /dev/null +++ b/website/pages/404.tsx @@ -0,0 +1,59 @@ +import { Button } from '@mui/material' +import Head from 'next/head' +import { useCookies } from 'react-cookie' +import styled from 'styled-components' +import { links } from '../config' +import ThemeInterface from '../types/theme' + +const Title = styled.h1` + margin: 0; + line-height: 1.15; + font-size: 1.5rem; + text-align: center; + + a { + color: ${({ theme }) => theme.pages.index.title.a}; + text-decoration: none; + animation: animate 1.5s linear infinite; + } + + @keyframes animate { + 0% { + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } + } + + a:hover, + a:focus, + a:active { + text-decoration: underline; + } +` + +const StyledButton = styled(Button)` + margin-top: 1rem; +` + +export default function NotFound() { + return ( + <> + + 404 - HomeDisk + + + + 404 | This page could not be found. + + + + Go to Home Page + + + ) +} diff --git a/website/pages/_app.tsx b/website/pages/_app.tsx new file mode 100644 index 0000000..94f5d68 --- /dev/null +++ b/website/pages/_app.tsx @@ -0,0 +1,132 @@ +import { Button, ThemeProvider as MuiThemeProvider, createTheme as muiCreateTheme, PaletteMode } from '@mui/material' +import { useEffect, useState } from 'react' +import { useCookies } from 'react-cookie' +import { createGlobalStyle, ThemeProvider } from 'styled-components' +import Container from '../components/container' +import Footer from '../components/footer' +import Header from '../components/header' +import Main from '../components/main' + +const GlobalStyle = createGlobalStyle` + html, + body { + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + } + + a { + color: inherit; + text-decoration: none; + } + + * { + box-sizing: border-box; + } +` + +const lightTheme = { + colors: { + background: "#ffffff", + color: "#000000", + error: "#f85b5b" + }, + pages: { + index: { + cards: { + signin: "#0a60cf", + register: "#a06800", + files: "#3d8011", + settings: "#75006f" + }, + title: { + a: "#a109c0" + } + } + }, + footer: { + borderTop: "#eaeaea" + } +} + +const darkTheme = { + colors: { + background: "#131212", + color: "#ffffff", + error: "#f85b5b" + }, + pages: { + index: { + cards: { + signin: "#0a60cf", + register: "#a06800", + files: "#54ad19", + settings: "#c90dbf" + }, + title: { + a: "#a109c0" + } + } + }, + footer: { + borderTop: "#161616" + } +} + +export default function App({ Component, pageProps }) { + const [cookies, setCookies] = useCookies(["theme"]) + + const [theme, setTheme] = useState(lightTheme) + const [themeName, setThemeName]: [PaletteMode, any] = useState("light") + + useEffect(() => { + if (!cookies.theme) setCookies("theme", "light") + + if (cookies.theme == "dark"){ + setTheme(darkTheme) + setThemeName("dark") + } + }, [setCookies, setTheme, setThemeName, cookies]) + + const toggleTheme = () => { + if (cookies.theme == "light") { + setTheme(darkTheme) + setThemeName("dark") + + setCookies("theme", "dark") + } + if (cookies.theme == "dark") { + setTheme(lightTheme) + setThemeName("light") + + setCookies("theme", "light") + } + } + + const muiThene = muiCreateTheme({ + palette: { + mode: themeName, + }, + }) + + return ( + <> + + + + + +
+ +
+ +
+ +