mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-11-03 23:55:44 +00:00 
			
		
		
		
	refactor: rename site to frontend
This commit is contained in:
		
							
								
								
									
										48
									
								
								frontend/src/components/auth/login-forn.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								frontend/src/components/auth/login-forn.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
import { TextInput, PasswordInput, Button } from "@mantine/core";
 | 
			
		||||
import { useForm, zodResolver } from "@mantine/form";
 | 
			
		||||
import { LoginFormValues, loginSchema } from "../../schemas/login-schema";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
 | 
			
		||||
interface LoginFormProps {
 | 
			
		||||
  isLoading: boolean;
 | 
			
		||||
  onSubmit: (values: LoginFormValues) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const LoginForm = (props: LoginFormProps) => {
 | 
			
		||||
  const { isLoading, onSubmit } = props;
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const form = useForm({
 | 
			
		||||
    mode: "uncontrolled",
 | 
			
		||||
    initialValues: {
 | 
			
		||||
      username: "",
 | 
			
		||||
      password: "",
 | 
			
		||||
    },
 | 
			
		||||
    validate: zodResolver(loginSchema),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <form onSubmit={form.onSubmit(onSubmit)}>
 | 
			
		||||
      <TextInput
 | 
			
		||||
        label={t("loginUsername")}
 | 
			
		||||
        placeholder="user@example.com"
 | 
			
		||||
        required
 | 
			
		||||
        disabled={isLoading}
 | 
			
		||||
        key={form.key("username")}
 | 
			
		||||
        {...form.getInputProps("username")}
 | 
			
		||||
      />
 | 
			
		||||
      <PasswordInput
 | 
			
		||||
        label={t("loginPassword")}
 | 
			
		||||
        placeholder="password"
 | 
			
		||||
        required
 | 
			
		||||
        mt="md"
 | 
			
		||||
        disabled={isLoading}
 | 
			
		||||
        key={form.key("password")}
 | 
			
		||||
        {...form.getInputProps("password")}
 | 
			
		||||
      />
 | 
			
		||||
      <Button fullWidth mt="xl" type="submit" loading={isLoading}>
 | 
			
		||||
        {t("loginSubmit")}
 | 
			
		||||
      </Button>
 | 
			
		||||
    </form>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										72
									
								
								frontend/src/components/auth/oauth-buttons.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								frontend/src/components/auth/oauth-buttons.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
import { Grid, Button } from "@mantine/core";
 | 
			
		||||
import { GithubIcon } from "../../icons/github";
 | 
			
		||||
import { GoogleIcon } from "../../icons/google";
 | 
			
		||||
import { OAuthIcon } from "../../icons/oauth";
 | 
			
		||||
import { TailscaleIcon } from "../../icons/tailscale";
 | 
			
		||||
 | 
			
		||||
interface OAuthButtonsProps {
 | 
			
		||||
  oauthProviders: string[];
 | 
			
		||||
  isLoading: boolean;
 | 
			
		||||
  mutate: (provider: string) => void;
 | 
			
		||||
  genericName: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const OAuthButtons = (props: OAuthButtonsProps) => {
 | 
			
		||||
  const { oauthProviders, isLoading, genericName, mutate } = props;
 | 
			
		||||
  return (
 | 
			
		||||
    <Grid mb="md" mt="md" align="center" justify="center">
 | 
			
		||||
      {oauthProviders.includes("google") && (
 | 
			
		||||
        <Grid.Col span="content">
 | 
			
		||||
          <Button
 | 
			
		||||
            radius="xl"
 | 
			
		||||
            leftSection={<GoogleIcon style={{ width: 14, height: 14 }} />}
 | 
			
		||||
            variant="default"
 | 
			
		||||
            onClick={() => mutate("google")}
 | 
			
		||||
            loading={isLoading}
 | 
			
		||||
          >
 | 
			
		||||
            Google
 | 
			
		||||
          </Button>
 | 
			
		||||
        </Grid.Col>
 | 
			
		||||
      )}
 | 
			
		||||
      {oauthProviders.includes("github") && (
 | 
			
		||||
        <Grid.Col span="content">
 | 
			
		||||
          <Button
 | 
			
		||||
            radius="xl"
 | 
			
		||||
            leftSection={<GithubIcon style={{ width: 14, height: 14 }} />}
 | 
			
		||||
            variant="default"
 | 
			
		||||
            onClick={() => mutate("github")}
 | 
			
		||||
            loading={isLoading}
 | 
			
		||||
          >
 | 
			
		||||
            Github
 | 
			
		||||
          </Button>
 | 
			
		||||
        </Grid.Col>
 | 
			
		||||
      )}
 | 
			
		||||
      {oauthProviders.includes("tailscale") && (
 | 
			
		||||
        <Grid.Col span="content">
 | 
			
		||||
          <Button
 | 
			
		||||
            radius="xl"
 | 
			
		||||
            leftSection={<TailscaleIcon style={{ width: 14, height: 14 }} />}
 | 
			
		||||
            variant="default"
 | 
			
		||||
            onClick={() => mutate("tailscale")}
 | 
			
		||||
            loading={isLoading}
 | 
			
		||||
          >
 | 
			
		||||
            Tailscale
 | 
			
		||||
          </Button>
 | 
			
		||||
        </Grid.Col>
 | 
			
		||||
      )}
 | 
			
		||||
      {oauthProviders.includes("generic") && (
 | 
			
		||||
        <Grid.Col span="content">
 | 
			
		||||
          <Button
 | 
			
		||||
            radius="xl"
 | 
			
		||||
            leftSection={<OAuthIcon style={{ width: 14, height: 14 }} />}
 | 
			
		||||
            variant="default"
 | 
			
		||||
            onClick={() => mutate("generic")}
 | 
			
		||||
            loading={isLoading}
 | 
			
		||||
          >
 | 
			
		||||
            {genericName}
 | 
			
		||||
          </Button>
 | 
			
		||||
        </Grid.Col>
 | 
			
		||||
      )}
 | 
			
		||||
    </Grid>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										40
									
								
								frontend/src/components/auth/totp-form.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								frontend/src/components/auth/totp-form.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
import { Button, PinInput } from "@mantine/core";
 | 
			
		||||
import { useForm, zodResolver } from "@mantine/form";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
 | 
			
		||||
const schema = z.object({
 | 
			
		||||
  code: z.string(),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
type FormValues = z.infer<typeof schema>;
 | 
			
		||||
 | 
			
		||||
interface TotpFormProps {
 | 
			
		||||
  onSubmit: (values: FormValues) => void;
 | 
			
		||||
  isLoading: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const TotpForm = (props: TotpFormProps) => {
 | 
			
		||||
  const { onSubmit, isLoading } = props;
 | 
			
		||||
 | 
			
		||||
  const form = useForm({
 | 
			
		||||
    mode: "uncontrolled",
 | 
			
		||||
    initialValues: {
 | 
			
		||||
      code: "",
 | 
			
		||||
    },
 | 
			
		||||
    validate: zodResolver(schema),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <form onSubmit={form.onSubmit(onSubmit)}>
 | 
			
		||||
      <PinInput
 | 
			
		||||
        length={6}
 | 
			
		||||
        type={"number"}
 | 
			
		||||
        placeholder=""
 | 
			
		||||
        {...form.getInputProps("code")}
 | 
			
		||||
      />
 | 
			
		||||
      <Button type="submit" mt="xl" loading={isLoading} fullWidth>
 | 
			
		||||
        Verify
 | 
			
		||||
      </Button>
 | 
			
		||||
    </form>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,40 @@
 | 
			
		||||
import { ComboboxItem, Select } from "@mantine/core";
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
import i18n from "../../lib/i18n/i18n";
 | 
			
		||||
import {
 | 
			
		||||
  SupportedLanguage,
 | 
			
		||||
  getLanguageName,
 | 
			
		||||
  languages,
 | 
			
		||||
} from "../../lib/i18n/locales";
 | 
			
		||||
 | 
			
		||||
export const LanguageSelector = () => {
 | 
			
		||||
  const [language, setLanguage] = useState<ComboboxItem>({
 | 
			
		||||
    value: i18n.language,
 | 
			
		||||
    label: getLanguageName(i18n.language as SupportedLanguage),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const languageOptions = Object.entries(languages).map(([code, name]) => ({
 | 
			
		||||
    value: code,
 | 
			
		||||
    label: name,
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  const handleLanguageChange = (option: string) => {
 | 
			
		||||
    i18n.changeLanguage(option as SupportedLanguage);
 | 
			
		||||
    setLanguage({
 | 
			
		||||
      value: option,
 | 
			
		||||
      label: getLanguageName(option as SupportedLanguage),
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Select
 | 
			
		||||
      data={languageOptions}
 | 
			
		||||
      value={language ? language.value : null}
 | 
			
		||||
      onChange={(_value, option) => handleLanguageChange(option.value)}
 | 
			
		||||
      allowDeselect={false}
 | 
			
		||||
      pos="absolute"
 | 
			
		||||
      right={10}
 | 
			
		||||
      top={10}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										16
									
								
								frontend/src/components/layouts/layout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								frontend/src/components/layouts/layout.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
import { Center, Flex } from "@mantine/core";
 | 
			
		||||
import { ReactNode } from "react";
 | 
			
		||||
import { LanguageSelector } from "../language-selector/language-selector";
 | 
			
		||||
 | 
			
		||||
export const Layout = ({ children }: { children: ReactNode }) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <LanguageSelector />
 | 
			
		||||
      <Center style={{ minHeight: "100vh" }}>
 | 
			
		||||
        <Flex direction="column" flex="1" maw={340}>
 | 
			
		||||
          {children}
 | 
			
		||||
        </Flex>
 | 
			
		||||
      </Center>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user