Skip to content

Commit

Permalink
user avatars
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon Huang authored and Simon Huang committed Oct 11, 2024
1 parent 47e2d3a commit d85deeb
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 215 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
TooltipContent,
TooltipTrigger,
} from "@components/ui/tooltip";
import { Avatar, AvatarFallback, AvatarImage } from "@components/ui/avatar";
import { ScrollArea, ScrollBar } from "@components/ui/scroll-area";
import Link from "next/link";
import { Button } from "@components/ui/button";
Expand All @@ -29,6 +28,7 @@ import { api } from "trpc/react";
import ApplicationForm from "app/opportunities/_components/ApplicationForm";
import { Input } from "@components/ui/input";
import { Separator } from "components/ui/separator";
import { AvatarStack } from "@components/user/users-stack";

interface Props {
phases: OpportunityPhase[];
Expand Down Expand Up @@ -81,80 +81,61 @@ export const ApplicationOverview = ({ phases, isAdmin }: Props) => {
Phases
</h3>
<Separator />
<div className="flex h-full flex-col gap-2 p-4 pt-0">
{phases.map((phase) => (
<div key={`phase-${phase.id}`} className="space-y-2">
{isAdmin && phase.isInterview ? (
<Button
variant="link"
className="h-min p-0 text-sm font-semibold"
asChild
>
<Link
href={`./interview/${phase.id}`}
className="flex items-center"

<ScrollArea className="h-full overflow-y-auto">
<div className="flex h-full flex-col gap-2 p-4 pt-0">
{phases.map((phase) => (
<div key={`phase-${phase.id}`} className="space-y-2">
{isAdmin && phase.isInterview ? (
<Button
variant="link"
className="h-min p-0 text-sm font-semibold"
asChild
>
<Handshake className="mr-2 h-4 w-4" />
{phase.name}
</Link>
</Button>
) : (
<span className="flex items-center text-sm font-semibold">
{phase.isInterview && (
<Handshake className="mr-2 h-4 w-4" />
)}
{phase.name}
</span>
)}
<div className="flex flex-col gap-2">
{phase.questionnaires.map((questionnaire) => (
<Card
key={`questionnaire-${questionnaire.id}`}
onClick={() =>
setSelectionState({
questionnaire: questionnaire.id,
application: undefined,
})
}
className={cn(
"flex cursor-pointer flex-row items-center justify-between p-2 transition-colors",
questionnaire.id === questionnaireId && "bg-muted",
<Link
href={`./interview/${phase.id}`}
className="flex items-center"
>
<Handshake className="mr-2 h-4 w-4" />
{phase.name}
</Link>
</Button>
) : (
<span className="flex items-center text-sm font-semibold">
{phase.isInterview && (
<Handshake className="mr-2 h-4 w-4" />
)}
>
<p className="text-sm">{questionnaire.name}</p>
<div className="flex -space-x-2">
{[...questionnaire.reviewers]
.splice(0, 4)
.map((reviewer) => (
<Tooltip
key={`reviewer-${questionnaire.name}-${reviewer.id}`}
>
<TooltipTrigger>
<Avatar className="h-6 w-6 ring-1 ring-border">
<AvatarImage
src={reviewer.image ?? undefined}
/>
</Avatar>
</TooltipTrigger>
<TooltipContent>
<p>{reviewer.name}</p>
</TooltipContent>
</Tooltip>
))}
{questionnaire.reviewers.length > 4 && (
<Avatar className="h-6 w-6 bg-primary-foreground ring-1 ring-border">
<AvatarFallback className="text-xs">
+{questionnaire.reviewers.length - 4}
</AvatarFallback>
</Avatar>
{phase.name}
</span>
)}
<div className="flex flex-col gap-2">
{phase.questionnaires.map((questionnaire) => (
<Card
key={`questionnaire-${questionnaire.id}`}
onClick={() =>
setSelectionState({
questionnaire: questionnaire.id,
application: undefined,
})
}
className={cn(
"flex cursor-pointer flex-row items-center justify-between p-2 transition-colors",
questionnaire.id === questionnaireId && "bg-muted",
)}
</div>
</Card>
))}
>
<p className="text-sm">{questionnaire.name}</p>
<AvatarStack
users={questionnaire.reviewers}
size="sm"
maxVisible={4}
/>
</Card>
))}
</div>
</div>
</div>
))}
</div>
))}
</div>
</ScrollArea>
</ResizablePanel>

<ResizableHandle withHandle />
Expand Down Expand Up @@ -190,35 +171,13 @@ export const ApplicationOverview = ({ phases, isAdmin }: Props) => {
)}
>
<p className="text-sm">{application.name}</p>
<div className="flex -space-x-2">
{application.reviews &&
[...application.reviews?.map((review) => review.user)]
.splice(0, 4)
.map((reviewer) => (
<Tooltip
key={`reviewer-${application.name}-${reviewer.id}`}
>
<TooltipTrigger>
<Avatar className="h-6 w-6 ring-1 ring-border">
<AvatarImage
src={reviewer.image ?? undefined}
/>
</Avatar>
</TooltipTrigger>
<TooltipContent>
<p>{reviewer.name}</p>
</TooltipContent>
</Tooltip>
))}
{application.reviews?.length &&
application.reviews?.length > 4 && (
<Avatar className="h-6 w-6 bg-primary-foreground ring-1 ring-border">
<AvatarFallback className="text-xs">
+{application.reviews.length - 4}
</AvatarFallback>
</Avatar>
)}
</div>
{application.reviews && (
<AvatarStack
users={application.reviews.map((review) => review.user)}
size="sm"
maxVisible={4}
/>
)}
</Card>
))}
</div>
Expand Down
3 changes: 3 additions & 0 deletions app/opportunities/[opportunity_id]/applications/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ export default async function ApplicationsPage({ params }: Props) {
select: {
id: true,
name: true,
reviews: {
select: isAdmin ? { user: true } : undefined,
},
},
orderBy: {
createdAt: "asc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,10 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@components/ui/dropdown-menu";
import { Avatar, AvatarImage, AvatarFallback } from "@components/ui/avatar";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@components/ui/tooltip";
import { usePhasesContext } from "../usePhasesStore";
import { cn } from "@lib/utils";
import { PhasePopover } from "./phasePopover";
import { AvatarStack } from "@components/user/users-stack";

interface Props {
index: number;
Expand Down Expand Up @@ -116,29 +111,11 @@ export default function Phase({ index: phaseIndex, className }: Props) {
<div className="flex flex-row justify-between">
<p>{questionnaire.name}</p>

<div className="flex -space-x-2">
{[...questionnaire.reviewers].splice(0, 4).map((reviewer) => (
<Tooltip
key={`reviewer-${questionnaire.name}-${reviewer.id}`}
>
<TooltipTrigger>
<Avatar className="h-6 w-6 ring-1 ring-border">
<AvatarImage src={reviewer.image ?? undefined} />
</Avatar>
</TooltipTrigger>
<TooltipContent>
<p>{reviewer.name}</p>
</TooltipContent>
</Tooltip>
))}
{questionnaire.reviewers.length > 4 && (
<Avatar className="h-6 w-6 bg-primary-foreground ring-1 ring-border">
<AvatarFallback className="text-xs">
+{questionnaire.reviewers.length - 4}
</AvatarFallback>
</Avatar>
)}
</div>
<AvatarStack
users={questionnaire.reviewers}
size="sm"
maxVisible={5}
/>
</div>
</Card>
</QuestionnaireDialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { QuestionView } from "./questionView";
import { Card } from "@components/ui/card";
import { QuestionForm, type QuestionFormProps } from "./question";
import type { Question } from "@lib/types/question";
import { UsersStack } from "../../usersStack";
import { AvatarStack } from "../../../../../../../components/user/users-stack";
import { useSession } from "next-auth/react";

const QuestionEdit = ({
Expand Down Expand Up @@ -213,10 +213,15 @@ export const QuestionnaireDialog = ({
<h4 className="scroll-m-20 text-xl font-semibold tracking-tight">
Reviewer
</h4>
<UsersStack
key={`questionnaire-${form.watch("id")}-`}
<AvatarStack
users={reviewers}
append={appendReviewer}
append={(user) =>
appendReviewer({
...user,
name: user.name ?? undefined,
image: user.image ?? undefined,
})
}
remove={removeReviewer}
/>
</div>
Expand Down
63 changes: 0 additions & 63 deletions app/opportunities/[opportunity_id]/edit/_components/usersStack.tsx

This file was deleted.

8 changes: 7 additions & 1 deletion app/opportunities/[opportunity_id]/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { getServerAuthSession } from "server/auth";
import { EditGeneral } from "./_components/general";
import { EditPhases } from "./_components/phases";
import { redirect } from "next/navigation";

interface Props {
params: {
opportunity_id: string;
};
}

export default function OpportunityEdit({ params }: Props) {
export default async function OpportunityEdit({ params }: Props) {
const session = await getServerAuthSession();
if (!session?.user.id) redirect("/auth");

const opportunityId = params.opportunity_id;

return (
<>
<EditGeneral opportunityId={opportunityId} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { getServerAuthSession } from "server/auth";
import db from "server/db";
import { DataTable } from "./_components/data-table";
import { columns } from "./_components/columns";
import { type Application, type Questionnaire, type User } from "@prisma/client";
import {
type Application,
type Questionnaire,
type User,
} from "@prisma/client";

interface Props {
params: {
Expand Down Expand Up @@ -52,9 +56,8 @@ export default async function PhasePage({ params }: Props) {
},
});

// TODO: redirect to unauthorized page
if (!opportunity?.admins.map((admin) => admin.id).includes(session.user.id))
return redirect("/");
return redirect("/auth");

const questionnaires = await db.questionnaire.findMany({
where: { phaseId: phase.id },
Expand Down
Loading

0 comments on commit d85deeb

Please sign in to comment.