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

Add the my team settings page #680

Merged
merged 3 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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