11"use client" ;
22
3- import { useState , useEffect } from "react" ;
3+ import { useState , useEffect , useCallback } from "react" ;
44import { SearchForm } from "@/components/search-form" ;
55import { Tabs } from "@/components/tabs" ;
66import { UserGrid } from "@/components/user-grid" ;
@@ -11,7 +11,7 @@ import { RateLimit } from "@/components/rate-limit";
1111import { useLocale } from "@/lib/locale-context" ;
1212import { getFollowData , type FollowData , type RateLimitInfo } from "@/lib/github" ;
1313
14- type TabId = "unfollowers" | "notFollowingBack" | "following" | "followers" ;
14+ type TabId = "unfollowers" | "notFollowingBack" | "mutuals" | " following" | "followers" ;
1515
1616export default function Home ( ) {
1717 const { t } = useLocale ( ) ;
@@ -24,20 +24,17 @@ export default function Home() {
2424 const [ rateLimit , setRateLimit ] = useState < RateLimitInfo | null > ( null ) ;
2525 const [ lastSearch , setLastSearch ] = useState < { user : string ; token ?: string } | null > ( null ) ;
2626
27- useEffect ( ( ) => {
28- if ( ! showPrivacy ) return ;
29- const handleKey = ( e : KeyboardEvent ) => {
30- if ( e . key === "Escape" ) setShowPrivacy ( false ) ;
31- } ;
32- document . addEventListener ( "keydown" , handleKey ) ;
33- return ( ) => document . removeEventListener ( "keydown" , handleKey ) ;
34- } , [ showPrivacy ] ) ;
35-
36- async function handleSearch ( user : string , token ?: string ) {
27+ const handleSearch = useCallback ( async ( user : string , token ?: string ) => {
3728 setIsLoading ( true ) ;
3829 setError ( null ) ;
3930 setData ( null ) ;
4031 setLastSearch ( { user, token } ) ;
32+
33+ // Update URL without reload
34+ const url = new URL ( window . location . href ) ;
35+ url . searchParams . set ( "user" , user ) ;
36+ window . history . pushState ( { } , "" , url . toString ( ) ) ;
37+
4138 try {
4239 const result = await getFollowData ( user , token ) ;
4340 setData ( result ) ;
@@ -50,7 +47,25 @@ export default function Home() {
5047 } finally {
5148 setIsLoading ( false ) ;
5249 }
53- }
50+ } , [ ] ) ;
51+
52+ // Read username from URL on mount
53+ useEffect ( ( ) => {
54+ const params = new URLSearchParams ( window . location . search ) ;
55+ const userFromUrl = params . get ( "user" ) ;
56+ if ( userFromUrl ) {
57+ handleSearch ( userFromUrl ) ;
58+ }
59+ } , [ handleSearch ] ) ;
60+
61+ useEffect ( ( ) => {
62+ if ( ! showPrivacy ) return ;
63+ const handleKey = ( e : KeyboardEvent ) => {
64+ if ( e . key === "Escape" ) setShowPrivacy ( false ) ;
65+ } ;
66+ document . addEventListener ( "keydown" , handleKey ) ;
67+ return ( ) => document . removeEventListener ( "keydown" , handleKey ) ;
68+ } , [ showPrivacy ] ) ;
5469
5570 function handleRetry ( ) {
5671 if ( lastSearch ) {
@@ -85,6 +100,19 @@ export default function Home() {
85100 </ svg >
86101 ) ,
87102 } ,
103+ {
104+ id : "mutuals" ,
105+ label : t . tabs . mutuals ,
106+ count : data . mutuals . length ,
107+ icon : (
108+ < svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" >
109+ < path d = "M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
110+ < circle cx = "8.5" cy = "7" r = "4" />
111+ < path d = "M17 14h6" />
112+ < path d = "M20 11v6" />
113+ </ svg >
114+ ) ,
115+ } ,
88116 {
89117 id : "following" ,
90118 label : t . tabs . following ,
@@ -118,6 +146,7 @@ export default function Home() {
118146 const emptyMessages : Record < TabId , string > = {
119147 unfollowers : t . empty . unfollowers ,
120148 notFollowingBack : t . empty . notFollowingBack ,
149+ mutuals : t . empty . mutuals ,
121150 following : t . empty . following ,
122151 followers : t . empty . followers ,
123152 } ;
@@ -234,6 +263,7 @@ export default function Home() {
234263 following = { data . following . length }
235264 unfollowers = { data . unfollowers . length }
236265 notFollowingBack = { data . notFollowingBack . length }
266+ mutuals = { data . mutuals . length }
237267 />
238268
239269 { rateLimit && < RateLimit rateLimit = { rateLimit } /> }
0 commit comments