Skip to content

Commit b0183ea

Browse files
Merge pull request #149 from HackYourFutureProjects/fix/home-page
Fix/home page
2 parents 8ea7274 + f72981b commit b0183ea

39 files changed

Lines changed: 845 additions & 168 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import LogoIcon from "../icons/LogoIcon";
2+
import { cva, type VariantProps } from "class-variance-authority";
3+
import { twMerge } from "tailwind-merge";
4+
5+
type Size = "lg" | "sm";
6+
7+
const wrapper = cva("flex items-center justify-center mt-5", {
8+
variants: {
9+
fullWidth: {
10+
true: "w-full",
11+
false: "",
12+
},
13+
},
14+
defaultVariants: { fullWidth: true },
15+
});
16+
17+
const logoPulseVariants = cva("relative", {
18+
variants: {
19+
size: {
20+
lg: "w-40 h-40",
21+
sm: "w-20 h-20",
22+
},
23+
},
24+
defaultVariants: { size: "lg" },
25+
});
26+
27+
const innerCircleVariants = cva(
28+
"absolute rounded-full bg-[#15141D] flex items-center justify-center",
29+
{
30+
variants: {
31+
size: {
32+
lg: "inset-2.5",
33+
sm: "inset-[5px]",
34+
},
35+
},
36+
defaultVariants: { size: "lg" },
37+
},
38+
);
39+
40+
const iconSizeMap: Record<Size, { w: number; h: number }> = {
41+
lg: { w: 90, h: 90 },
42+
sm: { w: 45, h: 45 },
43+
};
44+
45+
type Props = Omit<VariantProps<typeof logoPulseVariants>, "size"> & {
46+
size?: Size;
47+
className?: string;
48+
49+
fullWidth?: boolean;
50+
wrapperClassName?: string;
51+
};
52+
53+
export const LogoPulseIcon = ({
54+
size = "lg",
55+
className,
56+
fullWidth = true,
57+
wrapperClassName,
58+
}: Props) => {
59+
const { w, h } = iconSizeMap[size];
60+
61+
return (
62+
<div className={twMerge(wrapper({ fullWidth }), wrapperClassName)}>
63+
<div className={twMerge(logoPulseVariants({ size }), className)}>
64+
<div className="loader-ring" />
65+
<div className={innerCircleVariants({ size })}>
66+
<div className="loader-pulse text-purple-500">
67+
<LogoIcon width={String(w)} height={String(h)} />
68+
</div>
69+
</div>
70+
</div>
71+
</div>
72+
);
73+
};

‎client/src/components/auth/loginForm/LoginForm.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { ControlledTextField } from "../../ui/controlled/controlledTextField/Con
77
import { Button } from "../../ui/button/Button";
88
import { NavLink } from "react-router-dom";
99
import { authRoutesVariables } from "../../../router/routesVariables/pathVariables";
10-
import { Loader } from "../../loader/Loader.tsx";
1110
import { GoogleAuthButton } from "../../ui/button/GoogleAuthButton.tsx";
11+
import { LogoPulseIcon } from "../../LogoPulsIcon/LogoPulseIcon.tsx";
1212

1313
type LoginFormComponentTypes = {
1414
loading: boolean;
@@ -115,7 +115,7 @@ export const LoginForm = ({
115115
)}
116116
</div>
117117
</div>
118-
{loading && <Loader />}
118+
{loading && <LogoPulseIcon size="sm" />}
119119
</form>
120120
);
121121
};

‎client/src/components/auth/recoveryForm/RecoveryForm.tsx‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ControlledTextField } from "../../ui/controlled/controlledTextField/Con
44
import { Button } from "../../ui/button/Button.tsx";
55
import type { FormValues } from "./recoveryFormTypes.ts";
66
import { recoverySchema } from "./recoveryForm.validation.ts";
7-
import { Loader } from "../../loader/Loader.tsx";
7+
import { LogoPulseIcon } from "../../LogoPulsIcon/LogoPulseIcon.tsx";
88

99
type RecoveryFormProps = {
1010
loading: boolean;
@@ -57,8 +57,7 @@ export const RecoveryForm = ({
5757
</div>
5858
</div>
5959
</div>
60-
61-
{loading && <Loader />}
60+
{loading && <LogoPulseIcon size="sm" />}
6261
</form>
6362
);
6463
};

‎client/src/components/auth/resetPasswordForm/restPssswordForm.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ControlledTextField } from "../../ui/controlled/controlledTextField/Con
44
import { Button } from "../../ui/button/Button.tsx";
55
import type { ResetPasswordFormValues } from "./resetPasswordFromTypes.ts";
66
import { resetPasswordSchema } from "./resetPassword.validation.ts";
7-
import { Loader } from "../../loader/Loader.tsx";
7+
import { LogoPulseIcon } from "../../LogoPulsIcon/LogoPulseIcon.tsx";
88

99
type ResetPasswordFormProps = {
1010
loading: boolean;
@@ -64,7 +64,7 @@ export const ResetPasswordForm = ({
6464
</div>
6565
</div>
6666

67-
{loading && <Loader />}
67+
{loading && <LogoPulseIcon size="sm" />}
6868
</form>
6969
);
7070
};

‎client/src/components/auth/signUpForm/SignUpForm.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import {
1111
RegisterFormTypes,
1212
Role,
1313
} from "../../../api/auth/types";
14-
import { Loader } from "../../loader/Loader.tsx";
1514
import { GoogleAuthButton } from "../../ui/button/GoogleAuthButton.tsx";
15+
import { LogoPulseIcon } from "../../LogoPulsIcon/LogoPulseIcon.tsx";
1616

1717
type SignUpFormTypes = {
1818
loading: boolean;
@@ -131,7 +131,7 @@ export const SignUpForm = ({
131131
</div>
132132
</div>
133133

134-
{loading && <Loader />}
134+
{loading && <LogoPulseIcon size="sm" />}
135135
</form>
136136
);
137137
};

‎client/src/components/chat/chatSidebar/ChatSidebar.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { ChatSidebarItemLink } from "../chatSidebarItem/ChatSidebarItemLink.tsx";
22
import { useChatConversationsQuery } from "../../../features/chat/chat.query.ts";
3-
import { Loader } from "../../loader/Loader.tsx";
3+
import { LogoPulseIcon } from "../../LogoPulsIcon/LogoPulseIcon.tsx";
44

55
export const ChatSidebar = () => {
66
const { data, isLoading } = useChatConversationsQuery();
77
if (isLoading) {
8-
return <Loader />;
8+
return <LogoPulseIcon size="sm" />;
99
}
1010
return (
1111
<aside className="border-r border-[#E0E7FF80] overflow-auto">

‎client/src/components/controlPanel/ControlPanel.tsx‎

Lines changed: 0 additions & 87 deletions
This file was deleted.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { DropdownMenu } from "./DropdownMenu.tsx";
2+
import { Button } from "../ui/button/Button.tsx";
3+
import ArrowDown from "../icons/ArrowDown.tsx";
4+
import { useState } from "react";
5+
import { LinkOption } from "../../types/linkOptionsType.ts";
6+
7+
type ControlPanelTriggerProps = {
8+
options: LinkOption[];
9+
};
10+
11+
export const ControlPanelTrigger = ({ options }: ControlPanelTriggerProps) => {
12+
const [openMenu, setOpenMenu] = useState(false);
13+
const onSetOpenMenu = (open: boolean) => {
14+
setOpenMenu(open);
15+
};
16+
return (
17+
<DropdownMenu
18+
openMenu={openMenu}
19+
options={options}
20+
setOpenMenu={onSetOpenMenu}
21+
>
22+
<Button
23+
variant="secondary"
24+
className="items-center gap-2.5"
25+
onClick={() => setOpenMenu((prev) => !prev)}
26+
>
27+
<span className="text-light-100">Sign in </span>
28+
<ArrowDown />
29+
</Button>
30+
</DropdownMenu>
31+
);
32+
};
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { ReactNode, useEffect, useRef } from "react";
2+
import { NavLink } from "react-router-dom";
3+
import { twMerge } from "tailwind-merge";
4+
import { Button } from "../ui/button/Button";
5+
import { LinkOption } from "../../types/linkOptionsType";
6+
7+
type ControlPanelTypes = {
8+
classNames?: string;
9+
options: LinkOption[];
10+
children: ReactNode;
11+
openMenu: boolean;
12+
setOpenMenu: (open: boolean) => void;
13+
};
14+
15+
export const DropdownMenu = ({
16+
classNames,
17+
options,
18+
children,
19+
openMenu,
20+
setOpenMenu,
21+
}: ControlPanelTypes) => {
22+
const wrapperRef = useRef<HTMLDivElement | null>(null);
23+
24+
useEffect(() => {
25+
if (!openMenu) return;
26+
27+
const onPointerDown = (e: PointerEvent) => {
28+
const el = wrapperRef.current;
29+
if (!el) {
30+
return;
31+
}
32+
if (!el.contains(e.target as Node)) {
33+
setOpenMenu(false);
34+
}
35+
};
36+
37+
document.addEventListener("pointerdown", onPointerDown);
38+
return () => document.removeEventListener("pointerdown", onPointerDown);
39+
}, [openMenu]);
40+
41+
return (
42+
<div
43+
ref={wrapperRef}
44+
className={twMerge(
45+
"hidden md:flex relative flex-col items-end",
46+
classNames,
47+
)}
48+
>
49+
{children}
50+
<div
51+
className={[
52+
"absolute top-[130%] right-0 z-400 flex flex-col items-start rounded-2xl border border-white/10 bg-[#15141D] shadow-[0_12px_40px_rgba(0,0,0,0.35)] overflow-hidden",
53+
"min-w-40 w-max",
54+
"opacity-0 invisible transition-opacity duration-300 ease-in-out",
55+
openMenu && "opacity-100 visible",
56+
]
57+
.filter(Boolean)
58+
.join(" ")}
59+
>
60+
{options.map((option) => {
61+
const Icon = option.icon;
62+
63+
const content = (
64+
<div className="flex items-center gap-2.5 text-light-100">
65+
{Icon && <Icon className="w-5 h-5 shrink-0" />}
66+
<span>{option.title}</span>
67+
</div>
68+
);
69+
70+
return option.link ? (
71+
<Button
72+
key={option.id}
73+
as={NavLink}
74+
className="flex justify-start w-full rounded-none p-2.5 transition-none
75+
hover:text-(--color-dark-900) hover:bg-gray-400"
76+
to={option.link}
77+
variant="link"
78+
onClick={() => setOpenMenu(false)}
79+
>
80+
{content}
81+
</Button>
82+
) : (
83+
<Button
84+
key={option.id}
85+
className="flex justify-start w-full rounded-none p-2.5 transition-none
86+
hover:text-(--color-dark-900) hover:bg-gray-400"
87+
variant="link"
88+
onClick={() => {
89+
option.actionCallback?.();
90+
setOpenMenu(false);
91+
}}
92+
>
93+
{content}
94+
</Button>
95+
);
96+
})}
97+
</div>
98+
</div>
99+
);
100+
};

0 commit comments

Comments
 (0)