Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3178d4bff | ||
|
|
79fe5cac0f | ||
|
|
95d7d13f38 | ||
|
|
ce2cdfd409 | ||
|
|
b56d103ae7 |
No files matched your search
+3
-2
@@ -11,6 +11,7 @@ CACHE/
|
||||
media/
|
||||
tmp/
|
||||
frontend/dist
|
||||
static/modern
|
||||
static/assets
|
||||
static/index.html
|
||||
.python-version
|
||||
.env
|
||||
.env
|
||||
+15
-1
@@ -1,4 +1,6 @@
|
||||
import strawberry
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from strawberry.extensions import Extension
|
||||
|
||||
from bbs.api.mutations.shop import ShopMutations
|
||||
from bbs.api.mutations.users import UsersMutations
|
||||
@@ -17,4 +19,16 @@ class Mutation(ShopMutations, UsersMutations):
|
||||
pass
|
||||
|
||||
|
||||
schema = strawberry.Schema(query=Query, mutation=Mutation)
|
||||
class HandleNotFound(Extension):
|
||||
def on_request_end(self):
|
||||
result = self.execution_context.result
|
||||
if result.errors:
|
||||
for error in result.errors:
|
||||
if isinstance(error.original_error, ObjectDoesNotExist):
|
||||
error.extensions = {
|
||||
"code": "NOT_FOUND",
|
||||
"model": type(error.original_error).__qualname__.replace(".DoesNotExist", ""),
|
||||
}
|
||||
|
||||
|
||||
schema = strawberry.Schema(query=Query, mutation=Mutation, extensions=[HandleNotFound])
|
||||
Generated
+6
-6
@@ -8776,9 +8776,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
|
||||
"integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
@@ -19172,9 +19172,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
|
||||
"integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
|
||||
"dev": true
|
||||
},
|
||||
"cli-cursor": {
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Routes } from "./Routes";
|
||||
import client from "./client";
|
||||
import { Footer } from "./components/Footer";
|
||||
import { Header } from "./components/Header";
|
||||
import { Splash as Splash } from "./components/Splash";
|
||||
import { Toast } from "./components/Toast";
|
||||
import { ToastProvider } from "./components/ToastProvider";
|
||||
import { Wireframe } from "./components/Wireframe";
|
||||
|
||||
+24
-22
@@ -1,28 +1,30 @@
|
||||
import React from "react";
|
||||
import { Route, Routes as ReactRouterRoutes } from "react-router-dom";
|
||||
|
||||
import { Splash } from "./components/Splash";
|
||||
import { UserCreate } from "./features/auth/UserCreate";
|
||||
import { Deposit } from "./features/deposit/Deposit";
|
||||
import { Log } from "./features/log/Log";
|
||||
import { Register } from "./features/register/Register";
|
||||
import { OrderStats } from "./features/stats/OrderStats";
|
||||
import { ProductStats } from "./features/stats/ProductStats";
|
||||
import { Stats } from "./features/stats/Stats";
|
||||
import { UserDetail } from "./features/users/UserDetail";
|
||||
import { UserList } from "./features/users/UserList";
|
||||
const Splash = React.lazy(() => import("./components/Splash"));
|
||||
const UserCreate = React.lazy(() => import("./features/auth/UserCreate"));
|
||||
const Deposit = React.lazy(() => import("./features/deposit/Deposit"));
|
||||
const Log = React.lazy(() => import("./features/log/Log"));
|
||||
const Register = React.lazy(() => import("./features/register/Register"));
|
||||
const OrderStats = React.lazy(() => import("./features/stats/OrderStats"));
|
||||
const ProductStats = React.lazy(() => import("./features/stats/ProductStats"));
|
||||
const Stats = React.lazy(() => import("./features/stats/Stats"));
|
||||
const UserDetail = React.lazy(() => import("./features/users/UserDetail"));
|
||||
const UserList = React.lazy(() => import("./features/users/UserList"));
|
||||
|
||||
export const Routes = (): JSX.Element => (
|
||||
<ReactRouterRoutes>
|
||||
<Route path="/deposit" element={<Deposit />} />
|
||||
<Route path="/log" element={<Log />} />
|
||||
<Route path="/stats" element={<Stats />} />
|
||||
<Route path="/stats/orders" element={<OrderStats />} />
|
||||
<Route path="/stats/products" element={<ProductStats />} />
|
||||
<Route path="/users" element={<UserList />} />
|
||||
<Route path="/users/create" element={<UserCreate />} />
|
||||
<Route path="/user/:userId" element={<UserDetail />} />
|
||||
<Route path="/" element={<Register />} />
|
||||
<Route path="*" element={<Splash brandText="404" noOverlay />} />
|
||||
</ReactRouterRoutes>
|
||||
<React.Suspense fallback={<div>Loading...</div>}>
|
||||
<ReactRouterRoutes>
|
||||
<Route path="/deposit" element={<Deposit />} />
|
||||
<Route path="/log" element={<Log />} />
|
||||
<Route path="/stats" element={<Stats />} />
|
||||
<Route path="/stats/orders" element={<OrderStats />} />
|
||||
<Route path="/stats/products" element={<ProductStats />} />
|
||||
<Route path="/users" element={<UserList />} />
|
||||
<Route path="/users/create" element={<UserCreate />} />
|
||||
<Route path="/user/:userId" element={<UserDetail />} />
|
||||
<Route path="/" element={<Register />} />
|
||||
<Route path="*" element={<Splash brandText="404" noOverlay />} />
|
||||
</ReactRouterRoutes>
|
||||
</React.Suspense>
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
.errorMessage {
|
||||
font-family: Rocket Rinder, monospace;
|
||||
font-size: 3rem;
|
||||
font-style: italic;
|
||||
color: var(--brand);
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-top: 30vh;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ApolloError } from "@apollo/client";
|
||||
import React from "react";
|
||||
|
||||
import styles from "./GQLError.module.css";
|
||||
|
||||
const formatModel = (text: string) => {
|
||||
const withSpaces = text
|
||||
.replace(/([A-Z])/g, " $1")
|
||||
.toLocaleLowerCase()
|
||||
.trim();
|
||||
return withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1);
|
||||
};
|
||||
|
||||
export const GQLError = ({ error }: { error: ApolloError }) => {
|
||||
if (error?.graphQLErrors?.length == 1) {
|
||||
const single = error?.graphQLErrors[0];
|
||||
if (single?.extensions?.code === "NOT_FOUND") {
|
||||
const modelPretty = formatModel(single?.extensions?.model as string);
|
||||
return <div className={styles.errorMessage}>{`${modelPretty} not found`}</div>;
|
||||
}
|
||||
}
|
||||
return <div className={styles.errorMessage}>Error...</div>;
|
||||
};
|
||||
@@ -8,7 +8,7 @@ interface SplashProps {
|
||||
noOverlay?: boolean;
|
||||
}
|
||||
|
||||
export const Splash = ({ brandText = "BBS", noOverlay = false }: SplashProps): JSX.Element => (
|
||||
const Splash = ({ brandText = "BBS", noOverlay = false }: SplashProps): JSX.Element => (
|
||||
<div className={clsx(styles.splash, { [styles.overlay]: !noOverlay })}>
|
||||
<div className={styles.wrap}>
|
||||
<div className={styles.sun}></div>
|
||||
@@ -17,3 +17,4 @@ export const Splash = ({ brandText = "BBS", noOverlay = false }: SplashProps): J
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export default Splash;
|
||||
@@ -11,7 +11,7 @@ type FormValues = {
|
||||
email: string;
|
||||
};
|
||||
|
||||
export const UserCreate = () => {
|
||||
const UserCreate = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@@ -48,3 +48,4 @@ export const UserCreate = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default UserCreate;
|
||||
@@ -4,7 +4,7 @@ import { UserCard } from "../users/UserCard";
|
||||
import styles from "./Deposit.module.css";
|
||||
import { useDeposit } from "./useDeposit";
|
||||
|
||||
export const Deposit = (): JSX.Element => {
|
||||
const Deposit = (): JSX.Element => {
|
||||
const {
|
||||
loading,
|
||||
selectedUser,
|
||||
@@ -65,3 +65,4 @@ export const Deposit = (): JSX.Element => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Deposit;
|
||||
@@ -9,7 +9,7 @@ import styles from "./Log.module.css";
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
export const Log = () => {
|
||||
const Log = () => {
|
||||
const types = {
|
||||
orderOffset: Number,
|
||||
transactionOffset: Number,
|
||||
@@ -139,3 +139,4 @@ export const Log = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Log;
|
||||
@@ -8,15 +8,15 @@ import { useRegister } from "./useRegister";
|
||||
|
||||
const USER_EXTERNAL = "external";
|
||||
|
||||
export const Register = (): JSX.Element => {
|
||||
const Register = (): JSX.Element => {
|
||||
const {
|
||||
users,
|
||||
products,
|
||||
loading,
|
||||
showAll,
|
||||
setShowAll,
|
||||
setSelectedUser,
|
||||
selectedUser,
|
||||
toggleSelectedUser,
|
||||
addToOrder,
|
||||
order,
|
||||
total,
|
||||
@@ -36,14 +36,14 @@ export const Register = (): JSX.Element => {
|
||||
<UserCard
|
||||
key={USER_EXTERNAL}
|
||||
user={null}
|
||||
onUser={() => setSelectedUser(USER_EXTERNAL)}
|
||||
onUser={() => toggleSelectedUser(USER_EXTERNAL)}
|
||||
active={selectedUser == USER_EXTERNAL}
|
||||
/>
|
||||
{users.map((user) => (
|
||||
<UserCard
|
||||
key={user.id}
|
||||
user={user}
|
||||
onUser={() => setSelectedUser(user.id)}
|
||||
onUser={() => toggleSelectedUser(user.id)}
|
||||
active={selectedUser == user.id}
|
||||
/>
|
||||
))}
|
||||
@@ -80,3 +80,4 @@ export const Register = (): JSX.Element => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Register;
|
||||
@@ -38,6 +38,10 @@ export const useRegister = () => {
|
||||
setSelectedUser("");
|
||||
};
|
||||
|
||||
const toggleSelectedUser = (userId: string) => {
|
||||
setSelectedUser(selectedUser == userId ? "" : userId);
|
||||
};
|
||||
|
||||
const addToOrder = (productId: string) => {
|
||||
let added = false;
|
||||
const newOrder = order.map((orderLine: OrderLineInput) => {
|
||||
@@ -94,6 +98,7 @@ export const useRegister = () => {
|
||||
setShowAll,
|
||||
setSelectedUser,
|
||||
selectedUser,
|
||||
toggleSelectedUser,
|
||||
addToOrder,
|
||||
order,
|
||||
total,
|
||||
|
||||
@@ -35,7 +35,7 @@ const OrderBar = ({ data }: { data: BarDatum[] }) => (
|
||||
/>
|
||||
);
|
||||
|
||||
export const OrderStats = () => {
|
||||
const OrderStats = () => {
|
||||
const { data, loading } = useQuery(OrderStatsDocument);
|
||||
|
||||
return (
|
||||
@@ -77,3 +77,4 @@ export const OrderStats = () => {
|
||||
</StatsView>
|
||||
);
|
||||
};
|
||||
export default OrderStats;
|
||||
@@ -11,7 +11,7 @@ type UserCount = {
|
||||
count: number;
|
||||
};
|
||||
|
||||
export const ProductStats = () => {
|
||||
const ProductStats = () => {
|
||||
const { data, loading } = useQuery(AllProductStatsDocument, {
|
||||
variables: { active: true },
|
||||
});
|
||||
@@ -38,3 +38,4 @@ export const ProductStats = () => {
|
||||
</StatsView>
|
||||
);
|
||||
};
|
||||
export default ProductStats;
|
||||
@@ -5,7 +5,7 @@ import React from "react";
|
||||
import { ProductStatsDocument } from "../../types";
|
||||
import { StatsView } from "./StatsView";
|
||||
|
||||
export const Stats = () => {
|
||||
const Stats = () => {
|
||||
const { data, loading } = useQuery(ProductStatsDocument);
|
||||
|
||||
const series: any[] = [];
|
||||
@@ -68,3 +68,4 @@ export const Stats = () => {
|
||||
</StatsView>
|
||||
);
|
||||
};
|
||||
export default Stats;
|
||||
@@ -3,16 +3,19 @@ import { Line } from "@nivo/line";
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import { GQLError } from "../../components/GQLError";
|
||||
import { User, UserDetailDocument, UserProfile } from "../../types";
|
||||
import { formatMonth, formatTime } from "../../utils/time";
|
||||
import styles from "./UserDetail.module.css";
|
||||
|
||||
export const UserDetail = () => {
|
||||
const UserDetail = () => {
|
||||
const { userId } = useParams();
|
||||
const { data, loading } = useQuery(UserDetailDocument, { variables: { userId: userId || "" }, skip: !userId });
|
||||
const { data, loading, error } = useQuery(UserDetailDocument, { variables: { userId: userId || "" }, skip: !userId });
|
||||
|
||||
if (loading) return <div>Loading...</div>;
|
||||
|
||||
if (error) return <GQLError error={error} />;
|
||||
|
||||
const user = data?.user as User;
|
||||
const profile = user?.profile as UserProfile;
|
||||
// FIXME: types
|
||||
@@ -108,3 +111,4 @@ export const UserDetail = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default UserDetail;
|
||||
@@ -5,7 +5,7 @@ import { Link } from "react-router-dom";
|
||||
import { AllUsersDocument } from "../../types";
|
||||
import { activeUserFilter } from "../../utils/users";
|
||||
|
||||
export const UserList = () => {
|
||||
const UserList = () => {
|
||||
const { data, loading } = useQuery(AllUsersDocument);
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
|
||||
@@ -35,3 +35,4 @@ export const UserList = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default UserList;
|
||||
Reference in new issue
Block a user