Skip to content

Commit d6a0f76

Browse files
committed
cringe headlessui moment
1 parent 2f95aee commit d6a0f76

File tree

22 files changed

+620
-66
lines changed

22 files changed

+620
-66
lines changed

backend/main/pom.xml

+7
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@
9393
<version>6.2.0.Final</version>
9494
</dependency>
9595

96+
<!-- rate limiting -->
97+
<dependency>
98+
<groupId>com.github.vladimir-bukhtoyarov</groupId>
99+
<artifactId>bucket4j-core</artifactId>
100+
<version>7.5.0</version>
101+
</dependency>
102+
96103

97104
</dependencies>
98105

backend/main/src/main/java/com/microservice/tasks/controllers/WorkspaceController.java

-3
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,12 @@
1111
import org.springframework.web.bind.annotation.PutMapping;
1212
import org.springframework.web.bind.annotation.RequestBody;
1313
import org.springframework.web.bind.annotation.RequestMapping;
14-
import org.springframework.web.bind.annotation.RequestParam;
1514
import org.springframework.web.bind.annotation.ResponseStatus;
1615
import org.springframework.web.bind.annotation.RestController;
1716

1817
import com.microservice.tasks.models.Workspace;
1918
import com.microservice.tasks.services.WsService;
2019

21-
import lombok.Data;
22-
2320
@RestController
2421
@RequestMapping("/ws")
2522
public class WorkspaceController {

backend/main/src/main/java/com/microservice/tasks/services/AuthService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public ResponseEntity<?> googleAuth(String googleToken) {
116116
// Create new user
117117
String username = userGenService.generateUsername();
118118
String password = userGenService.generatePassword();
119-
User newUser = new User(username, sanitizedEmail, passwordEncoder.encode(password));
119+
User newUser = new User(sanitizedEmail, username, passwordEncoder.encode(password));
120120
newUser = userRepo.save(newUser);
121121
existingUser = newUser;
122122
}

frontend/src/App.jsx

+6
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@ import MyComp from "@/components/MyComp";
22
import { BrowserRouter, Route, Routes } from "react-router-dom";
33
import Main from "./pages/Main";
44
import Login from "./pages/Login";
5+
import ProfilePage from "./pages/main/Profile";
6+
import SettingsPage from "./pages/main/Settings";
7+
import WorkspacePage from "./pages/main/[workspace]/Workspace";
58

69
function App() {
710
return (
811
<BrowserRouter>
912
<Routes>
1013
<Route exact path="/" element={<Main />}>
1114
<Route path="" element={<MyComp />} />
15+
<Route path="profile" element={<ProfilePage />} />
16+
<Route path="settings" element={<SettingsPage />} />
17+
<Route path="workspace/:slug" element={<WorkspacePage />} />
1218
</Route>
1319
<Route path="login" element={<Login />} />
1420
<Route path="register" element={<h1>Register</h1>} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { ChevronDown, LogOut, Settings, UserIcon } from "lucide-react";
2+
import CustomMenu from "../CustomMenu";
3+
import { useNavigate } from "react-router-dom";
4+
5+
const DropdownAvatar = ({}) => {
6+
const navigate = useNavigate();
7+
const user = {
8+
email: "prodtf@gmail.com",
9+
username: "fluffy-duck-78",
10+
firstname: "Yassine",
11+
lastname: "Karoui",
12+
};
13+
14+
// squircle avatar showing user initials in a squircle shape, with a dropdown menu
15+
return (
16+
<CustomMenu
17+
info={"@" + user.username}
18+
triggerContent={
19+
<div className="flex items-center">
20+
<div className="text-stone-400 text-xs">
21+
{user.firstname[0]} {user.lastname[0]}
22+
</div>
23+
<ChevronDown className="size-4 text-stone-400 ml-2" />
24+
</div>
25+
}
26+
items={[
27+
{
28+
text: "Profile",
29+
action: () => navigate("/profile"),
30+
icon: UserIcon,
31+
shortcut: "⌘1",
32+
},
33+
{
34+
text: "Settings",
35+
action: () => navigate("/settings"),
36+
icon: Settings,
37+
shortcut: "⌘2",
38+
seperator: true,
39+
},
40+
{
41+
text: "Logout",
42+
action: () => navigate("/"),
43+
icon: LogOut,
44+
shortcut: "⌘3",
45+
},
46+
]}
47+
anchor="bottom start"
48+
ghost
49+
/>
50+
);
51+
};
52+
53+
export default DropdownAvatar;
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import clsx from "clsx";
2+
3+
const ButtonPrimary = ({ ghost, children, className, ...props }) => {
4+
return (
5+
<button
6+
className={clsx(
7+
"px-4 py-2 rounded-lg bg-secondary hover:bg-zinc-800 w-full h-10",
8+
className,
9+
{
10+
"bg-transparent hover:bg-secondary": ghost,
11+
}
12+
)}
13+
{...props}
14+
>
15+
{children || "Click me!"}
16+
</button>
17+
);
18+
};
19+
20+
export default ButtonPrimary;
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
2+
import clsx from "clsx";
3+
import { ChevronDown } from "lucide-react";
4+
import React from "react";
5+
6+
const CustomMenu = ({ triggerContent, items, anchor, ghost, info }) => {
7+
return (
8+
<Menu>
9+
<MenuButton
10+
className={clsx(
11+
"inline-flex items-center gap-2 bg-zinc-800 py-1.5 px-3 text-sm/6 font-semibold text-white focus:outline-none hover:bg-zinc-700 data-[open]:bg-zinc-700 data-[focus]:outline-1 data-[focus]:outline-white h-10 rounded-lg",
12+
ghost && "!bg-transparent hover:!bg-zinc-800"
13+
)}
14+
>
15+
{triggerContent || (
16+
<>
17+
Options
18+
<ChevronDown className="size-4 fill-white/60" />
19+
</>
20+
)}
21+
</MenuButton>
22+
23+
<MenuItems
24+
transition
25+
anchor={anchor || "bottom end"}
26+
className="w-52 origin-top-right rounded-xl border border-white/5 bg-white/5 p-1 text-sm/6 text-white transition duration-100 ease-out [--anchor-gap:var(--spacing-1)] focus:outline-none data-[closed]:scale-95 data-[closed]:opacity-0 !bg-secondary"
27+
>
28+
{info && (
29+
<>
30+
<div className="p-2 text-xs text-white/50">{info}</div>
31+
<div className="my-1 h-px bg-white/5" />
32+
</>
33+
)}
34+
{items.map((item, index) => (
35+
<div key={index}>
36+
<MenuItem>
37+
<button
38+
onClick={item.action}
39+
className="group flex w-full items-center gap-2 rounded-lg py-1.5 px-3 data-[focus]:bg-white/10"
40+
>
41+
{item.icon && (
42+
<item.icon className="size-4 text-white/40" />
43+
)}
44+
<span>{item.text}</span>
45+
<kbd className="ml-auto hidden font-sans text-xs text-white/50 group-data-[focus]:inline">
46+
{item.shortcut}
47+
</kbd>
48+
</button>
49+
</MenuItem>
50+
{item.seperator && (
51+
<div className="my-1 h-px bg-white/5" />
52+
)}
53+
</div>
54+
))}
55+
</MenuItems>
56+
</Menu>
57+
);
58+
};
59+
60+
export default CustomMenu;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import ButtonPrimary from "./ButtonPrimary";
2+
import { useGoogleLogin } from "@react-oauth/google";
3+
import Spinner from "./Spinner";
4+
5+
const googleLogoUrl =
6+
"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Google_%22G%22_logo.svg/768px-Google_%22G%22_logo.svg.png";
7+
8+
const GoogleLoginButton = ({ onSuccess, onError, isLoading }) => {
9+
const login = useGoogleLogin({
10+
onSuccess,
11+
onError,
12+
isSignedIn: true,
13+
accessType: "offline",
14+
});
15+
16+
return (
17+
<ButtonPrimary onClick={login} className="text-sm" type="button">
18+
<div className="flex items-center justify-center">
19+
{isLoading ? (
20+
<>...</>
21+
) : (
22+
<>
23+
<img
24+
src={googleLogoUrl}
25+
alt="google-icon"
26+
className="w-5 h-5 mr-2"
27+
/>
28+
Sign In with Google
29+
</>
30+
)}
31+
</div>
32+
</ButtonPrimary>
33+
);
34+
};
35+
36+
export default GoogleLoginButton;
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useLottie } from "lottie-react";
2+
import poker from "@/assets/lottie/poker.json";
3+
4+
const LottieTest = () => {
5+
const options = {
6+
animationData: poker,
7+
loop: true,
8+
style: {
9+
width: "100%",
10+
height: "100%",
11+
},
12+
};
13+
14+
const { View } = useLottie(options);
15+
16+
return <div className="w-full h-32">{View}</div>;
17+
};
18+
19+
export default LottieTest;

frontend/src/components/Spinner.jsx

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import clsx from "clsx";
2+
3+
const Spinner = ({ className, size }) => {
4+
return (
5+
<div className={clsx(className, "flex justify-center w-full h-full")}>
6+
<div
7+
className="border-[6px] border-accent-light/30 border-t-accent-light rounded-full animate-spin"
8+
style={{ width: `${size}rem`, height: `${size}rem` }}
9+
></div>
10+
</div>
11+
);
12+
};
13+
14+
export default Spinner;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { useTaskStore } from "@/stores";
2+
import { GripVertical, Trash } from "lucide-react";
3+
import { useRef } from "react";
4+
import { useDrag, useDrop } from "react-dnd";
5+
6+
const DraggableTask = ({ task, index, moveTask }) => {
7+
const removeTask = useTaskStore((state) => state.removeTask);
8+
const ref = useRef(null);
9+
10+
const [{ isDragging }, drag] = useDrag({
11+
type: "TASK",
12+
item: { index },
13+
collect: (monitor) => ({
14+
isDragging: monitor.isDragging(),
15+
}),
16+
});
17+
18+
const [, drop] = useDrop({
19+
accept: "TASK",
20+
hover: (draggedItem) => {
21+
if (draggedItem.index !== index) {
22+
moveTask(draggedItem.index, index);
23+
draggedItem.index = index;
24+
}
25+
},
26+
});
27+
28+
drag(drop(ref));
29+
30+
return (
31+
<div
32+
ref={ref}
33+
className={`relative group h-10 transition-opacity duration-300 ${
34+
isDragging ? "opacity-40" : "opacity-100"
35+
}`}
36+
>
37+
{/* <div className="absolute -left-5 hidden group-hover:flex h-full items-center">
38+
<GripVertical className="size-5 text-white/50 hover:cursor-grab" />
39+
</div> */}
40+
<div className="flex items-center justify-between gap-2 bg-white/5 rounded-lg px-3 py-2 h-full cursor-grab">
41+
<div className="font-medium">{task.title}</div>
42+
<button
43+
className="text-white/50 hidden group-hover:block"
44+
onClick={() => removeTask(task.id)}
45+
>
46+
<Trash className="size-4" />
47+
</button>
48+
</div>
49+
</div>
50+
);
51+
};
52+
53+
export default DraggableTask;
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { useState } from "react";
2+
import { DndProvider } from "react-dnd";
3+
import { HTML5Backend } from "react-dnd-html5-backend";
4+
import DraggableTask from "./DraggableTask";
5+
6+
const TaskList = ({ tasks }) => {
7+
const [taskList, setTaskList] = useState(tasks);
8+
const [draggingIndex, setDraggingIndex] = useState(null);
9+
10+
const moveTask = (fromIndex, toIndex) => {
11+
const updatedTasks = [...taskList];
12+
const [movedTask] = updatedTasks.splice(fromIndex, 1);
13+
updatedTasks.splice(toIndex, 0, movedTask);
14+
setTaskList(updatedTasks);
15+
};
16+
17+
return (
18+
<DndProvider backend={HTML5Backend}>
19+
<div className="flex flex-col gap-4">
20+
{taskList.map((task, index) => (
21+
<div key={task.id} className="relative">
22+
<DraggableTask
23+
task={task}
24+
index={index}
25+
moveTask={moveTask}
26+
setDraggingIndex={setDraggingIndex}
27+
/>
28+
{draggingIndex === index && (
29+
<div className="absolute inset-0 border-2 border-dashed border-gray-300 opacity-50 pointer-events-none"></div>
30+
)}
31+
</div>
32+
))}
33+
</div>
34+
</DndProvider>
35+
);
36+
};
37+
38+
export default TaskList;

0 commit comments

Comments
 (0)