Skip to content

Commit 0621cee

Browse files
committed
Fix portfolio footer responsiveness and username unavailable feedback.
Make the info icon sit inline with the branding pill on all screen sizes, return a clear error when a username is taken, and show that message in the selection UI.
1 parent c7d0a36 commit 0621cee

3 files changed

Lines changed: 34 additions & 15 deletions

File tree

backend/src/routes/auth.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,16 @@ router.get('/check-username', async (req, res) => {
3333

3434
const existingUser = await User.findOne({ username: username.toLowerCase() });
3535

36+
if (existingUser) {
37+
return res.json({
38+
available: false,
39+
username: username.toLowerCase(),
40+
error: 'Username is not available'
41+
});
42+
}
43+
3644
res.json({
37-
available: !existingUser,
45+
available: true,
3846
username: username.toLowerCase()
3947
});
4048
} catch (error) {

frontend/src/pages/UsernameSelection.jsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,19 @@ function UsernameSelection() {
5454
useEffect(() => {
5555
if (!username || username.length < 3) {
5656
setIsAvailable(null)
57+
setError('')
5758
return
5859
}
5960

6061
const timer = setTimeout(async () => {
6162
setIsChecking(true)
63+
setError('')
6264
try {
6365
const { data } = await checkUsername(username)
6466
setIsAvailable(data.available)
65-
setError(data.error || '')
67+
setError(
68+
data.error || (data.available === false ? 'Username is not available' : '')
69+
)
6670
} catch (err) {
6771
setError(err.response?.data?.error || 'Failed to check username')
6872
setIsAvailable(false)
@@ -99,6 +103,8 @@ function UsernameSelection() {
99103
const value = e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '')
100104
if (value.length <= MAX_USERNAME_LENGTH) {
101105
setUsername(value)
106+
setIsAvailable(null)
107+
setError('')
102108
}
103109
}
104110

@@ -154,7 +160,7 @@ function UsernameSelection() {
154160
<Check size={18} strokeWidth={ICON_STROKE} className="text-success" />
155161
)}
156162
{!isChecking && isAvailable === false && (
157-
<X size={18} strokeWidth={ICON_STROKE} className="text-muted" />
163+
<X size={18} strokeWidth={ICON_STROKE} className="text-error" />
158164
)}
159165
</div>
160166
</div>
@@ -175,7 +181,12 @@ function UsernameSelection() {
175181
✓ Username is available!
176182
</p>
177183
)}
178-
{error && (
184+
{isAvailable === false && !isChecking && (
185+
<p className="mt-2 text-sm text-error">
186+
{error || 'Username is not available'}
187+
</p>
188+
)}
189+
{error && isAvailable !== false && (
179190
<p className="mt-2 text-sm text-error">
180191
{error}
181192
</p>

frontend/src/portfolio/components/Footer.jsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ function Footer({ profile, compact = false }) {
2626
const customBranding = profile?.customBranding
2727

2828
return (
29-
<footer className={`relative flex-shrink-0 ${compact ? 'py-3 mt-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]' : 'py-12 px-4 mt-8'}`}>
29+
<footer className={`relative flex-shrink-0 overflow-visible ${compact ? 'px-2 sm:px-0 py-3 mt-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]' : 'py-12 px-4 mt-8'}`}>
3030
{!compact && (
3131
<div
3232
className="absolute top-0 left-1/2 -translate-x-1/2 w-1/2 max-w-md h-px"
3333
style={{ background: 'var(--pf-border-subtle)' }}
3434
/>
3535
)}
3636

37-
<div className={`${compact ? '' : 'max-w-6xl mx-auto'} flex justify-center`}>
37+
<div className={`${compact ? 'w-full' : 'max-w-6xl mx-auto'} flex justify-center`}>
3838
{customBranding?.enabled && customBranding?.text ? (
3939
<a
4040
href={safeHref(customBranding.url) || '#'}
@@ -55,20 +55,20 @@ function Footer({ profile, compact = false }) {
5555
)}
5656
</a>
5757
) : (
58-
<div className="relative">
58+
<div className="inline-flex items-center gap-2 sm:gap-3 max-w-full">
5959
<div
60-
className="absolute -top-2 -left-2 z-20 portfolio-no-print"
60+
className="relative flex-shrink-0 portfolio-no-print"
6161
onMouseEnter={handleMouseEnter}
6262
onMouseLeave={handleMouseLeave}
6363
>
6464
<button
6565
type="button"
66-
className="portfolio-btn-primary min-h-[44px] min-w-[44px] rounded-full flex items-center justify-center shadow-lg portfolio-touch-target"
66+
className="portfolio-btn-primary rounded-full flex items-center justify-center shadow-lg portfolio-touch-target w-9 h-9 sm:w-10 sm:h-10"
6767
aria-label="Premium branding information"
6868
aria-expanded={showPremiumInfo}
6969
onClick={() => setShowPremiumInfo((open) => !open)}
7070
>
71-
<Info size={14} strokeWidth={3} />
71+
<Info size={compact ? 12 : 14} strokeWidth={3} className="flex-shrink-0" />
7272
</button>
7373

7474
<AnimatePresence>
@@ -77,7 +77,7 @@ function Footer({ profile, compact = false }) {
7777
initial={{ opacity: 0, y: 8, scale: 0.96 }}
7878
animate={{ opacity: 1, y: 0, scale: 1 }}
7979
exit={{ opacity: 0, y: 8, scale: 0.96 }}
80-
className="portfolio-surface absolute bottom-full mb-3 left-1/2 -translate-x-1/2 w-56 p-4 rounded-xl text-xs text-center shadow-xl z-30"
80+
className="portfolio-surface absolute bottom-full mb-2 left-1/2 -translate-x-1/2 w-[min(14rem,calc(100vw-2rem))] p-3 sm:p-4 rounded-xl text-xs text-center shadow-xl z-30"
8181
onMouseEnter={handleMouseEnter}
8282
onMouseLeave={handleMouseLeave}
8383
>
@@ -101,17 +101,17 @@ function Footer({ profile, compact = false }) {
101101
href={getAppUrl()}
102102
target="_blank"
103103
rel="noopener noreferrer"
104-
className={`group portfolio-surface rounded-xl inline-flex items-center gap-3 transition-transform hover:-translate-y-0.5 ${compact ? 'px-4 py-2' : 'px-6 py-3'}`}
104+
className={`group portfolio-surface rounded-xl inline-flex items-center gap-2 sm:gap-3 transition-transform hover:-translate-y-0.5 min-w-0 ${compact ? 'px-3 py-2 sm:px-4' : 'px-6 py-3'}`}
105105
>
106106
<img
107107
src={BRAND_LOGO_SRC}
108108
alt={BRAND_LOGO_ALT}
109-
className={`object-contain flex-shrink-0 ${compact ? 'w-6 h-6' : 'w-8 h-8'}`}
109+
className={`object-contain flex-shrink-0 ${compact ? 'w-5 h-5 sm:w-6 sm:h-6' : 'w-8 h-8'}`}
110110
width={compact ? 24 : 32}
111111
height={compact ? 24 : 32}
112112
decoding="async"
113113
/>
114-
<div className="flex items-center gap-2">
114+
<div className="flex items-center gap-1.5 sm:gap-2 min-w-0 whitespace-nowrap">
115115
<span className={`portfolio-text-secondary ${compact ? 'text-xs' : 'text-sm'}`}>Built with</span>
116116
<span className={`font-semibold ${compact ? 'text-xs' : 'text-sm'}`}>
117117
<span className="portfolio-text">Portlify</span>
@@ -121,7 +121,7 @@ function Footer({ profile, compact = false }) {
121121
<ChevronRight
122122
size={16}
123123
strokeWidth={ICON_STROKE}
124-
className="portfolio-link opacity-0 group-hover:opacity-100 group-hover:translate-x-0.5 transition-all"
124+
className="portfolio-link opacity-0 group-hover:opacity-100 group-hover:translate-x-0.5 transition-all hidden sm:block flex-shrink-0"
125125
/>
126126
</a>
127127
</div>

0 commit comments

Comments
 (0)