forked from dubinc/dub
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Massive PR – built out most of the app, changed API route structure, …
…added favicons, etc. Still got lots to do though
- Loading branch information
1 parent
8f42e92
commit fc537e5
Showing
65 changed files
with
1,482 additions
and
163 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import BlurImage from "@/components/shared/blur-image"; | ||
import CopyButton from "@/components/shared/copy-button"; | ||
import { LoadingDots } from "@/components/shared/icons"; | ||
import { useRouter } from "next/router"; | ||
import useSWR from "swr"; | ||
import { fetcher, nFormatter, linkConstructor } from "@/lib/utils"; | ||
import Link from "next/link"; | ||
|
||
export default function LinkCard({ | ||
_key: key, | ||
url, | ||
}: { | ||
_key: string; | ||
url: string; | ||
}) { | ||
const urlHostname = new URL(url).hostname; | ||
|
||
const router = useRouter(); | ||
const { slug } = router.query as { | ||
slug: string; | ||
}; | ||
|
||
const { data: clicks, isValidating } = useSWR<string>( | ||
`/api/projects/${slug}/links/${key}/clicks`, | ||
fetcher | ||
); | ||
|
||
return ( | ||
<div className="flex items-center border border-gray-200 dark:border-gray-600 hover:border-black dark:hover:border-white p-3 max-w-md rounded-md transition-all"> | ||
<BlurImage | ||
src={`https://logo.clearbit.com/${urlHostname}`} | ||
alt={urlHostname} | ||
className="w-10 h-10 rounded-full mr-2 border border-gray-200 dark:border-gray-600" | ||
width={20} | ||
height={20} | ||
/> | ||
<div> | ||
<div className="flex items-center space-x-2 mb-1"> | ||
<a | ||
className="text-blue-800 dark:text-blue-400 font-semibold" | ||
href={linkConstructor(key)} | ||
target="_blank" | ||
rel="noreferrer" | ||
> | ||
{linkConstructor(key, true)} | ||
</a> | ||
<CopyButton url={linkConstructor(key)} /> | ||
<Link href={`${router.asPath}/${encodeURI(key)}`}> | ||
<a className="rounded-md bg-gray-100 dark:bg-gray-800 px-2 py-0.5 hover:scale-105 active:scale-95 transition-all duration-75"> | ||
<p className="text-sm text-gray-500 dark:text-white"> | ||
{isValidating || !clicks ? ( | ||
<LoadingDots color="#71717A" /> | ||
) : ( | ||
nFormatter(parseInt(clicks)) | ||
)}{" "} | ||
clicks | ||
</p> | ||
</a> | ||
</Link> | ||
</div> | ||
<p className="text-sm text-gray-500 dark:text-gray-400 truncate w-72"> | ||
{url} | ||
</p> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,49 @@ | ||
import { ReactNode } from "react"; | ||
import dynamic from "next/dynamic"; | ||
import Link from "next/link"; | ||
import Meta from "../meta"; | ||
import { useSession, signOut } from "next-auth/react"; | ||
import { Logo, Divider } from "@/components/shared/icons"; | ||
import ListBox from "./list-box"; | ||
|
||
const NavTabs = dynamic(() => import("./nav-tabs"), { | ||
ssr: false, | ||
loading: () => <div className="w-full h-12 -mb-0.5" />, | ||
}); // dynamic import to avoid react hydration mismatch error | ||
|
||
export default function AppLayout({ children }: { children: ReactNode }) { | ||
const { data: session } = useSession(); | ||
|
||
return ( | ||
<div> | ||
<Meta /> | ||
{children} | ||
<div className="min-h-screen w-full bg-gray-50"> | ||
<div className="sticky top-0 left-0 right-0 border-b bg-white border-gray-200 z-10"> | ||
<div className="max-w-screen-xl mx-auto px-5 sm:px-20"> | ||
<div className="h-10 flex justify-between items-center my-3"> | ||
<div className="flex items-center"> | ||
<Link href="/"> | ||
<a> | ||
<Logo className="w-8 h-8 active:scale-95 transition-all duration-75" /> | ||
</a> | ||
</Link> | ||
<Divider className="h-8 w-8 ml-3 text-gray-200" /> | ||
<ListBox /> | ||
</div> | ||
<button className="rounded-full overflow-hidden border border-gray-300 w-10 h-10 flex justify-center items-center active:scale-95 focus:outline-none transition-all duration-75"> | ||
{session && ( | ||
<img | ||
alt={session?.user?.email || "Avatar for logged in user"} | ||
src={`https://avatars.dicebear.com/api/micah/${session?.user?.email}.svg`} | ||
/> | ||
)} | ||
</button> | ||
</div> | ||
<NavTabs /> | ||
</div> | ||
</div> | ||
<div className="max-w-screen-xl mx-auto px-5 sm:px-20">{children}</div> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { Fragment, useMemo } from "react"; | ||
import { Listbox, Transition } from "@headlessui/react"; | ||
import { Tick, ChevronUpDown } from "@/components/shared/icons"; | ||
import { useRouter } from "next/router"; | ||
import BlurImage from "@/components/shared/blur-image"; | ||
import useSWR from "swr"; | ||
import { fetcher } from "@/lib/utils"; | ||
import { ProjectProps } from "@/lib/api/types"; | ||
|
||
export default function ListBox() { | ||
const { data: projects } = useSWR<ProjectProps[]>("/api/projects", fetcher); | ||
|
||
const router = useRouter(); | ||
const selected = useMemo(() => { | ||
const { teamSlug } = router.query; | ||
return ( | ||
projects?.find(({ slug }) => slug === teamSlug) || { | ||
name: "Dub.sh", | ||
slug: "dub", | ||
} | ||
); | ||
}, [router, projects]); | ||
|
||
if (!projects) | ||
return ( | ||
<div className="w-52 h-9 px-2 rounded-lg bg-gray-100 animate-pulse flex justify-end items-center"> | ||
<ChevronUpDown className="h-4 w-4 text-gray-400" aria-hidden="true" /> | ||
</div> | ||
); | ||
|
||
return ( | ||
<div className="w-52 -mt-1"> | ||
<Listbox | ||
value={selected} | ||
onChange={(e) => { | ||
router.push(`/${e.slug}`); | ||
}} | ||
> | ||
<div className="relative mt-1"> | ||
<Listbox.Button className="relative w-full rounded-lg bg-white hover:bg-gray-100 py-1.5 pl-3 pr-10 text-left focus:outline-none text-sm active:scale-95 transition-all duration-75"> | ||
<div className="flex justify-start items-center space-x-3"> | ||
<BlurImage | ||
src={`https://avatar.tobi.sh/${selected.slug}`} | ||
alt={selected.name} | ||
className="w-8 h-8 flex-shrink-0 rounded-full overflow-hidden border border-gray-300" | ||
width={48} | ||
height={48} | ||
/> | ||
<span className="block truncate font-medium"> | ||
{selected.name} | ||
</span> | ||
</div> | ||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> | ||
<ChevronUpDown | ||
className="h-4 w-4 text-gray-400" | ||
aria-hidden="true" | ||
/> | ||
</span> | ||
</Listbox.Button> | ||
<Transition | ||
as={Fragment} | ||
leave="transition ease-in duration-100" | ||
leaveFrom="opacity-100" | ||
leaveTo="opacity-0" | ||
> | ||
<Listbox.Options className="absolute mt-1 max-h-60 w-60 overflow-auto rounded-md bg-white p-2 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"> | ||
{projects.map(({ name, slug }) => ( | ||
<Listbox.Option | ||
key={slug} | ||
className={`relative flex items-center space-x-2 cursor-pointer p-2 rounded-md hover:bg-gray-100 active:scale-95 ${ | ||
selected.slug === slug ? "font-medium" : "" | ||
} transition-all duration-75`} | ||
value={{ name, slug }} | ||
> | ||
<BlurImage | ||
src={`https://avatar.tobi.sh/${slug}`} | ||
alt={name} | ||
className="w-7 h-7 flex-shrink-0 rounded-full overflow-hidden border border-gray-300" | ||
width={48} | ||
height={48} | ||
/> | ||
<span | ||
className={`block truncate ${ | ||
selected.slug === slug ? "font-medium" : "font-normal" | ||
}`} | ||
> | ||
{name} | ||
</span> | ||
{selected.slug === slug ? ( | ||
<span className="absolute inset-y-0 right-0 flex items-center pr-3 text-black"> | ||
<Tick className="h-5 w-5" aria-hidden="true" /> | ||
</span> | ||
) : null} | ||
</Listbox.Option> | ||
))} | ||
</Listbox.Options> | ||
</Transition> | ||
</div> | ||
</Listbox> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { useMemo } from "react"; | ||
import Link from "next/link"; | ||
import { NextRouter, useRouter } from "next/router"; | ||
|
||
const TabsHelper = (router: NextRouter): { name: string; href: string }[] => { | ||
const { slug } = router.query; | ||
if (slug) { | ||
return [{ name: "Overview", href: `/${slug}` }]; | ||
} | ||
return [{ name: "Overview", href: `/` }]; | ||
}; | ||
|
||
export default function NavTabs() { | ||
const router = useRouter(); | ||
const tabs = useMemo(() => { | ||
return TabsHelper(router); | ||
}, [router.query]); | ||
|
||
return ( | ||
<div className="flex justify-start space-x-8 items-center h-12 -mb-0.5"> | ||
{tabs.map(({ name, href }) => ( | ||
<Link key={href} href={href}> | ||
<a | ||
className={`px-1 py-3 border-b-2 ${ | ||
router.asPath === href | ||
? "border-black font-semibold" | ||
: "border-transparent text-gray-700 hover:text-black" | ||
} transition-all`} | ||
> | ||
<p className="text-sm active:scale-95 transition-all duration-75"> | ||
{name} | ||
</p> | ||
</a> | ||
</Link> | ||
))} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
export default function ChevronDown({ className }: { className?: string }) { | ||
return ( | ||
<svg | ||
className={className} | ||
viewBox="0 0 24 24" | ||
width="24" | ||
height="24" | ||
stroke="currentColor" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
fill="none" | ||
shapeRendering="geometricPrecision" | ||
> | ||
<path d="M6 9l6 6 6-6" /> | ||
</svg> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
export default function ChevronLeft({ className }: { className?: string }) { | ||
return ( | ||
<svg | ||
className={className} | ||
viewBox="0 0 24 24" | ||
width="24" | ||
height="24" | ||
stroke="currentColor" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
fill="none" | ||
shapeRendering="geometricPrecision" | ||
> | ||
<path d="M15 18l-6-6 6-6" /> | ||
</svg> | ||
); | ||
} |
Oops, something went wrong.