From 6cff94379944ee62be32830577498a1dd2b1eced Mon Sep 17 00:00:00 2001 From: Ruben Deisenroth Date: Thu, 21 Dec 2023 17:10:27 +0100 Subject: [PATCH 01/26] first ui draft --- common/eslint-config-custom/postcss.config.js | 6 + .../eslint-config-custom/tailwind.config.ts | 21 ++ common/ui/components/auth-stuff.tsx | 100 ++++++++++ common/ui/{ => components}/card.tsx | 0 common/ui/components/footer.tsx | 16 ++ common/ui/{ => components}/index.tsx | 0 common/ui/components/navbar.tsx | 188 ++++++++++++++++++ common/ui/components/order-preview.tsx | 41 ++++ common/ui/components/userShowcase.tsx | 36 ++++ common/ui/lib/ThemeRegistry.tsx | 68 +++++++ common/ui/lib/auth.tsx | 103 ++++++++++ common/ui/models/User.ts | 10 + common/ui/package.json | 24 ++- hub/ui/app/globals.css | 49 ++--- hub/ui/app/layout.tsx | 38 +++- hub/ui/app/page.tsx | 149 ++------------ hub/ui/next.config.js | 3 +- hub/ui/package.json | 11 +- hub/ui/postcss.config.js | 9 + hub/ui/tailwind.config.ts | 2 + hub/ui/tsconfig.json | 2 +- operator/ui/app/page.tsx | 2 +- operator/ui/next.config.js | 3 +- package.json | 4 + tsconfig.json | 2 +- 25 files changed, 700 insertions(+), 187 deletions(-) create mode 100644 common/eslint-config-custom/postcss.config.js create mode 100644 common/eslint-config-custom/tailwind.config.ts create mode 100644 common/ui/components/auth-stuff.tsx rename common/ui/{ => components}/card.tsx (100%) create mode 100644 common/ui/components/footer.tsx rename common/ui/{ => components}/index.tsx (100%) create mode 100644 common/ui/components/navbar.tsx create mode 100644 common/ui/components/order-preview.tsx create mode 100644 common/ui/components/userShowcase.tsx create mode 100644 common/ui/lib/ThemeRegistry.tsx create mode 100644 common/ui/lib/auth.tsx create mode 100644 common/ui/models/User.ts create mode 100644 hub/ui/postcss.config.js create mode 100644 hub/ui/tailwind.config.ts diff --git a/common/eslint-config-custom/postcss.config.js b/common/eslint-config-custom/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/common/eslint-config-custom/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/common/eslint-config-custom/tailwind.config.ts b/common/eslint-config-custom/tailwind.config.ts new file mode 100644 index 0000000..654999f --- /dev/null +++ b/common/eslint-config-custom/tailwind.config.ts @@ -0,0 +1,21 @@ +import type { Config } from 'tailwindcss' + +const config: Config = { + content: [ + '../../common/ui/components/**/*.{js,ts,jsx,tsx,mdx}', + '../../common/ui/lib/**/*.{js,ts,jsx,tsx,mdx}', + '../../common/ui/models/**/*.{js,ts,jsx,tsx,mdx}', + './app/**/*.{js,ts,jsx,tsx,mdx}', + ], + theme: { + extend: { + backgroundImage: { + 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', + 'gradient-conic': + 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', + }, + }, + }, + plugins: [], +} +export default config diff --git a/common/ui/components/auth-stuff.tsx b/common/ui/components/auth-stuff.tsx new file mode 100644 index 0000000..0f62168 --- /dev/null +++ b/common/ui/components/auth-stuff.tsx @@ -0,0 +1,100 @@ +"use client"; +import { useUser, loginOIDC, loginWithCredentials } from "@/lib/auth"; +import { redirect } from "next/navigation"; +import { useRouter } from "next/router"; +import LoginIcon from "@mui/icons-material/Login"; +import { useState } from "react"; + + +export function OIDCSignInButton() { + const handleOIDCClick = () => { + loginOIDC(); + }; + return ( + + ); +} + +interface CredentialsFormProps { + csrfToken?: string; + returnUrl?: string; +} + +export function CredentialsForm(props: CredentialsFormProps) { + const [error, setError] = useState(null); + + let returnURL = props.returnUrl; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const data = new FormData(e.currentTarget); + + let username = data.get("username")?.toString(); + let password = data.get("password")?.toString(); + if (!username || !password) { + setError("Please enter a username and password"); + return; + } + + const user = await loginWithCredentials(username, password); + + if (!user) { + setError("Your Email or Password is wrong!"); + } else { + redirectToReturnURL(returnURL); + } + }; + return ( +
+ {error && ( + + {error} + + )} + + + + + +
+ ); +} + +export function redirectToReturnURL(returnUrl?: unknown) { + // if no return url is set, redirect to home page + console.log("redirecting to return url", returnUrl); + redirect(returnUrl && typeof returnUrl === "string" ? returnUrl : "/"); +} + +export async function redirectToReturnURLIfLoggedIn(returnUrl?: unknown) { + // const user = await useUser(); + // if (user) { + // redirectToReturnURL(returnUrl); + // } +} diff --git a/common/ui/card.tsx b/common/ui/components/card.tsx similarity index 100% rename from common/ui/card.tsx rename to common/ui/components/card.tsx diff --git a/common/ui/components/footer.tsx b/common/ui/components/footer.tsx new file mode 100644 index 0000000..23bcebe --- /dev/null +++ b/common/ui/components/footer.tsx @@ -0,0 +1,16 @@ +import { Box, Typography } from "@mui/material"; + +export default function Footer() { + return ( + + + Copyright © 2023 Ruben Deisenroth + + + ); +} diff --git a/common/ui/index.tsx b/common/ui/components/index.tsx similarity index 100% rename from common/ui/index.tsx rename to common/ui/components/index.tsx diff --git a/common/ui/components/navbar.tsx b/common/ui/components/navbar.tsx new file mode 100644 index 0000000..238cf11 --- /dev/null +++ b/common/ui/components/navbar.tsx @@ -0,0 +1,188 @@ +"use client"; + +import * as React from "react"; +import AppBar from "@mui/material/AppBar"; +import Box from "@mui/material/Box"; +import Toolbar from "@mui/material/Toolbar"; +import IconButton from "@mui/material/IconButton"; +import Typography from "@mui/material/Typography"; +import Menu from "@mui/material/Menu"; +import MenuIcon from "@mui/icons-material/Menu"; +import Container from "@mui/material/Container"; +import Avatar from "@mui/material/Avatar"; +import Button from "@mui/material/Button"; +import Tooltip from "@mui/material/Tooltip"; +import MenuItem from "@mui/material/MenuItem"; +import AdbIcon from "@mui/icons-material/Adb"; +import md5 from "md5"; +import { Route } from "@mui/icons-material"; +import { useUser } from "../lib/auth"; + +const pages = [ + { name: "Dashboard", href: "/" }, + { name: "Courses", href: "/courses" }, + { name: "Jobs", href: "/jobs" }, + { name: "Imprint", href: "/imprint" }, +]; +const settings = ["Profile", "Account", "Dashboard", "Logout"]; + +export default function Navbar() { + let user = useUser(); + const [anchorElNav, setAnchorElNav] = React.useState( + null + ); + const [anchorElUser, setAnchorElUser] = React.useState( + null + ); + + const handleOpenNavMenu = (event: React.MouseEvent) => { + setAnchorElNav(event.currentTarget); + }; + const handleOpenUserMenu = (event: React.MouseEvent) => { + setAnchorElUser(event.currentTarget); + }; + + const handleCloseNavMenu = () => { + setAnchorElNav(null); + }; + + const handleCloseUserMenu = () => { + setAnchorElUser(null); + }; + + return ( + + + + + + YouGrade + + + + + + + + {pages.map((page) => ( + + + {page.name} + + + ))} + + + + + YouGrade + + + {pages.map((page) => ( + + ))} + + + + + + + + + + {settings.map((setting) => ( + + {setting} + + ))} + + + + + + ); +} diff --git a/common/ui/components/order-preview.tsx b/common/ui/components/order-preview.tsx new file mode 100644 index 0000000..9dea1fb --- /dev/null +++ b/common/ui/components/order-preview.tsx @@ -0,0 +1,41 @@ +import Image from 'next/image' +import Button from '@mui/material/Button'; + +// Assuming you have an Order type defined somewhere +type Order = { + creator: { + username: string; + profilePicture: string; + }; + titleImage: string; + sumOfPurchasedItems: number; +}; + +export default function Home() { + // This would be fetched from an API in a real application + const orders: Order[] = [ + { + creator: { + username: 'John Doe', + profilePicture: 'url-to-john-doe-profile-picture', + }, + titleImage: 'url-to-title-image', + sumOfPurchasedItems: 5, + }, + // More orders here... + ]; + + return ( +
+

Recent Orders

+ {orders.map((order, index) => ( +
+ {order.creator.username} +

{order.creator.username}

+ Title image +

{order.sumOfPurchasedItems}

+
+ ))} +
+ ); +} \ No newline at end of file diff --git a/common/ui/components/userShowcase.tsx b/common/ui/components/userShowcase.tsx new file mode 100644 index 0000000..4e5c30e --- /dev/null +++ b/common/ui/components/userShowcase.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { useUser } from "../lib/auth"; +import { Avatar, Button } from "@mui/material"; +import md5 from "md5"; + +export function UserDisplay() { + let user = useUser(); + return ( +
+ +

User: {user?.username ?? "null"}

+

Email: {user?.email ?? "null"}

+

Last Seen: {user?.lastSeen.toString() ?? "null"}

+ +
+ ); +} diff --git a/common/ui/lib/ThemeRegistry.tsx b/common/ui/lib/ThemeRegistry.tsx new file mode 100644 index 0000000..d0a1398 --- /dev/null +++ b/common/ui/lib/ThemeRegistry.tsx @@ -0,0 +1,68 @@ +"use client"; +import createCache from "@emotion/cache"; +import { useServerInsertedHTML } from "next/navigation"; +import { CacheProvider } from "@emotion/react"; +import { ThemeProvider, createTheme } from "@mui/material/styles"; +import CssBaseline from "@mui/material/CssBaseline"; +import React from "react"; + +// This implementation is from emotion-js +// https://github.com/emotion-js/emotion/issues/2928#issuecomment-1319747902 +export default function ThemeRegistry(props:any) { + const { options, children } = props; + + const [{ cache, flush }] = React.useState(() => { + const cache = createCache(options); + cache.compat = true; + const prevInsert = cache.insert; + let inserted: string[] = []; + cache.insert = (...args) => { + const serialized = args[1]; + if (cache.inserted[serialized.name] === undefined) { + inserted.push(serialized.name); + } + return prevInsert(...args); + }; + const flush = () => { + const prevInserted = inserted; + inserted = []; + return prevInserted; + }; + return { cache, flush }; + }); + + useServerInsertedHTML(() => { + const names = flush(); + if (names.length === 0) { + return null; + } + let styles = ""; + for (const name of names) { + styles += cache.inserted[name]; + } + return ( +