Skip to content

Commit e8700b0

Browse files
committed
landing page: search form
1 parent ed7ea29 commit e8700b0

File tree

13 files changed

+174
-35
lines changed

13 files changed

+174
-35
lines changed

.github/workflows/deploy.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ jobs:
4646
tags: ghcr.io/gitranks/gitranks-ui:latest
4747
build-args: |
4848
NEXT_PUBLIC_POSTHOG_KEY=${{ vars.NEXT_PUBLIC_POSTHOG_KEY }}
49+
NEXT_PUBLIC_URI=${{ vars.NEXT_PUBLIC_URI }}
4950
5051
# 4. SSH to droplet and deploy
5152
- name: SSH to droplet and deploy
@@ -71,9 +72,9 @@ jobs:
7172
-e AUTH_GITHUB_SECRET='${{ secrets.AUTH_GITHUB_SECRET }}' \
7273
-e GRAPHQL_SECRET_KEY='${{ secrets.GRAPHQL_SECRET_KEY }}' \
7374
-e AUTH_GITHUB_ID='${{ vars.AUTH_GITHUB_ID }}' \
74-
-e URI='${{ vars.URI }}' \
7575
-e AUTH_URL='${{ vars.AUTH_URL }}' \
7676
-e AUTH_TRUST_HOST=true \
77+
-e NEXT_PUBLIC_URI='${{ vars.NEXT_PUBLIC_URI }}' \
7778
-e NEXT_PUBLIC_POSTHOG_KEY='${{ vars.NEXT_PUBLIC_POSTHOG_KEY }}' \
7879
-p 3000:3000 \
7980
ghcr.io/gitranks/gitranks-ui:latest

app/components/search-profiile.tsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use client';
2+
3+
import { Search } from 'lucide-react';
4+
import { useRouter } from 'next/navigation';
5+
import { usePostHog } from 'posthog-js/react';
6+
import { useState } from 'react';
7+
import { ClipLoader } from 'react-spinners';
8+
import { toast } from 'sonner';
9+
10+
import { Button } from '@/components/ui/button';
11+
import { Input } from '@/components/ui/input';
12+
import { graphqlRequest } from '@/lib/graphql-request';
13+
import { IdByLoginDocument } from '@/types/generated/graphql';
14+
15+
export const SearchProfile = () => {
16+
const [login, setLogin] = useState('');
17+
const [loading, setLoading] = useState(false);
18+
const router = useRouter();
19+
const posthog = usePostHog();
20+
21+
const onSearch = async () => {
22+
if (!login) {
23+
return;
24+
}
25+
26+
setLoading(true);
27+
const data = await graphqlRequest(IdByLoginDocument, { login });
28+
29+
const profileFound = data.rankByLogin?.githubId;
30+
31+
posthog.capture('landingPage.search', {
32+
login,
33+
profileFound,
34+
});
35+
36+
if (profileFound) {
37+
return router.push(`/profile/${login}`);
38+
}
39+
40+
setLoading(false);
41+
toast.error('User not found', {
42+
description:
43+
'Our database currently includes only users who own or have contributed to repositories with more than 5 stars.',
44+
});
45+
};
46+
47+
return (
48+
<div className="flex flex-col gap-1">
49+
<form
50+
onSubmit={(event) => {
51+
event.preventDefault();
52+
onSearch();
53+
}}
54+
className="flex gap-4"
55+
>
56+
<Input
57+
placeholder="GitHub login"
58+
value={login}
59+
onChange={(event) => setLogin(event.target.value)}
60+
disabled={loading}
61+
/>
62+
<Button onClick={onSearch} disabled={loading} className="w-[96px]">
63+
{loading ? <ClipLoader loading={loading} size={16} /> : <Search className="size-4" />}
64+
{!loading && 'Search'}
65+
</Button>
66+
</form>
67+
</div>
68+
);
69+
};

app/layout.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import { NuqsAdapter } from 'nuqs/adapters/next/app';
55

66
import { Footer } from '@/components/footer/footer';
77
import { ThemeProvider } from '@/components/theme-provider/theme-provider';
8+
import { Toaster } from '@/components/ui/sonner';
89

910
import { PostHogProvider } from '../lib/posthog/post-hog-provider';
11+
1012
import './globals.css';
1113

1214
const inter = Inter({ subsets: ['latin'] });
@@ -33,6 +35,7 @@ export default function RootLayout({
3335
<div className="flex-grow">{children}</div>
3436
<Footer />
3537
</div>
38+
<Toaster richColors position="top-right" />
3639
</ThemeProvider>
3740
</NuqsAdapter>
3841
</PostHogProvider>

app/page.tsx

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
import { Search } from 'lucide-react';
2-
31
import { Header } from '@/components/header/header';
42
import { Link } from '@/components/link/link';
53
import { Page } from '@/components/page/page';
6-
import { Button } from '@/components/ui/button';
74
import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
8-
import { Input } from '@/components/ui/input';
9-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
105

6+
import { SearchProfile } from './components/search-profiile';
117
import MainImage from './main-image';
128

139
export default function Home() {
@@ -22,22 +18,7 @@ export default function Home() {
2218
Your GitHub Profile is More Impressive Than You Think
2319
</h1>
2420
<div>Just one repo with 5 stars puts you ahead of 95% of developers. See where you rank:</div>
25-
<div className="flex gap-4">
26-
<Input placeholder="GitHub login" />
27-
<TooltipProvider>
28-
<Tooltip>
29-
<TooltipTrigger asChild>
30-
<Button>
31-
<Search className="size-4" />
32-
Search
33-
</Button>
34-
</TooltipTrigger>
35-
<TooltipContent>
36-
<p>Coming soon</p>
37-
</TooltipContent>
38-
</Tooltip>
39-
</TooltipProvider>
40-
</div>
21+
<SearchProfile />
4122
</div>
4223
<div className="flex flex-grow items-center justify-center w-full md:w-auto min-w-xs">
4324
<MainImage />

app/profile/[login]/components/profile-card.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
import { FC } from 'react';
2+
13
import { Card, CardContent } from '@/components/ui/card';
24

3-
export const ProfileCard = ({ title, children }) => {
5+
type ProfileCardProps = {
6+
title: string;
7+
children: React.ReactNode;
8+
};
9+
10+
export const ProfileCard: FC<ProfileCardProps> = ({ title, children }) => {
411
return (
512
<Card className="border-border p-4 min-w-xs flex-grow basis-0">
613
<CardContent className="p-0 flex flex-col gap-4">
Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,34 @@
11
import { ComponentType, FC } from 'react';
22

3+
import { Link } from '@/components/link/link';
4+
35
type ProfileListItemProps = {
46
value?: string | number | null;
7+
url?: string;
58
Icon: ComponentType<{ size: number; className?: string }>;
69
};
710

8-
export const ProfileListItem: FC<ProfileListItemProps> = ({ value, Icon }) => {
11+
export const ProfileListItem: FC<ProfileListItemProps> = ({ value, url, Icon }) => {
912
if (!value) {
1013
return null;
1114
}
1215

16+
const getValue = () => {
17+
if (!url) {
18+
return value;
19+
}
20+
21+
return (
22+
<Link href={url} target="_blank" rel="noopener noreferrer">
23+
{value}
24+
</Link>
25+
);
26+
};
27+
1328
return (
1429
<div className="flex gap-2 items-center break-all">
1530
<Icon size={20} className="shrink-0" />
16-
{value}
31+
{getValue()}
1732
</div>
1833
);
1934
};

app/profile/[login]/page.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,14 @@ export default async function Profile({ params }: { params: Promise<{ login: str
9898
{showContact && (
9999
<div className="flex flex-col gap-1.5">
100100
<h4 className="text-lg font-semibold">Contacts</h4>
101-
<ProfileListItem value={user.email} Icon={Mail} />
102-
<ProfileListItem value={user.websiteUrl} Icon={Link2} />
101+
<ProfileListItem value={user.email} url={`mailto:${user.email}`} Icon={Mail} />
102+
<ProfileListItem value={user.websiteUrl} url={user.websiteUrl!} Icon={Link2} />
103103
{user.socialAccounts?.nodes?.map((account) => (
104104
<ProfileListItem
105105
key={account.displayName}
106106
value={account.displayName}
107-
Icon={getSocialIcon(account.provider)}
107+
url={account.url}
108+
Icon={getSocialIcon(account.provider, account.url)}
108109
/>
109110
))}
110111
</div>
Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { FaMastodon } from 'react-icons/fa';
2-
import { FaBluesky, FaXTwitter, FaInstagram, FaLinkedinIn, FaStackOverflow } from 'react-icons/fa6';
2+
import { FaBluesky, FaXTwitter, FaInstagram, FaLinkedinIn, FaStackOverflow, FaNpm } from 'react-icons/fa6';
33
import { IoShareSocialOutline } from 'react-icons/io5';
44

5-
export const getSocialIcon = (provider: string) => {
5+
export const getSocialIcon = (provider: string, url: string = '') => {
66
switch (provider) {
77
case 'TWITTER':
88
return FaXTwitter;
@@ -14,10 +14,17 @@ export const getSocialIcon = (provider: string) => {
1414
return FaInstagram;
1515
case 'LINKEDIN':
1616
return FaLinkedinIn;
17-
case 'STACKOVERFLOW':
18-
return FaStackOverflow;
1917
case 'GENERIC':
20-
default:
18+
default: {
19+
if (url.indexOf('npmjs.com') > -1) {
20+
return FaNpm;
21+
}
22+
23+
if (url.indexOf('stackoverflow.com') > -1) {
24+
return FaStackOverflow;
25+
}
26+
2127
return IoShareSocialOutline;
28+
}
2229
}
2330
};

components/ui/sonner.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"use client"
2+
3+
import { useTheme } from "next-themes"
4+
import { Toaster as Sonner, ToasterProps } from "sonner"
5+
6+
const Toaster = ({ ...props }: ToasterProps) => {
7+
const { theme = "system" } = useTheme()
8+
9+
return (
10+
<Sonner
11+
theme={theme as ToasterProps["theme"]}
12+
className="toaster group"
13+
style={
14+
{
15+
"--normal-bg": "var(--popover)",
16+
"--normal-text": "var(--popover-foreground)",
17+
"--normal-border": "var(--border)",
18+
} as React.CSSProperties
19+
}
20+
{...props}
21+
/>
22+
)
23+
}
24+
25+
export { Toaster }

lib/graphql-request.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export async function graphqlRequest<TData, TVariables>(
88
): Promise<TData> {
99
const query = print(document);
1010

11-
const res = await fetch(`${process.env.URI}/api/graphql`, {
11+
const res = await fetch(`${process.env.NEXT_PUBLIC_URI}/api/graphql`, {
1212
method: 'POST',
1313
headers: { 'Content-Type': 'application/json' },
1414
body: JSON.stringify({ query, variables }),

0 commit comments

Comments
 (0)