Skip to content

Commit 3b2c5f4

Browse files
committed
feat:impr responsive
1 parent 218974b commit 3b2c5f4

File tree

7 files changed

+96
-62
lines changed

7 files changed

+96
-62
lines changed

apps/masterbots.ai/app/auth/signin/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ export default function SignInPage() {
4545
<Link
4646
href="/auth/signup"
4747
className="underline transition-colors hover:text-primary"
48-
prefetch={false}
4948
>
5049
Sign up
5150
</Link>

apps/masterbots.ai/app/auth/signup/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ export default function SignUpPage() {
4646
{/* Optional: Add terms and conditions notice */}
4747
<p className="mt-6 text-xs text-center text-muted-foreground">
4848
By creating an account, you agree to our{' '}
49-
<a href="/terms" className="underline hover:text-primary">
49+
<a href="/terms#terms" className="underline hover:text-primary">
5050
Terms of Service
5151
</a>{' '}
5252
and{' '}
53-
<a href="/privacy" className="underline hover:text-primary">
53+
<a href="/terms#privacy" className="underline hover:text-primary">
5454
Privacy Policy
5555
</a>
5656
</p>

apps/masterbots.ai/components/layout/header/user-menu.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ function getUserInitials(name: string) {
2121
return lastName ? `${firstName[0]}${lastName[0]}` : firstName.slice(0, 2)
2222
}
2323

24-
function truncateUsername(username: string) {
25-
return username.length > 10 ? `${username.slice(0, 6)}..` : username
24+
function truncateUsername(username: string | null | undefined, maxLength = 10) {
25+
if (!username) return '';
26+
return username.length > maxLength ? `${username.slice(0, maxLength - 4)}...` : username;
2627
}
2728

2829
export function UserMenu({ user }: UserMenuProps) {

apps/masterbots.ai/components/routes/browse/browse-accordion.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ import { ChevronDown } from 'lucide-react'
3535
import type { Thread } from 'mb-genql'
3636
import React from 'react'
3737

38+
// Helper function to handle body scroll
39+
const toggleBodyScroll = (disable: boolean) => {
40+
if (typeof window === 'undefined') return
41+
42+
document.body.style.overflow = disable ? 'hidden' : 'auto'
43+
// Prevent iOS Safari bouncing
44+
document.body.style.position = disable ? 'fixed' : 'static'
45+
document.body.style.width = disable ? '100%' : 'auto'
46+
}
47+
3848
export function BrowseAccordion({
3949
thread = null,
4050
className,
@@ -83,6 +93,20 @@ export function BrowseAccordion({
8393
thread?.threadId !== activeThread?.threadId
8494
const shouldBeDisabled = disabled || isAnotherThreadOpen
8595

96+
//!!! Handle body scroll locking
97+
React.useEffect(() => {
98+
const isMobile = window.innerWidth < 640 // sm breakpoint
99+
100+
if (isMobile && open && !isNestedThread) {
101+
toggleBodyScroll(true)
102+
} else {
103+
toggleBodyScroll(false)
104+
}
105+
106+
// Cleanup on unmount
107+
return () => toggleBodyScroll(false)
108+
}, [open, isNestedThread])
109+
86110
React.useEffect(() => {
87111
if (
88112
(thread?.threadId &&
@@ -164,7 +188,10 @@ export function BrowseAccordion({
164188
!open &&
165189
'opacity-50 pointer-events-none filter grayscale',
166190
!isNestedThread && shouldBeDisabled && 'cursor-not-allowed',
167-
isNestedThread && 'my-2'
191+
isNestedThread && 'my-2',
192+
//! Add styles for mobile when open
193+
!isNestedThread && open && 'sm:relative fixed inset-0 sm:inset-auto'
194+
168195
)}
169196
id={`thread-${thread?.threadId}`}
170197
{...props}

apps/masterbots.ai/components/routes/browse/browse-list-item.tsx

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export default function BrowseListItem({
6565
initialUrl = location.href
6666
})
6767

68+
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
6869
React.useEffect(() => {
6970
initialUrl = location.href
7071
}, [tab])
@@ -150,53 +151,61 @@ export default function BrowseListItem({
150151
'relative flex items-center font-normal md:text-lg transition-all w-full gap-3 pr-4'
151152
)}
152153
>
153-
<div className="w-full flex justify-between items-center text-left">
154-
<div className='flex items-center gap-4 w-[calc(100%-124px)] m:w-[calc(100%-58px)] '>
154+
<div className="flex items-center justify-between w-full text-left">
155+
{/* Main content area - adjusted width and spacing */}
156+
<div className="flex items-center gap-2 sm:gap-4 w-[calc(100%-100px)] sm:w-[calc(100%-124px)]">
155157
{pageType !== 'bot' && <ChatbotAvatar thread={thread} />}
158+
159+
{/* Message content - adjusted spacing */}
156160
<div
157161
className={cn('truncate-title', {
158-
'no-truncate max-h-40 !overflow-y-auto sm:max-h-none sm:overflow-visible': isAccordionOpen
162+
'no-truncate max-h-40 !overflow-y-auto sm:max-h-none sm:overflow-visible':
163+
isAccordionOpen
159164
})}
160165
>
161166
{thread.messages?.[0]?.content}
162167
</div>
163168

164-
<div className="flex gap-3 px-4 items-center">
165-
{pageType !== 'user' && (
166-
<>
167-
<span className="opacity-50 text-sm"> by </span>
168-
<Button
169-
onClick={goToProfile}
170-
title={thread.user?.username.replace('_', ' ')}
171-
variant="icon"
172-
size="icon"
173-
>
174-
<Image
175-
className="transition-opacity duration-300 rounded-full select-none hover:opacity-80"
176-
src={thread.user?.profilePicture || '/images/robohash1.png'}
177-
alt={thread.user?.username ?? 'Avatar'}
178-
height={40}
179-
width={40}
180-
/>
181-
</Button>
182-
</>
183-
)}
184-
</div>
169+
{/* User section with tighter spacing on mobile */}
170+
{pageType !== 'user' && (
171+
<div className="flex items-center gap-1 ml-2 sm:gap-3 sm:ml-4">
172+
<span className="hidden text-sm opacity-50 sm:inline"> by </span>
173+
<Button
174+
onClick={goToProfile}
175+
title={thread.user?.username.replace('_', ' ')}
176+
variant="icon"
177+
size="icon"
178+
className="w-8 h-8 sm:h-10 sm:w-10" // Smaller on mobile
179+
>
180+
<Image
181+
className="transition-opacity duration-300 rounded-full select-none hover:opacity-80"
182+
src={
183+
thread.user?.profilePicture || '/images/robohash1.png'
184+
}
185+
alt={thread.user?.username ?? 'Avatar'}
186+
height={40}
187+
width={40}
188+
/>
189+
</Button>
190+
</div>
191+
)}
185192
</div>
186193
{/* Thread Options */}
187-
<div className="pl-4 pr-8">
188-
<ChatOptions threadId={thread.threadId} thread={thread} isBrowse />
194+
<div className="pl-2 pr-4 sm:pl-4 sm:pr-8">
195+
<ChatOptions
196+
threadId={thread.threadId}
197+
thread={thread}
198+
isBrowse
199+
/>
189200
</div>
190201
</div>
191-
192-
193202
</div>
194203

195204
{/* Thread Description */}
196205

197206
<div className="overflow-hidden text-sm text-left opacity-50">
198207
{thread.messages?.[1]?.content &&
199-
thread.messages?.[1]?.role !== 'user' ? (
208+
thread.messages?.[1]?.role !== 'user' ? (
200209
<div className="flex-1 space-y-2 overflow-hidden">
201210
<ShortMessage content={thread.messages?.[1]?.content} />
202211
</div>

apps/masterbots.ai/components/shared/password-strength-meter.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ interface PasswordStrengthMeterProps {
99
password: string
1010
}
1111

12-
export const PasswordStrengthMeter: React.FC<PasswordStrengthMeterProps> = ({
13-
password
14-
}) => {
12+
export function PasswordStrengthMeter({ password }: PasswordStrengthMeterProps) {
1513
const strength = calculatePasswordStrength(password)
1614
const color = getPasswordStrengthColor(strength)
1715
const label = getPasswordStrengthLabel(strength)

apps/masterbots.ai/lib/password.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import type React from 'react'
22

3-
4-
53
// * Valid password strength scores
6-
export type PasswordStrength = 0 | 1 | 2 | 3 | 4 | 5 | 6;
7-
4+
export type PasswordStrength = 0 | 1 | 2 | 3 | 4 | 5 | 6
85

96
//* Mapping of strength scores to labels
107
export const strengthLabels: Record<PasswordStrength, string> = {
@@ -15,46 +12,46 @@ export const strengthLabels: Record<PasswordStrength, string> = {
1512
4: 'Good',
1613
5: 'Strong',
1714
6: 'Very Strong'
18-
} as const;
15+
} as const
1916

2017
/**
2118
* Calculates password strength based on various criteria including Unicode characters.
22-
* Returns a score from 0 (weakest) to 6 (strongest).
19+
* Returns a score from 0 (weakest) to 6 (strongest).
2320
* @param password The password to evaluate
2421
*/
2522
export function calculatePasswordStrength(password: string): PasswordStrength {
2623
// Early return for empty or very short passwords
27-
if (!password || password.length < 4) return 0;
24+
if (!password || password.length < 4) return 0
2825

29-
let strength = 0;
26+
let strength = 0
3027

3128
// Length checks
32-
if (password.length > 7) strength += 1;
33-
if (password.length > 10) strength += 1;
29+
if (password.length > 7) strength += 1
30+
if (password.length > 10) strength += 1
3431

3532
// Unicode-aware regex patterns
3633
const patterns = {
3734
// Matches any uppercase letter, including non-ASCII uppercase
3835
uppercase: /\p{Lu}/u,
39-
36+
4037
// Matches any lowercase letter, including non-ASCII lowercase
4138
lowercase: /\p{Ll}/u,
42-
39+
4340
// Matches any decimal number
4441
numbers: /\p{Nd}/u,
45-
42+
4643
// Matches symbols, punctuation, and other non-alphanumeric characters
4744
special: /[^\p{L}\p{Nd}]/u
48-
};
45+
}
4946

5047
// Test each criteria
51-
if (patterns.uppercase.test(password)) strength += 1;
52-
if (patterns.lowercase.test(password)) strength += 1;
53-
if (patterns.numbers.test(password)) strength += 1;
54-
if (patterns.special.test(password)) strength += 1;
48+
if (patterns.uppercase.test(password)) strength += 1
49+
if (patterns.lowercase.test(password)) strength += 1
50+
if (patterns.numbers.test(password)) strength += 1
51+
if (patterns.special.test(password)) strength += 1
5552

5653
// Ensure return value is within valid range
57-
return Math.min(strength, 6) as PasswordStrength;
54+
return Math.min(strength, 6) as PasswordStrength
5855
}
5956

6057
/**
@@ -63,8 +60,11 @@ export function calculatePasswordStrength(password: string): PasswordStrength {
6360
*/
6461
export function getPasswordStrengthLabel(strength: number): string {
6562
// Ensure strength is within valid range
66-
const validStrength = Math.min(Math.max(Math.floor(strength), 0), 6) as PasswordStrength;
67-
return strengthLabels[validStrength];
63+
const validStrength = Math.min(
64+
Math.max(Math.floor(strength), 0),
65+
6
66+
) as PasswordStrength
67+
return strengthLabels[validStrength]
6868
}
6969

7070
export function getPasswordStrengthColor(strength: number): string {
@@ -83,15 +83,15 @@ export function getPasswordStrengthColor(strength: number): string {
8383
}
8484
}
8585

86-
8786
export function isPasswordStrong(password: string): boolean {
8887
const strength = calculatePasswordStrength(password)
8988
return strength >= 4 // Require at least a "Strong" password
9089
}
9190

92-
export function validatePassword(e: React.FocusEvent<HTMLInputElement>): void {
93-
const password = e.target.value
91+
export function validatePassword(e: React.FocusEvent<HTMLInputElement>,
92+
t: (key: string) => string): void {
9493

94+
const password = e.target.value
9595
if (password.length < 8) {
9696
e.target.setCustomValidity('Password must be at least 8 characters long')
9797
e.target.reportValidity()

0 commit comments

Comments
 (0)