Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Voor en achter #27

Merged
merged 56 commits into from
Jul 20, 2024
Merged
Changes from 1 commit
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
3f4d7c7
vinvoor: init
Topvennie Jun 11, 2024
0328a81
zess: development docker
Topvennie Jun 11, 2024
f230190
vingo: customizable redirect
Topvennie Jun 16, 2024
2a795d8
zess: add -c option
Topvennie Jun 16, 2024
ee1567e
vinvoor: add login
Topvennie Jun 16, 2024
ec72885
vingo: add cors
Topvennie Jun 21, 2024
3747e67
vingo: add json serialization
Topvennie Jun 21, 2024
f9d81c7
vinvoor: add login and logout
Topvennie Jun 21, 2024
4b9d47f
vinvoor: add a cards page
Topvennie Jun 21, 2024
9e861d3
zess: add development instructions
Topvennie Jun 21, 2024
ddf3e91
vinvoor: add a theme
Topvennie Jun 21, 2024
ffb934b
vinvoor: refactor
Topvennie Jun 21, 2024
874ccc8
vinvoor: add support for dark and light mode
Topvennie Jun 21, 2024
0b9ef39
vinvoor: move url's to .env
Topvennie Jun 21, 2024
66ff7cf
zess: start vinvoor as non root user
Topvennie Jun 21, 2024
ec0b38d
vinvoor: add a cards overview
Topvennie Jun 21, 2024
98c1bf4
vingo: more serialization
Topvennie Jun 22, 2024
506895d
vinvoor: refactor the cards page
Topvennie Jun 22, 2024
d7e9ec4
vinvoor: change to camelCase
Topvennie Jun 22, 2024
71e942a
vinvoor: add a leaderboard
Topvennie Jun 22, 2024
221bd9a
vinvoor: add a welcome page
Topvennie Jun 22, 2024
5fbf7d9
zess: change env var to export in dev script
hannes-dev Jun 25, 2024
4b8372d
vingo: add database migrations
hannes-dev Jun 25, 2024
7762f73
vingo: fix api returning null when no results
hannes-dev Jun 25, 2024
9bed987
vingo: add card register via api
hannes-dev Jun 25, 2024
52df507
vingo: add card id and name
hannes-dev Jun 25, 2024
632bfb0
vinvoor: add a github activity style overview
Topvennie Jun 28, 2024
060ac73
vinvoor: merge branch 'main' into vinvoor-heatmap
Topvennie Jun 28, 2024
f13d543
vinvoor: support adding new cards
Topvennie Jul 6, 2024
5b5a3a6
vinvoor: merge vinvoor-heatmap into voor-en-achter
Topvennie Jul 6, 2024
7c0519b
vinvoor: show current checkin status
Topvennie Jul 6, 2024
ed6d70a
vinvoor: show current streak days
Topvennie Jul 6, 2024
41e16cc
vinvoor: show the user's most common days
Topvennie Jul 6, 2024
dc2ef3d
vinvoor: simple overview page
Topvennie Jul 16, 2024
5d0f872
vinvoor: fix crash when no scans are registered
Topvennie Jul 16, 2024
bb8a855
vinvoor: center graphs in the overview
Topvennie Jul 16, 2024
3ab6c3d
vinvoor: add support for new comers
Topvennie Jul 16, 2024
c4bf180
vingo: move to gorm
hannes-dev Jul 16, 2024
c42c99e
vingo: go get -u && go mod tidy
hannes-dev Jul 16, 2024
371b89f
vingo: card register status endpoint
hannes-dev Jul 16, 2024
2f3737c
vingo: ability to add name to card
hannes-dev Jul 16, 2024
c295ae8
vingo: return if last register was success
hannes-dev Jul 16, 2024
40b24a3
vingo: return extra stats for cards
hannes-dev Jul 16, 2024
3735c44
vingo: time remaining on card status endpoint
hannes-dev Jul 16, 2024
1864bdc
vinvoor: support new card features
Topvennie Jul 17, 2024
2ac3951
vingo: add leaderboard position change
hannes-dev Jul 17, 2024
0deb98a
vingo: pieter post pieter post pieter post verdient de kost (met zijn…
hannes-dev Jul 17, 2024
77e6dac
vinvoor: pieter post deed ambetant
Topvennie Jul 17, 2024
21cf2e5
vinvoor: show position changes in the leaderboard
Topvennie Jul 17, 2024
9a208e3
vinvoor: show a scan overview
Topvennie Jul 17, 2024
db96f94
vingo: fix scans
hannes-dev Jul 18, 2024
e403886
vingo: card handlers to receivers
hannes-dev Jul 18, 2024
db59fd5
vingo: settings endpoint
hannes-dev Jul 18, 2024
61aa8fc
vingo: cleanup
hannes-dev Jul 18, 2024
656a329
vinvoor: add a new zess logo
Topvennie Jul 18, 2024
f70fb2f
vinvoor: scans
Topvennie Jul 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
vinvoor: support adding new cards
Topvennie committed Jul 6, 2024
commit f13d543715b70435f0b906e42ed42acd176a25a9
2 changes: 1 addition & 1 deletion vingo/handlers/cards.go
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ func StartCardRegisterAPI(c *fiber.Ctx) error {

logger.Println("Card registration started by user", registering_user)

return c.SendStatus(200)
return c.Status(200).JSON(map[string]bool{})
}

func StartCardRegister(c *fiber.Ctx) error {
2 changes: 2 additions & 0 deletions vinvoor/package.json
Original file line number Diff line number Diff line change
@@ -20,7 +20,9 @@
"@types/react-router-dom": "^5.3.3",
"@types/react-router-hash-link": "^2.4.9",
"js-cookie": "^3.0.5",
"material-ui-confirm": "^3.0.16",
"mdi-material-ui": "^7.9.1",
"notistack": "^3.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.23.1",
2 changes: 0 additions & 2 deletions vinvoor/src/App.tsx
Original file line number Diff line number Diff line change
@@ -29,5 +29,3 @@ export const App = () => {
</>
);
};

// TODO: Add link to the github repo
28 changes: 20 additions & 8 deletions vinvoor/src/cards/Cards.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import { useState } from "react";
import { createContext, Dispatch, SetStateAction, useState } from "react";
import { LoadingSkeleton } from "../components/LoadingSkeleton";
import { useFetch } from "../hooks/useFetch";
import { Card } from "../types/cards";
import { Card, convertCardJSON } from "../types/cards";
import { CardsEmpty } from "./CardsEmpty";
import { CardsTable } from "./CardsTable";

interface CardContextProps {
cards: readonly Card[];
setCards: Dispatch<SetStateAction<readonly Card[]>>;
}

export const CardContext = createContext<CardContextProps>({
cards: [],
setCards: () => {},
});

export const Cards = () => {
const [cards, setCards] = useState<readonly Card[]>([]);
const { loading, error: _ } = useFetch<readonly Card[]>("cards", setCards);
const { loading, error: _ } = useFetch<readonly Card[]>(
"cards",
setCards,
convertCardJSON
);

return (
<LoadingSkeleton loading={loading}>
{!!cards.length ? (
<CardsTable cards={cards} setCards={setCards} />
) : (
<CardsEmpty />
)}
<CardContext.Provider value={{ cards, setCards }}>
{!!cards.length ? <CardsTable /> : <CardsEmpty />}
</CardContext.Provider>
</LoadingSkeleton>
);
};
135 changes: 99 additions & 36 deletions vinvoor/src/cards/CardsAdd.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,110 @@
import { Add, CancelOutlined } from "@mui/icons-material";
import { Add } from "@mui/icons-material";
import { Button, Typography } from "@mui/material";
import { useState } from "react";
import { ConfirmationModal } from "../components/ConfirmationModal";
import { useConfirm } from "material-ui-confirm";
import { useSnackbar } from "notistack";
import { useContext, useState } from "react";
import { Card, CardPostResponse, convertCardJSON } from "../types/cards";
import { getApi, isResponseNot200Error, postApi } from "../util/fetch";
import { equal, randomInt } from "../util/util";
import { CardContext } from "./Cards";

const CHECK_INTERVAL = 1000;
const REGISTER_TIME = 60000;

const confirmTitle = "Register a new card";
const confirmContent = `
Once you click 'register' you will have 60 seconds to hold your card to the scanner.
A popup will appear when the card is registered successfully and it will be added to your cards table.
`;

const requestSuccess = "Register your card by holding it to vinscant";
const requestYou = "You are already registering a card!";
const requestOther =
"Failed to start the card registering process because another user is already registering a card. Please try again later.";
const requestFail =
"Failed to start the card registration process. Please try again later or contact a sysadmin";

const registerSucces = "Card registered successfully";
const registerFail = "Failed to register card";

const getCards = () =>
getApi<readonly Card[]>("cards", convertCardJSON).catch((_) => null);

const checkCardsChange = async (): Promise<
[boolean, readonly Card[] | null]
> => {
const startTime = Date.now();
const cardsStart = await getCards();

if (!cardsStart) return [false, null];

let cardsNow: readonly Card[] | null = null;
while (Date.now() - startTime < REGISTER_TIME) {
cardsNow = await getCards();

if (!equal(cardsStart, cardsNow)) break;

await new Promise((r) => setTimeout(r, CHECK_INTERVAL));
}

return [cardsNow !== null && cardsNow !== cardsStart, cardsNow];
};

export const CardsAdd = () => {
const [open, setOpen] = useState<boolean>(false);
const { setCards } = useContext(CardContext);
const [disabled, setDisabled] = useState<boolean>(false);
const confirm = useConfirm();
const { enqueueSnackbar, closeSnackbar } = useSnackbar();

const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const startRegistering = () =>
postApi<Card[]>("cards/register")
.then(() => {
const id = randomInt().toString();
enqueueSnackbar(requestSuccess, {
variant: "info",
persist: true,
key: id,
});
setDisabled(true);

const title = "Register a new card";
checkCardsChange().then((result) => {
closeSnackbar(id);
setDisabled(false);

const content = `
This feature is not yet implemented as I'm waiting for an endpoint.
Hannes................................................
`;
if (result[0] && result[1] !== null) {
enqueueSnackbar(registerSucces, { variant: "success" });
setCards(result[1]);
} else enqueueSnackbar(registerFail, { variant: "error" });
});
})
.catch((error) => {
if (isResponseNot200Error(error)) {
error.response.json().then((response: CardPostResponse) => {
if (response.is_current_user)
enqueueSnackbar(requestYou, { variant: "warning" });
else
enqueueSnackbar(requestOther, { variant: "error" });
});
} else enqueueSnackbar(requestFail, { variant: "error" });
});

const actions = (
<>
<Button onClick={handleClose} color="error" variant="contained">
<CancelOutlined sx={{ mr: 1 }} />
<Typography>Cancel</Typography>
</Button>
<Button color="success" variant="contained">
<Add sx={{ mr: 1 }} />
<Typography>Register</Typography>
</Button>
</>
);
const handleClick = () => {
confirm({
title: confirmTitle,
description: confirmContent,
confirmationText: "Register",
}).then(() => startRegistering());
};

return (
<>
<Button onClick={handleOpen} variant="contained" sx={{ my: "1%" }}>
<Add />
<Typography>Register new card</Typography>
</Button>
<ConfirmationModal
open={open}
onClose={handleClose}
title={title}
content={content}
actions={actions}
></ConfirmationModal>
</>
<Button
onClick={handleClick}
variant="contained"
sx={{ my: "1%" }}
disabled={disabled}
>
<Add />
<Typography>Register new card</Typography>
</Button>
);
};
56 changes: 17 additions & 39 deletions vinvoor/src/cards/CardsDelete.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
import { CancelOutlined } from "@mui/icons-material";
import DeleteIcon from "@mui/icons-material/Delete";
import { Button, IconButton, Tooltip, Typography } from "@mui/material";
import { Dispatch, FC, SetStateAction, useState } from "react";
import { ConfirmationModal } from "../components/ConfirmationModal";
import { Card } from "../types/cards";
import { IconButton, Tooltip } from "@mui/material";
import { useConfirm } from "material-ui-confirm";
import { FC } from "react";

interface CardDeleteProps {
selected: readonly string[];
setCards: Dispatch<SetStateAction<readonly Card[]>>;
}

export const CardsDelete: FC<CardDeleteProps> = ({ selected, setCards }) => {
const [open, setOpen] = useState<boolean>(false);

export const CardsDelete: FC<CardDeleteProps> = ({ selected }) => {
const confirm = useConfirm();
const numSelected = selected.length;

const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);

const title = `Delete card${numSelected > 1 ? "s" : ""}`;

const content = `
Are you sure you want to delete ${numSelected} card${
numSelected > 1 ? "s" : ""
@@ -29,33 +21,19 @@ export const CardsDelete: FC<CardDeleteProps> = ({ selected, setCards }) => {
Hannnneeeeeeees...........................
`;

const actions = (
<>
<Button onClick={handleClose} color="error" variant="contained">
<CancelOutlined sx={{ mr: 1 }} />
<Typography>Cancel</Typography>
</Button>
<Button color="warning" variant="contained">
<DeleteIcon sx={{ mr: 1 }} />
<Typography>Delete</Typography>
</Button>
</>
);
const handleClick = () => {
confirm({
title: title,
description: content,
confirmationText: "Delete",
}).then(() => console.log("Card deleted!"));
};

return (
<>
<Tooltip title="Delete">
<IconButton onClick={handleOpen}>
<DeleteIcon />
</IconButton>
</Tooltip>
<ConfirmationModal
open={open}
onClose={handleClose}
title={title}
content={content}
actions={actions}
></ConfirmationModal>
</>
<Tooltip title="Delete">
<IconButton onClick={handleClick}>
<DeleteIcon />
</IconButton>
</Tooltip>
);
};
43 changes: 14 additions & 29 deletions vinvoor/src/cards/CardsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,18 @@
import { Paper, Table, TableContainer, TablePagination } from "@mui/material";
import {
ChangeEvent,
Dispatch,
FC,
MouseEvent,
SetStateAction,
useMemo,
useState,
} from "react";
import { ChangeEvent, MouseEvent, useContext, useMemo, useState } from "react";
import { Card } from "../types/cards";
import { TableOrder } from "../types/table";
import { CardContext } from "./Cards";
import { CardsTableBody } from "./CardsTableBody";
import { CardsTableHead } from "./CardsTableHead";
import { CardsTableToolbar } from "./CardsTableToolbar";

interface CardTableProps {
cards: readonly Card[];
setCards: Dispatch<SetStateAction<readonly Card[]>>;
}

const rowsPerPageOptions = [10, 25, 50];

const descendingComparator = <T,>(a: T, b: T, orderBy: keyof T) => {
if (b[orderBy] < a[orderBy]) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
return 1;
}
if (b[orderBy] < a[orderBy]) return -1;
if (b[orderBy] > a[orderBy]) return 1;

return 0;
};

@@ -47,18 +32,19 @@ const stableSort = <T,>(
array: readonly T[],
comparator: (a: T, b: T) => number
) => {
const stabilizedThis = array.map((el, index) => [el, index] as [T, number]);
stabilizedThis.sort((a, b) => {
const stabilized = array.map((el, index) => [el, index] as [T, number]);
stabilized.sort((a, b) => {
const order = comparator(a[0], b[0]);
if (order !== 0) {
return order;
}
return a[1] - b[1];
});
return stabilizedThis.map((el) => el[0]);
return stabilized.map((el) => el[0]);
};

export const CardsTable: FC<CardTableProps> = ({ cards, setCards }) => {
export const CardsTable = () => {
const { cards } = useContext(CardContext);
const [order, setOrder] = useState<TableOrder>("asc");
const [orderBy, setOrderBy] = useState<keyof Card>("serial");
const [selected, setSelected] = useState<readonly string[]>([]);
@@ -78,6 +64,7 @@ export const CardsTable: FC<CardTableProps> = ({ cards, setCards }) => {
if (event.target.checked) {
const newSelected = cards.map((n) => n.serial);
setSelected(newSelected);

return;
}

@@ -114,9 +101,7 @@ export const CardsTable: FC<CardTableProps> = ({ cards, setCards }) => {
const handleChangePage = (
_: MouseEvent<HTMLButtonElement> | null,
newPage: number
) => {
setPage(newPage);
};
) => setPage(newPage);

const handleChangeRowsPerPage = (event: ChangeEvent<HTMLInputElement>) => {
setRowsPerPage(parseInt(event.target.value, 10));
@@ -134,12 +119,12 @@ export const CardsTable: FC<CardTableProps> = ({ cards, setCards }) => {
page * rowsPerPage,
page * rowsPerPage + rowsPerPage
),
[order, orderBy, page, rowsPerPage]
[cards, order, orderBy, page, rowsPerPage]
);

return (
<Paper elevation={4} sx={{ width: "100%", mb: 2 }}>
<CardsTableToolbar selected={selected} setCards={setCards} />
<CardsTableToolbar selected={selected} />
<TableContainer>
<Table>
<CardsTableHead
Loading