Dark theme

This commit is contained in:
John Groszko 2026-04-07 18:46:38 -05:00
parent 01589eedeb
commit 4c5c8bec04
12 changed files with 209 additions and 97 deletions

View file

@ -186,7 +186,7 @@ def timeline_event_put(type, text, user, orders_pool=None, order_status=None, ac
extra=json.dumps(extra) if extra is not None else None
)
def timeline_event_recent(user_ids, actor_ids=None):
def timeline_event_recent(user_ids, actor_ids=None, limit=5):
return (TimelineEvent
.select()
.where((
@ -194,5 +194,5 @@ def timeline_event_recent(user_ids, actor_ids=None):
(TimelineEvent.actor_user_id.in_(actor_ids) if actor_ids is not None else True)
))
.order_by(TimelineEvent.updated_at.desc())
.limit(10)
.limit(limit)
)

View file

@ -1,7 +1,7 @@
import React from "react";
import { useDisclosure } from "@mantine/hooks";
import { Button, Modal, Text, Flex } from "@mantine/core";
import { Button, Modal, Text, Flex, ButtonVariant } from "@mantine/core";
export const ConfirmDialogButton: React.FC<{
text: string;
@ -9,7 +9,15 @@ export const ConfirmDialogButton: React.FC<{
buttonColor: string;
onConfirm: () => void;
children: React.ReactNode;
}> = ({ text, buttonText, buttonColor, onConfirm, children }) => {
variant?: ButtonVariant;
}> = ({
text,
buttonText,
buttonColor,
onConfirm,
children,
variant = "filled",
}) => {
const [opened, { open, close }] = useDisclosure();
const handleConfirm = React.useCallback(() => {
@ -22,13 +30,15 @@ export const ConfirmDialogButton: React.FC<{
<Modal opened={opened} onClose={close} p="sm" withCloseButton={false}>
<Text mb="xl">{text}</Text>
<Flex gap="md" justify="flex-end">
<Button onClick={close}>Cancel</Button>
<Button variant="outline" onClick={close}>
Cancel
</Button>
<Button color={buttonColor} onClick={handleConfirm}>
{buttonText}
</Button>
</Flex>
</Modal>
<Button color={buttonColor} onClick={open}>
<Button variant={variant} color={buttonColor} onClick={open}>
{children}
</Button>
</>

View file

@ -25,15 +25,7 @@ const SubsList: React.FC<SubsListProps> = ({ subs }) => (
</Title>
<Flex gap="md" wrap="wrap">
{subs.map(({ sub_username, telegram_photo_url }) => (
<Card
key={sub_username}
shadow="sm"
padding="lg"
radius="md"
withBorder
bg="gray.2"
w="320px"
>
<Card key={sub_username} padding="lg" withBorder w="320px">
{telegram_photo_url ? (
<Card.Section>
<Image

View file

@ -1,7 +1,14 @@
import { Avatar, Text, Container, Flex, UnstyledButton } from "@mantine/core";
import {
Avatar,
Text,
Container,
Flex,
UnstyledButton,
Anchor,
} from "@mantine/core";
import React from "react";
import { useUserContext } from "./UserContext";
import { Outlet, useNavigate } from "react-router";
import { Link, Outlet, useNavigate } from "react-router";
export const Header: React.FC = () => {
const { username, telegram_photo_url } = useUserContext();
@ -14,12 +21,12 @@ export const Header: React.FC = () => {
return (
<Container p="sm">
<Flex justify="flex-end">
<UnstyledButton onClick={handleClick}>
<Anchor component={Link} to={"/profile/"}>
<Flex align="center" gap="sm">
<Text c="blue">{username}</Text>
<Text>{username}</Text>
<Avatar src={telegram_photo_url} />
</Flex>
</UnstyledButton>
</Anchor>
</Flex>
<Outlet />
</Container>

View file

@ -16,6 +16,7 @@ import {
NumberInput,
Text,
Select,
Anchor,
} from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { TimeInput } from "@mantine/dates";
@ -248,7 +249,9 @@ export const OrderSet: React.FC = () => {
<>
<Box mb="lg">
<Title order={1}>{orderSet?.name || "New Order Set"}</Title>
<Link to={`/orders/${username}`}>Return to {username}</Link>
<Anchor component={Link} to={`/orders/${username}`}>
Return to {username}
</Anchor>
</Box>
<form id="order-set" onSubmit={handleSubmit}>
<TextInput {...form.getInputProps("name")} label="Name" />
@ -259,7 +262,7 @@ export const OrderSet: React.FC = () => {
mt="lg"
/>
<Collapse in={showScheduling}>
<Paper bg="gray.2">
<Paper>
<Input.Wrapper
error={form.getInputProps("probability").error}
label="Probability"
@ -316,7 +319,7 @@ export const OrderSet: React.FC = () => {
</Title>
{form.getValues().orders.map(({ id: order_id, _delete }, idx) =>
_delete ? null : (
<Paper key={order_id} bg="gray.2">
<Paper key={order_id}>
<Flex gap="xl" justify="space-between">
<TextInput
{...form.getInputProps(`orders.${idx}.name`)}
@ -406,7 +409,7 @@ export const OrderSet: React.FC = () => {
</Button>
<Box my="40px" />
<Affix position={{ bottom: 0 }} w="100%">
<Paper mb={0} bg="gray.1">
<Paper mb={0}>
<Container>
<Flex justify="flex-end">
<Button type="submit" form="order-set">

View file

@ -9,6 +9,7 @@ import {
RingProgress,
Alert,
Grid,
Anchor,
} from "@mantine/core";
import { IconAlertTriangle } from "@tabler/icons-react";
import { TimeValue } from "@mantine/dates";
@ -21,18 +22,14 @@ import { useUserContext } from "./UserContext";
import { DonutChart } from "@mantine/charts";
const COLORS_ROTATION = [
"teal",
"pink",
"lime",
"violet",
"orange",
"blue",
"yellow",
"grape",
"green",
"red",
"cyan",
"gray",
"orange.8",
"gray.7",
"orange.7",
"gray.6",
"orange.6",
"gray.5",
"orange.5",
"gray.4",
];
export interface OrderSetProps {
@ -107,7 +104,9 @@ export const OrderSets: React.FC<OrderSetProps> = ({
{username === current_user ? (
<>
<br />
<Link to="/profile/">Edit Profile</Link>
<Anchor component={Link} to="/profile/">
Edit Profile
</Anchor>
</>
) : null}
</Alert>
@ -140,7 +139,6 @@ export const OrderSets: React.FC<OrderSetProps> = ({
padding="lg"
radius="md"
withBorder
bg="gray.2"
mb="0"
>
<Flex direction="column" gap="md" h="100%">
@ -163,10 +161,10 @@ export const OrderSets: React.FC<OrderSetProps> = ({
</Flex>
<Flex gap="md" align="center">
{weekdays ? (
<Badge color="blue">Weekdays</Badge>
<Badge color="orange.7">Weekdays</Badge>
) : null}
{weekends ? (
<Badge color="blue">Weekends</Badge>
<Badge color="orange.7">Weekends</Badge>
) : null}
<RingProgress
size={30}
@ -188,7 +186,7 @@ export const OrderSets: React.FC<OrderSetProps> = ({
<b>Punishments:</b> {punishment_pool_name}
</Text>
) : null}
<Flex justify="end" align="flex-end" gap="md">
<Flex justify="end" align="flex-end" gap="sm">
{orders.length > 0 ? (
<DonutChart
flex={1}
@ -206,6 +204,7 @@ export const OrderSets: React.FC<OrderSetProps> = ({
<ConfirmDialogButton
buttonColor="red.8"
buttonText="Delete"
variant="transparent"
text={`Are you sure you want to delete ${name}?`}
onConfirm={() => handleDelete(id)}
>

View file

@ -1,6 +1,7 @@
import React from "react";
import { useUserContext } from "./UserContext";
import {
Anchor,
Avatar,
Box,
Button,
@ -134,9 +135,11 @@ export const Profile: React.FC = () => {
<Title>{username}</Title>
</Flex>
<Box mb="md">
<Link to={`/dashboard/`}>Return to dashboard</Link>
<Anchor component={Link} to={`/dashboard/`}>
Return to dashboard
</Anchor>
</Box>
<Paper bg="gray.1">
<Paper withBorder>
<Title order={4}>Mastodon</Title>
<TextInput label="Account" w="50%" value={mastodon_account} />
<Button onClick={open}>Authorize with Mastodon</Button>

View file

@ -75,7 +75,7 @@ export const ProfileVerification: React.FC<ProfileVerificationProps> = ({
);
return (
<Paper bg="gray.1">
<Paper>
<form onSubmit={handleSubmit}>
<Title order={4} mb="md">
Order Verification

View file

@ -2,7 +2,7 @@ import React from "react";
import { Params, useLoaderData, useParams, Link } from "react-router";
import { OrderSetProps, OrderSets } from "./OrderSets";
import { ProfileVerification } from "./ProfileVerification";
import { Title } from "@mantine/core";
import { Anchor, Title } from "@mantine/core";
export const subOrderSetsLoader = async ({
params: { username },
@ -26,7 +26,11 @@ export const SubOrderSets: React.FC = () => {
<OrderSets
username={sub_username}
orderSets={orderSets}
linkBack={<Link to={`/dashboard/`}>Return to dashboard</Link>}
linkBack={
<Anchor component={Link} to={`/dashboard/`}>
Return to dashboard
</Anchor>
}
/>
{profile ? (
<>

View file

@ -1,4 +1,4 @@
import { Timeline, Text, Title, Box, Flex } from "@mantine/core";
import { Timeline, Text, Title, Box, Flex, Card, Anchor } from "@mantine/core";
import React from "react";
import moment from "moment";
import { Link } from "react-router";
@ -65,55 +65,60 @@ export const TimelineList: React.FC<{
<Title order={1} mb="lg">
Timeline
</Title>
<Timeline bulletSize={24} lineWidth={2} my="lg">
{timeline.map(
({
id,
updated_at,
type,
text,
username,
actor_username,
orders_pool,
extra,
}) => (
<Timeline.Item key={id} active {...(TIMELINE_TYPE[type] ?? {})}>
<Text size="sm">
{text.split("\n").map((str, idx) => (
<React.Fragment key={idx}>
{str}
<br />
</React.Fragment>
))}
</Text>
<Flex mt={4} gap="xs">
{extra?.mastodon_status_url ? (
<Text size="xs">
<a href={extra.mastodon_status_url} target="_blank">
Mastodon Post <IconExternalLink size="0.75rem" />
</a>
</Text>
) : null}
{orders_pool ? (
<Text size="xs">
<Link to={`/orders/${username}/${orders_pool.id}`}>
{orders_pool.name}
</Link>
</Text>
) : null}
{actor_username ? (
<Text size="xs">
<IconCrown size="0.75rem" /> {actor_username}
</Text>
) : null}
<Text size="xs">
<IconLock size="0.75rem" /> {username}
<Card py="xs">
<Timeline bulletSize={24} lineWidth={2} my="lg">
{timeline.map(
({
id,
updated_at,
type,
text,
username,
actor_username,
orders_pool,
extra,
}) => (
<Timeline.Item key={id} active {...(TIMELINE_TYPE[type] ?? {})}>
<Text size="sm">
{text.split("\n").map((str, idx) => (
<React.Fragment key={idx}>
{str}
<br />
</React.Fragment>
))}
</Text>
<Text size="xs">{moment(updated_at).fromNow()}</Text>
</Flex>
</Timeline.Item>
),
)}
</Timeline>
<Flex mt={4} gap="xs">
{extra?.mastodon_status_url ? (
<Text size="xs">
<Anchor href={extra.mastodon_status_url} target="_blank">
Mastodon Post <IconExternalLink size="0.75rem" />
</Anchor>
</Text>
) : null}
{orders_pool ? (
<Text size="xs">
<Anchor
component={Link}
to={`/orders/${username}/${orders_pool.id}`}
>
{orders_pool.name}
</Anchor>
</Text>
) : null}
{actor_username ? (
<Text size="xs">
<IconCrown size="0.75rem" /> {actor_username}
</Text>
) : null}
<Text size="xs">
<IconLock size="0.75rem" /> {username}
</Text>
<Text size="xs">{moment(updated_at).fromNow()}</Text>
</Flex>
</Timeline.Item>
),
)}
</Timeline>
</Card>
</Box>
);

View file

@ -8,9 +8,13 @@ import {
Input,
Paper,
Slider,
Image,
mergeThemeOverrides,
} from "@mantine/core";
import { Notifications } from "@mantine/notifications";
import { mantineTheme } from "./theme";
import "@mantine/core/styles.css";
import "@mantine/dates/styles.css";
import "@mantine/notifications/styles.css";
@ -46,6 +50,11 @@ const theme = createTheme({
},
}) as any,
}),
Image: Image.extend({
defaultProps: {
radius: "xs",
},
}),
},
});
@ -83,9 +92,11 @@ const router = createBrowserRouter([
},
]);
const mergedTheme = mergeThemeOverrides(mantineTheme, theme);
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<MantineProvider theme={theme}>
<MantineProvider defaultColorScheme="dark" theme={mergedTheme}>
<UserContextProvider>
<Notifications />
<RouterProvider router={router} />

78
web/vite/src/theme.ts Normal file
View file

@ -0,0 +1,78 @@
import { Card, Container, createTheme, Paper, rem, Select } from "@mantine/core";
import type { MantineThemeOverride } from "@mantine/core";
const CONTAINER_SIZES: Record<string, string> = {
xxs: rem("200px"),
xs: rem("300px"),
sm: rem("400px"),
md: rem("500px"),
lg: rem("600px"),
xl: rem("1400px"),
xxl: rem("1600px"),
};
export const mantineTheme: MantineThemeOverride = createTheme({
/** Put your mantine theme override here */
fontSizes: {
xs: rem("12px"),
sm: rem("14px"),
md: rem("16px"),
lg: rem("18px"),
xl: rem("20px"),
"2xl": rem("24px"),
"3xl": rem("30px"),
"4xl": rem("36px"),
"5xl": rem("48px"),
},
spacing: {
"3xs": rem("4px"),
"2xs": rem("8px"),
xs: rem("10px"),
sm: rem("12px"),
md: rem("16px"),
lg: rem("20px"),
xl: rem("24px"),
"2xl": rem("28px"),
"3xl": rem("32px"),
},
primaryColor: "orange",
components: {
/** Put your mantine component override here */
Container: Container.extend({
vars: (_, { size, fluid }) => ({
root: {
"--container-size": fluid
? "100%"
: size !== undefined && size in CONTAINER_SIZES
? CONTAINER_SIZES[size]
: rem(size),
},
}),
}),
Paper: Paper.extend({
defaultProps: {
p: "md",
shadow: "xl",
radius: "md",
withBorder: true,
},
}),
Card: Card.extend({
defaultProps: {
p: "xl",
shadow: "xl",
radius: "var(--mantine-radius-default)",
withBorder: true,
},
}),
Select: Select.extend({
defaultProps: {
checkIconPosition: "right",
},
}),
},
other: {
style: "mantine",
},
});