diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 5288836..50ddcd8 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -1,32 +1,32 @@ name: Build Container Image -on: [ push ] +on: [push] jobs: - image: - runs-on: ubuntu-latest - env: - DOCKER: podman - IMAGE_PREFIX: quay.io/ldcraft - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - steps: - - uses: actions/checkout@v4 - # reference: https://github.com/containers/podman/discussions/17868 - - name: Tar as root - run: | - sudo mv -fv /usr/bin/tar /usr/bin/tar.orig - echo -e '#!/bin/sh\n\nsudo /usr/bin/tar.orig "$@"' | sudo tee -a /usr/bin/tar - sudo chmod +x /usr/bin/tar - - name: Cache Podman - uses: actions/cache@v4 - with: - path: | - ~/.local/share/containers - ~/.config/containers - key: ${{ runner.os }}-${{ hashFiles('**/*.Dockerfile', 'build_image.sh') }} - - name: Login to Container Registry - uses: redhat-actions/podman-login@v1 - with: - registry: quay.io - username: ${{ secrets.CONTAINER_USERNAME }} - password: ${{ secrets.CONTAINER_PASSWORD }} - - name: Build UI Image - run: ./build_image.sh rootfs + image: + runs-on: ubuntu-latest + env: + DOCKER: podman + IMAGE_PREFIX: quay.io/ldcraft + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + steps: + - uses: actions/checkout@v4 + # reference: https://github.com/containers/podman/discussions/17868 + - name: Tar as root + run: | + sudo mv -fv /usr/bin/tar /usr/bin/tar.orig + echo -e '#!/bin/sh\n\nsudo /usr/bin/tar.orig "$@"' | sudo tee -a /usr/bin/tar + sudo chmod +x /usr/bin/tar + - name: Cache Podman + uses: actions/cache@v4 + with: + path: | + ~/.local/share/containers + ~/.config/containers + key: ${{ runner.os }}-${{ hashFiles('**/*.Dockerfile', 'build_image.sh') }} + - name: Login to Container Registry + uses: redhat-actions/podman-login@v1 + with: + registry: quay.io + username: ${{ secrets.CONTAINER_USERNAME }} + password: ${{ secrets.CONTAINER_PASSWORD }} + - name: Build UI Image + run: ./build_image.sh rootfs diff --git a/src/app/services/user.ts b/src/app/services/user.ts index e17101a..c688843 100644 --- a/src/app/services/user.ts +++ b/src/app/services/user.ts @@ -7,18 +7,6 @@ export enum UserRole { RoleGuest = 10, } -export interface UserRequest { - email?: string; - password?: string; - nickname?: string; - uid?: number; -} - -export interface UserLoginResponse { - nickname: string; - token: string; -} - export interface UserProfile { meta: Meta; email: string; @@ -29,22 +17,6 @@ export interface UserProfile { export const userApi = api.injectEndpoints({ endpoints: (builder) => ({ - register: builder.mutation, UserRequest>({ - query: (data: UserRequest) => ({ - url: "/user/create", - method: "POST", - body: data, - }), - invalidatesTags: ["User", "Status", "Submission"], - }), - login: builder.mutation, UserRequest>({ - query: (data: UserRequest) => ({ - url: "/user/login", - method: "POST", - body: data, - }), - invalidatesTags: ["User", "Status", "Submission"], - }), logout: builder.mutation, void>({ query: () => ({ url: "/user/logout", @@ -63,4 +35,4 @@ export const userApi = api.injectEndpoints({ }), }); -export const { useRegisterMutation, useLoginMutation, useLogoutMutation, useProfileQuery } = userApi; +export const { useLogoutMutation, useProfileQuery } = userApi; diff --git a/src/features/auth/authSlice.ts b/src/features/auth/authSlice.ts index cd2b7d0..897772b 100644 --- a/src/features/auth/authSlice.ts +++ b/src/features/auth/authSlice.ts @@ -26,23 +26,11 @@ export const authSlice = createAppSlice({ }, extraReducers: (builder) => { builder - .addMatcher(userApi.endpoints.login.matchFulfilled, (_state, action) => { - // Login Success - const { token } = action.payload.body; - localStorage.setItem("token", token); - return { ...initialState, token: token }; - }) .addMatcher(userApi.endpoints.logout.matchFulfilled, (_state, _action) => { // Logout Success localStorage.removeItem("token"); return { ...initialState, token: null }; }) - .addMatcher(userApi.endpoints.login.matchRejected, (_state, action) => { - // Login Failed - console.error("Login Failed", action.payload); - localStorage.removeItem("token"); - return { ...initialState, token: null }; - }) .addMatcher(userApi.endpoints.profile.matchRejected, (_state, action) => { // Profile Failed if (action.meta.arg.originalArgs != 0) return; diff --git a/src/pages/ErrorPage.tsx b/src/pages/ErrorPage.tsx index 7be7099..28b5f06 100644 --- a/src/pages/ErrorPage.tsx +++ b/src/pages/ErrorPage.tsx @@ -1,9 +1,11 @@ -import { isRouteErrorResponse, useNavigate, useRouteError } from "react-router-dom"; +import { isRouteErrorResponse, useNavigate, useRouteError, useSearchParams } from "react-router-dom"; import { Box, Button, ButtonGroup, Flex, Heading, Text } from "@chakra-ui/react"; import { CloseIcon } from "@chakra-ui/icons"; export const ErrorPage = () => { const navigate = useNavigate(); + const [searchParams, _] = useSearchParams(); + const serverError = searchParams.get("message"); const error = useRouteError(); const convertError = (error: unknown): string => { @@ -13,6 +15,8 @@ export const ErrorPage = () => { return error.message; } else if (typeof error === "string") { return error; + } else if (serverError) { + return serverError; } else { console.error(error); return "Unknown error"; diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index bec9da5..47ef4e2 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -1,32 +1,11 @@ -import type React from "react"; -import { useEffect, useState } from "react"; -import { - Box, - Button, - Center, - Container, - Divider, - FormControl, - FormLabel, - Heading, - HStack, - Input, - Link, - Stack, - Text, - useColorModeValue, - useToast, -} from "@chakra-ui/react"; -import { Link as ReactRouterLink, useNavigate, useSearchParams } from "react-router-dom"; +import { useEffect } from "react"; +import { Box, Button, Center, Container, Heading, Stack, Text, useColorModeValue, useToast } from "@chakra-ui/react"; +import { useNavigate, useSearchParams } from "react-router-dom"; import Cookies from "universal-cookie"; import { useAppDispatch } from "../hooks/store"; -import { PasswordField } from "../components/PasswordField"; import { api } from "../app/services/api"; -import type { UserRequest } from "../app/services/user"; -import { useLoginMutation } from "../app/services/user"; import { useOAuthUrlQuery } from "../app/services/oauth"; -import type { Wrap } from "../app/services/base"; import { RiLoginCircleLine } from "react-icons/ri"; import KeyCloakSVG from "../resources/keycloak.svg"; @@ -34,8 +13,7 @@ import { setToken } from "../features/auth/authSlice"; export default function LoginPage() { const bg = useColorModeValue("gray.50", "gray.900"); - const fg = "teal"; - const fg2 = "gray"; + const fg = "gray"; const dispatch = useAppDispatch(); const toast = useToast(); @@ -45,15 +23,9 @@ export default function LoginPage() { const [searchParams, _] = useSearchParams(); const redirectToken = searchParams.get("redirect_token"); - const [login, { isLoading: loginIsLoading }] = useLoginMutation(); const { data: oauthInfo, isSuccess: oauthIsSuccess } = useOAuthUrlQuery(undefined, { - // refresh every minute - pollingInterval: 60000, - }); - - const [formState, setFormState] = useState({ - email: "", - password: "", + // refresh every 10 minute + pollingInterval: 600000, }); useEffect(() => { @@ -64,40 +36,6 @@ export default function LoginPage() { navigate("/"); }, [dispatch, navigate, redirectToken]); - const handleChange = ({ target: { name, value } }: React.ChangeEvent) => - setFormState((prev) => ({ ...prev, [name]: value })); - - const validate = () => { - const { email, password } = formState; - return email && password && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); - }; - - const doLogin = async () => { - if (!validate()) { - toast({ - status: "error", - title: "Invalid Input", - description: "Please fill in all fields and make sure your email is valid.", - isClosable: true, - }); - return; - } - - try { - // authSlice will automatically handle the response - await login(formState).unwrap(); - navigate("/"); - } catch (err) { - const errTyped = err as { status: number | string; data: Wrap }; - toast({ - status: "error", - title: "Error", - description: errTyped.status === 200 ? errTyped.data.msg : "Oh no, there was an error!", - isClosable: true, - }); - } - }; - const doSSOLogin = () => { if (!oauthIsSuccess || !oauthInfo) { toast({ @@ -117,37 +55,9 @@ export default function LoginPage() { window.open(oauthInfo.body.url, "_self"); }; - const formArea = ( -
{ - e.preventDefault(); - void doLogin(); - }} - > - - - Email - - - - {/* eslint-disable-next-line @typescript-eslint/no-misused-promises */} - - -
- ); - const ssoArea = ( - - - - or continue with - - - - @@ -159,15 +69,9 @@ export default function LoginPage() {
- +
Log in to your account - - Don't have an account?{" "} - - Sign up - - - +
); @@ -182,10 +86,7 @@ export default function LoginPage() { boxShadow={{ base: "none", sm: "xl" }} borderRadius={{ base: "none", sm: "xl" }} > - - {formArea} - {ssoArea} - + {ssoArea}
diff --git a/src/routes.tsx b/src/routes.tsx index ab14c68..4db6f7d 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -25,6 +25,10 @@ export const router: RouteObject[] = [ index: true, element: , }, + { + path: "error", + element: , + }, { path: "home", element: ,