mirror of
https://github.com/yusufipk/distromatch.git
synced 2026-09-11 10:46:07 +00:00
Add a super like button and download links for Linux distros
Implement a "super like" functionality with a dedicated button and associated UI elements. Add download URLs to distro data and display them on the results screen with download links and visual indicators for super liked items. Add keyboard support for left, right, and up arrow keys to control swiping. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e522d728-b28a-437f-b576-83935afe7ea0 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 393186dc-5528-4239-9955-fe74ab3cb710 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/55e426a8-5a2b-421b-8bd4-30481ae0063b/e522d728-b28a-437f-b576-83935afe7ea0/LaBrqqz Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -4,6 +4,7 @@ import { Heart, X, RotateCcw, Zap } from 'lucide-react';
|
||||
interface ActionButtonsProps {
|
||||
onLike: () => void;
|
||||
onDislike: () => void;
|
||||
onSuperLike?: () => void;
|
||||
onReset?: () => void;
|
||||
showReset?: boolean;
|
||||
disabled?: boolean;
|
||||
@@ -12,6 +13,7 @@ interface ActionButtonsProps {
|
||||
export default function ActionButtons({
|
||||
onLike,
|
||||
onDislike,
|
||||
onSuperLike,
|
||||
onReset,
|
||||
showReset = false,
|
||||
disabled = false,
|
||||
@@ -69,6 +71,8 @@ export default function ActionButtons({
|
||||
|
||||
<motion.button
|
||||
className="group relative flex h-14 w-14 items-center justify-center rounded-full"
|
||||
onClick={onSuperLike}
|
||||
disabled={disabled}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
data-testid="button-superlike"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import SwipeCard from './SwipeCard';
|
||||
import ActionButtons from './ActionButtons';
|
||||
@@ -13,19 +13,24 @@ interface CardStackProps {
|
||||
export default function CardStack({ cards, onBackgroundChange }: CardStackProps) {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [likedCards, setLikedCards] = useState<DistroCard[]>([]);
|
||||
const [superLikedCards, setSuperLikedCards] = useState<DistroCard[]>([]);
|
||||
const [dislikedCards, setDislikedCards] = useState<DistroCard[]>([]);
|
||||
|
||||
const visibleCards = cards.slice(currentIndex, currentIndex + 3);
|
||||
const isFinished = currentIndex >= cards.length;
|
||||
|
||||
const handleSwipe = useCallback(
|
||||
(direction: 'left' | 'right') => {
|
||||
(direction: 'left' | 'right' | 'super') => {
|
||||
const currentCard = cards[currentIndex];
|
||||
if (!currentCard) return;
|
||||
|
||||
if (direction === 'right') {
|
||||
setLikedCards((prev) => [...prev, currentCard]);
|
||||
console.log('Liked:', currentCard.distroName);
|
||||
} else if (direction === 'super') {
|
||||
setSuperLikedCards((prev) => [...prev, currentCard]);
|
||||
setLikedCards((prev) => [...prev, currentCard]);
|
||||
console.log('Super Liked:', currentCard.distroName);
|
||||
} else {
|
||||
setDislikedCards((prev) => [...prev, currentCard]);
|
||||
console.log('Disliked:', currentCard.distroName);
|
||||
@@ -42,6 +47,23 @@ export default function CardStack({ cards, onBackgroundChange }: CardStackProps)
|
||||
[currentIndex, cards, onBackgroundChange]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (currentIndex >= cards.length) return;
|
||||
|
||||
if (e.key === 'ArrowLeft') {
|
||||
handleSwipe('left');
|
||||
} else if (e.key === 'ArrowRight') {
|
||||
handleSwipe('right');
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
handleSwipe('super');
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleSwipe, currentIndex, cards.length]);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
setCurrentIndex(0);
|
||||
setLikedCards([]);
|
||||
@@ -52,7 +74,7 @@ export default function CardStack({ cards, onBackgroundChange }: CardStackProps)
|
||||
}, [cards, onBackgroundChange]);
|
||||
|
||||
if (isFinished) {
|
||||
return <EndOfDeck likedCards={likedCards} onReset={handleReset} />;
|
||||
return <EndOfDeck likedCards={likedCards} superLikedCards={superLikedCards} onReset={handleReset} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -89,6 +111,7 @@ export default function CardStack({ cards, onBackgroundChange }: CardStackProps)
|
||||
<ActionButtons
|
||||
onLike={() => handleSwipe('right')}
|
||||
onDislike={() => handleSwipe('left')}
|
||||
onSuperLike={() => handleSwipe('super')}
|
||||
onReset={handleReset}
|
||||
showReset={currentIndex > 0}
|
||||
disabled={isFinished}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { PartyPopper, RotateCcw, Download, Trophy, Star, ExternalLink } from 'lucide-react';
|
||||
import { RotateCcw, Download, Trophy, Star, ExternalLink, Zap } from 'lucide-react';
|
||||
import type { DistroCard } from '@/data/distros';
|
||||
|
||||
interface EndOfDeckProps {
|
||||
likedCards: DistroCard[];
|
||||
superLikedCards: DistroCard[];
|
||||
onReset: () => void;
|
||||
}
|
||||
|
||||
export default function EndOfDeck({ likedCards, onReset }: EndOfDeckProps) {
|
||||
export default function EndOfDeck({ likedCards, superLikedCards, onReset }: EndOfDeckProps) {
|
||||
return (
|
||||
<motion.div
|
||||
className="flex flex-col items-center gap-10 text-center px-4"
|
||||
@@ -73,7 +74,14 @@ export default function EndOfDeck({ likedCards, onReset }: EndOfDeckProps) {
|
||||
data-testid={`result-card-${card.id}`}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-purple-500/20 to-pink-500/20 backdrop-blur-xl" />
|
||||
<div className="absolute inset-0 rounded-2xl border border-white/10" />
|
||||
<div className={`absolute inset-0 rounded-2xl border ${superLikedCards.some(s => s.id === card.id) ? 'border-cyan-400/50' : 'border-white/10'}`} />
|
||||
|
||||
{superLikedCards.some(s => s.id === card.id) && (
|
||||
<div className="absolute top-2 left-2 z-20 flex items-center gap-1 rounded-full bg-gradient-to-r from-cyan-500 to-blue-500 px-2 py-1">
|
||||
<Zap className="h-3 w-3 text-white" fill="currentColor" />
|
||||
<span className="text-xs font-bold text-white">SUPER</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="relative">
|
||||
<img
|
||||
@@ -83,11 +91,18 @@ export default function EndOfDeck({ likedCards, onReset }: EndOfDeckProps) {
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent" />
|
||||
|
||||
<div className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<div className="rounded-full bg-white/20 backdrop-blur-md p-2">
|
||||
<a
|
||||
href={card.downloadUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
data-testid={`link-download-${card.id}`}
|
||||
>
|
||||
<div className="rounded-full bg-white/20 backdrop-blur-md p-2 hover:bg-white/30 transition-colors">
|
||||
<ExternalLink className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="relative p-4 bg-gradient-to-r from-gray-900/90 to-gray-800/90">
|
||||
|
||||
@@ -3,11 +3,13 @@ import { distroCards } from '@/data/distros';
|
||||
|
||||
export default function EndOfDeckExample() {
|
||||
const likedCards = distroCards.slice(0, 3);
|
||||
const superLikedCards = distroCards.slice(0, 1);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-[500px] items-center justify-center bg-gradient-to-br from-purple-900/50 to-blue-900/50 p-8">
|
||||
<EndOfDeck
|
||||
likedCards={likedCards}
|
||||
superLikedCards={superLikedCards}
|
||||
onReset={() => console.log('Reset!')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface DistroCard {
|
||||
desktopEnvironment: string;
|
||||
tags: string[];
|
||||
colorPalette: string[];
|
||||
downloadUrl: string;
|
||||
}
|
||||
|
||||
export const distroCards: DistroCard[] = [
|
||||
@@ -24,6 +25,7 @@ export const distroCards: DistroCard[] = [
|
||||
desktopEnvironment: 'GNOME',
|
||||
tags: ['beginner-friendly', 'stable', 'popular'],
|
||||
colorPalette: ['#E95420', '#77216F', '#5E2750'],
|
||||
downloadUrl: 'https://ubuntu.com/download',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
@@ -33,6 +35,7 @@ export const distroCards: DistroCard[] = [
|
||||
desktopEnvironment: 'i3wm',
|
||||
tags: ['advanced', 'minimal', 'customizable'],
|
||||
colorPalette: ['#1793D1', '#333333', '#0D47A1'],
|
||||
downloadUrl: 'https://archlinux.org/download/',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
@@ -42,6 +45,7 @@ export const distroCards: DistroCard[] = [
|
||||
desktopEnvironment: 'Pantheon',
|
||||
tags: ['beautiful', 'simple', 'curated'],
|
||||
colorPalette: ['#64BAFF', '#3689E6', '#0D52BF'],
|
||||
downloadUrl: 'https://elementary.io/',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
@@ -51,6 +55,7 @@ export const distroCards: DistroCard[] = [
|
||||
desktopEnvironment: 'KDE Plasma',
|
||||
tags: ['cutting-edge', 'developer', 'reliable'],
|
||||
colorPalette: ['#51A2DA', '#294172', '#3C6EB4'],
|
||||
downloadUrl: 'https://fedoraproject.org/workstation/download',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
@@ -60,6 +65,7 @@ export const distroCards: DistroCard[] = [
|
||||
desktopEnvironment: 'Cinnamon',
|
||||
tags: ['familiar', 'stable', 'beginner-friendly'],
|
||||
colorPalette: ['#87CF3E', '#5FAD56', '#3E8E41'],
|
||||
downloadUrl: 'https://linuxmint.com/download.php',
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
@@ -69,5 +75,6 @@ export const distroCards: DistroCard[] = [
|
||||
desktopEnvironment: 'COSMIC',
|
||||
tags: ['gaming', 'developer', 'modern'],
|
||||
colorPalette: ['#FAA41A', '#48B9C7', '#574F4A'],
|
||||
downloadUrl: 'https://pop.system76.com/',
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user