Skip to content

Commit

Permalink
Feat/rename team (#191)
Browse files Browse the repository at this point in the history
* CHORE/todo added

* REFACTOR/iife added

* FEAT/rename team added

* CHORE/rename team todo removed
  • Loading branch information
AugustinSorel authored Jun 30, 2024
1 parent ce78bc1 commit 4d4b9b7
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 59 deletions.
199 changes: 162 additions & 37 deletions src/app/account/_components/userTeams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,30 @@ import { QueryErrorResetBoundary } from "@tanstack/react-query";
import { Edit, LogOut, Trash2 } from "lucide-react";
import { useSession } from "next-auth/react";
import Link from "next/link";
import { Fragment, type ComponentPropsWithoutRef, useState } from "react";
import { type ComponentPropsWithoutRef, useState } from "react";
import { ErrorBoundary, type FallbackProps } from "react-error-boundary";
import { Card } from "./card";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { teamSchema } from "@/schemas/team.schemas";
import type { z } from "zod";

export const UserTeamsCard = () => {
return (
Expand Down Expand Up @@ -99,7 +120,7 @@ const Content = () => {
</TeamItemBody>

{!isAuthor && <LeaveTeam team={team.team} />}
{isAuthor && <RenameTeam />}
{isAuthor && <RenameTeam team={team.team} />}
{isAuthor && <DeleteTeam />}
</TeamItem>
</ErrorBoundary>
Expand Down Expand Up @@ -157,7 +178,8 @@ const TeamItemErrorFallback = (props: FallbackProps) => {
: JSON.stringify(props.error);

return (
<TeamItem className="border border-destructive bg-destructive/10 p-2">
<TeamItem className="border-destructive bg-destructive/10 p-2 first:border-t">
<TeamIndex />
<TeamItemBody>
<TeamItemTitle>something went wrong</TeamItemTitle>

Expand All @@ -173,32 +195,125 @@ const TeamItemErrorFallback = (props: FallbackProps) => {
);
};

const RenameTeam = () => {
const RenameTeam = ({ team }: { team: Team }) => {
const [isDialogOpen, setIsDialogOpen] = useState(false);
const formSchema = useRenameTeamSchema();
const utils = api.useUtils();

const renameTeam = api.team.rename.useMutation({
onSuccess: () => {
setIsDialogOpen(false);
},
onMutate: async (variables) => {
await utils.team.all.cancel();
await utils.team.get.cancel({ id: variables.id });

utils.team.all.setData(
undefined,
(teams) =>
teams?.map((team) =>
team.teamId === variables.id
? { ...team, team: { ...team.team, name: variables.name } }
: team,
),
);

utils.team.get.setData({ id: variables.id }, (team) =>
!team ? undefined : { ...team, name: variables.name },
);
},
onSettled: (_data, _error, variables) => {
void utils.team.all.invalidate();
void utils.team.get.invalidate({ id: variables.id });
},
});

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: { name: team.name },
});

const onSubmit = (values: z.infer<typeof formSchema>) => {
renameTeam.mutate({
id: team.id,
name: values.name,
});
};

return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="ghost"
size="icon"
className="text-muted-foreground hover:text-foreground"
>
<Edit className="h-3.5 w-3.5" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<DialogTrigger asChild>
<Button
variant="ghost"
size="icon"
className="text-muted-foreground hover:text-foreground"
>
<Edit className="h-3.5 w-3.5" />
</Button>
</DialogTrigger>
</TooltipTrigger>
<TooltipContent>
<p className="capitalize">rename</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<DialogContent className="space-y-5 sm:max-w-[425px]">
<DialogHeader>
<DialogTitle className="capitalize">change team name</DialogTitle>
</DialogHeader>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col gap-4"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>exercise name</FormLabel>
<FormControl>
<Input placeholder="team name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
disabled={renameTeam.isPending}
className="ml-auto"
>
{renameTeam.isPending && <Loader className="mr-2" />}
<span className="capitalize">save</span>
</Button>
</form>
</Form>
</DialogContent>
</Dialog>
);
};

const useRenameTeamSchema = () => {
const utils = api.useUtils();

return teamSchema.pick({ name: true }).refine(
(data) => {
const teams = utils.team.all.getData();

return !teams?.find(
(team) => team.team.name.toLowerCase() === data.name.toLowerCase(),
);
},
(data) => {
return {
message: `${data.name} is already used`,
path: ["name"],
};
},
);
};

Expand Down Expand Up @@ -236,15 +351,25 @@ const LeaveTeam = ({ team }: { team: Team }) => {

return (
<AlertDialog open={isAlertDialogOpen} onOpenChange={setIsAlertDialogOpen}>
<AlertDialogTrigger asChild>
<Button
variant="ghost"
size="icon"
className="text-muted-foreground hover:text-foreground"
>
<LogOut className="h-3.5 w-3.5" />
</Button>
</AlertDialogTrigger>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<AlertDialogTrigger asChild>
<Button
variant="ghost"
size="icon"
className="text-muted-foreground hover:text-foreground"
>
<LogOut className="h-3.5 w-3.5" />
</Button>
</AlertDialogTrigger>
</TooltipTrigger>
<TooltipContent>
<p className="capitalize">leave</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>

<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Expand Down
2 changes: 1 addition & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ const FeaturesGridBackground = () => {
);
};

//TODO: renaming team
//TODO: refactor onMutate cb
//TODO: deleting team
//TODO: add random facts to team page eg heavier lifter or most active in team
//TODO: update header ui
Expand Down
56 changes: 35 additions & 21 deletions src/server/api/routers/team.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,26 @@ export const teamRouter = createTRPCRouter({
create: protectedProcedure
.input(teamSchema.pick({ name: true }))
.mutation(async ({ ctx, input }) => {
let team = null;

try {
const res = await ctx.db
.insert(teams)
.values({
name: input.name,
authorId: ctx.session.user.id,
})
.returning();

team = res.at(0);
} catch (e) {
if (isPgError(e) && e.code === "23505") {
throw new TRPCError({
code: "CONFLICT",
message: `${input.name} is already used`,
});
const [team] = await (async () => {
try {
return await ctx.db
.insert(teams)
.values({
name: input.name,
authorId: ctx.session.user.id,
})
.returning();
} catch (e) {
if (isPgError(e) && e.code === "23505") {
throw new TRPCError({
code: "CONFLICT",
message: `${input.name} is already used`,
});
}

throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
}

throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
}
})();

if (!team) {
throw new TRPCError({
Expand Down Expand Up @@ -179,4 +177,20 @@ export const teamRouter = createTRPCRouter({
.where(eq(teamInvites.teamId, input.teamId));
});
}),

rename: protectedProcedure
.input(teamSchema.pick({ id: true, name: true }))
.mutation(async ({ ctx, input }) => {
const [team] = await ctx.db
.update(teams)
.set({ name: input.name })
.where(eq(teams.id, input.id))
.returning();

if (!team) {
throw new TRPCError({ code: "NOT_FOUND", message: "team not found" });
}

return team;
}),
});

0 comments on commit 4d4b9b7

Please sign in to comment.