Allow users to edit their own order sets
This commit is contained in:
parent
06002c579b
commit
fd654153a4
10 changed files with 273 additions and 161 deletions
35
flask/api.py
35
flask/api.py
|
|
@ -6,6 +6,13 @@ from db.queries import user_get, domsubusers_list, orders_pool_list, orders_pool
|
||||||
|
|
||||||
api = Blueprint('api', __name__)
|
api = Blueprint('api', __name__)
|
||||||
|
|
||||||
|
@api.route("/me")
|
||||||
|
@login_required
|
||||||
|
def me():
|
||||||
|
return jsonify({
|
||||||
|
"username": current_user.db_user.telegram_username
|
||||||
|
})
|
||||||
|
|
||||||
@api.route('/subs')
|
@api.route('/subs')
|
||||||
@login_required
|
@login_required
|
||||||
def subs():
|
def subs():
|
||||||
|
|
@ -29,7 +36,8 @@ def authorized_sub(func):
|
||||||
abort(403)
|
abort(403)
|
||||||
return
|
return
|
||||||
|
|
||||||
if sub.telegram_username not in [dsu.sub.telegram_username for dsu in domsubusers_list(current_user.db_user)]:
|
if (sub.id != current_user.db_user.id and
|
||||||
|
sub.telegram_username not in [dsu.sub.telegram_username for dsu in domsubusers_list(current_user.db_user)]):
|
||||||
abort(403)
|
abort(403)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -37,7 +45,26 @@ def authorized_sub(func):
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
@api.route('/subs/<username>/sets')
|
@api.route('/orders/')
|
||||||
|
@login_required
|
||||||
|
def my_order_sets():
|
||||||
|
return jsonify([
|
||||||
|
{
|
||||||
|
'id': op.id,
|
||||||
|
'name': op.name,
|
||||||
|
'scheduled': op.scheduled,
|
||||||
|
'time': op.time,
|
||||||
|
'weekends': op.weekends,
|
||||||
|
'weekdays': op.weekdays,
|
||||||
|
'orders': [{
|
||||||
|
'id': order.id,
|
||||||
|
} for order in op.orders]
|
||||||
|
}
|
||||||
|
for op
|
||||||
|
in orders_pool_list(current_user.db_user)
|
||||||
|
])
|
||||||
|
|
||||||
|
@api.route('/orders/<username>/sets')
|
||||||
@login_required
|
@login_required
|
||||||
@authorized_sub
|
@authorized_sub
|
||||||
def sub_order_sets(username, sub):
|
def sub_order_sets(username, sub):
|
||||||
|
|
@ -57,7 +84,7 @@ def sub_order_sets(username, sub):
|
||||||
in orders_pool_list(sub.id)
|
in orders_pool_list(sub.id)
|
||||||
])
|
])
|
||||||
|
|
||||||
@api.route('/subs/<username>/sets/', methods=['POST'])
|
@api.route('/orders/<username>/sets/', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@authorized_sub
|
@authorized_sub
|
||||||
def sub_order_set_create(username, sub):
|
def sub_order_set_create(username, sub):
|
||||||
|
|
@ -95,7 +122,7 @@ def sub_order_set_create(username, sub):
|
||||||
|
|
||||||
return jsonify(new_order_pool.to_dict())
|
return jsonify(new_order_pool.to_dict())
|
||||||
|
|
||||||
@api.route('/subs/<username>/sets/<set_id>', methods = ['GET', 'POST', 'DELETE'])
|
@api.route('/orders/<username>/sets/<set_id>', methods = ['GET', 'POST', 'DELETE'])
|
||||||
@login_required
|
@login_required
|
||||||
@authorized_sub
|
@authorized_sub
|
||||||
def sub_order_set(username, set_id, sub):
|
def sub_order_set(username, set_id, sub):
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ def index():
|
||||||
return redirect('/dashboard/')
|
return redirect('/dashboard/')
|
||||||
|
|
||||||
@app.route('/dashboard/', defaults={'path': ''})
|
@app.route('/dashboard/', defaults={'path': ''})
|
||||||
|
@app.route('/orders/<path:path>')
|
||||||
@app.route('/dashboard/<path:path>')
|
@app.route('/dashboard/<path:path>')
|
||||||
@login_required
|
@login_required
|
||||||
def dashboard(path):
|
def dashboard(path):
|
||||||
|
|
|
||||||
71
flask/vite/src/Dashboard.tsx
Normal file
71
flask/vite/src/Dashboard.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Container, Text, Title, Flex, Card, Image, Box } from "@mantine/core";
|
||||||
|
import { useLoaderData } from "react-router";
|
||||||
|
import { IconPencil } from "@tabler/icons-react";
|
||||||
|
import { NavigateButton } from "./NavigateButton";
|
||||||
|
import { OrderSetProps, OrderSets } from "./OrderSets";
|
||||||
|
import { useUserContext } from "./UserContext";
|
||||||
|
|
||||||
|
export const subsListLoader = () =>
|
||||||
|
Promise.all([
|
||||||
|
fetch("/api/orders/").then((response) => response.json()),
|
||||||
|
fetch("/api/subs").then((response) => response.json()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
interface SubsListProps {
|
||||||
|
subs: { sub_username: string; telegram_photo_url: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubsList: React.FC<SubsListProps> = ({ subs }) => (
|
||||||
|
<>
|
||||||
|
<Title order={1} mb="lg">
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
{telegram_photo_url ? (
|
||||||
|
<Card.Section>
|
||||||
|
<Image
|
||||||
|
src={telegram_photo_url}
|
||||||
|
height={280}
|
||||||
|
alt={`Profile picture for ${sub_username}`}
|
||||||
|
/>
|
||||||
|
</Card.Section>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Flex direction="column" gap="md" mt="md" h="100%">
|
||||||
|
<Text fw={500}>{sub_username}</Text>
|
||||||
|
<Flex gap="md" justify="end">
|
||||||
|
<NavigateButton to={`/orders/${sub_username}`}>
|
||||||
|
<IconPencil style={{ marginRight: "0.5rem" }} />
|
||||||
|
Edit
|
||||||
|
</NavigateButton>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Dashboard: React.FC = () => {
|
||||||
|
const [orderSets, subs] =
|
||||||
|
useLoaderData<[OrderSetProps["orderSets"], SubsListProps["subs"]]>();
|
||||||
|
const { username } = useUserContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<OrderSets orderSets={orderSets} username={username} />
|
||||||
|
{subs.length > 0 ? <SubsList subs={subs} /> : null}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -31,16 +31,16 @@ import {
|
||||||
import { IconMinus, IconPlus } from "@tabler/icons-react";
|
import { IconMinus, IconPlus } from "@tabler/icons-react";
|
||||||
import { fetchHeaders } from "./fetch";
|
import { fetchHeaders } from "./fetch";
|
||||||
|
|
||||||
export const subOrderSetLoader = async ({
|
export const orderSetLoader = async ({
|
||||||
params: { username, set_id },
|
params: { username, set_id },
|
||||||
}: {
|
}: {
|
||||||
params: Params<string>;
|
params: Params<string>;
|
||||||
}) =>
|
}) =>
|
||||||
fetch(`/api/subs/${username}/sets/${set_id}`).then((response) =>
|
fetch(`/api/orders/${username}/sets/${set_id}`).then((response) =>
|
||||||
response.json(),
|
response.json(),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const subOrderSetAction = async ({
|
export const orderSetAction = async ({
|
||||||
request,
|
request,
|
||||||
params: { username, set_id: id },
|
params: { username, set_id: id },
|
||||||
}: {
|
}: {
|
||||||
|
|
@ -48,7 +48,7 @@ export const subOrderSetAction = async ({
|
||||||
params: Params<string>;
|
params: Params<string>;
|
||||||
}) => {
|
}) => {
|
||||||
if (request.method == "DELETE") {
|
if (request.method == "DELETE") {
|
||||||
const response = await fetch(`/api/subs/${username}/sets/${id}`, {
|
const response = await fetch(`/api/orders/${username}/sets/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: fetchHeaders(),
|
headers: fetchHeaders(),
|
||||||
});
|
});
|
||||||
|
|
@ -72,7 +72,7 @@ type FormOrderSet = Omit<OrderSet, "orders"> & {
|
||||||
orders: FormOrderSetOrder[];
|
orders: FormOrderSetOrder[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SubOrderSet: React.FC = () => {
|
export const OrderSet: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { username, set_id } = useParams();
|
const { username, set_id } = useParams();
|
||||||
const orderSet = useLoaderData<OrderSet>();
|
const orderSet = useLoaderData<OrderSet>();
|
||||||
|
|
@ -80,7 +80,7 @@ export const SubOrderSet: React.FC = () => {
|
||||||
orderSet?.scheduled,
|
orderSet?.scheduled,
|
||||||
);
|
);
|
||||||
|
|
||||||
const form = useForm<FormOrderSet>({
|
const form = useForm<Partial<FormOrderSet>>({
|
||||||
mode: "uncontrolled",
|
mode: "uncontrolled",
|
||||||
initialValues: orderSet ?? {
|
initialValues: orderSet ?? {
|
||||||
scheduled: false,
|
scheduled: false,
|
||||||
|
|
@ -115,7 +115,7 @@ export const SubOrderSet: React.FC = () => {
|
||||||
|
|
||||||
const handleSubmit = React.useCallback(
|
const handleSubmit = React.useCallback(
|
||||||
form.onSubmit((values) => {
|
form.onSubmit((values) => {
|
||||||
fetch(`/api/subs/${username}/sets/${set_id || ""}`, {
|
fetch(`/api/orders/${username}/sets/${set_id || ""}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: fetchHeaders(),
|
headers: fetchHeaders(),
|
||||||
body: JSON.stringify(values),
|
body: JSON.stringify(values),
|
||||||
|
|
@ -126,7 +126,7 @@ export const SubOrderSet: React.FC = () => {
|
||||||
message: `Updates to ${values.name} were successful`,
|
message: `Updates to ${values.name} were successful`,
|
||||||
color: "green",
|
color: "green",
|
||||||
});
|
});
|
||||||
navigate(`/dashboard/subs/${username}`);
|
navigate(`/orders/${username}`);
|
||||||
} else {
|
} else {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
|
|
@ -193,7 +193,7 @@ export const SubOrderSet: React.FC = () => {
|
||||||
<Container pb="xl">
|
<Container pb="xl">
|
||||||
<Box mb="lg">
|
<Box mb="lg">
|
||||||
<Title order={1}>{orderSet?.name || "New Order Set"}</Title>
|
<Title order={1}>{orderSet?.name || "New Order Set"}</Title>
|
||||||
<Link to={`/dashboard/subs/${username}`}>Return to {username}</Link>
|
<Link to={`/orders/${username}`}>Return to {username}</Link>
|
||||||
</Box>
|
</Box>
|
||||||
<form id="order-set" onSubmit={handleSubmit}>
|
<form id="order-set" onSubmit={handleSubmit}>
|
||||||
<TextInput {...form.getInputProps("name")} label="Name" />
|
<TextInput {...form.getInputProps("name")} label="Name" />
|
||||||
95
flask/vite/src/OrderSets.tsx
Normal file
95
flask/vite/src/OrderSets.tsx
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Title, Card, Text, Flex, Badge, Box } from "@mantine/core";
|
||||||
|
import { TimeValue } from "@mantine/dates";
|
||||||
|
import { IconPencil, IconPlus, IconTrash } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
import { ConfirmDialogButton } from "./ConfirmDialogButton";
|
||||||
|
import { NavigateButton } from "./NavigateButton";
|
||||||
|
import { useFetcher } from "react-router";
|
||||||
|
|
||||||
|
export interface OrderSetProps {
|
||||||
|
orderSets: (Pick<
|
||||||
|
OrderSet,
|
||||||
|
"id" | "name" | "scheduled" | "time" | "weekends" | "weekdays" | "orders"
|
||||||
|
> & { orders: Pick<OrderSetOrder, "id" | "name"> })[];
|
||||||
|
username: string;
|
||||||
|
linkBack?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrderSets: React.FC<OrderSetProps> = ({
|
||||||
|
orderSets,
|
||||||
|
username,
|
||||||
|
linkBack,
|
||||||
|
}) => {
|
||||||
|
const fetcher = useFetcher();
|
||||||
|
const handleDelete = React.useCallback(
|
||||||
|
(id: number) => {
|
||||||
|
fetcher.submit(null, {
|
||||||
|
action: `/orders/${username}/${id}`,
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[fetcher],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box mb="lg">
|
||||||
|
<Title order={1}>Order Sets for {username}</Title>
|
||||||
|
{linkBack ? linkBack : null}
|
||||||
|
</Box>
|
||||||
|
<Flex gap="md" wrap="wrap">
|
||||||
|
{orderSets
|
||||||
|
? orderSets.map(
|
||||||
|
({ id, name, scheduled, orders, time, weekdays, weekends }) => (
|
||||||
|
<Card
|
||||||
|
key={id}
|
||||||
|
shadow="sm"
|
||||||
|
padding="lg"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg="gray.2"
|
||||||
|
w="400px"
|
||||||
|
>
|
||||||
|
<Flex direction="column" gap="md" h="100%">
|
||||||
|
<Title order={4}>{name}</Title>
|
||||||
|
{scheduled ? (
|
||||||
|
<Flex gap="md" align="center">
|
||||||
|
<Text>
|
||||||
|
Scheduled: <TimeValue value={time} format="12h" />
|
||||||
|
</Text>
|
||||||
|
{weekdays ? <Badge color="blue">Weekdays</Badge> : null}
|
||||||
|
{weekends ? <Badge color="blue">Weekends</Badge> : null}
|
||||||
|
</Flex>
|
||||||
|
) : null}
|
||||||
|
<Text mb="md" flex={1}>
|
||||||
|
Orders: {orders.length}
|
||||||
|
</Text>
|
||||||
|
<Flex gap="md" justify="end">
|
||||||
|
<ConfirmDialogButton
|
||||||
|
buttonColor="red.8"
|
||||||
|
buttonText="Delete"
|
||||||
|
text={`Are you sure you want to delete ${name}?`}
|
||||||
|
onConfirm={() => handleDelete(id)}
|
||||||
|
>
|
||||||
|
<IconTrash />
|
||||||
|
</ConfirmDialogButton>
|
||||||
|
<NavigateButton to={`/orders/${username}/${id}`}>
|
||||||
|
<IconPencil style={{ marginRight: "0.5rem" }} />
|
||||||
|
Edit
|
||||||
|
</NavigateButton>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</Flex>
|
||||||
|
<NavigateButton to={`/orders/${username}/new`}>
|
||||||
|
<IconPlus style={{ marginRight: "0.5rem" }} />
|
||||||
|
New
|
||||||
|
</NavigateButton>
|
||||||
|
<Box mb="lg"></Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -11,95 +11,25 @@ import {
|
||||||
} from "react-router";
|
} from "react-router";
|
||||||
import { ConfirmDialogButton } from "./ConfirmDialogButton";
|
import { ConfirmDialogButton } from "./ConfirmDialogButton";
|
||||||
import { NavigateButton } from "./NavigateButton";
|
import { NavigateButton } from "./NavigateButton";
|
||||||
import { fetchHeaders } from "./fetch";
|
import { OrderSetProps, OrderSets } from "./OrderSets";
|
||||||
|
|
||||||
export const subOrderSetsLoader = async ({
|
export const subOrderSetsLoader = async ({
|
||||||
params: { username },
|
params: { username },
|
||||||
}: {
|
}: {
|
||||||
params: Params<string>;
|
params: Params<string>;
|
||||||
}) => fetch(`/api/subs/${username}/sets`).then((response) => response.json());
|
}) => fetch(`/api/orders/${username}/sets`).then((response) => response.json());
|
||||||
|
|
||||||
export const SubOrderSets: React.FC = () => {
|
export const SubOrderSets: React.FC = () => {
|
||||||
const fetcher = useFetcher();
|
|
||||||
const { username: sub_username } = useParams();
|
const { username: sub_username } = useParams();
|
||||||
const orderSets = useLoaderData<
|
const orderSets = useLoaderData<OrderSetProps["orderSets"]>();
|
||||||
(Pick<
|
|
||||||
OrderSet,
|
|
||||||
"id" | "name" | "scheduled" | "time" | "weekends" | "weekdays" | "orders"
|
|
||||||
> & {
|
|
||||||
orders: Pick<OrderSetOrder, "id" | "name">[];
|
|
||||||
})[]
|
|
||||||
>();
|
|
||||||
|
|
||||||
const handleDelete = React.useCallback(
|
|
||||||
(id: number) => {
|
|
||||||
fetcher.submit(null, {
|
|
||||||
action: `/dashboard/subs/${sub_username}/${id}`,
|
|
||||||
method: "DELETE",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[fetcher],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Box mb="lg">
|
<OrderSets
|
||||||
<Title order={1}>Order Sets for {sub_username}</Title>
|
username={sub_username}
|
||||||
<Link to={`/dashboard/`}>Return to all subs</Link>
|
orderSets={orderSets}
|
||||||
</Box>
|
linkBack={<Link to={`/dashboard/`}>Return to dashboard</Link>}
|
||||||
<Flex gap="md" wrap="wrap">
|
/>
|
||||||
{orderSets
|
|
||||||
? orderSets.map(
|
|
||||||
({ id, name, scheduled, orders, time, weekdays, weekends }) => (
|
|
||||||
<Card
|
|
||||||
key={id}
|
|
||||||
shadow="sm"
|
|
||||||
padding="lg"
|
|
||||||
radius="md"
|
|
||||||
withBorder
|
|
||||||
bg="gray.2"
|
|
||||||
w="400px"
|
|
||||||
>
|
|
||||||
<Flex direction="column" gap="md" h="100%">
|
|
||||||
<Title order={4}>{name}</Title>
|
|
||||||
{scheduled ? (
|
|
||||||
<Flex gap="md" align="center">
|
|
||||||
<Text>
|
|
||||||
Scheduled: <TimeValue value={time} format="12h" />
|
|
||||||
</Text>
|
|
||||||
{weekdays ? <Badge color="blue">Weekdays</Badge> : null}
|
|
||||||
{weekends ? <Badge color="blue">Weekends</Badge> : null}
|
|
||||||
</Flex>
|
|
||||||
) : null}
|
|
||||||
<Text mb="md" flex={1}>
|
|
||||||
Orders: {orders.length}
|
|
||||||
</Text>
|
|
||||||
<Flex gap="md" justify="end">
|
|
||||||
<ConfirmDialogButton
|
|
||||||
buttonColor="red.8"
|
|
||||||
buttonText="Delete"
|
|
||||||
text={`Are you sure you want to delete ${name}?`}
|
|
||||||
onConfirm={() => handleDelete(id)}
|
|
||||||
>
|
|
||||||
<IconTrash />
|
|
||||||
</ConfirmDialogButton>
|
|
||||||
<NavigateButton
|
|
||||||
to={`/dashboard/subs/${sub_username}/${id}`}
|
|
||||||
>
|
|
||||||
<IconPencil style={{ marginRight: "0.5rem" }} />
|
|
||||||
Edit
|
|
||||||
</NavigateButton>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null}
|
|
||||||
</Flex>
|
|
||||||
<NavigateButton to={`/dashboard/subs/${sub_username}/new`}>
|
|
||||||
<IconPlus style={{ marginRight: "0.5rem" }} />
|
|
||||||
New
|
|
||||||
</NavigateButton>
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { Container, Text, Title, Flex, Card, Image } from "@mantine/core";
|
|
||||||
import { useLoaderData } from "react-router";
|
|
||||||
import { IconPencil } from "@tabler/icons-react";
|
|
||||||
import { NavigateButton } from "./NavigateButton";
|
|
||||||
|
|
||||||
export const subsListLoader = () =>
|
|
||||||
fetch("/api/subs").then((response) => response.json());
|
|
||||||
|
|
||||||
export const SubsList: React.FC = () => {
|
|
||||||
const subs =
|
|
||||||
useLoaderData<{ sub_username: string; telegram_photo_url: string }[]>();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
<Title order={1} mb="lg">
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
{telegram_photo_url ? (
|
|
||||||
<Card.Section>
|
|
||||||
<Image
|
|
||||||
src={telegram_photo_url}
|
|
||||||
height={280}
|
|
||||||
alt={`Profile picture for ${sub_username}`}
|
|
||||||
/>
|
|
||||||
</Card.Section>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<Flex direction="column" gap="md" mt="md" h="100%">
|
|
||||||
<Text fw={500}>{sub_username}</Text>
|
|
||||||
<Flex gap="md" justify="end">
|
|
||||||
<NavigateButton to={`/dashboard/subs/${sub_username}`}>
|
|
||||||
<IconPencil style={{ marginRight: "0.5rem" }} />
|
|
||||||
Edit
|
|
||||||
</NavigateButton>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
35
flask/vite/src/UserContext.tsx
Normal file
35
flask/vite/src/UserContext.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export interface UserContextData {
|
||||||
|
username?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserContext = React.createContext<UserContextData>({});
|
||||||
|
|
||||||
|
export const useUserContext = (): UserContextData => {
|
||||||
|
return React.useContext(UserContext);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UserContextProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const [username, setUsername] = React.useState();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetch(`/api/me`)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ username }) => {
|
||||||
|
setUsername(username);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserContext.Provider
|
||||||
|
value={{
|
||||||
|
username,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</UserContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -15,13 +15,10 @@ import "@mantine/core/styles.css";
|
||||||
import "@mantine/dates/styles.css";
|
import "@mantine/dates/styles.css";
|
||||||
import "@mantine/notifications/styles.css";
|
import "@mantine/notifications/styles.css";
|
||||||
|
|
||||||
import { SubsList, subsListLoader } from "./SubsList";
|
import { Dashboard, subsListLoader } from "./Dashboard";
|
||||||
import { SubOrderSets, subOrderSetsLoader } from "./SubOrderSets";
|
import { SubOrderSets, subOrderSetsLoader } from "./SubOrderSets";
|
||||||
import {
|
import { OrderSet, orderSetLoader, orderSetAction } from "./OrderSet";
|
||||||
SubOrderSet,
|
import { UserContextProvider } from "./UserContext";
|
||||||
subOrderSetLoader,
|
|
||||||
subOrderSetAction,
|
|
||||||
} from "./SubOrderSet";
|
|
||||||
|
|
||||||
const theme = createTheme({
|
const theme = createTheme({
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -52,31 +49,33 @@ const theme = createTheme({
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
path: "dashboard",
|
path: "dashboard",
|
||||||
Component: SubsList,
|
Component: Dashboard,
|
||||||
loader: subsListLoader,
|
loader: subsListLoader,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "dashboard/subs/:username/",
|
path: "orders/:username",
|
||||||
Component: SubOrderSets,
|
Component: SubOrderSets,
|
||||||
loader: subOrderSetsLoader,
|
loader: subOrderSetsLoader,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "dashboard/subs/:username/new",
|
path: "orders/:username/new",
|
||||||
Component: SubOrderSet,
|
Component: OrderSet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "dashboard/subs/:username/:set_id",
|
path: "orders/:username/:set_id",
|
||||||
Component: SubOrderSet,
|
Component: OrderSet,
|
||||||
loader: subOrderSetLoader,
|
loader: orderSetLoader,
|
||||||
action: subOrderSetAction,
|
action: orderSetAction,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<MantineProvider theme={theme}>
|
<MantineProvider theme={theme}>
|
||||||
<Notifications />
|
<UserContextProvider>
|
||||||
<RouterProvider router={router} />
|
<Notifications />
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</UserContextProvider>
|
||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
8
flask/vite/tsconfig.json
Normal file
8
flask/vite/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"lib": ["ES2015", "DOM"],
|
||||||
|
"typeRoots": ["src/*.d.ts"],
|
||||||
|
"esModuleInterop": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue