mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-09-19 09:40:13 +00:00
Add simple-icons support for OAuth providers
This commit is contained in:
Generated
+20
@@ -50,6 +50,7 @@
|
|||||||
"react-i18next": "^17.0.8",
|
"react-i18next": "^17.0.8",
|
||||||
"react-router-dom": "^7.18.0",
|
"react-router-dom": "^7.18.0",
|
||||||
"react-virtuoso": "^4.18.7",
|
"react-virtuoso": "^4.18.7",
|
||||||
|
"simple-icons": "^16.24.1",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"swr": "^2.4.1",
|
"swr": "^2.4.1",
|
||||||
"tailwind-merge": "^3.6.0",
|
"tailwind-merge": "^3.6.0",
|
||||||
@@ -5940,6 +5941,25 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/simple-icons": {
|
||||||
|
"version": "16.24.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-16.24.1.tgz",
|
||||||
|
"integrity": "sha512-AnDQPrZAVzYSym7cBVIrnbhLk9auWGgkl+9hKvkbTqGEfH6TU7WKgumEXYYaJuM1Ib87+cnzr881UZniCM7t+A==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/simple-icons"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/simple-icons"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sonner": {
|
"node_modules/sonner": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
"react-i18next": "^17.0.8",
|
"react-i18next": "^17.0.8",
|
||||||
"react-router-dom": "^7.18.0",
|
"react-router-dom": "^7.18.0",
|
||||||
"react-virtuoso": "^4.18.7",
|
"react-virtuoso": "^4.18.7",
|
||||||
|
"simple-icons": "^16.24.1",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"swr": "^2.4.1",
|
"swr": "^2.4.1",
|
||||||
"tailwind-merge": "^3.6.0",
|
"tailwind-merge": "^3.6.0",
|
||||||
|
|||||||
@@ -1,7 +1,97 @@
|
|||||||
export function GitHubIcon(props: React.ComponentPropsWithoutRef<"svg">) {
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
|
type SVGProps = React.ComponentPropsWithoutRef<"svg">
|
||||||
|
|
||||||
|
const simpleIconModules = import.meta.glob("/node_modules/simple-icons/icons/*.svg", {
|
||||||
|
query: "?raw",
|
||||||
|
import: "default",
|
||||||
|
})
|
||||||
|
|
||||||
|
const iconLoadersBySlug = Object.fromEntries(
|
||||||
|
Object.entries(simpleIconModules).map(([path, loader]) => [
|
||||||
|
path.slice(path.lastIndexOf("/") + 1, -".svg".length),
|
||||||
|
loader as () => Promise<string>,
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
const iconMarkupCache = new Map<string, string | null>()
|
||||||
|
|
||||||
|
function toProviderSlug(provider: string) {
|
||||||
|
return provider.trim().replace(/[^a-z0-9]+/gi, "").toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadProviderIconMarkup(provider: string) {
|
||||||
|
const loader = iconLoadersBySlug[toProviderSlug(provider)]
|
||||||
|
return loader ? loader() : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSvgViewBox(markup: string) {
|
||||||
|
return markup.match(/viewBox="([^"]+)"/i)?.[1] ?? "0 0 24 24"
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSvgInnerMarkup(markup: string) {
|
||||||
|
return markup
|
||||||
|
.replace(/^<svg[^>]*>/i, "")
|
||||||
|
.replace(/<\/svg>\s*$/i, "")
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OAuthProviderIcon({
|
||||||
|
provider,
|
||||||
|
title,
|
||||||
|
...props
|
||||||
|
}: SVGProps & { provider: string; title?: string }) {
|
||||||
|
const providerSlug = toProviderSlug(provider)
|
||||||
|
const [iconMarkup, setIconMarkup] = useState<string | null>(() => {
|
||||||
|
return iconMarkupCache.get(providerSlug) ?? null
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const cached = iconMarkupCache.get(providerSlug)
|
||||||
|
if (cached !== undefined) {
|
||||||
|
setIconMarkup(cached)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadPromise = loadProviderIconMarkup(provider)
|
||||||
|
if (!loadPromise) {
|
||||||
|
iconMarkupCache.set(providerSlug, null)
|
||||||
|
setIconMarkup(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancelled = false
|
||||||
|
loadPromise
|
||||||
|
.then((markup) => {
|
||||||
|
if (cancelled) return
|
||||||
|
iconMarkupCache.set(providerSlug, markup)
|
||||||
|
setIconMarkup(markup)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (cancelled) return
|
||||||
|
iconMarkupCache.set(providerSlug, null)
|
||||||
|
setIconMarkup(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [provider, providerSlug])
|
||||||
|
|
||||||
|
if (!iconMarkup) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg viewBox="0 0 496 512" fill="currentColor" {...props}>
|
<svg
|
||||||
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
|
viewBox={getSvgViewBox(iconMarkup)}
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden={title ? undefined : "true"}
|
||||||
|
role={title ? "img" : undefined}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{title ? <title>{title}</title> : null}
|
||||||
|
<g dangerouslySetInnerHTML={{ __html: getSvgInnerMarkup(iconMarkup) }} />
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form"
|
} from "@/components/ui/form"
|
||||||
import { GitHubIcon } from "@/components/ui/icon"
|
import { OAuthProviderIcon } from "@/components/ui/icon"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { useAuth } from "@/hooks/useAuth"
|
import { useAuth } from "@/hooks/useAuth"
|
||||||
@@ -120,10 +120,11 @@ function Login() {
|
|||||||
<div className="mt-3 flex flex-col gap-3">
|
<div className="mt-3 flex flex-col gap-3">
|
||||||
{settingData?.config?.oauth2_providers?.map((p: string) => (
|
{settingData?.config?.oauth2_providers?.map((p: string) => (
|
||||||
<Button
|
<Button
|
||||||
|
key={p}
|
||||||
className="w-full rounded-lg shadow-[inset_0_1px_0_rgba(255,255,255,0.2)] bg-muted text-primary hover:bg-muted/80 hover:text-primary/80"
|
className="w-full rounded-lg shadow-[inset_0_1px_0_rgba(255,255,255,0.2)] bg-muted text-primary hover:bg-muted/80 hover:text-primary/80"
|
||||||
onClick={() => loginWith(p)}
|
onClick={() => loginWith(p)}
|
||||||
>
|
>
|
||||||
{p === "GitHub" && <GitHubIcon className="size-4" />}
|
<OAuthProviderIcon provider={p} className="size-4" />
|
||||||
{p}
|
{p}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ProfileCard } from "@/components/profile"
|
|||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { OAuthProviderIcon } from "@/components/ui/icon"
|
||||||
import { useMainStore } from "@/hooks/useMainStore"
|
import { useMainStore } from "@/hooks/useMainStore"
|
||||||
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
||||||
import { useServer } from "@/hooks/useServer"
|
import { useServer } from "@/hooks/useServer"
|
||||||
@@ -107,8 +108,15 @@ export default function ProfilePage() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="text-lg font-semibold">
|
<CardContent className="text-lg font-semibold">
|
||||||
{settingData?.config?.oauth2_providers?.map((provider) => (
|
{settingData?.config?.oauth2_providers?.map((provider) => (
|
||||||
<div className="flex justify-between items-center flex-wrap gap-2">
|
<div
|
||||||
|
key={provider}
|
||||||
|
className="flex justify-between items-center flex-wrap gap-2"
|
||||||
|
>
|
||||||
<section className="flex gap-2 items-center">
|
<section className="flex gap-2 items-center">
|
||||||
|
<OAuthProviderIcon
|
||||||
|
provider={provider}
|
||||||
|
className="size-4 text-muted-foreground"
|
||||||
|
/>
|
||||||
<p>{provider}: </p>
|
<p>{provider}: </p>
|
||||||
{profile.oauth2_bind?.[provider.toLowerCase()] && (
|
{profile.oauth2_bind?.[provider.toLowerCase()] && (
|
||||||
<p className=" bg-muted px-1.5 py-0.5 text-sm rounded-full">
|
<p className=" bg-muted px-1.5 py-0.5 text-sm rounded-full">
|
||||||
|
|||||||
Reference in New Issue
Block a user