1
0

Compare commits

...

12 Commits

Author SHA1 Message Date
2339aa5071 fix cards id 2025-07-23 02:15:57 +09:00
2767842aae fix planet 2025-07-20 13:18:25 +09:00
054846f8e5 Add planet display to user profile
- Display planet value with earth icon
- Format large numbers as M (millions) or K (thousands)
- Example: 2000336.062109 displays as 2.00M
- Show only when planet value exists
2025-07-20 13:00:35 +09:00
60076d0e83 fix 2025-07-18 16:45:46 +09:00
8fa9747847 fix gh-actions 2025-07-18 16:30:00 +09:00
syui
2a698fa9af Merge pull request #3 from aisyui/react-migration-merge
Add pull request merge support to GitHub Actions
2025-07-18 16:07:08 +09:00
8d6137ce67 Add pull request merge support to GitHub Actions
- Add pull_request trigger with closed type
- Add condition to run only when PR is merged or on push to main
- Enable deployment on both direct push and PR merge to main

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-18 16:05:46 +09:00
syui
2b954ec582 Merge pull request #2 from aisyui/react-migration
React migration
2025-07-18 15:55:13 +09:00
94062298ab Merge main branch into react-migration
- Resolve merge conflict in Navigation.tsx
- Keep user search functionality removed (react-migration version)
- Maintain mobile responsive design improvements
- Ready for merge back to main

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-18 15:53:28 +09:00
14873a9589 fix gh-actions 2025-07-18 15:50:22 +09:00
e829de6878 Remove user search functionality from navigation
- Delete both user and id search forms completely
- Simplify navigation to only show AI icon and Bluesky link
- Clean up UI by removing unused search features

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-18 15:46:24 +09:00
3302ceb852 Hide navigation elements on mobile devices
- Add 'hidden md:block' to Bluesky link to hide on mobile
- Add 'hidden md:flex' to form inputs to hide on mobile
- Keep only the AI icon visible on mobile for cleaner UI
- Desktop users still see full navigation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-18 15:46:05 +09:00
11 changed files with 149 additions and 59 deletions

View File

@@ -26,9 +26,10 @@ jobs:
npm run build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
force_orphan: true
user_name: 'ai[bot]'
user_email: '138105980+yui-syui-ai[bot]@users.noreply.github.com'

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ package-lock.json
yarn.lock
**DS_Store
.claude
repos

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/ai.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>card.syui.ai</title>
<link rel="stylesheet" href="/pkg/icomoon/style.css" />
<link rel="stylesheet" href="/pkg/font-awesome/css/all.min.css" />
@@ -12,4 +13,4 @@
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
</html>

View File

@@ -4,6 +4,7 @@ import HomePage from './components/pages/HomePage';
import DocsPage from './components/pages/DocsPage';
import OwnerPage from './components/pages/OwnerPage';
import UserPage from './components/pages/UserPage';
import CardDisplayPage from './components/pages/CardDisplayPage';
function App() {
useEffect(() => {
@@ -28,6 +29,7 @@ function App() {
<Route path="/fa" element={<DocsPage page="fanart" />} />
<Route path="/ph" element={<DocsPage page="photo" />} />
<Route path="/pr" element={<DocsPage page="favorite" />} />
<Route path="/cards/:id" element={<CardDisplayPage />} />
<Route path="/:username" element={<UserPage />} />
</Routes>
</div>

View File

@@ -45,32 +45,6 @@ export default function Navigation({ username: _username }: NavigationProps) {
</code>
</div>
<div className="flex gap-2 hidden md:flex">
<form className="flex gap-2">
<input
type="text"
placeholder="user"
className="px-2 py-1 text-black rounded"
/>
<input
type="submit"
value="Go"
className="px-3 py-1 bg-primary text-white rounded hover:bg-secondary cursor-pointer"
/>
</form>
<form className="flex gap-2">
<input
type="text"
placeholder="id"
className="px-2 py-1 text-black rounded"
/>
<input
type="submit"
value="Go"
className="px-3 py-1 bg-primary text-white rounded hover:bg-secondary cursor-pointer"
/>
</form>
</div>
</div>
</div>
);

View File

@@ -0,0 +1,62 @@
import { useParams } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { fetchCardById } from '../../utils/api';
import '../../styles/card-effects.css';
import '../../styles/card-fullscreen.css';
export default function CardDisplayPage() {
const { id } = useParams<{ id: string }>();
const cardId = parseInt(id || '0');
const { data: card, isLoading } = useQuery({
queryKey: ['card', cardId],
queryFn: () => fetchCardById(cardId),
enabled: cardId > 0,
});
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center">
<i className="fa-solid fa-spinner fa-spin text-6xl text-yellow-500"></i>
</div>
);
}
if (!card) {
return null;
}
const isSpecialStatus = ['yui', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seven'].includes(card.status);
return (
<div
className="min-h-screen flex items-center justify-center"
style={{
background: 'linear-gradient(to bottom, #1a1a1a, #2d2d2d, #1a1a1a)',
backgroundColor: '#1a1a1a'
}}
>
<div className="w-full h-full flex items-center justify-center">
<div className="relative" style={{ maxHeight: '100vh', height: '100vh', width: 'auto' }}>
<div className="h-full flex flex-col items-center justify-center">
<div className="card-wrapper-fullscreen" style={{ height: '100vh', width: '100%', maxWidth: 'none', aspectRatio: '5/7' }}>
<div className="card-reflection">
<img
src={`/card/card_${card.card}.webp`}
alt={`Card ${card.card}`}
className="w-full rounded-lg"
/>
</div>
{isSpecialStatus && (
<>
<div className={`card-status pattern-${card.status}`}></div>
<div className={`card-status color-${card.status}`}></div>
</>
)}
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,14 +1,35 @@
import { useQuery } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect } from 'react';
import Navigation from '../common/Navigation';
import { fetchUsers } from '../../utils/api';
import { fetchUsersWithCache } from '../../utils/api';
import { api } from '../../utils/api';
export default function HomePage() {
const queryClient = useQueryClient();
const { data: users, isLoading } = useQuery({
queryKey: ['users'],
queryFn: () => fetchUsers()
queryFn: () => fetchUsersWithCache(),
staleTime: 0, // Always consider data stale
});
// Refresh with API data after cache load
useEffect(() => {
if (users?.isFromCache) {
// Fetch fresh data from API in background
api.get('users?itemsPerPage=8000').then(response => {
console.log('Background API fetch successful, got', response.data.length, 'users with planet data');
// Update the query cache with fresh data
queryClient.setQueryData(['users'], {
data: response.data,
isFromCache: false
});
}).catch(error => {
console.error('Background API fetch failed:', error);
});
}
}, [users?.isFromCache, queryClient]);
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center">

View File

@@ -4,17 +4,27 @@ import Navigation from '../common/Navigation';
import CardGrid from '../card/CardGrid';
import UserProfile from '../user/UserProfile';
import SpecialCard from '../card/SpecialCard';
import { fetchUsers, fetchUserCards } from '../../utils/api';
import { fetchUsers, fetchUserCards, fetchUser } from '../../utils/api';
export default function UserPage() {
const { username } = useParams<{ username: string }>();
// First get users list to find the user ID
const { data: users, isLoading } = useQuery({
queryKey: ['users'],
queryFn: () => fetchUsers(),
});
const user = users?.data.find(u => u.username === username);
const userId = users?.data.find(u => u.username === username)?.id;
// Then fetch full user data from API to get planet value
const { data: userResponse } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId!),
enabled: !!userId,
});
const user = userResponse?.data || users?.data.find(u => u.username === username);
const { data: cards, isLoading: cardsLoading } = useQuery({
queryKey: ['userCards', user?.id],

View File

@@ -6,6 +6,7 @@ interface UserProfileProps {
}
export default function UserProfile({ user, cards }: UserProfileProps) {
return (
<div className="bg-white rounded-lg p-6 mb-8">
<h3 className="text-2xl font-bold mb-4 flex items-center gap-2">
@@ -29,7 +30,7 @@ export default function UserProfile({ user, cards }: UserProfileProps) {
)}
</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 text-sm">
<div>
<strong>ID:</strong> {user.id}
</div>
@@ -45,12 +46,10 @@ export default function UserProfile({ user, cards }: UserProfileProps) {
<span className="icon-ai"></span>
{cards.filter(c => c.card >= 96 && c.card <= 121).length}
</div>
{user.planet && (
<div className="flex items-center gap-1">
<i className="fa-solid fa-earth-americas"></i>
{user.planet}
</div>
)}
<div className="flex items-center gap-1">
<i className="fa-solid fa-earth-americas"></i>
{user.planet?.toLocaleString() || '0'}
</div>
</div>
{/* Badge Images */}

View File

@@ -0,0 +1,7 @@
/* Fullscreen Card Display Styles */
.card-wrapper-fullscreen {
display: grid;
place-items: center;
position: relative;
overflow: hidden;
}

View File

@@ -15,32 +15,44 @@ export const api = axios.create({
// API関数
export const fetchUsers = async (itemsPerPage = 8000): Promise<{ data: User[] }> => {
try {
// First try to load cached users.json for fast initial loading
const cachedResponse = await axios.get('/json/users.json');
// Background fetch for fresh data
setTimeout(async () => {
try {
await api.get(`users?itemsPerPage=${itemsPerPage}`);
} catch (error) {
console.error('Background fetch failed:', error);
}
}, 100);
return { data: cachedResponse.data };
// Directly fetch from API to get complete user data including planet
const response = await api.get(`users?itemsPerPage=${itemsPerPage}`);
return { data: response.data };
} catch (error) {
console.error('Failed to fetch cached users:', error);
// Fallback to API if cached JSON fails
console.error('Failed to fetch users from API:', error);
// Fallback to cached data (but it doesn't have planet field)
try {
const response = await api.get(`users?itemsPerPage=${itemsPerPage}`);
return { data: response.data };
} catch (apiError) {
console.error('API fallback failed:', apiError);
const cachedResponse = await axios.get('/json/users.json');
return { data: cachedResponse.data };
} catch (cacheError) {
console.error('Cache fallback failed:', cacheError);
return { data: [] };
}
}
};
// Fetch users with cache-first strategy for home page
export const fetchUsersWithCache = async (): Promise<{ data: User[], isFromCache?: boolean }> => {
try {
// First, try to get cached data
console.log('Attempting to load cached users.json');
const cachedResponse = await axios.get('/json/users.json');
console.log('Successfully loaded cached data:', cachedResponse.data.length, 'users');
return { data: cachedResponse.data, isFromCache: true };
} catch (error) {
console.error('Cache read failed, fetching from API:', error);
// If cache fails, fetch from API
try {
const response = await api.get('users?itemsPerPage=8000');
console.log('Successfully loaded from API:', response.data.length, 'users');
return { data: response.data, isFromCache: false };
} catch (apiError) {
console.error('API fetch also failed:', apiError);
return { data: [], isFromCache: false };
}
}
};
export const fetchUserCards = async (userId: number, itemsPerPage = 8000): Promise<{ data: Card[] }> => {
try {
const response = await api.get(`users/${userId}/card?itemsPerPage=${itemsPerPage}`);