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

Admin panel #154

Merged
merged 44 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
f5a2196
feat(admin panel): add left nav bar
swh00tw Feb 28, 2024
36ea347
refactor: refactor admin panel components
swh00tw Feb 29, 2024
61d6674
feat(admin page): add page protection
swh00tw Feb 29, 2024
2805927
Add margin top to AdminPanelNav and change text color in NavItem
swh00tw Feb 29, 2024
acf8f5f
Merge branch 'master' into feat/admin-panel
swh00tw Feb 29, 2024
71ab310
refactor: refactor code to use parsed env vars
swh00tw Feb 29, 2024
bcbd699
Merge branch 'refactor/unify-prettier-config' into feat/admin-panel
swh00tw Feb 29, 2024
f36d44c
fix: highlight nav item when user is on corresponding page
swh00tw Feb 29, 2024
4493481
Update Headerbar and AdminPanelNav styles
swh00tw Feb 29, 2024
481c983
feat: add stat box component
swh00tw Feb 29, 2024
88642c3
feat: add comment
swh00tw Feb 29, 2024
e2efcdc
feat(admin panel): implement RecsCycleBarChart component
swh00tw Mar 1, 2024
dcd7ec9
feat: fill in missing cycle in the bar chart
swh00tw Mar 1, 2024
7a479e6
Refactor RecsCycleBarChart component and update package dependencies
swh00tw Mar 1, 2024
fc08cff
feat(admin panel): add AxisBottom component to RecsCycleBarChart
swh00tw Mar 4, 2024
eb25320
feat: finish admin panel user & rec page
swh00tw Mar 4, 2024
87a6220
Redirect to user-rec stats page in admin page
swh00tw Mar 4, 2024
249d38f
Add Tooltip component to AdminPanelNav
swh00tw Mar 4, 2024
2008daf
feat: add invite code monitor page
swh00tw Mar 5, 2024
9385674
feat: add invite codes hookk
swh00tw Mar 5, 2024
b9352b0
Add invite code table to admin page
swh00tw Mar 5, 2024
b5b8da9
Add TODO item for clearing console logs
swh00tw Mar 5, 2024
dee9c38
Add MIT License
swh00tw Mar 5, 2024
d12f6ac
Refactor invite code monitor page and add useCopyToClipboard hook
swh00tw Mar 5, 2024
99c03ad
Add AdminSectionTitle component to UserRecStats page
swh00tw Mar 5, 2024
a4f5dcc
add invite code generation section
swh00tw Mar 5, 2024
8e84bb8
refactor: refactor invite code monitor page
swh00tw Mar 5, 2024
21014fd
chore: clear console.log
swh00tw Mar 5, 2024
154c664
feat(invite code provision page): add invite code generation feature
swh00tw Mar 5, 2024
66c3d7c
refactor(monitor page): refactor monitor page and extract component
swh00tw Mar 5, 2024
c270483
Refactor invite code table in admin monitor page
swh00tw Mar 5, 2024
6cddb11
feat: finish generate invite code
swh00tw Mar 5, 2024
c734e3b
Merge branch 'master' into feat/admin-panel
swh00tw Mar 5, 2024
7883379
Refactor table layout and styling
swh00tw Mar 6, 2024
53a295f
Refactor invite code generation form layout
swh00tw Mar 6, 2024
b83b8b0
refactor: extract components to new file
swh00tw Mar 6, 2024
9d799ab
Add admin dropdown menu to MobileNavigator component
swh00tw Mar 6, 2024
2eb640e
clear comment
swh00tw Mar 6, 2024
b8ce71c
refactor: refactor code
swh00tw Mar 6, 2024
79f9637
Refactor user mapping in inviteCodes route
swh00tw Mar 6, 2024
8e906ed
Fix type declaration for userIds in GET function
swh00tw Mar 6, 2024
ebde350
chore: update gitignore
swh00tw Mar 8, 2024
ddf63a0
chore: test new ignore script
swh00tw Mar 8, 2024
a79b1c3
fix: clamp invite code gen at 1
swh00tw Mar 11, 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
1 change: 1 addition & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
## TODO

- [ ] Paste the testing link
- [ ] Clear `console.log` or `console.error` for debug usage
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,7 @@ Thumbs.db

# typescript
*.tsbuildinfo
next-env.d.ts
next-env.d.ts

# misc
apps/**/pnpm-lock.yaml
21 changes: 21 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 LIL Lab

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
8 changes: 7 additions & 1 deletion apps/recnet/src/app/Headerbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export function UserDropdown({ user }: { user: User }) {
<DropdownMenu.Item>
<Link href={`/${user.username}`}>Profile</Link>
</DropdownMenu.Item>
{user.role && user.role === "admin" ? (
<DropdownMenu.Item>
<Link href={`/admin`}>Admin Panel</Link>
</DropdownMenu.Item>
) : null}
<DropdownMenu.Item color="red" onClick={handleLogout}>
Log out
</DropdownMenu.Item>
Expand Down Expand Up @@ -120,7 +125,8 @@ export function Headerbar() {
"border-b-[1px]",
"border-slate-8",
"sticky",
"top-0"
"top-0",
"z-[1000]"
)}
>
<Flex className="items-center" gap="4">
Expand Down
44 changes: 42 additions & 2 deletions apps/recnet/src/app/MobileNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@ import {
AvatarIcon,
Cross2Icon,
HomeIcon,
MagicWandIcon,
Pencil2Icon,
} from "@radix-ui/react-icons";
import { useRouter, usePathname } from "next/navigation";
import { useAuth } from "./AuthContext";
import { UserDropdown } from "./Headerbar";
import { useGoogleLogin } from "@/firebase/auth";
import { toast } from "sonner";
import { Dialog, Text, Button, Flex } from "@radix-ui/themes";
import { Dialog, Text, Button, Flex, DropdownMenu } from "@radix-ui/themes";
import { SkeletonText, Skeleton } from "@/components/Skeleton";
import { useState } from "react";
import { useRec } from "@/hooks/useRec";
import { getDateFromFirebaseTimestamp, getNextCutOff } from "@/utils/date";
import { RecForm } from "./feeds/LeftPanel";
import { RecForm } from "@/components/RecForm";
import Link from "next/link";

function RecFormContent(props: { setOpen: (open: boolean) => void }) {
const { setOpen } = props;
Expand Down Expand Up @@ -119,6 +121,38 @@ function RecFormContent(props: { setOpen: (open: boolean) => void }) {
);
}

function AdminDropdown(props: { children: React.ReactNode }) {
const { children } = props;
const dropdownSectionStyle = "p-2 text-[14px] text-gray-12 font-medium";

return (
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<div>{children}</div>
</DropdownMenu.Trigger>
<DropdownMenu.Content align="center" className="mt-1 sm:w-[120px]">
<div className={cn(dropdownSectionStyle)}>Stats</div>
<DropdownMenu.Item>
<Link href={`/admin/stats/user-rec`}>User & Rec</Link>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<div className={cn(dropdownSectionStyle)}>Email</div>
<DropdownMenu.Item>
<Link href={`/admin`}>🚧 Announcement</Link>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<div className={cn(dropdownSectionStyle)}>Invite Codes</div>
<DropdownMenu.Item>
<Link href={`/admin/invite-code/monitor`}>Monitor</Link>
</DropdownMenu.Item>
<DropdownMenu.Item>
<Link href={`/admin/invite-code/provision`}>Provision</Link>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
);
}

function MobileNavigator() {
const { user } = useAuth();
const router = useRouter();
Expand Down Expand Up @@ -189,6 +223,12 @@ function MobileNavigator() {
</Dialog.Content>
</Dialog.Root>

{user && user?.role === "admin" && pathname.startsWith("/admin") ? (
<AdminDropdown>
<MagicWandIcon width="24" height="24" />
</AdminDropdown>
) : null}

{user ? (
<UserDropdown user={user} />
) : (
Expand Down
133 changes: 133 additions & 0 deletions apps/recnet/src/app/admin/AdminPanelNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"use client";

import { cn } from "@/utils/cn";
import { Text, Tooltip } from "@radix-ui/themes";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { createContext, useContext } from "react";

const AdminPanelNavContext = createContext({});

function useAdminPanelNavContext() {
const context = useContext(AdminPanelNavContext);

if (!context) {
throw new Error(
"Child components of AdminPanelNav cannot be rendered outside the AdminPanelNav component."
);
}

return context;
}

function AdminPanelNav({ children }: { children: React.ReactNode }) {
return (
<div
className={cn(
"w-[17%]",
"min-w-[250px]",
`min-h-[90svh]`,
"border-r-[1px]",
"border-gray-6",
"hidden",
"md:flex",
"flex-col"
)}
>
<AdminPanelNavContext.Provider value={{}}>
<div
className={cn(
"p-4",
"gap-y-4",
"sticky",
"flex",
"flex-col",
"top-[80px]"
)}
>
{children}
</div>
</AdminPanelNavContext.Provider>
</div>
);
}

function NavItem(props: { route: string; label: string; wip?: boolean }) {
useAdminPanelNavContext();
const { route, label, wip = false } = props;
const pathname = usePathname();
const isActive = pathname === `/admin/${route}`;

const ItemWrapper = ({ children }: { children: React.ReactNode }) => {
if (wip) {
return (
<Tooltip content="Work in progress" side="right" arrowPadding={0}>
{children}
</Tooltip>
);
}
return <Link href={`/admin/${route}`}>{children}</Link>;
};

return (
<ItemWrapper>
<div
className={cn(
"px-3 py-2 rounded-[999px] hover:bg-accentA-3 cursor-pointer transition-all ease-in-out duration-200",
"text-gray-11",
{
"bg-accentA-4": isActive,
"cursor-not-allowed": wip,
}
)}
>
{`${wip ? "🚧 " : ""}${label}`}
</div>
</ItemWrapper>
);
}
AdminPanelNav.Item = NavItem;

function NavSection({
children,
label,
}: {
children: React.ReactNode;
label: string;
}) {
useAdminPanelNavContext();

return (
<div className="flex-col flex gap-y-2 w-full">
<Text size="3" weight={"medium"} className="text-gray-12">
{label}
</Text>
<div className="flex flex-col gap-y-1">{children}</div>
</div>
);
}
AdminPanelNav.Section = NavSection;

export function AdminPanelNavbar() {
return (
<AdminPanelNav>
<AdminPanelNav.Section label="Stats">
<AdminPanelNav.Item route="stats/user-rec" label="User & Rec" />
</AdminPanelNav.Section>
<AdminPanelNav.Section label="Email">
<AdminPanelNav.Item
route="email/announcement"
label="Announcement"
wip
/>
</AdminPanelNav.Section>
<AdminPanelNav.Section label="Invite Code">
<AdminPanelNav.Item route="invite-code/monitor" label="Monitor" />
<AdminPanelNav.Item
route="invite-code/provision"
label="Provision code"
/>
</AdminPanelNav.Section>
</AdminPanelNav>
);
}
41 changes: 41 additions & 0 deletions apps/recnet/src/app/admin/AdminSections.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Text } from "@radix-ui/themes";
import { cn } from "@/utils/cn";

export function AdminSectionTitle(props: {
children: React.ReactNode;
description?: string;
}) {
const { children, description } = props;
return (
<div className={cn("w-full py-2 flex flex-col gap-y-2")}>
<Text size="6" weight={"medium"} className="text-gray-12">
{children}
</Text>
{description ? (
<Text size="2" className="text-gray-10">
{description}
</Text>
) : null}
</div>
);
}

export function AdminSectionBox(props: { children: React.ReactNode }) {
const { children } = props;

return (
<div
className={cn(
"flex",
"w-full",
"p-4",
"border-[1px]",
"rounded-4",
"border-gray-6",
"mb-4"
)}
>
{children}
</div>
);
}
Loading
Loading