feat: Implement person management features
- Added hooks for listing, creating, updating, deleting, sharing, and merging people. - Introduced a new route for person details and media. - Implemented clustering faces functionality. - Created services for person-related API interactions. feat: Introduce tag management functionality - Added hooks for listing, adding, and removing tags from media. - Created services for tag-related API interactions. feat: Enhance user authentication handling - Added a hook to fetch current user details. - Updated auth storage to manage user state more effectively. feat: Update album management features - Enhanced album service to return created album details. - Updated API handlers to return album responses upon creation. - Modified album repository to return created album. feat: Implement media management improvements - Added media details fetching and processing of media URLs. - Enhanced media upload functionality to return processed media. feat: Introduce face management features - Added services for listing faces for media and assigning faces to persons. fix: Update API client to clear authentication state on 401 errors.
This commit is contained in:
@@ -1,5 +1,23 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { addMediaToAlbum, createAlbum, getAlbum, getAlbumMedia, getAlbums, removeMediaFromAlbum, type AddMediaToAlbumPayload, type RemoveMediaFromAlbumPayload } from '@/services/album-service'
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
addMediaToAlbum,
|
||||
createAlbum,
|
||||
deleteAlbum,
|
||||
getAlbum,
|
||||
getAlbumMedia,
|
||||
getAlbums,
|
||||
removeMediaFromAlbum,
|
||||
setAlbumThumbnail,
|
||||
shareAlbum,
|
||||
updateAlbum,
|
||||
type AddMediaToAlbumPayload,
|
||||
type CreateAlbumPayload,
|
||||
type RemoveMediaFromAlbumPayload,
|
||||
type SetAlbumThumbnailPayload,
|
||||
type ShareAlbumPayload,
|
||||
type UpdateAlbumPayload,
|
||||
} from "@/services/album-service";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
const ALBUMS_KEY = ["albums"];
|
||||
|
||||
@@ -8,60 +26,8 @@ const ALBUMS_KEY = ["albums"];
|
||||
*/
|
||||
export const useGetAlbums = () => {
|
||||
return useQuery({
|
||||
queryKey: ['albums'],
|
||||
queryKey: [ALBUMS_KEY, "list"],
|
||||
queryFn: getAlbums,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutation hook to create a new album.
|
||||
*/
|
||||
export const useCreateAlbum = () => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: createAlbum,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['albums'] })
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Failed to create album:', error)
|
||||
// TODO: Add user-facing toast
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const useGetAlbumMedia = (albumId: string) => {
|
||||
return useQuery({
|
||||
queryKey: [ALBUMS_KEY, albumId, "media"],
|
||||
queryFn: () => getAlbumMedia(albumId),
|
||||
enabled: !!albumId,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to add media to an album.
|
||||
*/
|
||||
export const useAddMediaToAlbum = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
albumId,
|
||||
payload,
|
||||
}: {
|
||||
albumId: string;
|
||||
payload: AddMediaToAlbumPayload;
|
||||
}) => addMediaToAlbum(albumId, payload),
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ALBUMS_KEY, variables.albumId, "media"],
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Failed to add media to album:", error);
|
||||
// TODO: Add user-facing toast
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -70,35 +36,146 @@ export const useAddMediaToAlbum = () => {
|
||||
*/
|
||||
export const useGetAlbum = (albumId: string) => {
|
||||
return useQuery({
|
||||
queryKey: [ALBUMS_KEY, albumId],
|
||||
queryKey: [ALBUMS_KEY, "details", albumId],
|
||||
queryFn: () => getAlbum(albumId),
|
||||
enabled: !!albumId,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to remove media from an album.
|
||||
* Query hook to fetch all media for a single album.
|
||||
*/
|
||||
export const useRemoveMediaFromAlbum = () => {
|
||||
export const useGetAlbumMedia = (albumId: string) => {
|
||||
return useQuery({
|
||||
queryKey: [ALBUMS_KEY, "details", albumId, "media"],
|
||||
queryFn: () => getAlbumMedia(albumId),
|
||||
enabled: !!albumId,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to create a new album.
|
||||
*/
|
||||
export const useCreateAlbum = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
albumId,
|
||||
payload,
|
||||
}: {
|
||||
albumId: string;
|
||||
payload: RemoveMediaFromAlbumPayload;
|
||||
}) => removeMediaFromAlbum(albumId, payload),
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ALBUMS_KEY, variables.albumId, "media"],
|
||||
});
|
||||
// TODO: Add success toast
|
||||
mutationFn: createAlbum,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [ALBUMS_KEY, "list"] });
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Failed to remove media from album:", error);
|
||||
// TODO: Add error toast
|
||||
console.error("Failed to create album:", error);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to update an album's details.
|
||||
*/
|
||||
export const useUpdateAlbum = (albumId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (payload: UpdateAlbumPayload) => updateAlbum(albumId, payload),
|
||||
onSuccess: (updatedAlbum) => {
|
||||
// Update the list query
|
||||
queryClient.invalidateQueries({ queryKey: [ALBUMS_KEY, "list"] });
|
||||
// Update the details query
|
||||
queryClient.setQueryData(
|
||||
[ALBUMS_KEY, "details", albumId],
|
||||
updatedAlbum,
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to delete an album.
|
||||
*/
|
||||
export const useDeleteAlbum = (albumId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: () => deleteAlbum(albumId),
|
||||
onSuccess: () => {
|
||||
// Invalidate the list
|
||||
queryClient.invalidateQueries({ queryKey: [ALBUMS_KEY, "list"] });
|
||||
// Remove the details query
|
||||
queryClient.removeQueries({
|
||||
queryKey: [ALBUMS_KEY, "details", albumId],
|
||||
});
|
||||
// Navigate away from the deleted album
|
||||
navigate({ to: "/albums" });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to add media to an album.
|
||||
*/
|
||||
export const useAddMediaToAlbum = (albumId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (payload: AddMediaToAlbumPayload) =>
|
||||
addMediaToAlbum(albumId, payload),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ALBUMS_KEY, "details", albumId, "media"],
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to remove media from an album.
|
||||
*/
|
||||
export const useRemoveMediaFromAlbum = (albumId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (payload: RemoveMediaFromAlbumPayload) =>
|
||||
removeMediaFromAlbum(albumId, payload),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ALBUMS_KEY, "details", albumId, "media"],
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to share an album with another user.
|
||||
*/
|
||||
export const useShareAlbum = (albumId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (payload: ShareAlbumPayload) => shareAlbum(albumId, payload),
|
||||
onSuccess: () => {
|
||||
// Invalidate sharing info (when we add that query)
|
||||
// queryClient.invalidateQueries({ queryKey: [ALBUMS_KEY, "details", albumId, "shares"] });
|
||||
// TODO: Add success toast
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to set an album's thumbnail.
|
||||
*/
|
||||
export const useSetAlbumThumbnail = (albumId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (payload: SetAlbumThumbnailPayload) =>
|
||||
setAlbumThumbnail(albumId, payload),
|
||||
onSuccess: () => {
|
||||
// Invalidate both the album details (for the thumbnail_id) and the list
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ALBUMS_KEY, "details", albumId],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [ALBUMS_KEY, "list"] });
|
||||
// TODO: Add success toast
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,37 +1,58 @@
|
||||
import type { User } from "@/domain/types"
|
||||
import { useAuthStorage } from "@/hooks/use-auth-storage"
|
||||
import apiClient from "@/services/api-client"
|
||||
import { useNavigate } from "@tanstack/react-router"
|
||||
import { useMutation } from "@tanstack/react-query"
|
||||
import { useAuthStorage } from "@/hooks/use-auth-storage";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { login, register } from "@/services/auth-service";
|
||||
|
||||
type LoginCredentials = {
|
||||
usernameOrEmail: string
|
||||
password: string
|
||||
}
|
||||
// Types
|
||||
export type LoginCredentials = {
|
||||
usernameOrEmail: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
type LoginResponse = {
|
||||
token: string
|
||||
user: User
|
||||
}
|
||||
export type LoginResponse = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
const login = async (credentials: LoginCredentials): Promise<LoginResponse> => {
|
||||
const { data } = await apiClient.post('/auth/login', credentials)
|
||||
return data
|
||||
}
|
||||
export type RegisterPayload = LoginCredentials & {
|
||||
email: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook for user login.
|
||||
*/
|
||||
export const useLogin = () => {
|
||||
const navigate = useNavigate()
|
||||
const { setToken } = useAuthStorage()
|
||||
const navigate = useNavigate();
|
||||
const { setToken } = useAuthStorage();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: login,
|
||||
onSuccess: (data) => {
|
||||
setToken(data.token, data.user)
|
||||
navigate({ to: '/' })
|
||||
setToken(data.token);
|
||||
navigate({ to: "/" });
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Login failed:', error)
|
||||
console.error("Login failed:", error);
|
||||
// TODO: Add user-facing error toast
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook for user registration.
|
||||
*/
|
||||
export const useRegister = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: register,
|
||||
onSuccess: () => {
|
||||
// After successful registration, send them to the login page
|
||||
// TODO: Add a success toast: "Registration successful! Please log in."
|
||||
navigate({ to: "/login" });
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Registration failed:", error);
|
||||
// TODO: Add user-facing error toast
|
||||
},
|
||||
});
|
||||
};
|
||||
50
libertas-frontend/src/features/faces/use-faces.ts
Normal file
50
libertas-frontend/src/features/faces/use-faces.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
assignFaceToPerson,
|
||||
listFacesForMedia,
|
||||
type AssignFacePayload,
|
||||
} from "@/services/face-service";
|
||||
import type { FaceRegion } from "@/domain/types";
|
||||
|
||||
const FACE_KEY = ["faces"];
|
||||
const PERSON_KEY = ["people"];
|
||||
|
||||
/**
|
||||
* Query hook to fetch all faces for a specific media item.
|
||||
*/
|
||||
export const useListMediaFaces = (mediaId: string) => {
|
||||
return useQuery({
|
||||
queryKey: [FACE_KEY, "list", mediaId],
|
||||
queryFn: () => listFacesForMedia(mediaId),
|
||||
enabled: !!mediaId,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to assign a face to a person.
|
||||
*/
|
||||
export const useAssignFace = (faceId: string, mediaId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (payload: AssignFacePayload) =>
|
||||
assignFaceToPerson(faceId, payload),
|
||||
onSuccess: (updatedFace) => {
|
||||
// Update the list of faces for this media
|
||||
queryClient.setQueryData(
|
||||
[FACE_KEY, "list", mediaId],
|
||||
(oldData: FaceRegion[] | undefined) => {
|
||||
return oldData?.map((face) =>
|
||||
face.id === faceId ? updatedFace : face,
|
||||
);
|
||||
},
|
||||
);
|
||||
// Invalidate the media list for the person
|
||||
if (updatedFace.person_id) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [PERSON_KEY, "details", updatedFace.person_id, "media"],
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,11 +1,17 @@
|
||||
import {
|
||||
useInfiniteQuery,
|
||||
useMutation,
|
||||
useQuery, // Import useQuery
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
import { getMediaList, uploadMedia } from '@/services/media-service'
|
||||
} from "@tanstack/react-query";
|
||||
import {
|
||||
deleteMedia, // Import deleteMedia
|
||||
getMediaDetails, // Import getMediaDetails
|
||||
getMediaList,
|
||||
uploadMedia,
|
||||
} from "@/services/media-service";
|
||||
|
||||
const MEDIA_LIST_KEY = ['mediaList']
|
||||
const MEDIA_KEY = ["media"];
|
||||
|
||||
/**
|
||||
* Query hook to fetch a paginated list of all media.
|
||||
@@ -13,33 +19,65 @@ const MEDIA_LIST_KEY = ['mediaList']
|
||||
*/
|
||||
export const useGetMediaList = () => {
|
||||
return useInfiniteQuery({
|
||||
queryKey: MEDIA_LIST_KEY,
|
||||
queryKey: [MEDIA_KEY, "list"],
|
||||
queryFn: ({ pageParam = 1 }) => getMediaList({ page: pageParam, limit: 20 }),
|
||||
getNextPageParam: (lastPage) => {
|
||||
return lastPage.has_next_page ? lastPage.page + 1 : undefined
|
||||
return lastPage.has_next_page ? lastPage.page + 1 : undefined;
|
||||
},
|
||||
initialPageParam: 1,
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Query hook to fetch details for a single media item.
|
||||
*/
|
||||
export const useGetMediaDetails = (mediaId: string) => {
|
||||
return useQuery({
|
||||
queryKey: [MEDIA_KEY, "details", mediaId],
|
||||
queryFn: () => getMediaDetails(mediaId),
|
||||
enabled: !!mediaId,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to upload a new media file.
|
||||
*/
|
||||
export const useUploadMedia = () => {
|
||||
const queryClient = useQueryClient()
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ file }: { file: File }) =>
|
||||
uploadMedia(file, (progress) => {
|
||||
// TODO: Update upload progress state
|
||||
console.log('Upload Progress:', progress)
|
||||
console.log("Upload Progress:", progress);
|
||||
}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: MEDIA_LIST_KEY })
|
||||
// Invalidate the entire media list
|
||||
queryClient.invalidateQueries({ queryKey: [MEDIA_KEY, "list"] });
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Upload failed:', error)
|
||||
console.error("Upload failed:", error);
|
||||
// TODO: Add user-facing toast
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to delete a media item.
|
||||
*/
|
||||
export const useDeleteMedia = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (mediaId: string) => deleteMedia(mediaId),
|
||||
onSuccess: () => {
|
||||
// Invalidate the list to remove the deleted item
|
||||
queryClient.invalidateQueries({ queryKey: [MEDIA_KEY, "list"] });
|
||||
// TODO: Invalidate any open details queries for this media
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Delete media failed:", error);
|
||||
// TODO: Add user-facing toast
|
||||
},
|
||||
});
|
||||
};
|
||||
140
libertas-frontend/src/features/people/use-people.ts
Normal file
140
libertas-frontend/src/features/people/use-people.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import {
|
||||
useInfiniteQuery,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import {
|
||||
createPerson,
|
||||
deletePerson,
|
||||
getPerson,
|
||||
listMediaForPerson,
|
||||
listPeople,
|
||||
mergePerson,
|
||||
setPersonThumbnail,
|
||||
sharePerson,
|
||||
unsharePerson,
|
||||
updatePerson,
|
||||
clusterFaces,
|
||||
type CreatePersonPayload,
|
||||
type MergePersonPayload,
|
||||
type SetPersonThumbnailPayload,
|
||||
type SharePersonPayload,
|
||||
type UnsharePersonPayload,
|
||||
type UpdatePersonPayload,
|
||||
} from "@/services/person-service";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
const PERSON_KEY = ["people"];
|
||||
|
||||
export const useListPeople = () => {
|
||||
return useQuery({
|
||||
queryKey: [PERSON_KEY, "list"],
|
||||
queryFn: listPeople,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetPerson = (personId: string) => {
|
||||
return useQuery({
|
||||
queryKey: [PERSON_KEY, "details", personId],
|
||||
queryFn: () => getPerson(personId),
|
||||
enabled: !!personId,
|
||||
});
|
||||
};
|
||||
|
||||
export const useListPersonMedia = (personId: string) => {
|
||||
return useInfiniteQuery({
|
||||
queryKey: [PERSON_KEY, "details", personId, "media"],
|
||||
queryFn: ({ pageParam = 1 }) => listMediaForPerson({personId, page: pageParam, limit: 20} ),
|
||||
getNextPageParam: (lastPage) => {
|
||||
return lastPage.has_next_page ? lastPage.page + 1 : undefined;
|
||||
},
|
||||
initialPageParam: 1,
|
||||
enabled: !!personId,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreatePerson = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (payload: CreatePersonPayload) => createPerson(payload),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [PERSON_KEY, "list"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdatePerson = (personId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (payload: UpdatePersonPayload) =>
|
||||
updatePerson(personId, payload),
|
||||
onSuccess: (updatedPerson) => {
|
||||
queryClient.invalidateQueries({ queryKey: [PERSON_KEY, "list"] });
|
||||
queryClient.setQueryData(
|
||||
[PERSON_KEY, "details", personId],
|
||||
updatedPerson,
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeletePerson = (personId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: () => deletePerson(personId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [PERSON_KEY, "list"] });
|
||||
queryClient.removeQueries({
|
||||
queryKey: [PERSON_KEY, "details", personId],
|
||||
});
|
||||
navigate({ to: "/people" });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useSharePerson = (personId: string) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload: SharePersonPayload) => sharePerson(personId, payload),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUnsharePerson = (personId: string) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload: UnsharePersonPayload) =>
|
||||
unsharePerson(personId, payload),
|
||||
});
|
||||
};
|
||||
|
||||
export const useMergePerson = (targetPersonId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (payload: MergePersonPayload) =>
|
||||
mergePerson(targetPersonId, payload),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [PERSON_KEY] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useSetPersonThumbnail = (personId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (payload: SetPersonThumbnailPayload) =>
|
||||
setPersonThumbnail(personId, payload),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [PERSON_KEY, "details", personId],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [PERSON_KEY, "list"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useClusterFaces = () => {
|
||||
return useMutation({
|
||||
mutationFn: clusterFaces,
|
||||
});
|
||||
};
|
||||
48
libertas-frontend/src/features/tags/use-tags.ts
Normal file
48
libertas-frontend/src/features/tags/use-tags.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
addTagsToMedia,
|
||||
listTagsForMedia,
|
||||
removeTagFromMedia,
|
||||
type AddTagsPayload,
|
||||
} from "@/services/tag-service";
|
||||
|
||||
const TAG_KEY = ["tags"];
|
||||
|
||||
/**
|
||||
* Query hook to fetch all tags for a specific media item.
|
||||
*/
|
||||
export const useListMediaTags = (mediaId: string) => {
|
||||
return useQuery({
|
||||
queryKey: [TAG_KEY, "list", mediaId],
|
||||
queryFn: () => listTagsForMedia(mediaId),
|
||||
enabled: !!mediaId,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to add tags to a media item.
|
||||
*/
|
||||
export const useAddMediaTags = (mediaId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (payload: AddTagsPayload) => addTagsToMedia(mediaId, payload),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [TAG_KEY, "list", mediaId] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to remove a tag from a media item.
|
||||
*/
|
||||
export const useRemoveMediaTag = (mediaId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (tagName: string) => removeTagFromMedia(mediaId, tagName),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [TAG_KEY, "list", mediaId] });
|
||||
},
|
||||
});
|
||||
};
|
||||
17
libertas-frontend/src/features/user/use-user.ts
Normal file
17
libertas-frontend/src/features/user/use-user.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getMe } from "@/services/user-service";
|
||||
|
||||
const USER_KEY = ["user"];
|
||||
|
||||
/**
|
||||
* Query hook to fetch the current user's details.
|
||||
* @param enabled Whether the query should be enabled to run.
|
||||
*/
|
||||
export const useGetMe = (enabled: boolean) => {
|
||||
return useQuery({
|
||||
queryKey: [USER_KEY, "me"],
|
||||
queryFn: getMe,
|
||||
enabled: enabled, // Only run if enabled (e.g., if token exists)
|
||||
staleTime: 1000 * 60 * 5, // Cache user data for 5 minutes
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user