Skip to content

Commit

Permalink
Add the my team settings page (#680)
Browse files Browse the repository at this point in the history
  • Loading branch information
acrantel committed Feb 8, 2024
1 parent e520dbe commit 329829b
Show file tree
Hide file tree
Showing 14 changed files with 347 additions and 11 deletions.
2 changes: 2 additions & 0 deletions frontend2/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Resources from "./views/Resources";
import { CurrentTeamProvider } from "./contexts/CurrentTeamProvider";
import { EpisodeProvider } from "./contexts/EpisodeProvider";
import Scrimmaging from "./views/Scrimmaging";
import MyTeam from "./views/MyTeam";

const App: React.FC = () => {
return (
Expand Down Expand Up @@ -55,6 +56,7 @@ const router = createBrowserRouter([
// TODO: /:episodeId/submissions
{
path: "/:episodeId/team",
element: <MyTeam />,
},
{ path: "/:episodeId/scrimmaging", element: <Scrimmaging /> },
],
Expand Down
2 changes: 1 addition & 1 deletion frontend2/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const Header: React.FC = () => {
const { episodeId } = useEpisodeId();

return (
<nav className="fixed top-0 h-16 w-full bg-gray-700">
<nav className="fixed top-0 z-30 h-16 w-full bg-gray-700">
<div className="w-full px-2 sm:px-6 lg:px-8">
<div className="relative flex h-16 items-center justify-between">
{/* mobile menu */}
Expand Down
7 changes: 7 additions & 0 deletions frontend2/src/components/JoinTeam.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

const JoinTeam: React.FC = () => {
return <div>you have no team! this page is tbd</div>;
};

export default JoinTeam;
27 changes: 27 additions & 0 deletions frontend2/src/components/SectionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react";

interface SectionCardProps {
children?: React.ReactNode;
title?: string;
className?: string;
}

const SectionCard: React.FC<SectionCardProps> = ({
children,
title,
className = "",
}) => {
return (
<div className={className}>
{title !== undefined && (
<div className="mb-2 text-xl font-semibold tracking-wide">{title}</div>
)}
<div className={`relative rounded bg-white p-6 shadow-md`}>
<div className="absolute inset-0 h-full w-1 rounded-l bg-cyan-600" />
{children}
</div>
</div>
);
};

export default SectionCard;
46 changes: 46 additions & 0 deletions frontend2/src/components/elements/DescriptiveCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from "react";
import Icon from "./Icon";
import { Switch } from "@headlessui/react";

interface DescriptiveCheckboxProps {
checked: boolean;
onChange: (checked: boolean) => void;
title: string;
description: string;
}

const DescriptiveCheckbox: React.FC<DescriptiveCheckboxProps> = ({
checked,
onChange,
title,
description,
}) => {
return (
<Switch
checked={checked}
onChange={onChange}
className={`flex w-full
flex-row items-center justify-between gap-3 rounded-lg px-6 py-4 shadow ring-2 ring-inset
ring-cyan-600/20 transition-all ui-checked:bg-cyan-900/80 ui-checked:ring-0`}
>
<div className="flex flex-col gap-2 text-left">
<div className="font-semibold ui-checked:text-white ">{title}</div>
<div className="text-sm text-cyan-700 ui-checked:text-cyan-100">
{description}
</div>
</div>
<div
className="rounded-full p-1.5 ring-2 ring-inset ring-cyan-600/20 transition-all
ui-checked:bg-cyan-500/50 ui-checked:ring-0"
>
<Icon
name="check"
size="sm"
className={`text-cyan-100 opacity-0 ui-checked:opacity-100`}
/>
</div>
</Switch>
);
};

export default DescriptiveCheckbox;
2 changes: 1 addition & 1 deletion frontend2/src/components/elements/FormLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const FormLabel: React.FC<{
}> = ({ label, required = false, className }) => {
return (
<div
className={`flex flex-row items-center gap-1 text-sm font-medium leading-6 text-gray-700 ${
className={`flex flex-row items-center gap-1 text-sm leading-6 text-gray-700 ${
className ?? ""
}`}
>
Expand Down
9 changes: 7 additions & 2 deletions frontend2/src/components/elements/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
<input
ref={ref}
aria-invalid={errorMessage !== undefined ? "true" : "false"}
className={`block w-full rounded-md border-0 px-2 py-1.5 text-gray-900 ring-1 ring-inset
className={`block w-full rounded-md border-0 px-2 py-1.5 ring-1 ring-inset
ring-gray-300 placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset
focus:ring-cyan-600 sm:text-sm sm:leading-6
${invalid ? "ring-red-500" : ""}`}
${invalid ? "ring-red-500" : ""}
${
rest.disabled === true
? "bg-gray-400/20 text-gray-600"
: "text-gray-900"
}`}
{...rest}
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend2/src/components/elements/SelectMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function SelectMenu<T extends React.Key | null | undefined>({
leaveTo="opacity-0"
>
<Listbox.Options
className="absolute z-10 mt-1 max-h-48 w-full overflow-auto rounded-md
className="absolute z-10 ml-0 mt-1 max-h-48 w-full overflow-auto rounded-md
bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none
sm:max-h-60 sm:text-sm"
>
Expand Down
38 changes: 38 additions & 0 deletions frontend2/src/components/elements/TextArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { forwardRef } from "react";
import FormError from "./FormError";
import FormLabel from "./FormLabel";

interface TextAreaProps extends React.ComponentPropsWithoutRef<"textarea"> {
label?: string;
required?: boolean;
className?: string;
errorMessage?: string;
}

const Input = forwardRef<HTMLTextAreaElement, TextAreaProps>(function Input(
{ label, required = false, className = "", errorMessage, ...rest },
ref,
) {
const invalid = errorMessage !== undefined;
return (
<div className={`relative ${invalid ? "mb-1" : ""} ${className}`}>
<label>
<FormLabel label={label} required={required} />
<div className="relative rounded-md shadow-sm">
<textarea
ref={ref}
aria-invalid={errorMessage !== undefined ? "true" : "false"}
className={`block w-full rounded-md border-0 px-2 py-1.5 text-gray-900 ring-1 ring-inset
ring-gray-300 placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset
focus:ring-cyan-600 sm:text-sm sm:leading-6
${invalid ? "ring-red-500" : ""}`}
{...rest}
/>
</div>
{invalid && <FormError message={errorMessage} />}
</label>
</div>
);
});

export default Input;
57 changes: 57 additions & 0 deletions frontend2/src/components/team/MemberList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from "react";
import { type UserPublic } from "../../utils/types";
import { useCurrentUser } from "../../contexts/CurrentUserContext";

interface MemberListProps {
members: UserPublic[];
className?: string;
}

interface UserRowProps {
user: UserPublic;
isCurrentUser?: boolean;
}
const UserRow: React.FC<UserRowProps> = ({ user, isCurrentUser = false }) => {
return (
<div className="flex flex-row items-center rounded">
<img
className="h-8 w-8 rounded-full bg-blue-100"
src={user.profile?.avatar_url}
/>
<div className="ml-6 font-semibold">
{user.username}
{isCurrentUser && (
<span className="ml-1 font-normal text-gray-600">(you)</span>
)}
{user.is_staff && (
<span className="ml-1 font-normal text-gray-600">(staff)</span>
)}
</div>
<div className="ml-12 flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap text-end text-gray-600">
{user.profile?.school}
</div>
</div>
);
};

// Displays a list of the users in members. If the current user is in members,
// displays the current user first.
const MemberList: React.FC<MemberListProps> = ({ members, className = "" }) => {
const { user: currentUser } = useCurrentUser();
return (
<div className={`flex flex-col gap-6 ${className}`}>
{/* display current user first */}
{currentUser !== undefined && (
<UserRow isCurrentUser user={currentUser} />
)}
{members.map(
(member) =>
member.id !== currentUser?.id && (
<UserRow key={member.id} user={member} />
),
)}
</div>
);
};

export default MemberList;
2 changes: 2 additions & 0 deletions frontend2/src/contexts/CurrentTeamContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { createContext, useContext } from "react";
import { type TeamPrivate } from "../utils/types";

export enum TeamStateEnum {
// the current team state is still loading
LOADING = "loading",
// the current user is not part of a team (or is not logged in)
NO_TEAM = "no_team",
// the current user is part of a team
Expand Down
4 changes: 3 additions & 1 deletion frontend2/src/contexts/CurrentTeamProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import { retrieveTeam } from "../utils/api/team";
export const CurrentTeamProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
// invariant: team must be undefined when teamState is LOADING or NO_TEAM
// team must be defined when teamState is IN_TEAM
const [teamData, setTeamData] = useState<{
team?: TeamPrivate;
teamState: TeamStateEnum;
}>({
teamState: TeamStateEnum.NO_TEAM,
teamState: TeamStateEnum.LOADING,
});
const { authState } = useCurrentUser();
const { episodeId } = useEpisodeId();
Expand Down
10 changes: 5 additions & 5 deletions frontend2/src/index.css
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Josefin+Sans:wght@100;300;400;500;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;600&family=Josefin+Sans:wght@100;300;400;500;700&display=swap");

@layer base {
h1 {
@apply pb-4 text-3xl font-medium text-gray-900;
@apply pb-4 text-3xl font-semibold text-gray-900;
}
h2 {
@apply pb-4 pt-6 text-2xl font-medium text-gray-900;
@apply pb-4 pt-6 text-2xl font-semibold text-gray-900;
}
h3 {
@apply pb-4 text-xl font-medium text-gray-900;
@apply pb-4 text-xl font-semibold text-gray-900;
}
h4 {
@apply pb-4 text-lg font-medium text-gray-700;
@apply pb-4 text-lg font-semibold text-gray-700;
}
p {
@apply pb-4 text-gray-900;
Expand Down
Loading

0 comments on commit 329829b

Please sign in to comment.