mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-02-04 04:30:06 +00:00
feat: oauth2 登录
This commit is contained in:
@@ -6,7 +6,7 @@ interface CommonResponse<T> {
|
|||||||
|
|
||||||
function buildUrl(path: string, data?: any): string {
|
function buildUrl(path: string, data?: any): string {
|
||||||
if (!data) return path
|
if (!data) return path
|
||||||
const url = new URL(path)
|
const url = new URL(window.location.origin + path)
|
||||||
for (const key in data) {
|
for (const key in data) {
|
||||||
url.searchParams.append(key, data[key])
|
url.searchParams.append(key, data[key])
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/api/oauth2.ts
Normal file
33
src/api/oauth2.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { ModelOauth2LoginResponse } from "@/types"
|
||||||
|
|
||||||
|
import { FetcherMethod, fetcher } from "./api"
|
||||||
|
|
||||||
|
export enum Oauth2RequestType {
|
||||||
|
LOGIN = 1,
|
||||||
|
BIND = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getOauth2RedirectURL = async (provider: string, rType: Oauth2RequestType): Promise<ModelOauth2LoginResponse> => {
|
||||||
|
return fetcher<ModelOauth2LoginResponse>(FetcherMethod.GET, `/api/v1/oauth2/${provider}`, {
|
||||||
|
"type": rType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const bindOauth2 = async (provider: string, state: string, code: string): Promise<ModelOauth2LoginResponse> => {
|
||||||
|
return fetcher<ModelOauth2LoginResponse>(FetcherMethod.POST, `/api/v1/oauth2/${provider}/bind`, {
|
||||||
|
"state": state,
|
||||||
|
"code": code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const unbindOauth2 = async (provider: string): Promise<ModelOauth2LoginResponse> => {
|
||||||
|
return fetcher<ModelOauth2LoginResponse>(FetcherMethod.POST, `/api/v1/oauth2/${provider}/unbind`)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const oauth2callback = async (provider: string, state: string, code: string): Promise<void> => {
|
||||||
|
return fetcher<void>(FetcherMethod.POST, `/api/v1/oauth2/${provider}/callback`, {
|
||||||
|
state, code
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import { useAuth } from "@/hooks/useAuth"
|
import { useAuth } from "@/hooks/useAuth"
|
||||||
import useSettings from "@/hooks/useSetting"
|
import useSettings from "@/hooks/useSetting"
|
||||||
import { copyToClipboard } from "@/lib/utils"
|
import { copyToClipboard } from "@/lib/utils"
|
||||||
import { ModelProfile, ModelSettingResponse } from "@/types"
|
import { ModelProfile, ModelConfig } from "@/types"
|
||||||
import i18next from "i18next"
|
import i18next from "i18next"
|
||||||
import { Check, Clipboard } from "lucide-react"
|
import { Check, Clipboard } from "lucide-react"
|
||||||
import { forwardRef, useState } from "react"
|
import { forwardRef, useState } from "react"
|
||||||
@@ -33,8 +33,8 @@ export const InstallCommandsMenu = forwardRef<HTMLButtonElement, ButtonProps>((p
|
|||||||
try {
|
try {
|
||||||
setCopy(true)
|
setCopy(true)
|
||||||
if (!profile) throw new Error("Profile is not found.")
|
if (!profile) throw new Error("Profile is not found.")
|
||||||
if (!settings) throw new Error("Settings is not found.")
|
if (!settings?.config) throw new Error("Settings is not found.")
|
||||||
await copyToClipboard(generateCommand(type, settings, profile) || "")
|
await copyToClipboard(generateCommand(type, settings!.config, profile) || "")
|
||||||
} catch (e: Error | any) {
|
} catch (e: Error | any) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
toast(t("Error"), {
|
toast(t("Error"), {
|
||||||
@@ -88,7 +88,7 @@ export const InstallCommandsMenu = forwardRef<HTMLButtonElement, ButtonProps>((p
|
|||||||
|
|
||||||
const generateCommand = (
|
const generateCommand = (
|
||||||
type: number,
|
type: number,
|
||||||
{ agent_secret_key, install_host, tls }: ModelSettingResponse,
|
{ agent_secret_key, install_host, tls }: ModelConfig,
|
||||||
{ agent_secret, role }: ModelProfile,
|
{ agent_secret, role }: ModelProfile,
|
||||||
) => {
|
) => {
|
||||||
if (!install_host) throw new Error(i18next.t("Results.InstallHostRequired"))
|
if (!install_host) throw new Error(i18next.t("Results.InstallHostRequired"))
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
|||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof serverFormSchema>) => {
|
const onSubmit = async (values: z.infer<typeof serverFormSchema>) => {
|
||||||
await updateServer(data.id, values)
|
await updateServer(data!.id!, values)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
await mutate()
|
await mutate()
|
||||||
form.reset()
|
form.reset()
|
||||||
@@ -122,7 +122,6 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
|||||||
{...field}
|
{...field}
|
||||||
value={conv.arrToStr(field.value || [])}
|
value={conv.arrToStr(field.value || [])}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
console.log(field.value)
|
|
||||||
const arr = conv
|
const arr = conv
|
||||||
.strToArr(e.target.value)
|
.strToArr(e.target.value)
|
||||||
.map(Number)
|
.map(Number)
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import { useNavigate } from "react-router-dom"
|
|||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
|
||||||
import { useMainStore } from "./useMainStore"
|
import { useMainStore } from "./useMainStore"
|
||||||
|
import { oauth2callback } from "@/api/oauth2"
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextProps>({
|
const AuthContext = createContext<AuthContextProps>({
|
||||||
profile: undefined,
|
profile: undefined,
|
||||||
login: () => {},
|
login: () => { },
|
||||||
logout: () => {},
|
loginOauth2: () => { },
|
||||||
|
logout: () => { },
|
||||||
})
|
})
|
||||||
|
|
||||||
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
@@ -17,7 +19,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const setProfile = useMainStore((store) => store.setProfile)
|
const setProfile = useMainStore((store) => store.setProfile)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
;(async () => {
|
; (async () => {
|
||||||
try {
|
try {
|
||||||
const user = await getProfile()
|
const user = await getProfile()
|
||||||
user.role = user.role || 0
|
user.role = user.role || 0
|
||||||
@@ -43,6 +45,20 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loginOauth2 = async (provider: string, state: string, code: string) => {
|
||||||
|
try {
|
||||||
|
await oauth2callback(provider, state, code)
|
||||||
|
const user = await getProfile()
|
||||||
|
user.role = user.role || 0
|
||||||
|
setProfile(user)
|
||||||
|
navigate("/dashboard")
|
||||||
|
} catch (error: any) {
|
||||||
|
toast(error.message)
|
||||||
|
} finally {
|
||||||
|
window.history.replaceState({}, document.title, window.location.pathname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
document.cookie.split(";").forEach(function (c) {
|
document.cookie.split(";").forEach(function (c) {
|
||||||
document.cookie = c
|
document.cookie = c
|
||||||
@@ -57,6 +73,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
() => ({
|
() => ({
|
||||||
profile,
|
profile,
|
||||||
login,
|
login,
|
||||||
|
loginOauth2,
|
||||||
logout,
|
logout,
|
||||||
}),
|
}),
|
||||||
[profile],
|
[profile],
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { swrFetcher } from "@/api/api"
|
import { swrFetcher } from "@/api/api"
|
||||||
import { ModelSettingResponse } from "@/types"
|
import { GithubComNezhahqNezhaModelSettingResponseModelConfig } from "@/types"
|
||||||
import useSWR from "swr"
|
import useSWR from "swr"
|
||||||
|
|
||||||
export default function useSetting() {
|
export default function useSetting() {
|
||||||
return useSWR<ModelSettingResponse>("/api/v1/setting", swrFetcher)
|
return useSWR<GithubComNezhahqNezhaModelSettingResponseModelConfig>("/api/v1/setting", swrFetcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { getOauth2RedirectURL, Oauth2RequestType } from "@/api/oauth2"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@@ -9,10 +10,13 @@ import {
|
|||||||
} from "@/components/ui/form"
|
} from "@/components/ui/form"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { useAuth } from "@/hooks/useAuth"
|
import { useAuth } from "@/hooks/useAuth"
|
||||||
|
import useSetting from "@/hooks/useSetting"
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import i18next from "i18next"
|
import i18next from "i18next"
|
||||||
|
import { useEffect } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { toast } from "sonner"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
@@ -25,7 +29,17 @@ const formSchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function Login() {
|
function Login() {
|
||||||
const { login } = useAuth()
|
const { login, loginOauth2 } = useAuth()
|
||||||
|
const { data: settingData } = useSetting()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const oauth2Code = new URLSearchParams(window.location.search).get("code")
|
||||||
|
const oauth2State = new URLSearchParams(window.location.search).get("state")
|
||||||
|
const oauth2Provider = new URLSearchParams(window.location.search).get("provider")
|
||||||
|
if (oauth2Code && oauth2State && oauth2Provider) {
|
||||||
|
loginOauth2(oauth2Provider, oauth2State, oauth2Code)
|
||||||
|
}
|
||||||
|
}, [window.location.search])
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
@@ -39,6 +53,15 @@ function Login() {
|
|||||||
login(values.username, values.password)
|
login(values.username, values.password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loginWith(provider: string) {
|
||||||
|
try {
|
||||||
|
const redirectUrl = await getOauth2RedirectURL(provider, Oauth2RequestType.LOGIN)
|
||||||
|
window.location.href = redirectUrl.redirect!
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -79,6 +102,11 @@ function Login() {
|
|||||||
<Button type="submit">{t("Login")}</Button>
|
<Button type="submit">{t("Login")}</Button>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
<div className="mt-4">
|
||||||
|
{settingData?.config?.oauth2_providers?.map((p: string) =>
|
||||||
|
<Button onClick={() => loginWith(p)}>{p}</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,62 @@
|
|||||||
|
import { bindOauth2, getOauth2RedirectURL, Oauth2RequestType, unbindOauth2 } from "@/api/oauth2"
|
||||||
|
import { getProfile } from "@/api/user"
|
||||||
import { ProfileCard } from "@/components/profile"
|
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 { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
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"
|
||||||
|
import useSetting from "@/hooks/useSetting"
|
||||||
import { Boxes, Server } from "lucide-react"
|
import { Boxes, Server } from "lucide-react"
|
||||||
|
import { useEffect } from "react"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
export default function ProfilePage() {
|
export default function ProfilePage() {
|
||||||
const { profile } = useMainStore()
|
const { profile, setProfile } = useMainStore()
|
||||||
const { servers, serverGroups } = useServer()
|
const { servers, serverGroups } = useServer()
|
||||||
|
const { data: settingData } = useSetting()
|
||||||
const isDesktop = useMediaQuery("(min-width: 890px)")
|
const isDesktop = useMediaQuery("(min-width: 890px)")
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const oauth2Code = new URLSearchParams(window.location.search).get("code")
|
||||||
|
const oauth2State = new URLSearchParams(window.location.search).get("state")
|
||||||
|
const oauth2Provider = new URLSearchParams(window.location.search).get("provider")
|
||||||
|
if (oauth2Code && oauth2State && oauth2Provider) {
|
||||||
|
bindOauth2(oauth2Provider, oauth2State, oauth2Code)
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
getProfile().then((profile) => {
|
||||||
|
setProfile(profile)
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
window.history.replaceState({}, document.title, window.location.pathname)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [window.location.search])
|
||||||
|
|
||||||
|
const bindO2 = async (provider: string) => {
|
||||||
|
try {
|
||||||
|
const redirectUrl = await getOauth2RedirectURL(provider, Oauth2RequestType.BIND)
|
||||||
|
window.location.href = redirectUrl.redirect!
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unbindO2 = async (provider: string) => {
|
||||||
|
try {
|
||||||
|
await unbindOauth2(provider)
|
||||||
|
getProfile().then((profile) => {
|
||||||
|
setProfile(profile)
|
||||||
|
})
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
profile && (
|
profile && (
|
||||||
<div className={`flex p-8 gap-4 ${isDesktop ? "ml-6" : "flex-col"}`}>
|
<div className={`flex p-8 gap-4 ${isDesktop ? "ml-6" : "flex-col"}`}>
|
||||||
@@ -62,6 +108,22 @@ export default function ProfilePage() {
|
|||||||
{serverGroups?.length || 0}
|
{serverGroups?.length || 0}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card className="w-full">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex gap-2 text-xl items-center">
|
||||||
|
<Boxes /> Oauth2 bindings
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="text-lg font-semibold">
|
||||||
|
{settingData?.config?.oauth2_providers?.map((provider) => <div>
|
||||||
|
{provider}: {profile.oauth2_bind?.[provider.toLowerCase()]} {profile.oauth2_bind?.[provider.toLowerCase()] ?
|
||||||
|
<Button size="sm" onClick={() => unbindO2(provider)}>Unbind</Button>
|
||||||
|
:
|
||||||
|
<Button size="sm" onClick={() => bindO2(provider)}>Bind</Button>}
|
||||||
|
</div>)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ export default function Root() {
|
|||||||
const { data: settingData, error } = useSetting()
|
const { data: settingData, error } = useSetting()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = settingData?.site_name || "哪吒监控 Nezha Monitoring"
|
document.title = settingData?.config?.site_name || "哪吒监控 Nezha Monitoring"
|
||||||
}, [settingData?.site_name])
|
}, [settingData?.config?.site_name])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (settingData?.custom_code_dashboard) {
|
if (settingData?.config?.custom_code_dashboard) {
|
||||||
InjectContext(settingData?.custom_code_dashboard)
|
InjectContext(settingData?.config?.custom_code_dashboard)
|
||||||
}
|
}
|
||||||
}, [settingData?.custom_code_dashboard])
|
}, [settingData?.config?.custom_code_dashboard])
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error
|
throw error
|
||||||
@@ -30,8 +30,8 @@ export default function Root() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingData?.language && !localStorage.getItem("language")) {
|
if (settingData?.config?.language && !localStorage.getItem("language")) {
|
||||||
i18n.changeLanguage(settingData?.language)
|
i18n.changeLanguage(settingData?.config?.language)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -67,31 +67,31 @@ export default function SettingsPage() {
|
|||||||
resolver: zodResolver(settingFormSchema),
|
resolver: zodResolver(settingFormSchema),
|
||||||
defaultValues: config
|
defaultValues: config
|
||||||
? {
|
? {
|
||||||
...config,
|
...config,
|
||||||
language: config.language,
|
language: config?.config?.language,
|
||||||
site_name: config.site_name || "",
|
site_name: config.config?.site_name || "",
|
||||||
user_template:
|
user_template:
|
||||||
config.user_template ||
|
config.config?.user_template ||
|
||||||
Object.keys(config.frontend_templates?.filter((t) => !t.is_admin) || {})[0] ||
|
Object.keys(config.frontend_templates?.filter((t) => !t.is_admin) || {})[0] ||
|
||||||
"user-dist",
|
"user-dist",
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
ip_change_notification_group_id: 0,
|
ip_change_notification_group_id: 0,
|
||||||
cover: 1,
|
cover: 1,
|
||||||
site_name: "",
|
site_name: "",
|
||||||
language: "",
|
language: "",
|
||||||
user_template: "user-dist",
|
user_template: "user-dist",
|
||||||
},
|
},
|
||||||
resetOptions: {
|
resetOptions: {
|
||||||
keepDefaultValues: false,
|
keepDefaultValues: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (config) {
|
if (config?.config) {
|
||||||
form.reset(config)
|
form.reset(config?.config)
|
||||||
}
|
}
|
||||||
}, [config, form])
|
}, [config?.config, form])
|
||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof settingFormSchema>) => {
|
const onSubmit = async (values: z.infer<typeof settingFormSchema>) => {
|
||||||
try {
|
try {
|
||||||
@@ -172,7 +172,7 @@ export default function SettingsPage() {
|
|||||||
(t) => t.path === value,
|
(t) => t.path === value,
|
||||||
)
|
)
|
||||||
if (template) {
|
if (template) {
|
||||||
form.setValue("user_template", template.path)
|
form.setValue("user_template", template!.path!)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -188,7 +188,7 @@ export default function SettingsPage() {
|
|||||||
) || []
|
) || []
|
||||||
).map((template) => (
|
).map((template) => (
|
||||||
<div key={template.path}>
|
<div key={template.path}>
|
||||||
<SelectItem value={template.path}>
|
<SelectItem value={template.path!}>
|
||||||
<div className="flex flex-col items-start gap-1">
|
<div className="flex flex-col items-start gap-1">
|
||||||
<div className="font-medium">
|
<div className="font-medium">
|
||||||
{template.name}
|
{template.name}
|
||||||
@@ -229,15 +229,15 @@ export default function SettingsPage() {
|
|||||||
{!config?.frontend_templates?.find(
|
{!config?.frontend_templates?.find(
|
||||||
(t) => t.path === field.value,
|
(t) => t.path === field.value,
|
||||||
)?.is_official && (
|
)?.is_official && (
|
||||||
<div className="mt-2 text-sm text-yellow-700 dark:text-yellow-200 bg-yellow-100 dark:bg-yellow-900 border border-yellow-200 dark:border-yellow-700 rounded-md p-2">
|
<div className="mt-2 text-sm text-yellow-700 dark:text-yellow-200 bg-yellow-100 dark:bg-yellow-900 border border-yellow-200 dark:border-yellow-700 rounded-md p-2">
|
||||||
<div className="font-medium text-lg mb-1">
|
<div className="font-medium text-lg mb-1">
|
||||||
{t("CommunityThemeWarning")}
|
{t("CommunityThemeWarning")}
|
||||||
|
</div>
|
||||||
|
<div className="text-yellow-700 dark:text-yellow-200">
|
||||||
|
{t("CommunityThemeDescription")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-yellow-700 dark:text-yellow-200">
|
)}
|
||||||
{t("CommunityThemeDescription")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
822
src/types/api.ts
822
src/types/api.ts
File diff suppressed because it is too large
Load Diff
@@ -3,5 +3,6 @@ import { ModelProfile } from "@/types"
|
|||||||
export interface AuthContextProps {
|
export interface AuthContextProps {
|
||||||
profile: ModelProfile | undefined
|
profile: ModelProfile | undefined
|
||||||
login: (username: string, password: string) => void
|
login: (username: string, password: string) => void
|
||||||
|
loginOauth2: (provider: string, state: string, code: string) => void
|
||||||
logout: () => void
|
logout: () => void
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user