Update package dependencies, enhance README for clarity, and implement new features in the admin panel and script detail pages. Added support for collections, improved script submission previews, and refactored comment handling in the script detail view.

This commit is contained in:
2025-08-15 20:29:02 +01:00
parent 5fdfe3e790
commit ef211ebe0a
27 changed files with 3457 additions and 353 deletions

57
src/hooks/useAnalytics.ts Normal file
View File

@ -0,0 +1,57 @@
import { useQuery, useMutation } from '@tanstack/react-query';
import * as analyticsApi from '@/lib/api/analytics';
// Query keys
export const analyticsKeys = {
all: ['analytics'] as const,
script: (scriptId: string) => [...analyticsKeys.all, 'script', scriptId] as const,
platform: (days: number) => [...analyticsKeys.all, 'platform', days] as const,
user: (userId: string, days: number) => [...analyticsKeys.all, 'user', userId, days] as const,
events: (filters: analyticsApi.AnalyticsFilters) => [...analyticsKeys.all, 'events', filters] as const,
};
// Track event mutation
export function useTrackEvent() {
return useMutation({
mutationFn: analyticsApi.trackEvent,
// Don't show success/error messages for event tracking
});
}
// Get analytics events
export function useAnalyticsEvents(filters: analyticsApi.AnalyticsFilters = {}) {
return useQuery({
queryKey: analyticsKeys.events(filters),
queryFn: () => analyticsApi.getAnalyticsEvents(filters),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
// Get script analytics
export function useScriptAnalytics(scriptId: string, days: number = 30) {
return useQuery({
queryKey: analyticsKeys.script(scriptId),
queryFn: () => analyticsApi.getScriptAnalytics(scriptId, days),
enabled: !!scriptId,
staleTime: 10 * 60 * 1000, // 10 minutes
});
}
// Get platform analytics (admin only)
export function usePlatformAnalytics(days: number = 30) {
return useQuery({
queryKey: analyticsKeys.platform(days),
queryFn: () => analyticsApi.getPlatformAnalytics(days),
staleTime: 15 * 60 * 1000, // 15 minutes
});
}
// Get user analytics
export function useUserAnalytics(userId: string, days: number = 30) {
return useQuery({
queryKey: analyticsKeys.user(userId, days),
queryFn: () => analyticsApi.getUserAnalytics(userId, days),
enabled: !!userId,
staleTime: 10 * 60 * 1000,
});
}

94
src/hooks/useAuth.ts Normal file
View File

@ -0,0 +1,94 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import * as authApi from '@/lib/api/auth';
// Note: This would create circular dependency, import useAuth from context directly where needed
import { showSuccess, showError } from '@/utils/toast';
// Login mutation
export function useLogin() {
const navigate = useNavigate();
return useMutation({
mutationFn: authApi.login,
onSuccess: (data) => {
// Store token in localStorage
localStorage.setItem('scriptshare-auth-token', data.token);
localStorage.setItem('scriptshare-user-data', JSON.stringify(data.user));
// Update auth context
setUser(data.user as any);
showSuccess('Login successful!');
navigate('/dashboard');
},
onError: (error: any) => {
showError(error.message || 'Login failed');
},
});
}
// Register mutation
export function useRegister() {
const navigate = useNavigate();
return useMutation({
mutationFn: authApi.register,
onSuccess: (data) => {
// Store token in localStorage
localStorage.setItem('scriptshare-auth-token', data.token);
localStorage.setItem('scriptshare-user-data', JSON.stringify(data.user));
// Update auth context
setUser(data.user as any);
showSuccess('Registration successful!');
navigate('/dashboard');
},
onError: (error: any) => {
showError(error.message || 'Registration failed');
},
});
}
// Logout mutation
export function useLogout() {
const navigate = useNavigate();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async () => {
// Clear local storage
localStorage.removeItem('scriptshare-auth-token');
localStorage.removeItem('scriptshare-user-data');
// Auth context will be updated by the context provider
// In a real app, you would update the auth context here
// Clear query cache
queryClient.clear();
return { success: true };
},
onSuccess: () => {
showSuccess('Logged out successfully');
navigate('/');
},
});
}
// Change password mutation
export function useChangePassword() {
return useMutation({
mutationFn: ({ userId, currentPassword, newPassword }: {
userId: string;
currentPassword: string;
newPassword: string;
}) => authApi.changePassword(userId, currentPassword, newPassword),
onSuccess: () => {
showSuccess('Password changed successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to change password');
},
});
}

149
src/hooks/useCollections.ts Normal file
View File

@ -0,0 +1,149 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import * as collectionsApi from '@/lib/api/collections';
import { showSuccess, showError } from '@/utils/toast';
// Query keys
export const collectionKeys = {
all: ['collections'] as const,
lists: () => [...collectionKeys.all, 'list'] as const,
details: () => [...collectionKeys.all, 'detail'] as const,
detail: (id: string) => [...collectionKeys.details(), id] as const,
user: (userId: string) => [...collectionKeys.all, 'user', userId] as const,
public: () => [...collectionKeys.all, 'public'] as const,
};
// Get collection by ID
export function useCollection(id: string) {
return useQuery({
queryKey: collectionKeys.detail(id),
queryFn: () => collectionsApi.getCollectionById(id),
enabled: !!id,
staleTime: 5 * 60 * 1000,
});
}
// Get user collections
export function useUserCollections(userId: string) {
return useQuery({
queryKey: collectionKeys.user(userId),
queryFn: () => collectionsApi.getUserCollections(userId),
enabled: !!userId,
staleTime: 5 * 60 * 1000,
});
}
// Get public collections
export function usePublicCollections(limit?: number, offset?: number) {
return useQuery({
queryKey: [...collectionKeys.public(), limit, offset],
queryFn: () => collectionsApi.getPublicCollections(limit, offset),
staleTime: 10 * 60 * 1000, // 10 minutes
});
}
// Create collection mutation
export function useCreateCollection() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: collectionsApi.createCollection,
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: collectionKeys.user(data.authorId) });
queryClient.invalidateQueries({ queryKey: collectionKeys.public() });
showSuccess('Collection created successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to create collection');
},
});
}
// Update collection mutation
export function useUpdateCollection() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data, userId }: {
id: string;
data: collectionsApi.UpdateCollectionData;
userId: string
}) => collectionsApi.updateCollection(id, data, userId),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: collectionKeys.detail(data.id) });
queryClient.invalidateQueries({ queryKey: collectionKeys.user(data.authorId) });
queryClient.invalidateQueries({ queryKey: collectionKeys.public() });
showSuccess('Collection updated successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to update collection');
},
});
}
// Delete collection mutation
export function useDeleteCollection() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, userId }: { id: string; userId: string }) =>
collectionsApi.deleteCollection(id, userId),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: collectionKeys.lists() });
queryClient.removeQueries({ queryKey: collectionKeys.detail(variables.id) });
showSuccess('Collection deleted successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to delete collection');
},
});
}
// Add script to collection mutation
export function useAddScriptToCollection() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ collectionId, scriptId, userId }: {
collectionId: string;
scriptId: string;
userId: string
}) => collectionsApi.addScriptToCollection(collectionId, scriptId, userId),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: collectionKeys.detail(variables.collectionId) });
showSuccess('Script added to collection!');
},
onError: (error: any) => {
showError(error.message || 'Failed to add script to collection');
},
});
}
// Remove script from collection mutation
export function useRemoveScriptFromCollection() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ collectionId, scriptId, userId }: {
collectionId: string;
scriptId: string;
userId: string
}) => collectionsApi.removeScriptFromCollection(collectionId, scriptId, userId),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: collectionKeys.detail(variables.collectionId) });
showSuccess('Script removed from collection!');
},
onError: (error: any) => {
showError(error.message || 'Failed to remove script from collection');
},
});
}
// Check if script is in collection
export function useIsScriptInCollection(collectionId: string, scriptId: string) {
return useQuery({
queryKey: [...collectionKeys.all, 'check', collectionId, scriptId],
queryFn: () => collectionsApi.isScriptInCollection(collectionId, scriptId),
enabled: !!collectionId && !!scriptId,
staleTime: 5 * 60 * 1000,
});
}

88
src/hooks/useRatings.ts Normal file
View File

@ -0,0 +1,88 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import * as ratingsApi from '@/lib/api/ratings';
import { showSuccess, showError } from '@/utils/toast';
// Query keys
export const ratingKeys = {
all: ['ratings'] as const,
script: (scriptId: string) => [...ratingKeys.all, 'script', scriptId] as const,
userRating: (scriptId: string, userId: string) => [...ratingKeys.all, 'user', scriptId, userId] as const,
stats: (scriptId: string) => [...ratingKeys.all, 'stats', scriptId] as const,
};
// Get user's rating for a script
export function useUserRating(scriptId: string, userId?: string) {
return useQuery({
queryKey: ratingKeys.userRating(scriptId, userId || ''),
queryFn: () => ratingsApi.getUserRating(scriptId, userId!),
enabled: !!scriptId && !!userId,
staleTime: 5 * 60 * 1000,
});
}
// Get all ratings for a script
export function useScriptRatings(scriptId: string) {
return useQuery({
queryKey: ratingKeys.script(scriptId),
queryFn: () => ratingsApi.getScriptRatings(scriptId),
enabled: !!scriptId,
staleTime: 5 * 60 * 1000,
});
}
// Get rating statistics for a script
export function useScriptRatingStats(scriptId: string) {
return useQuery({
queryKey: ratingKeys.stats(scriptId),
queryFn: () => ratingsApi.getScriptRatingStats(scriptId),
enabled: !!scriptId,
staleTime: 10 * 60 * 1000,
});
}
// Rate script mutation
export function useRateScript() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ratingsApi.rateScript,
onSuccess: (_, variables) => {
// Invalidate related queries
queryClient.invalidateQueries({ queryKey: ratingKeys.script(variables.scriptId) });
queryClient.invalidateQueries({ queryKey: ratingKeys.userRating(variables.scriptId, variables.userId) });
queryClient.invalidateQueries({ queryKey: ratingKeys.stats(variables.scriptId) });
// Also invalidate script details to update average rating
queryClient.invalidateQueries({ queryKey: ['scripts', 'detail', variables.scriptId] });
showSuccess('Rating submitted successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to submit rating');
},
});
}
// Delete rating mutation
export function useDeleteRating() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ scriptId, userId }: { scriptId: string; userId: string }) =>
ratingsApi.deleteRating(scriptId, userId),
onSuccess: (_, variables) => {
// Invalidate related queries
queryClient.invalidateQueries({ queryKey: ratingKeys.script(variables.scriptId) });
queryClient.invalidateQueries({ queryKey: ratingKeys.userRating(variables.scriptId, variables.userId) });
queryClient.invalidateQueries({ queryKey: ratingKeys.stats(variables.scriptId) });
// Also invalidate script details to update average rating
queryClient.invalidateQueries({ queryKey: ['scripts', 'detail', variables.scriptId] });
showSuccess('Rating removed successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to remove rating');
},
});
}

139
src/hooks/useScripts.ts Normal file
View File

@ -0,0 +1,139 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import * as scriptsApi from '@/lib/api/scripts';
import { showSuccess, showError } from '@/utils/toast';
// Query keys
export const scriptKeys = {
all: ['scripts'] as const,
lists: () => [...scriptKeys.all, 'list'] as const,
list: (filters: scriptsApi.ScriptFilters) => [...scriptKeys.lists(), filters] as const,
details: () => [...scriptKeys.all, 'detail'] as const,
detail: (id: string) => [...scriptKeys.details(), id] as const,
popular: () => [...scriptKeys.all, 'popular'] as const,
recent: () => [...scriptKeys.all, 'recent'] as const,
};
// Get scripts with filters
export function useScripts(filters: scriptsApi.ScriptFilters = {}) {
return useQuery({
queryKey: scriptKeys.list(filters),
queryFn: () => scriptsApi.getScripts(filters),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
// Get script by ID
export function useScript(id: string) {
return useQuery({
queryKey: scriptKeys.detail(id),
queryFn: () => scriptsApi.getScriptById(id),
enabled: !!id,
staleTime: 5 * 60 * 1000,
});
}
// Get popular scripts
export function usePopularScripts(limit?: number) {
return useQuery({
queryKey: [...scriptKeys.popular(), limit],
queryFn: () => scriptsApi.getPopularScripts(limit),
staleTime: 10 * 60 * 1000, // 10 minutes
});
}
// Get recent scripts
export function useRecentScripts(limit?: number) {
return useQuery({
queryKey: [...scriptKeys.recent(), limit],
queryFn: () => scriptsApi.getRecentScripts(limit),
staleTime: 5 * 60 * 1000,
});
}
// Create script mutation
export function useCreateScript() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: scriptsApi.createScript,
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: scriptKeys.lists() });
showSuccess('Script created successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to create script');
},
});
}
// Update script mutation
export function useUpdateScript() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data, userId }: { id: string; data: scriptsApi.UpdateScriptData; userId: string }) =>
scriptsApi.updateScript(id, data, userId),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: scriptKeys.detail(data.id) });
queryClient.invalidateQueries({ queryKey: scriptKeys.lists() });
showSuccess('Script updated successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to update script');
},
});
}
// Delete script mutation
export function useDeleteScript() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, userId }: { id: string; userId: string }) =>
scriptsApi.deleteScript(id, userId),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: scriptKeys.lists() });
queryClient.removeQueries({ queryKey: scriptKeys.detail(variables.id) });
showSuccess('Script deleted successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to delete script');
},
});
}
// Moderate script mutation (admin only)
export function useModerateScript() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, isApproved, moderatorId }: { id: string; isApproved: boolean; moderatorId: string }) =>
scriptsApi.moderateScript(id, isApproved, moderatorId),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: scriptKeys.detail(data.id) });
queryClient.invalidateQueries({ queryKey: scriptKeys.lists() });
showSuccess(`Script ${data.isApproved ? 'approved' : 'rejected'} successfully!`);
},
onError: (error: any) => {
showError(error.message || 'Failed to moderate script');
},
});
}
// Track view mutation
export function useTrackView() {
return useMutation({
mutationFn: scriptsApi.incrementViewCount,
// Don't show success/error messages for view tracking
});
}
// Track download mutation
export function useTrackDownload() {
return useMutation({
mutationFn: scriptsApi.incrementDownloadCount,
onSuccess: () => {
showSuccess('Download started!');
},
});
}

95
src/hooks/useUsers.ts Normal file
View File

@ -0,0 +1,95 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import * as usersApi from '@/lib/api/users';
import { showSuccess, showError } from '@/utils/toast';
// Query keys
export const userKeys = {
all: ['users'] as const,
lists: () => [...userKeys.all, 'list'] as const,
details: () => [...userKeys.all, 'detail'] as const,
detail: (id: string) => [...userKeys.details(), id] as const,
search: (query: string) => [...userKeys.all, 'search', query] as const,
};
// Get user by ID
export function useUser(id: string) {
return useQuery({
queryKey: userKeys.detail(id),
queryFn: () => usersApi.getUserById(id),
enabled: !!id,
staleTime: 5 * 60 * 1000,
});
}
// Get all users (admin only)
export function useUsers(limit?: number, offset?: number) {
return useQuery({
queryKey: [...userKeys.lists(), limit, offset],
queryFn: () => usersApi.getAllUsers(limit, offset),
staleTime: 5 * 60 * 1000,
});
}
// Search users
export function useSearchUsers(query: string, limit?: number) {
return useQuery({
queryKey: userKeys.search(query),
queryFn: () => usersApi.searchUsers(query, limit),
enabled: !!query && query.length >= 2,
staleTime: 2 * 60 * 1000, // 2 minutes for search results
});
}
// Create user mutation
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: usersApi.createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
showSuccess('User created successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to create user');
},
});
}
// Update user mutation
export function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: usersApi.UpdateUserData }) =>
usersApi.updateUser(id, data),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: userKeys.detail(data.id) });
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
showSuccess('User updated successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to update user');
},
});
}
// Update user permissions mutation (admin only)
export function useUpdateUserPermissions() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, permissions }: {
id: string;
permissions: { isAdmin?: boolean; isModerator?: boolean }
}) => usersApi.updateUserPermissions(id, permissions),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: userKeys.detail(data.id) });
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
showSuccess('User permissions updated successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to update user permissions');
},
});
}