diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c4014c28..8ef3009d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,19 +55,6 @@ jobs: APP=admin START_COMMAND=start - - name: Build Server - uses: docker/build-push-action@v3 - with: - context: . - push: true - tags: ghcr.io/${{ github.repository }}/server:${{ github.event.inputs.tag }} - cache-from: type=gha - cache-to: type=gha,mode=max - build-args: | - COMMIT=${{ steps.vars.outputs.sha_short }} - APP=server - START_COMMAND=start - - name: Build Frontend uses: docker/build-push-action@v3 with: diff --git a/.gitignore b/.gitignore index c3c9c20b..dcbd94ae 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ dist/ .vscode .backups +.ethereal diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index ba5db7b3..6194c33e 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index 2dacc90c..7f15f2ef 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -13,7 +13,6 @@ import { openApiDocument } from './openapi'; const trpcApiEndpoint = '/trpc' - async function main() { // express implementation const app = express(); @@ -48,7 +47,6 @@ async function main() { createContext: createRPCContext, }), ); - // Handle incoming OpenAPI requests app.use('/api', createOpenApiExpressMiddleware({ router: appRouter, createContext: createRPCContext })); diff --git a/apps/frontend/jsconfig.json b/apps/frontend/jsconfig.json index 82413fbb..749280c0 100644 --- a/apps/frontend/jsconfig.json +++ b/apps/frontend/jsconfig.json @@ -40,9 +40,6 @@ ], "~/*": [ "src/*" - ], - "~login/*": [ - "src/components/login/*" ] } } diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 38125642..2a09cdd5 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -33,7 +33,6 @@ "@mui/lab": "^5.0.0-alpha.148", "@mui/material": "^5.14.13", "@mui/styles": "^5.14.13", - "@reduxjs/toolkit": "^1.8.6", "@tanstack/react-query": "^4.36.1", "@trpc/client": "^10.40.0", "@trpc/react-query": "^10.40.0", @@ -42,7 +41,6 @@ "autosuggest-highlight": "^3.3.4", "axios": "^1.3.4", "change-case": "^4.1.2", - "connected-react-router": "6.9.3", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.10", "enzyme": "^3.3.0", @@ -61,7 +59,6 @@ "notistack": "^3.0.1", "passport": "^0.6.0", "passport-local": "^1.0.0", - "prop-types": "^15.6.2", "query-string": "^6.1.0", "ramda": "^0.28.0", "randomcolor": "^0.5.3", @@ -73,21 +70,18 @@ "react-error-boundary": "^4.0.11", "react-full-screen": "^0.2.2", "react-i18next": "^13.2.2", - "react-redux": "^8.0.4", "react-router": "^6.17.0", "react-router-dom": "^6.17.0", "react-scripts": "5.0.1", "react-transition-group": "^2.3.1", "react-use-event": "^1.1.1", "recoil": "^0.7.7", - "redux": "^4.0.0", - "redux-devtools-extension": "^2.13.5", - "redux-thunk": "^2.3.0", "rooks": "^7.4.1", "serve": "^14.2.1", "shiitake": "^3.0.2", "typescript": "^5.2.2", - "yup": "^1.3.2" + "yup": "^1.3.2", + "yup-locales": "^1.2.18" }, "browserslist": { "production": [ diff --git a/apps/frontend/src/App.tsx b/apps/frontend/src/App.tsx index 53c34a23..759d9baa 100644 --- a/apps/frontend/src/App.tsx +++ b/apps/frontend/src/App.tsx @@ -13,20 +13,25 @@ import { ConfirmProvider } from "material-ui-confirm"; import { SnackbarProvider } from "notistack"; import React, { Suspense, useState } from "react"; import { initReactI18next } from "react-i18next"; -import { Provider } from "react-redux"; import { BrowserRouter, Route, Routes, useLocation } from "react-router-dom"; import { RecoilRoot } from "recoil"; +import { setLocale } from "yup"; +import { fr } from "yup-locales"; -import { LogintDialog } from "~components/LoginDialog"; +import { ConfirmDialog } from "~components/login/ConfirmDialog"; +import { ForgotDialog } from "~components/login/ForgotDialog"; +import { JoinDialog } from "~components/login/JoinDialog"; +import { LoginDialog } from "~components/login/LoginDialog"; +import { RecoverDialog } from "~components/login/RecoverDialog"; +import { SignupDialog } from "~components/login/SignupDialog"; +import { StudentSignupDialog } from "~components/login/StudentSignupDialog"; import { SharedLayout } from "~components/SharedLayout"; -import { SignupDialog } from "~components/SignupDialog"; import { trpc } from "~utils/trpc"; import ResetScroll from "./components/ResetScroll"; -import UpdateIndicator from "./components/UpdateIndicator"; -// import { ConnectedRouter } from "connected-react-router"; -import en from "./locales/en/common.json"; -import fr from "./locales/fr/common.json"; +import { UpdateIndicator } from "./components/UpdateIndicator"; +import commonEN from "./locales/en/common.json"; +import commonFR from "./locales/fr/common.json"; import { About } from "./pages/about"; import { CreateProjectPage } from "./pages/create"; import { HomePage } from "./pages/home"; @@ -35,7 +40,6 @@ import UserProfile from "./pages/profile"; import ProjectPage from "./pages/project"; import { SharePage } from "./pages/share"; import { TermsAndConditions } from "./pages/terms"; -import createAppStore from "./store"; import { createTheme } from "./theme"; dayjs.extend(relativeTime); @@ -43,6 +47,8 @@ dayjs.extend(isLeapYear); // use plugin dayjs.extend(duration); dayjs.locale("fr-fr"); // use locale +setLocale(fr); + i18next .use(LanguageDetector) .use(initReactI18next) @@ -50,10 +56,10 @@ i18next debug: false, resources: { en_US: { - translations: en, + translations: commonEN, }, fr_FR: { - translations: fr, + translations: commonFR, }, }, ns: ["translations"], @@ -64,8 +70,6 @@ i18next }, } as i18next.InitOptions); -const store = createAppStore(); - const AppRouters = () => { const location = useLocation(); const { state } = location; @@ -88,8 +92,13 @@ const AppRouters = () => { {state?.backgroundLocation && ( - } /> + } /> + } /> + } /> + } /> } /> + } /> + } /> )} @@ -116,28 +125,26 @@ const App = () => { return ( - - - - - - - + + + + + + + - - - - - - - + + + + + - - - - - - + + + + + + ); diff --git a/apps/frontend/src/actions/AppActions.tsx b/apps/frontend/src/actions/AppActions.tsx deleted file mode 100644 index 7673f4b5..00000000 --- a/apps/frontend/src/actions/AppActions.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { ActionType, createEmptyAction } from "~types/ActionTypes"; - -export const applicationUpdated = () => - createEmptyAction(ActionType.APPLICATION_UPDATED); diff --git a/apps/frontend/src/actions/Signin/LoginActions.tsx b/apps/frontend/src/actions/Signin/LoginActions.tsx deleted file mode 100644 index 3b6230c6..00000000 --- a/apps/frontend/src/actions/Signin/LoginActions.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Credentials, SigninErrors, SigninResult } from "@celluloid/types"; -import { Dispatch } from "redux"; - -import UserService from "~services/UserService"; -import { Action, ActionType, createEmptyAction } from "~types/ActionTypes"; - -import { triggerSigninLoading } from "."; -import { openConfirmSignup } from "./SignupActions"; -import { fetchCurrentUserThunk } from "./UserActions"; - -export const openLogin = () => createEmptyAction(ActionType.OPEN_LOGIN); - -export const succeedLogin = () => createEmptyAction(ActionType.SUCCEED_LOGIN); - -export function failLogin(errors: SigninErrors): Action { - return { - type: ActionType.FAIL_LOGIN, - payload: errors, - error: true, - }; -} - -export const doLoginThunk = - (credentials: Credentials) => (dispatch: Dispatch) => { - dispatch(triggerSigninLoading()); - return UserService.login(credentials) - .then((result: SigninResult) => { - if (!result.success) { - if (result.errors.server === "UserNotConfirmed") { - return dispatch(openConfirmSignup(credentials)); - } else { - return dispatch(failLogin(result.errors)); - } - } else { - fetchCurrentUserThunk()(dispatch); - return dispatch(succeedLogin()); - } - }) - .catch(() => { - return dispatch(failLogin({ server: "RequestFailed" })); - }); - }; diff --git a/apps/frontend/src/actions/Signin/ResetPasswordActions.tsx b/apps/frontend/src/actions/Signin/ResetPasswordActions.tsx deleted file mode 100644 index 36982543..00000000 --- a/apps/frontend/src/actions/Signin/ResetPasswordActions.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { - SigninErrors, - SigninResult, - TeacherConfirmResetPasswordData, -} from "@celluloid/types"; -import { Dispatch } from "redux"; - -import UserService from "~services/UserService"; -import { - ActionType, - createAction, - createEmptyAction, - createErrorAction, -} from "~types/ActionTypes"; - -import { doLoginThunk, triggerSigninLoading } from "."; - -export const openResetPassword = () => - createEmptyAction(ActionType.OPEN_RESET_PASSWORD); - -export const succeedResetPassword = () => - createEmptyAction(ActionType.SUCCEED_RESET_PASSWORD); - -export const openConfirmResetPassword = (email: string) => - createAction(ActionType.OPEN_CONFIRM_RESET_PASSWORD, email); - -export const failResetPassword = (errors: SigninErrors) => - createErrorAction(ActionType.FAIL_RESET_PASSWORD, errors); - -export const failConfirmResetPassword = (errors: SigninErrors) => - createErrorAction(ActionType.FAIL_CONFIRM_RESET_PASSWORD, errors); - -export const doResetPasswordThunk = (email: string) => (dispatch: Dispatch) => { - dispatch(triggerSigninLoading()); - return UserService.resetPassword(email) - .then((result: SigninResult) => { - if (!result.success) { - return dispatch(failResetPassword(result.errors)); - } else { - return dispatch(openConfirmResetPassword(email)); - } - }) - .catch(() => { - return dispatch(failResetPassword({ server: "RequestFailed" })); - }); -}; - -export const doConfirmResetPasswordThunk = - (data: TeacherConfirmResetPasswordData) => (dispatch: Dispatch) => { - dispatch(triggerSigninLoading()); - return UserService.confirmResetPassword(data) - .then((result: SigninResult) => { - if (!result.success) { - return dispatch(failConfirmResetPassword(result.errors)); - } else { - doLoginThunk({ - login: data.login, - password: data.password, - })(dispatch); - return dispatch(succeedResetPassword()); - } - }) - .catch(() => { - return dispatch(failResetPassword({ server: "RequestFailed" })); - }); - }; diff --git a/apps/frontend/src/actions/Signin/SignupActions.tsx b/apps/frontend/src/actions/Signin/SignupActions.tsx deleted file mode 100644 index 6cc66b3c..00000000 --- a/apps/frontend/src/actions/Signin/SignupActions.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { - Credentials, - SigninErrors, - SigninResult, - TeacherConfirmData, - TeacherSignupData, -} from "@celluloid/types"; -import { Dispatch } from "redux"; - -import UserService from "~services/UserService"; -import { - ActionType, - createEmptyAction, - createErrorAction, - createOptionalAction, -} from "~types/ActionTypes"; - -import { doLoginThunk, triggerSigninLoading } from "."; - -export const openSignup = () => createEmptyAction(ActionType.OPEN_SIGNUP); - -export const failSignup = (errors: SigninErrors) => - createErrorAction(ActionType.FAIL_SIGNUP, errors); - -export const openConfirmSignup = (credentials?: Credentials) => - createOptionalAction(ActionType.OPEN_CONFIRM_SIGNUP, credentials); - -export const succeedSignup = () => createEmptyAction(ActionType.SUCCEED_SIGNUP); - -export const failConfirmSignup = (errors: SigninErrors) => - createErrorAction(ActionType.FAIL_CONFIRM_SIGNUP, errors); - -export const doSignupThunk = - (data: TeacherSignupData) => (dispatch: Dispatch) => { - dispatch(triggerSigninLoading()); - return UserService.signup(data) - .then((result: SigninResult) => { - if (!result.success) { - return dispatch(failSignup(result.errors)); - } else { - return dispatch( - openConfirmSignup({ - login: data.email, - password: data.password, - }) - ); - } - }) - .catch(() => dispatch(failSignup({ server: "RequestFailed" }))); - }; - -export const doConfirmSignupThunk = - (data: TeacherConfirmData, credentials?: Credentials) => - (dispatch: Dispatch) => { - dispatch(triggerSigninLoading()); - return UserService.confirmSignup(data) - .then((result: SigninResult) => { - if (!result.success) { - return dispatch(failConfirmSignup(result.errors)); - } else { - if (credentials) { - doLoginThunk(credentials)(dispatch); - } - return dispatch(succeedSignup()); - } - }) - .catch(() => { - return dispatch(failConfirmSignup({ server: "RequestFailed" })); - }); - }; - -export const doResendCodeThunk = (email: string) => (dispatch: Dispatch) => { - dispatch(triggerSigninLoading()); - return UserService.resendCode(email) - .then((result: SigninResult) => { - if (!result.success) { - return dispatch(failConfirmSignup(result.errors)); - } else { - return dispatch(succeedSignup()); - } - }) - .catch(() => { - return dispatch(failConfirmSignup({ server: "RequestFailed" })); - }); -}; diff --git a/apps/frontend/src/actions/Signin/StudentSignupActions.tsx b/apps/frontend/src/actions/Signin/StudentSignupActions.tsx deleted file mode 100644 index a6627741..00000000 --- a/apps/frontend/src/actions/Signin/StudentSignupActions.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { - SigninErrors, - SigninResult, - StudentSignupData, -} from "@celluloid/types"; -import { Dispatch } from "redux"; - -import UserService from "~services/UserService"; -import { - ActionType, - createEmptyAction, - createErrorAction, - EmptyAction, -} from "~types/ActionTypes"; - -import { doLoginThunk, triggerSigninLoading } from "."; - -export const openStudentSignup = (): EmptyAction => - createEmptyAction(ActionType.OPEN_STUDENT_SIGNUP); - -export const failStudentSignup = (errors: SigninErrors) => - createErrorAction(ActionType.FAIL_STUDENT_SIGNUP, errors); - -export const succeedStudentSignup = (): EmptyAction => - createEmptyAction(ActionType.SUCCEED_STUDENT_SIGNUP); - -export const doStudentSignupThunk = - (data: StudentSignupData) => (dispatch: Dispatch) => { - dispatch(triggerSigninLoading()); - return UserService.studentSignup(data) - .then((result: SigninResult) => { - if (!result.success) { - return dispatch(failStudentSignup(result.errors)); - } else { - doLoginThunk({ - login: data.username, - password: data.password, - })(dispatch); - return dispatch(succeedStudentSignup()); - } - }) - .catch(() => dispatch(failStudentSignup({ server: "RequestFailed" }))); - }; diff --git a/apps/frontend/src/actions/Signin/UserActions.tsx b/apps/frontend/src/actions/Signin/UserActions.tsx deleted file mode 100644 index bdbb5a4c..00000000 --- a/apps/frontend/src/actions/Signin/UserActions.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { TeacherRecord } from "@celluloid/types"; -import { Dispatch } from "redux"; - -import UserService from "~services/UserService"; -import { - Action, - ActionType, - createErrorAction, - createOptionalAction, -} from "~types/ActionTypes"; - -export const failCurrentUser = (error: string): Action => - createErrorAction(ActionType.FAIL_GET_CURRENT_USER, error); - -export const succeedCurrentUser = ( - user?: TeacherRecord -): Action => - createOptionalAction(ActionType.SUCCEED_GET_CURRENT_USER, user); - -export const failLogout = (error: string): Action => - createOptionalAction(ActionType.FAIL_LOGOUT, error); - -export const fetchCurrentUserThunk = () => (dispatch: Dispatch) => { - return UserService.me() - .then((result) => { - if (result.teacher) { - return dispatch(succeedCurrentUser(result.teacher)); - } else { - return dispatch(succeedCurrentUser()); - } - }) - .catch((error) => { - return dispatch(failCurrentUser(error.message)); - }); -}; - -export const doLogoutThunk = () => (dispatch: Dispatch) => { - console.log("doLogoutThunk"); - return UserService.logout() - .then(() => { - console.log("logout result"); - return dispatch(succeedCurrentUser()); - }) - .catch((error) => { - return dispatch(failLogout(error.message)); - }); -}; diff --git a/apps/frontend/src/actions/Signin/index.ts b/apps/frontend/src/actions/Signin/index.ts deleted file mode 100644 index 5f77e17b..00000000 --- a/apps/frontend/src/actions/Signin/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ActionType, createEmptyAction } from "~types/ActionTypes"; - -export const closeSignin = () => createEmptyAction(ActionType.CLOSE_SIGNIN); - -export const triggerSigninLoading = () => - createEmptyAction(ActionType.TRIGGER_SIGNIN_LOADING); - -export * from "./LoginActions"; -export * from "./ResetPasswordActions"; -export * from "./SignupActions"; -export * from "./StudentSignupActions"; -export * from "./UserActions"; diff --git a/apps/frontend/src/components/AppBarMenu.tsx b/apps/frontend/src/components/AppBarMenu.tsx index c0c182c2..c78a9e60 100644 --- a/apps/frontend/src/components/AppBarMenu.tsx +++ b/apps/frontend/src/components/AppBarMenu.tsx @@ -1,82 +1,41 @@ -import { AppBar, Box, Button, styled, Toolbar } from "@mui/material"; +import { AppBar, Box, BoxProps, Button, styled, Toolbar } from "@mui/material"; import * as React from "react"; import { useTranslation } from "react-i18next"; -import { connect, useDispatch } from "react-redux"; import { useLocation, useNavigate } from "react-router"; -import { Link } from "react-router-dom"; -import { Dispatch } from "redux"; -import { - closeSignin, - openLogin, - openSignup, - openStudentSignup, -} from "~actions/Signin"; import { getButtonLink } from "~components/ButtonLink"; import { Footer } from "~components/Footer"; import { LogoWithLabel } from "~components/LogoWithLabel"; -import SigninDialog, { SigninState } from "~components/Signin"; import { SigninMenu } from "~components/SigninMenu"; -import { EmptyAction } from "~types/ActionTypes"; -import { AppState } from "~types/StateTypes"; import { trpc } from "~utils/trpc"; import { LanguageMenu } from "./LanguageMenu"; const Offset = styled("div")(({ theme }) => theme.mixins.toolbar); -type Props = React.PropsWithChildren & { - signinDialog: SigninState; - onClickLogin(): EmptyAction; - onClickSignup(): EmptyAction; - onCloseSignin(): EmptyAction; -}; - -const mapStateToProps = (state: AppState) => { - return { - signinDialog: state.signin.dialog, - }; -}; - -const mapDispatchToProps = (dispatch: Dispatch) => { - return { - onClickLogin: () => dispatch(openLogin()), - onClickSignup: () => dispatch(openSignup()), - onCloseSignin: () => dispatch(closeSignin()), - }; -}; - -export const AppBarMenuWrapper: React.FC = ({ - onClickLogin, - onClickSignup, - onCloseSignin, - signinDialog, - children, -}) => { +export const AppBarMenu: React.FC = ({ children }) => { const { t } = useTranslation(); const navigate = useNavigate(); - const meQuery = trpc.user.me.useQuery(); - const logoutMutation = trpc.user.logout.useMutation(); - const location = useLocation(); + const { data, isError } = trpc.user.me.useQuery(); - const dispatch = useDispatch(); + const location = useLocation(); const handleCreate = () => { - if (!meQuery.error) { + if (!isError) { navigate(`/create`); } else { - dispatch(openStudentSignup()); + navigate("/signup", { state: { backgroundLocation: location } }); } }; const handleJoin = () => { - dispatch(openStudentSignup()); + if (isError) { + navigate("/signup-student", { state: { backgroundLocation: location } }); + } else { + navigate("/join", { state: { backgroundLocation: location } }); + } }; - const handleLogout = async () => { - await logoutMutation.mutateAsync(); - navigate("/"); - }; return ( = ({ {t("menu.about")} - + - {children}