parent
17b4b05368
commit
5ef8a3f82a
12 changed files with 235 additions and 378 deletions
@ -0,0 +1,42 @@ |
|||||||
|
import { useCopyText } from '../../hooks/use-copy-text'; |
||||||
|
import CopyIcon from '../../icons/copy.svg'; |
||||||
|
|
||||||
|
type EditorProps = { |
||||||
|
title: string; |
||||||
|
text: string; |
||||||
|
}; |
||||||
|
|
||||||
|
export function Editor(props: EditorProps) { |
||||||
|
const { text, title } = props; |
||||||
|
|
||||||
|
const { isCopied, copyText } = useCopyText(); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="flex w-full flex-grow flex-col overflow-hidden rounded border border-gray-300 bg-gray-50"> |
||||||
|
<div className="flex items-center justify-between gap-2 border-b border-gray-300 px-3 py-2"> |
||||||
|
<span className="text-xs uppercase leading-none text-gray-400"> |
||||||
|
{title} |
||||||
|
</span> |
||||||
|
<button className="flex items-center" onClick={() => copyText(text)}> |
||||||
|
{isCopied && ( |
||||||
|
<span className="mr-1 text-xs leading-none text-gray-700"> |
||||||
|
Copied! |
||||||
|
</span> |
||||||
|
)} |
||||||
|
|
||||||
|
<img src={CopyIcon} alt="Copy" className="inline-block h-4 w-4" /> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
<textarea |
||||||
|
className="no-scrollbar block h-12 w-full overflow-x-auto whitespace-nowrap bg-gray-200/70 p-3 text-sm text-gray-900 focus:bg-gray-50 focus:outline-0" |
||||||
|
readOnly |
||||||
|
onClick={(e: any) => { |
||||||
|
e.target.select(); |
||||||
|
copyText(e.target.value); |
||||||
|
}} |
||||||
|
> |
||||||
|
{text} |
||||||
|
</textarea> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
@ -1,6 +1,6 @@ |
|||||||
export function GithubReadmeBanner() { |
export function GitHubReadmeBanner() { |
||||||
return ( |
return ( |
||||||
<p className="mt-3 rounded-md border p-2 text-sm"> |
<p className="mt-3 rounded-md border p-2 text-sm w-full bg-yellow-100 border-yellow-400 text-yellow-900"> |
||||||
Add this badge to your{' '} |
Add this badge to your{' '} |
||||||
<a |
<a |
||||||
href="https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/managing-your-profile-readme" |
href="https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/managing-your-profile-readme" |
@ -0,0 +1,23 @@ |
|||||||
|
type SelectionButtonProps = { |
||||||
|
text: string; |
||||||
|
isDisabled: boolean; |
||||||
|
isSelected: boolean; |
||||||
|
onClick: () => void; |
||||||
|
}; |
||||||
|
|
||||||
|
export function SelectionButton(props: SelectionButtonProps) { |
||||||
|
const { text, isDisabled, isSelected, onClick } = props; |
||||||
|
|
||||||
|
return ( |
||||||
|
<button |
||||||
|
className={`rounded-md border p-1 px-2 text-sm ${ |
||||||
|
isSelected ? ' border-gray-500 bg-gray-300 ' : '' |
||||||
|
} ${ |
||||||
|
!isDisabled ? ' cursor-pointer ' : ' cursor-not-allowed opacity-40 ' |
||||||
|
}`}
|
||||||
|
onClick={onClick} |
||||||
|
> |
||||||
|
{text} |
||||||
|
</button> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
type StepCounterProps = { |
||||||
|
step: number; |
||||||
|
}; |
||||||
|
|
||||||
|
export function StepCounter(props: StepCounterProps) { |
||||||
|
const { step } = props; |
||||||
|
|
||||||
|
return ( |
||||||
|
<span |
||||||
|
className={ |
||||||
|
'flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-gray-300 text-white' |
||||||
|
} |
||||||
|
> |
||||||
|
{step} |
||||||
|
</span> |
||||||
|
); |
||||||
|
} |
@ -1,41 +0,0 @@ |
|||||||
import { downloadImage } from '../../helper/download-image'; |
|
||||||
import { useCopyText } from '../../hooks/use-copy-text'; |
|
||||||
import type { BadgeProps } from './RoadCardPage'; |
|
||||||
|
|
||||||
export function LongBadge({ badgeUrl }: BadgeProps) { |
|
||||||
const { isCopied, copyText } = useCopyText(); |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className="col-span-2"> |
|
||||||
<a |
|
||||||
href={badgeUrl} |
|
||||||
target="_blank" |
|
||||||
rel="noopener noreferrer" |
|
||||||
className="relative block aspect-[422/551] w-full hover:cursor-pointer" |
|
||||||
> |
|
||||||
<img |
|
||||||
src={badgeUrl} |
|
||||||
alt="Road Card" |
|
||||||
className="absolute left-0 top-0 h-full w-full object-cover" |
|
||||||
/> |
|
||||||
</a> |
|
||||||
|
|
||||||
<div className="mt-3 grid grid-cols-2 gap-2"> |
|
||||||
<button |
|
||||||
className="flex h-8 items-center justify-center whitespace-nowrap rounded border border-gray-300 bg-gray-50 px-2 text-sm font-medium leading-none hover:opacity-75" |
|
||||||
onClick={() => |
|
||||||
downloadImage({ url: badgeUrl, name: 'road-card', scale: 4 }) |
|
||||||
} |
|
||||||
> |
|
||||||
Download |
|
||||||
</button> |
|
||||||
<button |
|
||||||
className="flex h-8 cursor-pointer items-center justify-center whitespace-nowrap rounded border border-gray-300 bg-gray-50 px-2 text-sm font-medium leading-none hover:opacity-75" |
|
||||||
onClick={() => copyText(badgeUrl)} |
|
||||||
> |
|
||||||
{isCopied ? 'Copied!' : 'Copy Link'} |
|
||||||
</button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
@ -1,90 +0,0 @@ |
|||||||
import { useState } from 'preact/hooks'; |
|
||||||
import { LongBadge } from './TallBadge'; |
|
||||||
import { Editor } from './RoadCardPage'; |
|
||||||
import { GithubReadmeBanner } from './GithubReadmeBanner'; |
|
||||||
import { useAuth } from '../../hooks/use-auth'; |
|
||||||
import { getBadgeLink } from '../../helper/get-badge-link'; |
|
||||||
import type { RoadmapOptionProps } from './RoadmapSelect'; |
|
||||||
|
|
||||||
export function TallBadgeTab({ |
|
||||||
selectedRoadmaps, |
|
||||||
}: { |
|
||||||
selectedRoadmaps: RoadmapOptionProps[]; |
|
||||||
}) { |
|
||||||
const [selectedVariant, setSelectedVariant] = useState<'dark' | 'light'>( |
|
||||||
'dark' |
|
||||||
); |
|
||||||
const user = useAuth(); |
|
||||||
|
|
||||||
if (!user) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
const { badgeUrl, textareaContent, markdownSnippet } = getBadgeLink({ |
|
||||||
user, |
|
||||||
variant: selectedVariant, |
|
||||||
badge: 'tall', |
|
||||||
roadmaps: selectedRoadmaps, |
|
||||||
}); |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className="sm:grid sm:grid-cols-5 sm:gap-6"> |
|
||||||
<div className="block sm:hidden mb-6"> |
|
||||||
<span className="text-xs uppercase leading-none text-gray-400"> |
|
||||||
Variant |
|
||||||
</span> |
|
||||||
|
|
||||||
<div className="mt-2 flex items-center gap-2"> |
|
||||||
<button |
|
||||||
className={`flex h-7 items-center justify-center rounded-lg border border-gray-200 px-3 text-sm leading-none hover:opacity-80 ${selectedVariant === 'dark' && 'border-gray-300 bg-gray-100' |
|
||||||
}`}
|
|
||||||
onClick={() => setSelectedVariant('dark')} |
|
||||||
> |
|
||||||
Dark |
|
||||||
</button> |
|
||||||
|
|
||||||
<button |
|
||||||
className={`flex h-7 items-center justify-center rounded-lg border border-gray-200 px-3 text-sm leading-none hover:opacity-80 ${selectedVariant === 'light' && 'border-gray-300 bg-gray-100' |
|
||||||
}`}
|
|
||||||
onClick={() => setSelectedVariant('light')} |
|
||||||
> |
|
||||||
Light |
|
||||||
</button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<LongBadge badgeUrl={badgeUrl} /> |
|
||||||
|
|
||||||
<div className="mt-6 sm:col-span-3 sm:mt-0"> |
|
||||||
<div className="hidden sm:block"> |
|
||||||
<span className="text-xs uppercase leading-none text-gray-400"> |
|
||||||
Variant |
|
||||||
</span> |
|
||||||
|
|
||||||
<div className="mt-2 flex items-center gap-2"> |
|
||||||
<button |
|
||||||
className={`flex h-7 items-center justify-center rounded-lg border border-gray-200 px-3 text-sm leading-none hover:opacity-80 ${selectedVariant === 'dark' && 'border-gray-300 bg-gray-100' |
|
||||||
}`}
|
|
||||||
onClick={() => setSelectedVariant('dark')} |
|
||||||
> |
|
||||||
Dark |
|
||||||
</button> |
|
||||||
|
|
||||||
<button |
|
||||||
className={`flex h-7 items-center justify-center rounded-lg border border-gray-200 px-3 text-sm leading-none hover:opacity-80 ${selectedVariant === 'light' && 'border-gray-300 bg-gray-100' |
|
||||||
}`}
|
|
||||||
onClick={() => setSelectedVariant('light')} |
|
||||||
> |
|
||||||
Light |
|
||||||
</button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div className="sm:mt-4 flex flex-col gap-3"> |
|
||||||
<Editor title={'HTML'} text={textareaContent} /> |
|
||||||
<Editor title={'Markdown'} text={markdownSnippet} /> |
|
||||||
</div> |
|
||||||
|
|
||||||
<GithubReadmeBanner /> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
@ -1,40 +0,0 @@ |
|||||||
import { downloadImage } from '../../helper/download-image'; |
|
||||||
import { useCopyText } from '../../hooks/use-copy-text'; |
|
||||||
import type { BadgeProps } from './RoadCardPage'; |
|
||||||
|
|
||||||
export function WideBadge({ badgeUrl }: BadgeProps) { |
|
||||||
const { isCopied, copyText } = useCopyText(); |
|
||||||
return ( |
|
||||||
<div> |
|
||||||
<a |
|
||||||
href={badgeUrl} |
|
||||||
target="_blank" |
|
||||||
rel="noopener noreferrer" |
|
||||||
className="relative block aspect-[2.63/1] w-full hover:cursor-pointer" |
|
||||||
> |
|
||||||
<img |
|
||||||
src={badgeUrl} |
|
||||||
alt="Road Card" |
|
||||||
className="absolute left-0 top-0 h-full w-full object-cover" |
|
||||||
/> |
|
||||||
</a> |
|
||||||
|
|
||||||
<div className="mt-3 grid grid-cols-2 gap-4"> |
|
||||||
<button |
|
||||||
className="flex h-8 items-center justify-center whitespace-nowrap rounded border border-gray-300 bg-gray-50 px-2 text-sm font-medium leading-none hover:opacity-75" |
|
||||||
onClick={() => |
|
||||||
downloadImage({ url: badgeUrl, name: 'road-card', scale: 4 }) |
|
||||||
} |
|
||||||
> |
|
||||||
Download |
|
||||||
</button> |
|
||||||
<button |
|
||||||
className="flex h-8 cursor-pointer items-center justify-center whitespace-nowrap rounded border border-gray-300 bg-gray-50 px-2 text-sm font-medium leading-none hover:opacity-75" |
|
||||||
onClick={() => copyText(badgeUrl)} |
|
||||||
> |
|
||||||
{isCopied ? 'Copied!' : 'Copy Link'} |
|
||||||
</button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
@ -1,67 +0,0 @@ |
|||||||
import { useState } from 'preact/hooks'; |
|
||||||
import { useAuth } from '../../hooks/use-auth'; |
|
||||||
import { WideBadge } from './WideBadge'; |
|
||||||
import { Editor } from './RoadCardPage'; |
|
||||||
import { GithubReadmeBanner } from './GithubReadmeBanner'; |
|
||||||
import { getBadgeLink } from '../../helper/get-badge-link'; |
|
||||||
import type { RoadmapOptionProps } from './RoadmapSelect'; |
|
||||||
|
|
||||||
export function WideBadgeTab({ |
|
||||||
selectedRoadmaps, |
|
||||||
}: { |
|
||||||
selectedRoadmaps: RoadmapOptionProps[]; |
|
||||||
}) { |
|
||||||
const [selectedVariant, setSelectedVariant] = useState<'dark' | 'light'>( |
|
||||||
'dark' |
|
||||||
); |
|
||||||
const user = useAuth(); |
|
||||||
if (!user) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
const { badgeUrl, textareaContent, markdownSnippet } = getBadgeLink({ |
|
||||||
user, |
|
||||||
variant: selectedVariant, |
|
||||||
badge: 'wide', |
|
||||||
roadmaps: selectedRoadmaps, |
|
||||||
}); |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className="flex flex-col gap-6"> |
|
||||||
<div> |
|
||||||
<span className="text-xs uppercase leading-none text-gray-400"> |
|
||||||
Variant |
|
||||||
</span> |
|
||||||
|
|
||||||
<div className="mt-2 flex items-center gap-2"> |
|
||||||
<button |
|
||||||
className={`flex h-7 items-center justify-center rounded-lg border border-gray-200 px-3 text-sm leading-none hover:opacity-80 ${selectedVariant === 'dark' && 'border-gray-300 bg-gray-100' |
|
||||||
}`}
|
|
||||||
onClick={() => setSelectedVariant('dark')} |
|
||||||
> |
|
||||||
Dark |
|
||||||
</button> |
|
||||||
|
|
||||||
<button |
|
||||||
className={`flex h-7 items-center justify-center rounded-lg border border-gray-200 px-3 text-sm leading-none hover:opacity-80 ${selectedVariant === 'light' && 'border-gray-300 bg-gray-100' |
|
||||||
}`}
|
|
||||||
onClick={() => setSelectedVariant('light')} |
|
||||||
> |
|
||||||
Light |
|
||||||
</button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<WideBadge badgeUrl={badgeUrl} /> |
|
||||||
|
|
||||||
<div> |
|
||||||
<div className={`flex flex-col gap-3 sm:flex-row`}> |
|
||||||
<Editor title={'HTML'} text={textareaContent} /> |
|
||||||
<Editor title={'Markdown'} text={markdownSnippet} /> |
|
||||||
</div> |
|
||||||
|
|
||||||
<GithubReadmeBanner /> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
@ -1,43 +0,0 @@ |
|||||||
import type { useAuth } from '../hooks/use-auth'; |
|
||||||
|
|
||||||
export type GetBadgeLinkProps = { |
|
||||||
user: ReturnType<typeof useAuth>; |
|
||||||
variant: 'dark' | 'light'; |
|
||||||
badge: 'tall' | 'wide'; |
|
||||||
roadmaps?: string[]; |
|
||||||
}; |
|
||||||
|
|
||||||
export function getBadgeLink({ |
|
||||||
user, |
|
||||||
variant, |
|
||||||
badge, |
|
||||||
roadmaps, |
|
||||||
}: GetBadgeLinkProps) { |
|
||||||
const badgeUrl = new URL( |
|
||||||
`${import.meta.env.PUBLIC_API_URL}/v1-badge/${badge}/${user?.id}` |
|
||||||
); |
|
||||||
if (variant) { |
|
||||||
badgeUrl.searchParams.set('variant', variant); |
|
||||||
} |
|
||||||
|
|
||||||
if (roadmaps?.length) { |
|
||||||
badgeUrl.searchParams.set('roadmaps', roadmaps.join(',')); |
|
||||||
} |
|
||||||
|
|
||||||
const textareaContent = ` |
|
||||||
<a href="${badgeUrl}"> |
|
||||||
<img src="${badgeUrl}" alt="${user?.name}${user?.name && "'s"} Road Card"/> |
|
||||||
</a> |
|
||||||
`.trim();
|
|
||||||
const markdownSnippet = ` |
|
||||||
[![${user?.name}${ |
|
||||||
user?.name && "'s" |
|
||||||
} Road Card](${badgeUrl})](${badgeUrl}) |
|
||||||
`.trim();
|
|
||||||
|
|
||||||
return { |
|
||||||
badgeUrl: badgeUrl.toString(), |
|
||||||
textareaContent, |
|
||||||
markdownSnippet, |
|
||||||
}; |
|
||||||
} |
|
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 331 B |
Loading…
Reference in new issue