+31
-16
src/components/FaviconIcon.tsx
+31
-16
src/components/FaviconIcon.tsx
···
1
import { useState } from "react";
2
-
import { Globe, Settings as SettingsIcon } from "lucide-react";
3
4
interface FaviconIconProps {
5
url: string;
6
alt: string;
7
className?: string;
8
}
9
10
-
export default function FaviconIcon({ url, alt, className }: FaviconIconProps) {
11
const [loaded, setLoaded] = useState(false);
12
-
const [error, setError] = useState(false);
13
14
if (error) {
15
return (
16
-
<Globe
17
-
className={
18
-
className || "w-4 h-4 text-neutral-400 dark:text-neutral-500"
19
-
}
20
-
/>
21
);
22
}
23
24
return (
25
-
<>
26
-
{/* Fallback/Placeholder */}
27
{!loaded && (
28
<Globe
29
-
className={
30
-
className || "w-4 h-4 text-neutral-400 dark:text-neutral-500"
31
-
}
32
/>
33
)}
34
···
36
<img
37
src={url}
38
alt={`${alt} favicon`}
39
-
className={className || "h-4 w-4"}
40
-
// Use inline style to show only when loaded, preventing a broken image icon flicker
41
style={{ display: loaded ? "block" : "none" }}
42
onLoad={() => {
43
setLoaded(true);
···
47
setError(true);
48
}}
49
/>
50
-
</>
51
);
52
}
···
1
+
// FaviconIcon.tsx (Conceptual Component Update)
2
+
3
import { useState } from "react";
4
+
import { Globe } from "lucide-react";
5
6
interface FaviconIconProps {
7
url: string;
8
alt: string;
9
className?: string;
10
+
useButtonStyling?: boolean; // ⬅️ NEW OPTIONAL PROP
11
}
12
13
+
export default function FaviconIcon({
14
+
url,
15
+
alt,
16
+
className,
17
+
useButtonStyling = false,
18
+
}: FaviconIconProps) {
19
+
const [error, setError] = useState(false);
20
const [loaded, setLoaded] = useState(false);
21
+
22
+
// Define the base classes (applied to the image itself)
23
+
const imageClasses = "w-full h-full object-contain";
24
+
25
+
// Define the special button classes conditionally
26
+
const boundaryClasses = useButtonStyling
27
+
? "bg-white p-1 rounded-full shadow-md flex items-center justify-center"
28
+
: ""; // No special styling by default
29
+
30
+
// Combine the passed-in class name (for size) with the conditional boundary classes
31
+
const finalWrapperClasses = `${className || "w-4 h-4"} ${boundaryClasses}`;
32
33
if (error) {
34
return (
35
+
<div className={`${finalWrapperClasses}`}>
36
+
<Globe className={`${imageClasses} text-neutral-500`} />
37
+
</div>
38
);
39
}
40
41
return (
42
+
// Use the final combined classes on the wrapper
43
+
<div className={finalWrapperClasses}>
44
+
{/* Placeholder while loading */}
45
{!loaded && (
46
<Globe
47
+
className={`${imageClasses} text-neutral-400 dark:text-neutral-500`}
48
/>
49
)}
50
···
52
<img
53
src={url}
54
alt={`${alt} favicon`}
55
+
className={imageClasses}
56
style={{ display: loaded ? "block" : "none" }}
57
onLoad={() => {
58
setLoaded(true);
···
62
setError(true);
63
}}
64
/>
65
+
</div>
66
);
67
}
+3
-3
src/components/HistoryTab.tsx
+3
-3
src/components/HistoryTab.tsx
···
45
<div className="p-6">
46
{/* Setup Assistant Banner - Only show if wizard not completed */}
47
{!wizardCompleted && (
48
-
<div className="bg-firefly-banner-dark dark:bg-firefly-banner-dark rounded-2xl p-6 text-white">
49
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
50
<div className="flex-1">
51
<h2 className="text-2xl font-bold mb-2">
···
67
</div>
68
)}
69
70
-
<div className="flex items-center space-x-3 mt-3 mb-4">
71
<div>
72
<h2 className="text-xl font-bold text-purple-950 dark:text-cyan-50">
73
Previously Uploaded
···
171
<span>{destApp.name}</span>
172
</a>
173
)}
174
-
<div className="flex items-center flex-wrap gap-2">
175
<span className="text-xs px-2 py-0.5 rounded-full bg-purple-100 dark:bg-slate-900 text-purple-950 dark:text-cyan-50 font-medium">
176
{upload.totalUsers}{" "}
177
{upload.totalUsers === 1 ? "user found" : "users found"}
···
45
<div className="p-6">
46
{/* Setup Assistant Banner - Only show if wizard not completed */}
47
{!wizardCompleted && (
48
+
<div className="bg-firefly-banner-dark dark:bg-firefly-banner-dark rounded-2xl p-6 text-white mb-3">
49
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
50
<div className="flex-1">
51
<h2 className="text-2xl font-bold mb-2">
···
67
</div>
68
)}
69
70
+
<div className="flex items-center space-x-3 mb-4">
71
<div>
72
<h2 className="text-xl font-bold text-purple-950 dark:text-cyan-50">
73
Previously Uploaded
···
171
<span>{destApp.name}</span>
172
</a>
173
)}
174
+
<div className="flex items-center flex-wrap gap-2 py-1.5">
175
<span className="text-xs px-2 py-0.5 rounded-full bg-purple-100 dark:bg-slate-900 text-purple-950 dark:text-cyan-50 font-medium">
176
{upload.totalUsers}{" "}
177
{upload.totalUsers === 1 ? "user found" : "users found"}
+1
-1
src/components/PlatformSelector.tsx
+1
-1
src/components/PlatformSelector.tsx
···
26
title={isEnabled ? `Upload ${p.name} data` : "Coming soon"}
27
>
28
<PlatformIcon
29
-
className={`w-8 h-8 mx-auto mb-2 ${isEnabled ? "text-purple-750 dark:text-cyan-250" : "text-purple-750/50 dark:text-cyan-250/50"}`}
30
/>
31
<div className="text-sm font-medium text-center text-purple-900 dark:text-cyan-100">
32
{p.name}
···
26
title={isEnabled ? `Upload ${p.name} data` : "Coming soon"}
27
>
28
<PlatformIcon
29
+
className={`w-6 h-6 mx-auto mb-2 ${isEnabled ? "text-purple-750 dark:text-cyan-250" : "text-purple-750/50 dark:text-cyan-250/50"}`}
30
/>
31
<div className="text-sm font-medium text-center text-purple-900 dark:text-cyan-100">
32
{p.name}
+12
-10
src/components/SearchResultCard.tsx
+12
-10
src/components/SearchResultCard.tsx
···
118
119
{/* User Stats and Match Percent */}
120
<div className="flex items-center flex-wrap gap-2">
121
-
{match.postCount && match.postCount > 0 && (
122
-
<span className="text-xs px-2 py-0.5 rounded-full bg-purple-100 dark:bg-slate-900 text-purple-950 dark:text-cyan-50 font-medium">
123
-
{match.postCount.toLocaleString()} posts
124
-
</span>
125
-
)}
126
-
{match.followerCount && match.followerCount > 0 && (
127
-
<span className="text-xs px-2 py-0.5 rounded-full bg-purple-100 dark:bg-slate-900 text-purple-950 dark:text-cyan-50 font-medium">
128
-
{match.followerCount.toLocaleString()} followers
129
-
</span>
130
-
)}
131
<span className="text-xs px-2 py-0.5 rounded-full bg-purple-100 dark:bg-slate-900 text-purple-950 dark:text-cyan-50 font-medium">
132
{match.matchScore}% match
133
</span>
···
118
119
{/* User Stats and Match Percent */}
120
<div className="flex items-center flex-wrap gap-2">
121
+
{typeof match.postCount === "number" &&
122
+
match.postCount > 0 && (
123
+
<span className="text-xs px-2 py-0.5 rounded-full bg-purple-100 dark:bg-slate-900 text-purple-950 dark:text-cyan-50 font-medium">
124
+
{match.postCount.toLocaleString()} posts
125
+
</span>
126
+
)}
127
+
{typeof match.followerCount === "number" &&
128
+
match.followerCount > 0 && (
129
+
<span className="text-xs px-2 py-0.5 rounded-full bg-purple-100 dark:bg-slate-900 text-purple-950 dark:text-cyan-50 font-medium">
130
+
{match.followerCount.toLocaleString()} followers
131
+
</span>
132
+
)}
133
<span className="text-xs px-2 py-0.5 rounded-full bg-purple-100 dark:bg-slate-900 text-purple-950 dark:text-cyan-50 font-medium">
134
{match.matchScore}% match
135
</span>
+5
-5
src/components/UploadTab.tsx
+5
-5
src/components/UploadTab.tsx
···
31
<div className="p-6">
32
{/* Upload Section */}
33
<div className="space-y-3">
34
-
<div className="flex items-center justify-between mb-4">
35
-
<div className="flex items-center space-x-3">
36
<div>
37
<h2 className="text-xl font-bold text-purple-950 dark:text-cyan-50">
38
Upload Following Data
···
42
</p>
43
</div>
44
</div>
45
-
{wizardCompleted && (
46
<button
47
onClick={onShowWizard}
48
-
className="text-sm text-orange-650 hover:text-orange-500 dark:text-amber-400 dark:hover:text-amber-300 font-medium transition-colors flex items-center space-x-1"
49
>
50
<Settings className="w-4 h-4" />
51
-
<span>Reconfigure</span>
52
</button>
53
)}
54
</div>
···
31
<div className="p-6">
32
{/* Upload Section */}
33
<div className="space-y-3">
34
+
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between mb-4">
35
+
<div className="flex items-center space-x-3 mb-2 sm:mb-0">
36
<div>
37
<h2 className="text-xl font-bold text-purple-950 dark:text-cyan-50">
38
Upload Following Data
···
42
</p>
43
</div>
44
</div>
45
+
{!wizardCompleted && (
46
<button
47
onClick={onShowWizard}
48
+
className="text-md text-orange-650 hover:text-orange-500 dark:text-amber-400 dark:hover:text-amber-300 font-medium transition-colors flex items-center space-x-1"
49
>
50
<Settings className="w-4 h-4" />
51
+
<span>Configure</span>
52
</button>
53
)}
54
</div>
+2
-2
src/pages/Loading.tsx
+2
-2
src/pages/Loading.tsx
···
64
<div className="max-w-3xl mx-auto px-4 py-6">
65
<div className="flex items-center justify-between">
66
<div className="flex items-center space-x-4">
67
-
<div className="relative w-14 h-14">
68
-
<PlatformIcon className="w-12 h-12" />
69
</div>
70
<div>
71
<h2 className="text-xl font-bold">Finding Your Fireflies</h2>
···
64
<div className="max-w-3xl mx-auto px-4 py-6">
65
<div className="flex items-center justify-between">
66
<div className="flex items-center space-x-4">
67
+
<div className="relative w-12 h-12">
68
+
<PlatformIcon className="w-10 h-10" />
69
</div>
70
<div>
71
<h2 className="text-xl font-bold">Finding Your Fireflies</h2>
+10
-2
src/pages/Results.tsx
+10
-2
src/pages/Results.tsx
···
1
import { Sparkles, Heart } from "lucide-react";
2
import { PLATFORMS } from "../constants/platforms";
3
import AppHeader from "../components/AppHeader";
4
import SearchResultCard from "../components/SearchResultCard";
5
import type { AtprotoAppId } from "../types/settings";
6
7
interface atprotoSession {
···
73
}: ResultsPageProps) {
74
const platform = PLATFORMS[sourcePlatform] || PLATFORMS.tiktok;
75
const PlatformIcon = platform.icon;
76
77
return (
78
<div className="min-h-screen pb-24">
···
203
disabled={isFollowing}
204
className="w-full bg-firefly-banner dark:bg-firefly-banner-dark text-white hover:from-amber-600 hover:via-orange-600 hover:to-pink-600 py-5 rounded-2xl font-bold text-lg transition-all shadow-2xl hover:shadow-3xl flex items-center justify-center space-x-3 hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100"
205
>
206
-
<Sparkles className="w-6 h-6" />
207
<span>
208
Light Up {totalSelected} Connection
209
-
{totalSelected === 1 ? "" : "s"} ✨
210
</span>
211
</button>
212
</div>
···
1
import { Sparkles, Heart } from "lucide-react";
2
import { PLATFORMS } from "../constants/platforms";
3
+
import { ATPROTO_APPS } from "../constants/atprotoApps";
4
import AppHeader from "../components/AppHeader";
5
import SearchResultCard from "../components/SearchResultCard";
6
+
import FaviconIcon from "../components/FaviconIcon";
7
import type { AtprotoAppId } from "../types/settings";
8
9
interface atprotoSession {
···
75
}: ResultsPageProps) {
76
const platform = PLATFORMS[sourcePlatform] || PLATFORMS.tiktok;
77
const PlatformIcon = platform.icon;
78
+
const destinationApp = ATPROTO_APPS[destinationAppId];
79
80
return (
81
<div className="min-h-screen pb-24">
···
206
disabled={isFollowing}
207
className="w-full bg-firefly-banner dark:bg-firefly-banner-dark text-white hover:from-amber-600 hover:via-orange-600 hover:to-pink-600 py-5 rounded-2xl font-bold text-lg transition-all shadow-2xl hover:shadow-3xl flex items-center justify-center space-x-3 hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100"
208
>
209
+
<FaviconIcon
210
+
url={destinationApp.icon}
211
+
alt={destinationApp.name}
212
+
className="w-5 h-5"
213
+
useButtonStyling={true}
214
+
/>
215
<span>
216
Light Up {totalSelected} Connection
217
+
{totalSelected === 1 ? "" : "s"}
218
</span>
219
</button>
220
</div>
+1
-1
src/pages/Settings.tsx
+1
-1
src/pages/Settings.tsx
···
162
className="flex items-center justify-between px-3 py-2 rounded-xl transition-colors"
163
>
164
<div className="flex items-center space-x-3 flex-1">
165
-
<Icon className="w-6 h-6 text-purple-950 dark:text-cyan-50 flex-shrink-0" />
166
<div className="flex-1 min-w-0">
167
<div className="font-medium text-purple-950 dark:text-cyan-50">
168
{p.name}
···
162
className="flex items-center justify-between px-3 py-2 rounded-xl transition-colors"
163
>
164
<div className="flex items-center space-x-3 flex-1">
165
+
<Icon className="w-4 h-4 text-purple-950 dark:text-cyan-50 flex-shrink-0" />
166
<div className="flex-1 min-w-0">
167
<div className="font-medium text-purple-950 dark:text-cyan-50">
168
{p.name}