Skip to content

Commit 30a8f08

Browse files
authored
Merge pull request #75 from 4byte-dev/refactor-ui/update-forms
refactor(ui): use react-query, validation, and rhf on forms
2 parents 0786495 + 20f2402 commit 30a8f08

34 files changed

+1923
-1416
lines changed

packages/React/src/Http/Controllers/ReactController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public function comment(ReactRequest $request): JsonResponse
119119
[$baseClass, $itemId] = $request->resolveTarget();
120120
$userId = Auth::id();
121121
$content = $request->input('content');
122-
$parentId = $request->route('parent', null);
122+
$parentId = $request->input('parent', null);
123123

124124
$comment = $this->reactService->insertComment($baseClass, $itemId, $content, $userId, $parentId);
125125

resources/css/app.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ html {
122122
}
123123

124124
.w-md-editor-text-pre > code,
125-
.w-md-editor-text-input {
125+
.w-md-editor-text-input,
126+
.wmde-markdown-color .code-line {
126127
font-size: 20px !important;
127128
line-height: 21px !important;
128129
}

resources/js/Api/AuthApi.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@ export default {
1313
resetPassword: (data) => {
1414
return ApiService.fetchJson(route("api.auth.reset-password.request"), data);
1515
},
16+
logout: () => {
17+
return ApiService.fetchJson(route("api.auth.logout"));
18+
},
1619
};

resources/js/Api/ContentApi.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import ApiService from "@/Services/ApiService";
2+
3+
export default {
4+
search: (query) => {
5+
return ApiService.fetchJson(
6+
route("api.search") + "?q=" + encodeURIComponent(query),
7+
{},
8+
{ method: "GET" },
9+
);
10+
},
11+
12+
feedData: () => {
13+
return ApiService.fetchJson(route("api.feed.data"), {}, { method: "GET" });
14+
},
15+
16+
createEntry: (data) => {
17+
return ApiService.fetchJson(route("api.entry.crud.create"), data, {
18+
isMultipart: true,
19+
});
20+
},
21+
createArticle: (data) => {
22+
return ApiService.fetchJson(route("api.article.crud.create"), data, {
23+
isMultipart: true,
24+
});
25+
},
26+
editArticle: (slug, data) => {
27+
return ApiService.fetchJson(route("api.article.crud.edit", { slug }), data, {
28+
isMultipart: true,
29+
});
30+
},
31+
};

resources/js/Api/ReactApi.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import ApiService from "@/Services/ApiService";
2+
3+
export default {
4+
save: (data) => {
5+
return ApiService.fetchJson(route("api.react.save", data));
6+
},
7+
follow: (data) => {
8+
return ApiService.fetchJson(route("api.react.follow", data));
9+
},
10+
like: (data) => {
11+
return ApiService.fetchJson(route("api.react.like", data));
12+
},
13+
dislike: (data) => {
14+
return ApiService.fetchJson(route("api.react.dislike", data));
15+
},
16+
comments: (data) => {
17+
return ApiService.fetchJson(route("api.react.comments", data));
18+
},
19+
replies: (data) => {
20+
return ApiService.fetchJson(route("api.react.comment.replies", data));
21+
},
22+
submitComment: (query, body) => {
23+
return ApiService.fetchJson(route("api.react.comment", query), body);
24+
},
25+
};

resources/js/Api/UserApi.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import ApiService from "@/Services/ApiService";
22

33
export default {
4+
preview: (data) => {
5+
return ApiService.fetchJson(route("api.user.preview", data), {}, { method: "GET" });
6+
},
7+
48
getNotifications: () => {
59
return ApiService.fetchJson(
610
route("api.notification.list"),
@@ -35,4 +39,7 @@ export default {
3539
logOutOtherBrowserSessions: (data) => {
3640
return ApiService.fetchJson(route("api.user.settings.logout-other-sessions"), data);
3741
},
42+
resendVerify: () => {
43+
return ApiService.fetchJson(route("api.user.verification.resend"));
44+
},
3845
};

resources/js/Components/Common/MarkdownRenderer.jsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ export default function MarkdownRenderer({ content }) {
2929
useEffect(() => {
3030
initCodeGroups();
3131
document.querySelectorAll("pre code").forEach((block) => {
32-
hljs.highlightElement(block);
32+
document.querySelectorAll("pre code").forEach((block) => {
33+
if (!block.dataset.highlighted) {
34+
hljs.highlightElement(block);
35+
}
36+
});
37+
3338
if (!block.parentElement.querySelector(".copy-btn")) {
3439
const btn = document.createElement("button");
3540
btn.innerText = t("Copy");

resources/js/Components/Common/UserProfileHover.jsx

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,42 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/Components/Ui/Avatar";
44
import { Button } from "@/Components/Ui/Form/Button";
55
import { Card, CardContent } from "@/Components/Ui/Card";
66
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/Components/Ui/HoverCard";
7-
import ApiService from "@/Services/ApiService";
87
import { Link } from "@inertiajs/react";
98
import { useAuthStore } from "@/Stores/AuthStore";
109
import { Trans, useTranslation } from "react-i18next";
1110
import { toast } from "@/Hooks/useToast";
11+
import { useMutation } from "@tanstack/react-query";
12+
import ReactApi from "@/Api/ReactApi";
13+
import UserApi from "@/Api/UserApi";
1214

1315
export function UserProfileHover({ username, children }) {
1416
const [isFollowing, setIsFollowing] = useState(false);
1517
const [followers, setFollowers] = useState(0);
1618
const [followings, setFollowings] = useState(0);
1719
const [user, setUser] = useState({});
1820
const [profile, setProfile] = useState({});
19-
const [isLoading, setIsLoading] = useState(true);
2021
const [hasFetched, setHasFetched] = useState(false);
2122
const authStore = useAuthStore();
2223
const [isOwnProfile, setIsOwnProfile] = useState(false);
2324
const hoverTimeoutRef = useRef(null);
2425
const { t } = useTranslation();
2526

27+
const previewMutation = useMutation({
28+
mutationFn: () => UserApi.preview({ username }),
29+
onSuccess: (response) => {
30+
setUser(response.user);
31+
setProfile(response.profile);
32+
setFollowers(Number(response.user.followers));
33+
setFollowings(Number(response.user.followings));
34+
setIsFollowing(response.user.isFollowing);
35+
setHasFetched(true);
36+
},
37+
});
38+
2639
const handleHover = () => {
2740
if (hasFetched) return;
28-
setIsLoading(true);
29-
3041
hoverTimeoutRef.current = setTimeout(() => {
31-
setIsLoading(true);
32-
ApiService.fetchJson(
33-
route("api.user.preview", { username }),
34-
{},
35-
{ method: "GET" },
36-
).then((response) => {
37-
setUser(response.user);
38-
setProfile(response.profile);
39-
setFollowers(Number(response.user.followers));
40-
setFollowings(Number(response.user.followings));
41-
setIsFollowing(response.user.isFollowing);
42-
setIsLoading(false);
43-
setHasFetched(true);
44-
});
42+
previewMutation.mutate();
4543
}, 600);
4644
};
4745

@@ -61,26 +59,26 @@ export function UserProfileHover({ username, children }) {
6159
}, []);
6260

6361
useEffect(() => {
64-
setIsOwnProfile(authStore.isAuthenticated && user.username == authStore.user.username);
62+
setIsOwnProfile(authStore.isAuthenticated && username == authStore.user.username);
6563
}, [user]);
6664

67-
const handleFollow = async () => {
68-
ApiService.fetchJson(
69-
route("api.react.follow", { type: "user", slug: user.username }),
70-
{},
71-
{ method: "POST" },
72-
)
73-
.then(() => {
74-
setIsFollowing(!isFollowing);
75-
setFollowers(isFollowing ? followers - 1 : followers + 1);
76-
})
77-
.catch(() => {
78-
toast({
79-
title: t("Error"),
80-
description: t("You can react to the same user once a day"),
81-
variant: "destructive",
82-
});
65+
const followMutation = useMutation({
66+
mutationFn: () => ReactApi.follow({ type: "user", slug: username }),
67+
onSuccess: () => {
68+
setIsFollowing(!isFollowing);
69+
setFollowers(isFollowing ? followers - 1 : followers + 1);
70+
},
71+
onError: () => {
72+
toast({
73+
title: t("Error"),
74+
description: t("You can react to the same user once a day"),
75+
variant: "destructive",
8376
});
77+
},
78+
});
79+
80+
const handleFollow = () => {
81+
followMutation.mutate();
8482
};
8583

8684
return (
@@ -97,7 +95,7 @@ export function UserProfileHover({ username, children }) {
9795
<HoverCardContent className="w-80" side="bottom" align="start">
9896
<Card className="border-0 shadow-none">
9997
<CardContent className="p-4">
100-
{isLoading && (
98+
{previewMutation.isPending && (
10199
<div className="flex justify-center py-8">
102100
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
103101
</div>

resources/js/Components/Content/Card/EntryCard.jsx

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ import {
1616
ThumbsUp,
1717
Trash,
1818
} from "lucide-react";
19-
import ApiService from "@/Services/ApiService";
2019
import { useTranslation } from "react-i18next";
2120
import { toast } from "@/Hooks/useToast";
2221
import { Link } from "@inertiajs/react";
22+
import { useMutation } from "@tanstack/react-query";
23+
import ReactApi from "@/Api/ReactApi";
2324

2425
export function EntryCard({
2526
user,
@@ -45,61 +46,77 @@ export function EntryCard({
4546
const { t } = useTranslation();
4647
const hasMedia = media && media.length > 0;
4748

49+
const likeMutation = useMutation({
50+
mutationFn: () => ReactApi.like({ type: "entry", slug: slug }),
51+
onSuccess: () => {
52+
if (isLiked) {
53+
setIsLiked(false);
54+
setLikes(likes - 1);
55+
} else {
56+
if (isDisliked) {
57+
setIsDisliked(false);
58+
setDislikes(dislikes - 1);
59+
}
60+
setIsLiked(true);
61+
setLikes(likes + 1);
62+
}
63+
},
64+
onError: () => {
65+
toast({
66+
title: t("Error"),
67+
description: t("You can react to the same entry once a day"),
68+
variant: "destructive",
69+
});
70+
},
71+
});
72+
4873
const handleLike = () => {
4974
if (!authStore.isAuthenticated) return;
50-
ApiService.fetchJson(route("api.react.like", { type: "entry", slug: slug }))
51-
.then(() => {
75+
76+
likeMutation.mutate();
77+
};
78+
79+
const dislikeMutation = useMutation({
80+
mutationFn: () => ReactApi.dislike({ type: "entry", slug: slug }),
81+
onSuccess: () => {
82+
if (isDisliked) {
83+
setIsDisliked(false);
84+
setDislikes(dislikes - 1);
85+
} else {
5286
if (isLiked) {
5387
setIsLiked(false);
5488
setLikes(likes - 1);
55-
} else {
56-
if (isDisliked) {
57-
setIsDisliked(false);
58-
setDislikes(dislikes - 1);
59-
}
60-
setIsLiked(true);
61-
setLikes(likes + 1);
6289
}
63-
})
64-
.catch(() => {
65-
toast({
66-
title: t("Error"),
67-
description: t("You can react to the same entry once a day"),
68-
variant: "destructive",
69-
});
90+
setIsDisliked(true);
91+
setDislikes(dislikes + 1);
92+
}
93+
},
94+
onError: () => {
95+
toast({
96+
title: t("Error"),
97+
description: t("You can react to the same entry once a day"),
98+
variant: "destructive",
7099
});
71-
};
100+
},
101+
});
72102

73103
const handleDislike = () => {
74104
if (!authStore.isAuthenticated) return;
75-
ApiService.fetchJson(route("api.react.dislike", { type: "entry", slug: slug }))
76-
.then(() => {
77-
if (isDisliked) {
78-
setIsDisliked(false);
79-
setDislikes(dislikes - 1);
80-
} else {
81-
if (isLiked) {
82-
setIsLiked(false);
83-
setLikes(likes - 1);
84-
}
85-
setIsDisliked(true);
86-
setDislikes(dislikes + 1);
87-
}
88-
})
89-
.catch(() => {
90-
toast({
91-
title: t("Error"),
92-
description: t("You can react to the same entry once a day"),
93-
variant: "destructive",
94-
});
95-
});
105+
106+
dislikeMutation.mutate();
96107
};
97108

109+
const saveMutation = useMutation({
110+
mutationFn: () => ReactApi.save({ type: "entry", slug: slug }),
111+
onSuccess: () => {
112+
setIsSaved(!isSaved);
113+
},
114+
});
115+
98116
const handleSave = () => {
99117
if (!authStore.isAuthenticated) return;
100-
ApiService.fetchJson(route("api.react.save", { type: "entry", slug: slug })).then(() => {
101-
setIsSaved(!isSaved);
102-
});
118+
119+
saveMutation.mutate();
103120
};
104121

105122
const handleShare = () => {

0 commit comments

Comments
 (0)