From a48ac3cacc96400a792bb904d0954212ec77c69e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atakan=20Ya=C5=9Far?= Date: Tue, 10 Dec 2024 22:11:14 +0300 Subject: [PATCH 1/4] refactor(mobile): rename FollowButton to FollowUserButton --- mobile/app/(tabs)/profile.tsx | 4 ++-- mobile/app/question/[questionId].tsx | 4 ++-- mobile/components/{FollowButton.tsx => FollowUserButton.tsx} | 2 +- mobile/components/Profile.tsx | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename mobile/components/{FollowButton.tsx => FollowUserButton.tsx} (97%) diff --git a/mobile/app/(tabs)/profile.tsx b/mobile/app/(tabs)/profile.tsx index dbac5ea5..e78b3bc6 100644 --- a/mobile/app/(tabs)/profile.tsx +++ b/mobile/app/(tabs)/profile.tsx @@ -1,5 +1,5 @@ import ErrorAlert from "@/components/ErrorAlert"; -import FollowButton from "@/components/FollowButton"; +import FollowUserButton from "@/components/FollowUserButton"; import { FullscreenLoading } from "@/components/FullscreenLoading"; import { QuestionCard } from "@/components/QuestionCard"; import { @@ -255,7 +255,7 @@ export function UserProfile({ userId }: { userId: string }) { ) ) : ( - data?.data && + data?.data && )} {/* diff --git a/mobile/app/question/[questionId].tsx b/mobile/app/question/[questionId].tsx index 7eb83e46..cb9315c7 100644 --- a/mobile/app/question/[questionId].tsx +++ b/mobile/app/question/[questionId].tsx @@ -1,7 +1,7 @@ import { Answers } from "@/components/Answers"; import ErrorAlert from "@/components/ErrorAlert"; -import FollowButton from "@/components/FollowButton"; +import FollowUserButton from "@/components/FollowUserButton"; import { FullscreenLoading } from "@/components/FullscreenLoading"; import { Button, @@ -259,7 +259,7 @@ export default function QuestionPage() { {token && selfProfile?.id !== question.author.id && ( - + )} diff --git a/mobile/components/FollowButton.tsx b/mobile/components/FollowUserButton.tsx similarity index 97% rename from mobile/components/FollowButton.tsx rename to mobile/components/FollowUserButton.tsx index b154937b..c675c2ef 100644 --- a/mobile/components/FollowButton.tsx +++ b/mobile/components/FollowUserButton.tsx @@ -6,7 +6,7 @@ import { import { useState } from "react"; import { Button, ButtonText } from "./ui/button"; -export default function FollowButton({ +export default function FollowUserButton({ profile, }: { profile: { id?: number; selfFollowing?: boolean }; diff --git a/mobile/components/Profile.tsx b/mobile/components/Profile.tsx index f1145cbc..9a5e0c79 100644 --- a/mobile/components/Profile.tsx +++ b/mobile/components/Profile.tsx @@ -1,7 +1,7 @@ import { UserSummary } from "@/services/api/programmingForumSchemas"; import useAuthStore from "@/services/auth"; import { Link } from "expo-router"; -import FollowButton from "./FollowButton"; +import FollowUserButton from "./FollowUserButton"; import { Text, View } from "./ui"; interface ProfileProps { @@ -18,7 +18,7 @@ export const Profile = ({ profile }: ProfileProps) => { {profile.username} - {profile.id !== selfProfile?.id && } + {profile.id !== selfProfile?.id && } ); }; From 574086780c86297e1ee6739bb63b84a962227e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atakan=20Ya=C5=9Far?= Date: Tue, 10 Dec 2024 22:18:54 +0300 Subject: [PATCH 2/4] fix(api): change tagId type from string to number --- .../services/api/programmingForumComponents.ts | 6 +++--- mobile/services/api/programmingForumSchemas.ts | 12 ++++++------ swagger/openapi.yml | 18 +++++++++--------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/mobile/services/api/programmingForumComponents.ts b/mobile/services/api/programmingForumComponents.ts index 63141bb8..64ed72cd 100644 --- a/mobile/services/api/programmingForumComponents.ts +++ b/mobile/services/api/programmingForumComponents.ts @@ -1787,7 +1787,7 @@ export const useCreateTag = ( }; export type GetTagDetailsPathParams = { - tagId: string; + tagId: number; }; export type GetTagDetailsError = Fetcher.ErrorWrapper<{ @@ -1850,7 +1850,7 @@ export const useGetTagDetails = ( }; export type FollowTagPathParams = { - tagId: string; + tagId: number; }; export type FollowTagError = Fetcher.ErrorWrapper< @@ -1900,7 +1900,7 @@ export const useFollowTag = ( }; export type UnfollowTagPathParams = { - tagId: string; + tagId: number; }; export type UnfollowTagError = Fetcher.ErrorWrapper< diff --git a/mobile/services/api/programmingForumSchemas.ts b/mobile/services/api/programmingForumSchemas.ts index 8c73d7f9..50f2bb46 100644 --- a/mobile/services/api/programmingForumSchemas.ts +++ b/mobile/services/api/programmingForumSchemas.ts @@ -212,13 +212,13 @@ export type AnswerDetails = { }; /** - * @example {"tagId":"python","name":"Python","description":"Python is a programming language.","questionCount":100,"followersCount":1000,"following":false,"photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Python-logo-notext.svg/220px-Python-logo-notext.svg.png","authors":["Guido van Rossum"],"inceptionYear":"1991","fileExtension":".py","officialWebsite":"https://www.python.org","stackExchangeTag":"python"} - * @example {"tagId":"java","name":"Java","type":"PROGRAMMING_LANGUAGE","description":"Java is a class-based, object-oriented programming language.","questionCount":200,"followersCount":800,"following":true,"photo":"https://upload.wikimedia.org/wikipedia/en/thumb/3/30/Java_programming_language_logo.svg/182px-Java_programming_language_logo.svg.png","authors":["James Gosling"],"inceptionYear":"1995","fileExtension":".java","officialWebsite":"https://www.java.com","stackExchangeTag":"java"} - * @example {"tagId":"react","name":"React","type":"SOFTWARE_LIBRARY","description":"React is a JavaScript library for building user interfaces.","questionCount":150,"followersCount":600,"following":false,"photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/180px-React-icon.svg.png","officialWebsite":"https://reactjs.org","stackExchangeTag":"reactjs"} - * @example {"tagId":"oop","name":"Object-Oriented Programming","type":"PROGRAMMING_PARADIGM","description":"OOP is a programming paradigm based on objects containing data and code.","questionCount":80,"followersCount":400,"following":true,"photo":"https://example.com/oop-icon.png","stackExchangeTag":"oop"} + * @example {"tagId":1,"name":"Python","description":"Python is a programming language.","questionCount":100,"followersCount":1000,"following":false,"photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Python-logo-notext.svg/220px-Python-logo-notext.svg.png","authors":["Guido van Rossum"],"inceptionYear":"1991","fileExtension":".py","officialWebsite":"https://www.python.org","stackExchangeTag":"python"} + * @example {"tagId":2,"name":"Java","type":"PROGRAMMING_LANGUAGE","description":"Java is a class-based, object-oriented programming language.","questionCount":200,"followersCount":800,"following":true,"photo":"https://upload.wikimedia.org/wikipedia/en/thumb/3/30/Java_programming_language_logo.svg/182px-Java_programming_language_logo.svg.png","authors":["James Gosling"],"inceptionYear":"1995","fileExtension":".java","officialWebsite":"https://www.java.com","stackExchangeTag":"java"} + * @example {"tagId":3,"name":"React","type":"SOFTWARE_LIBRARY","description":"React is a JavaScript library for building user interfaces.","questionCount":150,"followersCount":600,"following":false,"photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/180px-React-icon.svg.png","officialWebsite":"https://reactjs.org","stackExchangeTag":"reactjs"} + * @example {"tagId":4,"name":"Object-Oriented Programming","type":"PROGRAMMING_PARADIGM","description":"OOP is a programming paradigm based on objects containing data and code.","questionCount":80,"followersCount":400,"following":true,"photo":"https://example.com/oop-icon.png","stackExchangeTag":"oop"} */ export type TagDetails = { - tagId: string; + tagId: number; name: string; tagType?: TagType; description: string; @@ -258,7 +258,7 @@ export type TagDetails = { }; /** - * @example {"tagId":"python","name":"Python","questionCount":100,"photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Python-logo-notext.svg/220px-Python-logo-notext.svg.png"} + * @example {"tagId":1,"name":"Python","questionCount":100,"photo":"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Python-logo-notext.svg/220px-Python-logo-notext.svg.png"} */ export type TagSummary = { id?: string; diff --git a/swagger/openapi.yml b/swagger/openapi.yml index f619eefd..467787f1 100644 --- a/swagger/openapi.yml +++ b/swagger/openapi.yml @@ -844,7 +844,7 @@ paths: in: path required: true schema: - type: string + type: integer responses: "200": description: Successful response @@ -873,7 +873,7 @@ paths: in: path required: true schema: - type: string + type: integer responses: "200": description: Successfully followed the tag @@ -894,7 +894,7 @@ paths: in: path required: true schema: - type: string + type: integer responses: "200": description: Successfully unfollowed the tag @@ -1619,7 +1619,7 @@ components: - officialWebsite properties: tagId: - type: string + type: integer name: type: string tagType: @@ -1662,7 +1662,7 @@ components: items: $ref: "#/components/schemas/QuestionSummary" examples: - - tagId: "python" + - tagId: 1 name: "Python" description: "Python is a programming language." questionCount: 100 @@ -1675,7 +1675,7 @@ components: fileExtension: ".py" officialWebsite: "https://www.python.org" stackExchangeTag: "python" - - tagId: "java" + - tagId: 2 name: "Java" type: "PROGRAMMING_LANGUAGE" description: "Java is a class-based, object-oriented programming language." @@ -1689,7 +1689,7 @@ components: fileExtension: ".java" officialWebsite: "https://www.java.com" stackExchangeTag: "java" - - tagId: "react" + - tagId: 3 name: "React" type: "SOFTWARE_LIBRARY" description: "React is a JavaScript library for building user interfaces." @@ -1699,7 +1699,7 @@ components: photo: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/180px-React-icon.svg.png" officialWebsite: "https://reactjs.org" stackExchangeTag: "reactjs" - - tagId: "oop" + - tagId: 4 name: "Object-Oriented Programming" type: "PROGRAMMING_PARADIGM" description: "OOP is a programming paradigm based on objects containing data and code." @@ -1724,7 +1724,7 @@ components: type: string format: url examples: - - tagId: "python" + - tagId: 1 name: "Python" questionCount: 100 photo: "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Python-logo-notext.svg/220px-Python-logo-notext.svg.png" From c0ee4342b7d6cc70a0add9c53a8c4e35a3c82adc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atakan=20Ya=C5=9Far?= Date: Tue, 10 Dec 2024 23:55:36 +0300 Subject: [PATCH 3/4] feat(mobile): add follow tag feature --- mobile/app/tags/[tagId].tsx | 39 +++++++---- mobile/components/FollowTagButton.tsx | 94 +++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 mobile/components/FollowTagButton.tsx diff --git a/mobile/app/tags/[tagId].tsx b/mobile/app/tags/[tagId].tsx index a81a264a..647b9fd7 100644 --- a/mobile/app/tags/[tagId].tsx +++ b/mobile/app/tags/[tagId].tsx @@ -1,4 +1,5 @@ import ErrorAlert from "@/components/ErrorAlert"; +import FollowTagButton from "@/components/FollowTagButton"; import { FullscreenLoading } from "@/components/FullscreenLoading"; import { QuestionList } from "@/components/QuestionsList"; import { @@ -30,13 +31,14 @@ import useAuthStore from "@/services/auth"; import { Link, router, useLocalSearchParams } from "expo-router"; import { ArrowLeftIcon, ChevronDownIcon, Plus } from "lucide-react-native"; import { useState } from "react"; +import { SvgUri } from "react-native-svg"; export default function TagPage() { const { tagId } = useLocalSearchParams<{ tagId: string }>(); const { data, isLoading, error } = useGetTagDetails( { - pathParams: { tagId: tagId! }, + pathParams: { tagId: Number(tagId) }, }, { enabled: !!tagId, @@ -68,11 +70,11 @@ export default function TagPage() { } return ( - + + + {tag?.logoImage && tag.logoImage.endsWith(".svg") && ( + + )} + + {tag?.logoImage && tag.logoImage.endsWith(".png") && ( + + )} + + {tag.name} + - {tag.name} {tag.description} @@ -155,7 +166,7 @@ export default function TagPage() { diff --git a/mobile/components/FollowTagButton.tsx b/mobile/components/FollowTagButton.tsx new file mode 100644 index 00000000..e97451fb --- /dev/null +++ b/mobile/components/FollowTagButton.tsx @@ -0,0 +1,94 @@ +import { + useFollowTag, + useGetTagDetails, + useUnfollowTag, +} from "@/services/api/programmingForumComponents"; +import { useEffect, useState } from "react"; +import { Button, ButtonText } from "./ui/button"; + +export default function FollowTagButton({ + tag, +}: { + tag: { id?: number; selfFollowing?: boolean }; +}) { + const { isLoading, data, error, refetch } = useGetTagDetails( + { + pathParams: { + tagId: tag.id!, + }, + }, + ); + + + const [optimisticFollowing, setOptimisticFollowing] = useState( + false + ); + + useEffect(() => { + setOptimisticFollowing(tag.selfFollowing ?? false); + }, [tag.selfFollowing]); + + const { mutateAsync: follow } = useFollowTag({ + onMutate: async () => { + setOptimisticFollowing(true); + }, + onSuccess: (data) => { + refetch().then(() => { + setOptimisticFollowing(true); + }); + console.log(data); + }, + onError: (error) => { + console.error(error); + setOptimisticFollowing(false); + }, + }); + + const { mutateAsync: unfollow } = useUnfollowTag({ + onMutate: async () => { + setOptimisticFollowing(false); + }, + onSuccess: () => { + refetch().then(() => { + setOptimisticFollowing(false); + }); + }, + onError: () => { + setOptimisticFollowing(true); + }, + }); + + const following = optimisticFollowing ?? data?.data?.following; + + return ( + + ); +} From b1b4606bba5e50cdd1b68ec9f52b91aa5ffd2f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atakan=20Ya=C5=9Far?= Date: Mon, 16 Dec 2024 13:44:35 +0300 Subject: [PATCH 4/4] fix(mobile): update new backend URL --- mobile/services/api/programmingForumFetcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/services/api/programmingForumFetcher.ts b/mobile/services/api/programmingForumFetcher.ts index f1c8d7d8..4fe0448c 100644 --- a/mobile/services/api/programmingForumFetcher.ts +++ b/mobile/services/api/programmingForumFetcher.ts @@ -13,7 +13,7 @@ const baseUrl = process.env.NODE_ENV === "development" ? `http://${new URL(Constants.default.experienceUrl).hostname}:5173/api/v1` : - "https://programming-languages-forum-ahwzj.ondigitalocean.app/api/v1"; + "https://programming-languages-forum-psrb6.ondigitalocean.app/api/v1"; console.log(baseUrl);