managarten/apps-archived/maerchenzauber/apps/mobile/hooks/usePublicCharacters.ts
Till-JS 61d181fbc2 chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace:
- bauntown (community website)
- maerchenzauber (AI story generation)
- memoro (voice memo app)
- news (news aggregation)
- nutriphi (nutrition tracking)
- reader (reading app)
- uload (URL shortener)
- wisekeep (AI wisdom extraction)

Update CLAUDE.md documentation:
- Add presi to active projects
- Document archived projects section
- Update workspace configuration

Archived apps can be re-activated by moving back to apps/

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 07:03:59 +01:00

310 lines
7.7 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
import { fetchWithAuth } from '../src/utils/api';
import { Alert } from 'react-native';
export interface PublicCharacter {
id: string;
name: string;
original_description: string;
character_description_prompt: string;
image_url?: string;
images_data?: any[];
is_published: boolean;
published_at?: string;
share_code?: string;
total_vote_score: number;
stories_count: number;
sharing_preference: 'private' | 'link_only' | 'public' | 'commons';
user_id: string;
created_at: string;
updated_at: string;
}
export interface CharacterCollection {
id: string;
name: string;
description: string;
type: 'official' | 'community' | 'seasonal' | 'contest';
is_official: boolean;
is_active: boolean;
sort_order: number;
icon_url?: string;
banner_url?: string;
character_count?: number;
}
type FilterType = 'popular' | 'new' | 'featured' | 'collections';
export function usePublicCharacters(filter: FilterType = 'popular', collectionId?: string | null) {
const [characters, setCharacters] = useState<PublicCharacter[]>([]);
const [collections, setCollections] = useState<CharacterCollection[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState<string | null>(null);
const [hasMore, setHasMore] = useState(true);
const [offset, setOffset] = useState(0);
const limit = 20;
// Load collections
const loadCollections = useCallback(async () => {
try {
const response = await fetchWithAuth('/characters/public/collections');
if (!response.ok) throw new Error('Failed to fetch collections');
const data = await response.json();
setCollections(data);
} catch (err) {
console.error('Error loading collections:', err);
}
}, []);
// Load characters
const loadCharacters = useCallback(
async (reset: boolean = false) => {
try {
const currentOffset = reset ? 0 : offset;
let url = `/characters/public?filter=${filter}&limit=${limit}&offset=${currentOffset}`;
if (collectionId) {
url += `&collection=${collectionId}`;
}
const response = await fetchWithAuth(url);
if (!response.ok) {
if (response.status === 404) {
throw new Error('404: Endpoint not found');
}
throw new Error('Failed to fetch characters');
}
const data = await response.json();
if (reset) {
setCharacters(data.characters);
} else {
setCharacters((prev) => [...prev, ...data.characters]);
}
setHasMore(data.hasMore);
setOffset(currentOffset + limit);
setError(null);
} catch (err: any) {
console.error('Error loading public characters:', err);
if (err.message?.includes('404')) {
setError('404: Backend endpoints not yet available');
} else {
setError('Fehler beim Laden der Charaktere');
}
} finally {
setLoading(false);
setRefreshing(false);
}
},
[filter, collectionId, offset, limit]
);
// Initial load
useEffect(() => {
setLoading(true);
setOffset(0);
setCharacters([]);
loadCollections();
loadCharacters(true);
}, [filter, collectionId]);
// Refresh function
const refresh = useCallback(() => {
setRefreshing(true);
setOffset(0);
loadCharacters(true);
}, [loadCharacters]);
// Load more function
const loadMore = useCallback(() => {
if (!loading && !refreshing && hasMore) {
loadCharacters(false);
}
}, [loading, refreshing, hasMore, loadCharacters]);
// Vote for character
const voteForCharacter = useCallback(
async (characterId: string, voteType: 'like' | 'love' | 'star') => {
try {
const response = await fetchWithAuth('/characters/public/vote', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
characterId,
voteType,
}),
});
if (!response.ok) {
throw new Error('Failed to vote');
}
// Update local state
setCharacters((prev) =>
prev.map((char) => {
if (char.id === characterId) {
const voteWeight = voteType === 'star' ? 3 : voteType === 'love' ? 2 : 1;
return {
...char,
total_vote_score: char.total_vote_score + voteWeight,
};
}
return char;
})
);
return true;
} catch (err) {
console.error('Error voting for character:', err);
Alert.alert(
'Fehler',
'Konnte nicht abstimmen\n\nBei anhaltenden Problemen helfen wir gerne unter support@manacore.ai weiter.'
);
return false;
}
},
[]
);
// Remove vote
const removeVote = useCallback(async (characterId: string) => {
try {
const response = await fetchWithAuth(`/characters/public/vote/${characterId}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error('Failed to remove vote');
}
// Update local state (rough estimate, actual weight unknown)
setCharacters((prev) =>
prev.map((char) => {
if (char.id === characterId) {
return {
...char,
total_vote_score: Math.max(0, char.total_vote_score - 1),
};
}
return char;
})
);
return true;
} catch (err) {
console.error('Error removing vote:', err);
Alert.alert(
'Fehler',
'Konnte Abstimmung nicht entfernen\n\nBei anhaltenden Problemen helfen wir gerne unter support@manacore.ai weiter.'
);
return false;
}
}, []);
// Clone character
const cloneCharacter = useCallback(async (characterId: string) => {
try {
const response = await fetchWithAuth(`/characters/public/clone/${characterId}`, {
method: 'POST',
});
if (!response.ok) {
throw new Error('Failed to clone character');
}
const data = await response.json();
Alert.alert('Erfolg', 'Charakter wurde erfolgreich kopiert!');
return data;
} catch (err) {
console.error('Error cloning character:', err);
Alert.alert(
'Fehler',
'Konnte Charakter nicht kopieren\n\nBei anhaltenden Problemen helfen wir gerne unter support@manacore.ai weiter.'
);
return null;
}
}, []);
// Publish character
const publishCharacter = useCallback(
async (
characterId: string,
sharingPreference: 'private' | 'link_only' | 'public' | 'commons'
) => {
try {
const response = await fetchWithAuth('/character/publish', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
characterId,
sharingPreference,
}),
});
if (!response.ok) {
throw new Error('Failed to publish character');
}
const data = await response.json();
Alert.alert('Erfolg', 'Charakter wurde veröffentlicht!');
return data;
} catch (err) {
console.error('Error publishing character:', err);
Alert.alert(
'Fehler',
'Konnte Charakter nicht veröffentlichen\n\nBei anhaltenden Problemen helfen wir gerne unter support@manacore.ai weiter.'
);
return null;
}
},
[]
);
// Unpublish character
const unpublishCharacter = useCallback(async (characterId: string) => {
try {
const response = await fetchWithAuth(`/character/unpublish/${characterId}`, {
method: 'POST',
});
if (!response.ok) {
throw new Error('Failed to unpublish character');
}
const data = await response.json();
Alert.alert('Erfolg', 'Charakter wurde zurückgezogen!');
return data;
} catch (err) {
console.error('Error unpublishing character:', err);
Alert.alert(
'Fehler',
'Konnte Charakter nicht zurückziehen\n\nBei anhaltenden Problemen helfen wir gerne unter support@manacore.ai weiter.'
);
return null;
}
}, []);
return {
characters,
collections,
loading,
refreshing,
error,
hasMore,
refresh,
loadMore,
voteForCharacter,
removeVote,
cloneCharacter,
publishCharacter,
unpublishCharacter,
};
}