setup frontend

This commit is contained in:
Vomitblood 2024-07-30 16:47:58 +08:00
parent dc10901a57
commit 5294b46e64
40 changed files with 406 additions and 460 deletions

BIN
bun.lockb

Binary file not shown.

View file

@ -9,9 +9,14 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@emotion/cache": "^11.13.1",
"@emotion/react": "^11.13.0",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.13.0",
"@mui/material": "^5.16.5",
"next": "14.2.5",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18"
"next": "14.2.5"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^1.6.0", "@tauri-apps/cli": "^1.6.0",

BIN
public/fonts/ComicSans/comic.ttf Executable file

Binary file not shown.

Binary file not shown.

BIN
public/fonts/ComicSans/comici.ttf Executable file

Binary file not shown.

BIN
public/fonts/ComicSans/comicz.ttf Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,127 @@
// TODO: add settings functionality, refer to whensapp project
import { FC, ReactNode } from "react";
import { createTheme, ThemeProvider } from "@mui/material/styles";
interface UserThemeProviderProps {
children: ReactNode;
}
// palette and accent settings
export const userPalette = {
primary: {
// light: '#a1c3f9',
main: "#8ab4f8",
// dark: '#4285f4',
},
secondary: {
// light: '#a1c3f9',
main: "#8ab4f8",
// dark: '#4285f4',
},
error: {
light: "#e57373",
main: "#f44336",
dark: "#d32f2f",
},
warning: {
light: "#ffb74d",
main: "#ffa726",
dark: "#f57c00",
},
info: {
light: "#a1c3f9",
main: "#8ab4f8",
dark: "#4285f4",
},
success: {
light: "#81c784",
main: "#66bb6a",
dark: "#388e3c",
},
grey: {
50: "#fafafa",
100: "#f5f5f5",
200: "#eeeeee",
300: "#e0e0e0",
400: "#bdbdbd",
500: "#9e9e9e",
600: "#757575",
700: "#616161",
800: "#424242",
900: "#212121",
A100: "#f5f5f5",
A200: "#eeeeee",
A400: "#bdbdbd",
A700: "#616161",
},
background: {
paper: "#303134",
default: "#202124",
},
};
export const UserThemeProvider: FC<UserThemeProviderProps> = ({ children }) => {
// font family settings
// TODO: figure out how to bundle fonts in tauri
let fontFamily = "JetBrains Mono";
// switch (settings.display.font_family) {
// case "sans_serif":
// fontFamily = "GoogleSans, sans-serif";
// break;
// case "monospace":
// fontFamily = "JetBrainsMono, monospace";
// break;
// case "comic_sans":
// fontFamily = "ComicSans, sans-serif";
// break;
// case "system_font":
// fontFamily = "system-ui";
// break;
// }
// font scaling settings
// const fontSize = (settings.display.font_scaling / 100) * 14;
const userTheme = createTheme({
typography: {
fontFamily: fontFamily,
// fontSize: fontSize,
},
palette: {
mode: "dark",
...userPalette,
},
transitions: {
duration: {
// shortest: settings.display.transition_duration,
// shorter: settings.display.transition_duration,
// short: settings.display.transition_duration,
// // most basic recommended timing
// standard: settings.display.transition_duration,
// // this is to be used in complex animations
// complex: settings.display.transition_duration,
// // recommended when something is entering screen
// enteringScreen: settings.display.transition_duration,
// // recommended when something is leaving screen
// leavingScreen: settings.display.transition_duration,
},
},
components: {
MuiButton: {
styleOverrides: {
root: { textTransform: "none" },
},
},
MuiTab: {
styleOverrides: {
root: {
textTransform: "none",
},
},
},
},
});
return <ThemeProvider theme={userTheme}>{children}</ThemeProvider>;
};

View file

@ -0,0 +1,19 @@
import createCache from "@emotion/cache";
const isBrowser = typeof document !== "undefined";
// On the client side, Create a meta tag at the top of the <head> and set it as insertionPoint.
// This assures that MUI styles are loaded first.
// It allows developers to easily override MUI styles with other styling solutions, like CSS modules.
export default function createEmotionCache() {
let insertionPoint;
if (isBrowser) {
const emotionInsertionPoint = document.querySelector<HTMLMetaElement>(
'meta[name="emotion-insertion-point"]',
);
insertionPoint = emotionInsertionPoint ?? undefined;
}
return createCache({ key: "mui-style", insertionPoint });
}

View file

@ -1,6 +1,31 @@
import "@/styles/globals.css"; import { CacheProvider, EmotionCache } from "@emotion/react";
import type { AppProps } from "next/app"; import CssBaseline from "@mui/material/CssBaseline";
import { AppProps } from "next/app";
import Head from "next/head";
import { UserThemeProvider } from "../contexts/ThemeContext";
import createEmotionCache from "../lib/createEmotionCache";
import "../styles/global.css";
export default function App({ Component, pageProps }: AppProps) { // Client-side cache, shared for the whole session of the user in the browser.
return <Component {...pageProps} />; const clientSideEmotionCache = createEmotionCache();
export interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
}
export default function MyApp(props: MyAppProps) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
return (
<CacheProvider value={emotionCache}>
<Head>
<title>WhensApp</title>
<meta name='viewport' content='initial-scale=1, width=device-width' />
</Head>
<UserThemeProvider>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</UserThemeProvider>
</CacheProvider>
);
} }

View file

@ -1,9 +1,34 @@
import { Html, Head, Main, NextScript } from "next/document"; import createEmotionServer from "@emotion/server/create-instance";
import { useTheme } from "@mui/material";
import { AppType } from "next/app";
import Document, {
DocumentContext,
DocumentProps,
Head,
Html,
Main,
NextScript,
} from "next/document";
import createEmotionCache from "../lib/createEmotionCache";
import { MyAppProps } from "./_app";
interface MyDocumentProps extends DocumentProps {
emotionStyleTags: JSX.Element[];
}
export default function MyDocument({ emotionStyleTags }: MyDocumentProps) {
const theme = useTheme();
export default function Document() {
return ( return (
<Html lang="en"> <Html lang='en'>
<Head /> <Head>
{/* PWA primary color */}
<meta name='theme-color' content={theme.palette.primary.main} />
<link rel='shortcut icon' href='/favicon.png' />
<link rel='manifest' href='/manifest.json' />
<meta name='emotion-insertion-point' content='' />
{emotionStyleTags}
</Head>
<body> <body>
<Main /> <Main />
<NextScript /> <NextScript />
@ -11,3 +36,64 @@ export default function Document() {
</Html> </Html>
); );
} }
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with static-site generation (SSG).
MyDocument.getInitialProps = async (ctx: DocumentContext) => {
// Resolution order
//
// On the server:
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. document.getInitialProps
// 4. app.render
// 5. page.render
// 6. document.render
//
// On the server with error:
// 1. document.getInitialProps
// 2. app.render
// 3. page.render
// 4. document.render
//
// On the client
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. app.render
// 4. page.render
const originalRenderPage = ctx.renderPage;
// You can consider sharing the same Emotion cache between all the SSR requests to speed up performance.
// However, be aware that it can have global side effects.
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (
App: React.ComponentType<React.ComponentProps<AppType> & MyAppProps>,
) =>
function EnhanceApp(props) {
return <App emotionCache={cache} {...props} />;
},
});
const initialProps = await Document.getInitialProps(ctx);
// This is important. It prevents Emotion to render invalid HTML.
// See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map((style) => (
<style
data-emotion={`${style.key} ${style.ids.join(" ")}`}
key={style.key}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: style.css }}
/>
));
return {
...initialProps,
emotionStyleTags,
};
};

View file

@ -1,114 +1,10 @@
import Head from "next/head"; import { Box, Button } from "@mui/material";
import Image from "next/image";
import { Inter } from "next/font/google";
import styles from "@/styles/Home.module.css";
const inter = Inter({ subsets: ["latin"] });
export default function Home() { export default function Home() {
return ( return (
<> <Box>
<Head> <h1>WhensApp</h1>
<title>Create Next App</title> <Button>hello</Button>
<meta name="description" content="Generated by create next app" /> </Box>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={`${styles.main} ${inter.className}`}>
<div className={styles.description}>
<p>
Get started by editing&nbsp;
<code className={styles.code}>src/pages/index.tsx</code>
</p>
<div>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
By{" "}
<Image
src="/vercel.svg"
alt="Vercel Logo"
className={styles.vercelLogo}
width={100}
height={24}
priority
/>
</a>
</div>
</div>
<div className={styles.center}>
<Image
className={styles.logo}
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</div>
<div className={styles.grid}>
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Docs <span>-&gt;</span>
</h2>
<p>
Find in-depth information about Next.js features and&nbsp;API.
</p>
</a>
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Learn <span>-&gt;</span>
</h2>
<p>
Learn about Next.js in an interactive course with&nbsp;quizzes!
</p>
</a>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Templates <span>-&gt;</span>
</h2>
<p>
Discover and deploy boilerplate example Next.js&nbsp;projects.
</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Deploy <span>-&gt;</span>
</h2>
<p>
Instantly deploy your Next.js site to a shareable URL
with&nbsp;Vercel.
</p>
</a>
</div>
</main>
</>
); );
} }

View file

@ -1,229 +0,0 @@
.main {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
padding: 6rem;
min-height: 100vh;
}
.description {
display: inherit;
justify-content: inherit;
align-items: inherit;
font-size: 0.85rem;
max-width: var(--max-width);
width: 100%;
z-index: 2;
font-family: var(--font-mono);
}
.description a {
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
}
.description p {
position: relative;
margin: 0;
padding: 1rem;
background-color: rgba(var(--callout-rgb), 0.5);
border: 1px solid rgba(var(--callout-border-rgb), 0.3);
border-radius: var(--border-radius);
}
.code {
font-weight: 700;
font-family: var(--font-mono);
}
.grid {
display: grid;
grid-template-columns: repeat(4, minmax(25%, auto));
max-width: 100%;
width: var(--max-width);
}
.card {
padding: 1rem 1.2rem;
border-radius: var(--border-radius);
background: rgba(var(--card-rgb), 0);
border: 1px solid rgba(var(--card-border-rgb), 0);
transition: background 200ms, border 200ms;
}
.card span {
display: inline-block;
transition: transform 200ms;
}
.card h2 {
font-weight: 600;
margin-bottom: 0.7rem;
}
.card p {
margin: 0;
opacity: 0.6;
font-size: 0.9rem;
line-height: 1.5;
max-width: 30ch;
}
.center {
display: flex;
justify-content: center;
align-items: center;
position: relative;
padding: 4rem 0;
}
.center::before {
background: var(--secondary-glow);
border-radius: 50%;
width: 480px;
height: 360px;
margin-left: -400px;
}
.center::after {
background: var(--primary-glow);
width: 240px;
height: 180px;
z-index: -1;
}
.center::before,
.center::after {
content: "";
left: 50%;
position: absolute;
filter: blur(45px);
transform: translateZ(0);
}
.logo {
position: relative;
}
/* Enable hover only on non-touch devices */
@media (hover: hover) and (pointer: fine) {
.card:hover {
background: rgba(var(--card-rgb), 0.1);
border: 1px solid rgba(var(--card-border-rgb), 0.15);
}
.card:hover span {
transform: translateX(4px);
}
}
@media (prefers-reduced-motion) {
.card:hover span {
transform: none;
}
}
/* Mobile */
@media (max-width: 700px) {
.content {
padding: 4rem;
}
.grid {
grid-template-columns: 1fr;
margin-bottom: 120px;
max-width: 320px;
text-align: center;
}
.card {
padding: 1rem 2.5rem;
}
.card h2 {
margin-bottom: 0.5rem;
}
.center {
padding: 8rem 0 6rem;
}
.center::before {
transform: none;
height: 300px;
}
.description {
font-size: 0.8rem;
}
.description a {
padding: 1rem;
}
.description p,
.description div {
display: flex;
justify-content: center;
position: fixed;
width: 100%;
}
.description p {
align-items: center;
inset: 0 0 auto;
padding: 2rem 1rem 1.4rem;
border-radius: 0;
border: none;
border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
background: linear-gradient(
to bottom,
rgba(var(--background-start-rgb), 1),
rgba(var(--callout-rgb), 0.5)
);
background-clip: padding-box;
backdrop-filter: blur(24px);
}
.description div {
align-items: flex-end;
pointer-events: none;
inset: auto 0 0;
padding: 2rem;
height: 200px;
background: linear-gradient(
to bottom,
transparent 0%,
rgb(var(--background-end-rgb)) 40%
);
z-index: 1;
}
}
/* Tablet and Smaller Desktop */
@media (min-width: 701px) and (max-width: 1120px) {
.grid {
grid-template-columns: repeat(2, 50%);
}
}
@media (prefers-color-scheme: dark) {
.vercelLogo {
filter: invert(1);
}
.logo {
filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
}
}
@keyframes rotate {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}

129
src/styles/global.css Normal file
View file

@ -0,0 +1,129 @@
@font-face {
font-family: 'JetBrainsMono';
src: url('../../public/fonts/JetBrainsMono/JetBrainsMono-VariableFont_wght.ttf') format('truetype-variations');
font-style: normal italic;
font-display: swap;
}
@font-face {
font-family: 'GoogleSans';
src: url('../../public/fonts/GoogleSans/GoogleSans-Regular.ttf') format('truetype');
font-style: normal;
font-weight: 400;
}
@font-face {
font-family: 'GoogleSans';
src: url('../../public/fonts/GoogleSans/GoogleSans-Medium.ttf') format('truetype');
font-style: normal;
font-weight: 500;
}
@font-face {
font-family: 'GoogleSans';
src: url('../../public/fonts/GoogleSans/GoogleSans-Bold.ttf') format('truetype');
font-style: normal;
font-weight: 700;
}
@font-face {
font-family: 'GoogleSans';
src: url('../../public/fonts/GoogleSans/GoogleSans-Italic.ttf') format('truetype');
font-style: italic;
font-weight: 400;
}
@font-face {
font-family: 'GoogleSans';
src: url('../../public/fonts/GoogleSans/GoogleSans-MediumItalic.ttf') format('truetype');
font-style: italic;
font-weight: 500;
}
@font-face {
font-family: 'GoogleSans';
src: url('../../public/fonts/GoogleSans/GoogleSans-BoldItalic.ttf') format('truetype');
font-style: italic;
font-weight: 700;
}
@font-face {
font-family: 'GoogleSans';
src: url('../../public/fonts/GoogleSans/GoogleSans-Thin.ttf') format('truetype');
font-style: normal;
font-weight: 100;
}
@font-face {
font-family: 'GoogleSans';
src: url('../../public/fonts/GoogleSans/GoogleSans-ThinItalic.ttf') format('truetype');
font-style: italic;
font-weight: 100;
}
@font-face {
font-family: 'GoogleSans';
src: url('../../public/fonts/GoogleSans/GoogleSans-Light.ttf') format('truetype');
font-style: normal;
font-weight: 300;
}
@font-face {
font-family: 'GoogleSans';
src: url('../../public/fonts/GoogleSans/GoogleSans-LightItalic.ttf') format('truetype');
font-style: italic;
font-weight: 300;
}
@font-face {
font-family: 'GoogleSans';
src: url('../../public/fonts/GoogleSans/GoogleSans-Black.ttf') format('truetype');
font-style: normal;
font-weight: 900;
}
@font-face {
font-family: 'GoogleSans';
src: url('../../public/fonts/GoogleSans/GoogleSans-BlackItalic.ttf') format('truetype');
font-style: italic;
font-weight: 900;
}
@font-face {
font-family: 'GoogleSans';
src: url('../../public/fonts/GoogleSans/GoogleSans-ThinItalic.ttf') format('truetype');
font-style: italic;
font-weight: 100;
}
@font-face {
font-family: 'ComicSans';
src: url('../../public/fonts/ComicSans/comic.ttf') format('truetype');
font-style: normal;
font-weight: 400;
}
@font-face {
font-family: 'ComicSans';
src: url('../../public/fonts/ComicSans/comicbd.ttf') format('truetype');
font-style: normal;
font-weight: 700;
}
@font-face {
font-family: 'ComicSans';
src: url('../../public/fonts/ComicSans/comici.ttf') format('truetype');
font-style: italic;
font-weight: 400;
}
@font-face {
font-family: 'ComicSans';
src: url('../../public/fonts/ComicSans/comicz.ttf') format('truetype');
font-style: italic;
font-weight: 700;
}
html {
color-scheme: dark;
}

View file

@ -1,107 +0,0 @@
:root {
--max-width: 1100px;
--border-radius: 12px;
--font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono",
"Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
"Fira Mono", "Droid Sans Mono", "Courier New", monospace;
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
--primary-glow: conic-gradient(
from 180deg at 50% 50%,
#16abff33 0deg,
#0885ff33 55deg,
#54d6ff33 120deg,
#0071ff33 160deg,
transparent 360deg
);
--secondary-glow: radial-gradient(
rgba(255, 255, 255, 1),
rgba(255, 255, 255, 0)
);
--tile-start-rgb: 239, 245, 249;
--tile-end-rgb: 228, 232, 233;
--tile-border: conic-gradient(
#00000080,
#00000040,
#00000030,
#00000020,
#00000010,
#00000010,
#00000080
);
--callout-rgb: 238, 240, 241;
--callout-border-rgb: 172, 175, 176;
--card-rgb: 180, 185, 188;
--card-border-rgb: 131, 134, 135;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
--primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
--secondary-glow: linear-gradient(
to bottom right,
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0.3)
);
--tile-start-rgb: 2, 13, 46;
--tile-end-rgb: 2, 5, 19;
--tile-border: conic-gradient(
#ffffff80,
#ffffff40,
#ffffff30,
#ffffff20,
#ffffff10,
#ffffff10,
#ffffff80
);
--callout-rgb: 20, 20, 20;
--callout-border-rgb: 108, 108, 108;
--card-rgb: 100, 100, 100;
--card-border-rgb: 200, 200, 200;
}
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
a {
color: inherit;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}

View file

@ -16,11 +16,6 @@
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "preserve",
"incremental": true, "incremental": true,
"paths": {
"@/*": [
"./src/*"
]
}
}, },
"include": [ "include": [
"next-env.d.ts", "next-env.d.ts",