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:
jungwoo choi
2025-10-23 14:30:25 +09:00
commit 919afe56f2
1516 changed files with 64072 additions and 0 deletions

View File

@ -0,0 +1 @@
export { useColorScheme } from 'react-native';

View 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';
}

View 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];
}
}

View 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'] });
},
});
}