Skip to content

Commit

Permalink
Merge pull request #167 from risixdzn/test
Browse files Browse the repository at this point in the history
🌌 `Notifications` feat: Add realtime
  • Loading branch information
risixdzn authored Aug 30, 2024
2 parents a7423b1 + 6db71e5 commit 63ff555
Show file tree
Hide file tree
Showing 12 changed files with 1,054 additions and 575 deletions.
127 changes: 127 additions & 0 deletions app/(logged-in)/dashboard/notifications/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"use client";

import { Button } from "@/components/ui/button";
import { Check, Info, Trash } from "lucide-react";
import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { NotificationData } from "@/app/api/notifications/route";
import axios from "axios";
import Notification from "@/components/Dashboard/Notifications/Notification";
import { AnimatePresence, motion } from "framer-motion";
import { Skeleton } from "@/components/ui/skeleton";
import { Tumbleweed } from "@/public/svg/Tumbleweed";

interface APIResponse<T> {
success: boolean;
data: T[];
}

export default function NotificationsPage() {
const supabase = createClientComponentClient();

const { data, isLoading } = useQuery<APIResponse<NotificationData>>({
queryKey: ["notifications"], //key and params to define the query
queryFn: () => {
return axios.get(`/api/notifications`).then((res) => res.data);
},
retry: false,
refetchOnWindowFocus: false,
});

const queryClient = useQueryClient();

supabase
.channel("notifications")
.on(
"postgres_changes",
{ event: "INSERT", schema: "public", table: "notifications" },
() => {
queryClient.refetchQueries(["notifications"]);
}
)
.on(
"postgres_changes",
{ event: "DELETE", schema: "public", table: "notifications" },
() => {
queryClient.refetchQueries(["notifications"]);
}
)
.subscribe();

return (
<div className='flex gap-4'>
<section className=' w-full min-h-screen space-y-6 '>
<div className='flex flex-col lg:flex-row justify-between lg:items-center gap-2 lg:gap-0'>
<div className='space-y-1'>
<h1 className='text-3xl font-semibold tracking-tight'>Notificações</h1>
<p className='text-muted-foreground'>Gerencie suas notificações aqui.</p>
</div>
<div className='flex gap-1'>
<Button variant={"outline"}>
<Check className='w-4 h-4 inline-block mr-1' />
Marcar como lidas
</Button>
<button className='text-sm text-muted-foreground hover:text-accent'></button>

<Button variant={"ghost"}>
<Trash className='w-4 h-4 inline-block mr-1' /> Limpar
</Button>
</div>
</div>
<div className='w-full flex flex-col gap-2'>
{!isLoading ? (
<>
{data?.data!?.length > 0 ? (
<AnimatePresence>
{data?.data.map((not, index) => (
<motion.div
key={index}
initial={
index < 35
? { opacity: 0, scale: 0.8 }
: { opacity: 1, scale: 1 }
}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: index * 0.075 }}
>
<Notification not={not} />
</motion.div>
))}
</AnimatePresence>
) : (
<div className='w-full h-auto aspect-video bg-card rounded-lg border-dashed border-[2px] flex items-center justify-center flex-col space-y-2 lg:space-y-4 p-6'>
<Tumbleweed className='w-24 h-24 fill-neutral-500' />
<h3 className='font-semibold text-muted-foreground text-lg lg:text-2xl tracking-tight'>
Ainda não há nada aqui.
</h3>
<p className='max-w-xs text-xs lg:text-sm text-muted-foreground text-center '>
Volte mais tarde e poderá ver as notificações recebidas por
aqui.
</p>
</div>
)}
</>
) : (
<>
{Array.from({ length: 5 }).map((_, index) => (
<Skeleton key={index} className='w-full h-32' />
))}
</>
)}
</div>
</section>
<div className='h-screen w-[1px] bg-border xl:block hidden'></div>
<section className='w-[30rem] pl-4 xl:block hidden'>
<p className='text-muted-foreground text-sm'>
<Info className='w-4 h-4 mr-1 inline-block' />
As notificações são geradas automaticamente com base em ações suas ou de outros
usuários dentro do Gymn.
<br />
<br />
Como exemplo, uma notificação é enviada quando um aluno se afilia ou sai de sua
academia, ou quando você recebe uma mensagem.
</p>
</section>
</div>
);
}
6 changes: 4 additions & 2 deletions app/api/affiliates/invite/existing/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,10 @@ export async function POST(req: Request) {
}

const createdAffiliation = await supabase.from("affiliates").insert({
user_id: session.user.id,
user_id: userProfile.data![0].id,
belongs_to: gymId.data![0].id,
verified: false,
invite_type: "existing",
});
return NextResponse.json(
{ success: true, data: createdAffiliation.data },
Expand Down Expand Up @@ -245,9 +246,10 @@ export async function POST(req: Request) {
}

const createdAffiliation = await supabase.from("affiliates").insert({
user_id: session.user.id,
user_id: userProfile.data![0].id,
belongs_to: gymId.data![0].id,
verified: false,
invite_type: "existing",
});
return NextResponse.json(
{ success: true, data: createdAffiliation.data },
Expand Down
52 changes: 51 additions & 1 deletion app/api/gym/join/[slug]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function GET(req: Request, { params }: { params: { slug: string } }
.from("affiliates")
.select("*")
.eq("user_id", session?.user?.id);
if (afilliateStatus.data?.length !== 0) {
if (afilliateStatus.data?.length !== 0 && afilliateStatus.data![0].verified) {
return NextResponse.json("Unauthorized", { status: 401 });
}
//3. if both pass, affiliate the user to the gym.
Expand All @@ -57,6 +57,53 @@ export async function GET(req: Request, { params }: { params: { slug: string } }
);
}

console.log(afilliateStatus.data![0]);

//5. If the user has an active unverified affiliation, update the table, else, insert.
if (!afilliateStatus.data![0].verified) {
console.log("UNVERIFIED VERIFYING");

console.log(session.user.id);
const updatedAffiliation = await supabase
.from("affiliates")
.update({
verified: true,
})
.eq("user_id", session.user.id)
.select();

if (updatedAffiliation.error) {
return NextResponse.json(
{ success: false, error: updatedAffiliation.error },
{ status: 400 }
);
}

console.log(updatedAffiliation.data);
//+ Delete any gym invite notification assigned to the user
const deleteNotifications = await supabase
.from("notifications")
.delete()
.eq("notified_user_id", session.user.id)
.eq("source_gym_id", gymId.data![0].id)
.eq("event", "gym_invite");

if (deleteNotifications.error) {
return NextResponse.json(
{ success: false, error: deleteNotifications.error },
{ status: 400 }
);
}

//return res
const response = NextResponse.redirect(new URL("/dashboard/gym", req.url));
response.headers.set(
"Set-Cookie",
`JoinGymSuccess=true; Max-Age=${60 * 6 * 24}; Path=/`
);
return response;
}

const createdAffiliation = await supabase.from("affiliates").insert({
user_id: session.user.id,
belongs_to: gymId.data![0].id,
Expand All @@ -69,6 +116,8 @@ export async function GET(req: Request, { params }: { params: { slug: string } }
{ status: 400 }
);
}

//return res
const response = NextResponse.redirect(new URL("/dashboard/gym", req.url));
response.headers.set("Set-Cookie", `JoinGymSuccess=true; Max-Age=${60 * 6 * 24}; Path=/`);
return response;
Expand Down Expand Up @@ -135,6 +184,7 @@ export async function POST(req: Request, { params }: { params: { slug: string }
{ status: 400 }
);
}

const response = NextResponse.redirect(new URL("/dashboard/gym", req.url));
return response;
} catch (error) {
Expand Down
4 changes: 2 additions & 2 deletions app/api/gym/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export async function GET(request: Request) {
return NextResponse.json({ success: false, error: "no_gym" }, { status: 404 });
}

if (affiliate.data) {
if (affiliate.data![0].verified) {
const gym = await supabase
.from("gym")
.select(
Expand All @@ -128,7 +128,7 @@ export async function GET(request: Request) {
location)
`
)
.eq("id", affiliate.data[0].belongs_to);
.eq("id", affiliate.data![0].belongs_to);

const gymData = gym.data!.map((gym) => {
return {
Expand Down
95 changes: 95 additions & 0 deletions app/api/notifications/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { NextResponse } from "next/server";
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";

interface UserData {
id: string;
created_at: string;
username: string;
display_name: string;
profile: string;
bio: string;
location: string;
}

interface GymData {
id: string;
created_at: string;
name: string;
address: string;
owner: string;
referral_code: string;
ownerData: UserData; // Renamed property
}

export interface NotificationData {
id: string;
time: string;
notified_user_id: string;
source_user_id: string | null;
event: string;
read: boolean;
action: string | null;
source_gym_id: string;
gym?: GymData;
user?: UserData;
}

export async function GET(request: Request) {
const cookieStore = cookies();
const supabase = createRouteHandlerClient({ cookies: () => cookieStore });

const {
data: { session },
} = await supabase.auth.getSession();

if (!session) {
return NextResponse.json({ success: false, error: "Unauthorized" }, { status: 401 });
}

try {
const { data, error } = await supabase
.from("notifications")
.select(
`*,
gym(*,users(id,created_at,username,display_name,profile,bio,location))
users(id,created_at,username,display_name,profile,bio,location)`
)
.eq("notified_user_id", session.user.id);

if (!error) {
function renameUsersToOwner(data: any[]): NotificationData[] {
return data.map((event) => {
if (event && event.gym && event.gym.users) {
event.gym.owner = event.gym.users;
delete event.gym.users;
}
return event;
});
}

const modifiedData = renameUsersToOwner(data);

return NextResponse.json(
{ success: "true", data: modifiedData },
{
status: 200,
}
);
} else {
return NextResponse.json(
{ success: "false", error },
{
status: 403,
}
);
}
} catch (error) {
return NextResponse.json(
{ success: "false", error },
{
status: 403,
}
);
}
}
2 changes: 1 addition & 1 deletion components/Auth/Register/ui/VerifyYourEmail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type Props = {
export default function VerifyYourEmail({ values, setSignUpSuccess }: Props) {
const { setAuthState } = useContext<AuthCardContextType>(AuthCardContext);
return (
<div className='flex flex-col'>
<div className='flex flex-col w-full'>
<div className='w-full h-32 bg-purple-600/20 flex items-center justify-center rounded-lg'>
<MailCheck className='text-purple-600 inline-block' width={60} height={60} />
</div>
Expand Down
Loading

0 comments on commit 63ff555

Please sign in to comment.