feat: SAPIENS Mobile App - Initial commit
React Native mobile application for SAPIENS news platform. Consolidated all previous history into single commit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1
sapiense-ai-app/hooks/use-color-scheme.ts
Normal file
1
sapiense-ai-app/hooks/use-color-scheme.ts
Normal file
@ -0,0 +1 @@
|
||||
export { useColorScheme } from 'react-native';
|
||||
21
sapiense-ai-app/hooks/use-color-scheme.web.ts
Normal file
21
sapiense-ai-app/hooks/use-color-scheme.web.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useColorScheme as useRNColorScheme } from 'react-native';
|
||||
|
||||
/**
|
||||
* To support static rendering, this value needs to be re-calculated on the client side for web
|
||||
*/
|
||||
export function useColorScheme() {
|
||||
const [hasHydrated, setHasHydrated] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setHasHydrated(true);
|
||||
}, []);
|
||||
|
||||
const colorScheme = useRNColorScheme();
|
||||
|
||||
if (hasHydrated) {
|
||||
return colorScheme;
|
||||
}
|
||||
|
||||
return 'light';
|
||||
}
|
||||
21
sapiense-ai-app/hooks/use-theme-color.ts
Normal file
21
sapiense-ai-app/hooks/use-theme-color.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Learn more about light and dark modes:
|
||||
* https://docs.expo.dev/guides/color-schemes/
|
||||
*/
|
||||
|
||||
import { Colors } from '@/constants/theme';
|
||||
import { useColorScheme } from '@/hooks/use-color-scheme';
|
||||
|
||||
export function useThemeColor(
|
||||
props: { light?: string; dark?: string },
|
||||
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
|
||||
) {
|
||||
const theme = useColorScheme() ?? 'light';
|
||||
const colorFromProps = props[theme];
|
||||
|
||||
if (colorFromProps) {
|
||||
return colorFromProps;
|
||||
} else {
|
||||
return Colors[theme][colorName];
|
||||
}
|
||||
}
|
||||
104
sapiense-ai-app/hooks/useApi.ts
Normal file
104
sapiense-ai-app/hooks/useApi.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import { useQuery, useMutation, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { outletsApi, articlesApi, searchApi, feedApi } from '../lib/api';
|
||||
|
||||
// Outlets hooks
|
||||
export function useOutlets(category?: string, language = 'ko') {
|
||||
return useQuery({
|
||||
queryKey: ['/outlets', category, language],
|
||||
queryFn: () => outletsApi.getAll(category, language),
|
||||
});
|
||||
}
|
||||
|
||||
export function useOutlet(id: string, language = 'ko') {
|
||||
return useQuery({
|
||||
queryKey: ['outlet', id, language],
|
||||
queryFn: () => outletsApi.getById(id, language),
|
||||
enabled: !!id && id.trim().length > 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Articles hooks
|
||||
export function useArticles(language = 'en') {
|
||||
return useQuery({
|
||||
queryKey: ['/articles', language],
|
||||
queryFn: () => articlesApi.getAll(language),
|
||||
});
|
||||
}
|
||||
|
||||
export function useArticlesByOutlet(outletId: string, language = 'en') {
|
||||
return useQuery({
|
||||
queryKey: ['/articles', outletId, language],
|
||||
queryFn: () => articlesApi.getByOutlet(outletId, language),
|
||||
enabled: !!outletId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useFeaturedArticles(limit = 10, language = 'en') {
|
||||
return useQuery({
|
||||
queryKey: ['/articles', 'featured', limit, language],
|
||||
queryFn: () => articlesApi.getFeatured(limit, language),
|
||||
});
|
||||
}
|
||||
|
||||
export function useArticle(id: string, language = 'en', useNewsId = true) {
|
||||
return useQuery({
|
||||
queryKey: ['/articles', id, language, useNewsId],
|
||||
queryFn: () => articlesApi.getById(id, language, useNewsId),
|
||||
enabled: !!id,
|
||||
});
|
||||
}
|
||||
|
||||
// Search hooks
|
||||
export function useSearch(
|
||||
query: string,
|
||||
type: 'all' | 'articles' | 'outlets' = 'all',
|
||||
language = 'en',
|
||||
outletId?: string
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: ['/search', query, type, language, outletId],
|
||||
queryFn: () => searchApi.search(query, type, language, outletId),
|
||||
enabled: !!query.trim(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSearchArticles(query: string, language = 'en') {
|
||||
return useQuery({
|
||||
queryKey: ['/search', query, 'articles', language],
|
||||
queryFn: () => searchApi.articles(query, language),
|
||||
enabled: !!query.trim(),
|
||||
});
|
||||
}
|
||||
|
||||
// Feed hooks for YouTube-style interface
|
||||
export function useFeed(filter: 'all' | 'people' | 'topics' | 'companies' = 'all') {
|
||||
return useInfiniteQuery({
|
||||
queryKey: ['/feed', filter],
|
||||
queryFn: ({ pageParam }) => {
|
||||
return feedApi.getFeed({
|
||||
cursor: pageParam,
|
||||
limit: 10,
|
||||
filter: filter,
|
||||
});
|
||||
},
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: (lastPage: any) => lastPage?.nextCursor,
|
||||
});
|
||||
}
|
||||
|
||||
export function useIncrementView(filter?: 'all' | 'people' | 'topics' | 'companies') {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: feedApi.incrementView,
|
||||
onSuccess: (_, articleId) => {
|
||||
console.log(`[DEBUG] View incremented for article ${articleId}, invalidating cache with filter:`, filter);
|
||||
if (filter) {
|
||||
queryClient.invalidateQueries({ queryKey: ['/feed', filter] });
|
||||
} else {
|
||||
queryClient.invalidateQueries({ queryKey: ['/feed'] });
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: ['/articles'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user