Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 71 additions & 13 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { MovieDetailModal } from './components/MovieDetailModal';
import { StatisticsDashboard } from './components/StatisticsDashboard';
import { AchievementsGrid } from './components/AchievementsGrid';
import { BottomNav } from './components/BottomNav';
import { Recommendations } from './components/Recommendations';
import { useToast } from './components/Toast';
import { Search, Plus, Trash2, Heart, Eye, Shield, ListPlus } from 'lucide-react';
import { Search, Plus, Trash2, Heart, Eye, Shield, ListPlus, Sparkles } from 'lucide-react';
import { SplashScreen } from '@capacitor/splash-screen';
import { shareMovie } from './lib/share';

Expand Down Expand Up @@ -121,11 +122,16 @@ function App({ conductor }: AppProps) {
}

const filteredItems = state.items.filter((movie) => {
if (state.filter === 'favorites') return movie.favorite;
if (state.filter === 'watched') return movie.watched;
if (state.filter === 'favorites' && !movie.favorite) return false;
if (state.filter === 'watched' && !movie.watched) return false;
if (state.tagFilter && !(movie.tags || []).includes(state.tagFilter)) return false;
return true;
});

const allTags = Array.from(
new Set(state.items.flatMap((m) => m.tags || []).filter(Boolean))
).sort();

return (
<div className="min-h-screen bg-app-bg text-app-text font-sans pb-24">

Expand Down Expand Up @@ -191,6 +197,12 @@ function App({ conductor }: AppProps) {
<AchievementsGrid achievements={state.achievements} />
) : state.filter === 'statistics' ? (
<StatisticsDashboard statistics={state.statistics} />
) : state.filter === 'recommendations' ? (
<Recommendations
library={state.items}
conductor={conductor}
onAddToLibrary={handleAddMovie}
/>
) : (
/* Movie Grid */
<>
Expand All @@ -201,6 +213,37 @@ function App({ conductor }: AppProps) {
{state.customLists.find(l => l.id === state.activeListId)?.name}
</div>
)}

{/* Tag filter chips: nur sichtbar, wenn Tags existieren */}
{allTags.length > 0 && (
<div className="mb-4 flex flex-wrap items-center gap-2 text-xs">
<span className="text-app-text-muted uppercase tracking-wider">Tags:</span>
{state.tagFilter && (
<button
onClick={() => conductor.dispatch({ type: 'SET_TAG_FILTER', payload: null })}
className="bg-app-secondary text-app-text-muted hover:text-app-text px-2 py-1 rounded-full border border-app-border"
>
Alle
</button>
)}
{allTags.slice(0, 12).map(tag => {
const active = state.tagFilter === tag;
return (
<button
key={tag}
onClick={() => conductor.dispatch({ type: 'SET_TAG_FILTER', payload: active ? null : tag })}
className={`px-2.5 py-1 rounded-full border transition ${
active
? 'bg-blue-500 text-white border-blue-500'
: 'bg-blue-500/10 text-blue-300 border-blue-500/30 hover:bg-blue-500/20'
}`}
>
#{tag}
</button>
);
})}
</div>
)}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
{filteredItems.map((movie) => (
<div
Expand Down Expand Up @@ -258,16 +301,30 @@ function App({ conductor }: AppProps) {
)}
</div>
{movie.source !== 'tmdb' && (
<div className="flex items-center gap-3 mt-2">
<Heart
className={`w-5 h-5 cursor-pointer transition hover:scale-110 ${movie.favorite ? "fill-red-500 text-red-500" : "text-app-text-muted"}`}
onClick={(e) => { e.stopPropagation(); conductor.dispatch({ type: 'TOGGLE_FAVORITE', payload: movie.id }); }}
/>
<Eye
className={`w-5 h-5 cursor-pointer transition hover:scale-110 ${movie.watched ? "text-blue-400" : "text-gray-600"}`}
onClick={(e) => { e.stopPropagation(); conductor.dispatch({ type: 'TOGGLE_WATCHED', payload: movie.id }); }}
/>
</div>
<>
{!!movie.tags?.length && (
<div className="flex flex-wrap gap-1 mt-1">
{movie.tags!.slice(0, 2).map(tag => (
<span key={tag} className="text-[10px] bg-blue-500/15 text-blue-300 border border-blue-500/30 rounded px-1.5 py-0.5">
#{tag}
</span>
))}
</div>
)}
<div className="flex items-center gap-3 mt-2">
<Heart
className={`w-5 h-5 cursor-pointer transition hover:scale-110 ${movie.favorite ? "fill-red-500 text-red-500" : "text-app-text-muted"}`}
onClick={(e) => { e.stopPropagation(); conductor.dispatch({ type: 'TOGGLE_FAVORITE', payload: movie.id }); }}
/>
<Eye
className={`w-5 h-5 cursor-pointer transition hover:scale-110 ${movie.watched ? "text-blue-400" : "text-gray-600"}`}
onClick={(e) => { e.stopPropagation(); conductor.dispatch({ type: 'TOGGLE_WATCHED', payload: movie.id }); }}
/>
{typeof movie.userRating === 'number' && movie.userRating > 0 && (
<span className="text-xs text-yellow-400 font-bold">★ {movie.userRating}</span>
)}
</div>
</>
)}
</div>
</div>
Expand Down Expand Up @@ -301,6 +358,7 @@ function App({ conductor }: AppProps) {
onShowWatched={() => conductor.dispatch({ type: 'SET_FILTER', payload: 'watched' })}
onShowAchievements={() => conductor.dispatch({ type: 'SET_FILTER', payload: 'achievements' })}
onShowStatistics={() => conductor.dispatch({ type: 'SET_FILTER', payload: 'statistics' })}
onShowRecommendations={() => conductor.dispatch({ type: 'SET_FILTER', payload: 'recommendations' })}
/>

{/* Profile Modal */}
Expand Down
18 changes: 16 additions & 2 deletions src/components/BottomNav.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next';
import { Home, Heart, User, Eye, Zap, BarChart2 } from 'lucide-react';
import { Home, Heart, User, Eye, Zap, BarChart2, Sparkles } from 'lucide-react';

interface BottomNavProps {
currentFilter: string;
Expand All @@ -10,6 +10,7 @@ interface BottomNavProps {
onShowWatched: () => void;
onShowAchievements: () => void;
onShowStatistics: () => void;
onShowRecommendations?: () => void;
}

export function BottomNav({
Expand All @@ -20,7 +21,8 @@ export function BottomNav({
onShowProfile,
onShowWatched,
onShowAchievements,
onShowStatistics
onShowStatistics,
onShowRecommendations
}: BottomNavProps) {
const { t } = useTranslation();

Expand Down Expand Up @@ -85,6 +87,18 @@ export function BottomNav({
>
<BarChart2 className="w-6 h-6" />
</button>

{onShowRecommendations && (
<button
className={`transition-colors ${
currentFilter === 'recommendations' ? 'text-blue-500' : 'text-app-text-muted hover:text-app-text'
}`}
onClick={onShowRecommendations}
aria-label="Empfehlungen"
>
<Sparkles className="w-6 h-6" />
</button>
)}
</nav>
);
}
4 changes: 2 additions & 2 deletions src/components/LoginScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function LoginScreen({ onLoginSuccess }: LoginScreenProps) {
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full bg-app-bg/40 border border-app-border rounded-xl py-3 pl-12 pr-4 text-app-text focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500/50 transition-all placeholder-app-text-muted"
className="auth-input w-full bg-app-bg/40 border border-app-border rounded-xl py-3 pl-12 pr-4 text-app-text focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500/50 transition-all placeholder-app-text-muted"
placeholder="name@example.com"
/>
</div>
Expand All @@ -115,7 +115,7 @@ export function LoginScreen({ onLoginSuccess }: LoginScreenProps) {
minLength={6}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full bg-app-bg/40 border border-app-border rounded-xl py-3 pl-12 pr-4 text-app-text focus:outline-none focus:ring-2 focus:ring-purple-500/50 focus:border-purple-500/50 transition-all placeholder-app-text-muted"
className="auth-input w-full bg-app-bg/40 border border-app-border rounded-xl py-3 pl-12 pr-4 text-app-text focus:outline-none focus:ring-2 focus:ring-purple-500/50 focus:border-purple-500/50 transition-all placeholder-app-text-muted"
placeholder="••••••••"
/>
</div>
Expand Down
Loading
Loading