diff --git a/backend/routes/auth/signup.js b/backend/routes/auth/signup.js index 7f1106f..8cca345 100644 --- a/backend/routes/auth/signup.js +++ b/backend/routes/auth/signup.js @@ -11,7 +11,7 @@ module.exports = function (sequelize) { router.post("/signup", async (req, res) => { try { - const { email, password, username } = req.body; + const { email, password, username, avatarPath } = req.body; const hashedPassword = await bcrypt.hash(password, 10); const userCount = await User.count(); @@ -25,7 +25,7 @@ module.exports = function (sequelize) { password: hashedPassword, username: username, role: initialRole, - avatarPath: null, + avatarPath: avatarPath, }); const accessToken = jwt.sign({ userId: user.id }, "your-secret-key", { diff --git a/frontend/config/selection.ts b/frontend/config/selection.ts index 915fa5a..80aa0e2 100644 --- a/frontend/config/selection.ts +++ b/frontend/config/selection.ts @@ -64,6 +64,22 @@ const testRunCaseStatus = [ { uid: "skipped", color: "primary", chartColor: "#805aab" }, ]; +const avatars = [ + "/avatar/bear.png", + "/avatar/cat.png", + "/avatar/cow.png", + "/avatar/dog.png", + "/avatar/giraffe.png", + "/avatar/koala.png", + "/avatar/lion.png", + "/avatar/owl.png", + "/avatar/panda.png", + "/avatar/penguin.png", + "/avatar/rhinoceros.png", + "/avatar/shark.png", + "/avatar/sloth.png", +]; + export { roles, priorities, @@ -72,4 +88,5 @@ export { templates, testRunStatus, testRunCaseStatus, + avatars, }; diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 3dd82a0..a680fd7 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -46,7 +46,8 @@ "email_already_exist": "Already registered email", "email_not_exist": "Email address not found", "signup_error": "Sign up failed", - "signin_error": "Sign in failed" + "signin_error": "Sign in failed", + "your_projects": "Your Projects" }, "Projects": { "projectList": "Project List", diff --git a/frontend/messages/ja.json b/frontend/messages/ja.json index acf9121..cd9827c 100644 --- a/frontend/messages/ja.json +++ b/frontend/messages/ja.json @@ -45,7 +45,8 @@ "email_already_exist": "登録済みのメールアドレスです", "email_not_exist": "メールアドレスが見つかりません", "signup_error": "新規登録に失敗しました", - "signin_error": "サインインに失敗しました" + "signin_error": "サインインに失敗しました", + "your_projects": "保有プロジェクト" }, "Projects": { "projectList": "プロジェクト一覧", diff --git a/frontend/public/avatar/bear.png b/frontend/public/avatar/bear.png new file mode 100644 index 0000000..31f51b7 Binary files /dev/null and b/frontend/public/avatar/bear.png differ diff --git a/frontend/public/avatar/cat.png b/frontend/public/avatar/cat.png new file mode 100644 index 0000000..bc7d433 Binary files /dev/null and b/frontend/public/avatar/cat.png differ diff --git a/frontend/public/avatar/cow.png b/frontend/public/avatar/cow.png new file mode 100644 index 0000000..256fb64 Binary files /dev/null and b/frontend/public/avatar/cow.png differ diff --git a/frontend/public/avatar/dog.png b/frontend/public/avatar/dog.png new file mode 100644 index 0000000..d729b8b Binary files /dev/null and b/frontend/public/avatar/dog.png differ diff --git a/frontend/public/avatar/giraffe.png b/frontend/public/avatar/giraffe.png new file mode 100644 index 0000000..4f3166a Binary files /dev/null and b/frontend/public/avatar/giraffe.png differ diff --git a/frontend/public/avatar/koala.png b/frontend/public/avatar/koala.png new file mode 100644 index 0000000..e3e5f2c Binary files /dev/null and b/frontend/public/avatar/koala.png differ diff --git a/frontend/public/avatar/lion.png b/frontend/public/avatar/lion.png new file mode 100644 index 0000000..6080b4b Binary files /dev/null and b/frontend/public/avatar/lion.png differ diff --git a/frontend/public/avatar/owl.png b/frontend/public/avatar/owl.png new file mode 100644 index 0000000..01c7bb3 Binary files /dev/null and b/frontend/public/avatar/owl.png differ diff --git a/frontend/public/avatar/panda.png b/frontend/public/avatar/panda.png new file mode 100644 index 0000000..ddc1981 Binary files /dev/null and b/frontend/public/avatar/panda.png differ diff --git a/frontend/public/avatar/penguin.png b/frontend/public/avatar/penguin.png new file mode 100644 index 0000000..a7f31c0 Binary files /dev/null and b/frontend/public/avatar/penguin.png differ diff --git a/frontend/public/avatar/rhinoceros.png b/frontend/public/avatar/rhinoceros.png new file mode 100644 index 0000000..2edc589 Binary files /dev/null and b/frontend/public/avatar/rhinoceros.png differ diff --git a/frontend/public/avatar/shark.png b/frontend/public/avatar/shark.png new file mode 100644 index 0000000..3200072 Binary files /dev/null and b/frontend/public/avatar/shark.png differ diff --git a/frontend/public/avatar/sloth.png b/frontend/public/avatar/sloth.png new file mode 100644 index 0000000..9f742b4 Binary files /dev/null and b/frontend/public/avatar/sloth.png differ diff --git a/frontend/src/app/[locale]/TokenProvider.tsx b/frontend/src/app/[locale]/TokenProvider.tsx index 26d0ffb..b90d46c 100644 --- a/frontend/src/app/[locale]/TokenProvider.tsx +++ b/frontend/src/app/[locale]/TokenProvider.tsx @@ -2,8 +2,10 @@ import { createContext, useState, useEffect } from "react"; import { TokenType } from "@/types/user"; import { TokenProps } from "@/types/user"; +import { useRouter, usePathname } from "@/src/navigation"; const LOCAL_STORAGE_KEY = "testplat-auth-token"; +const privatePaths = ["/account"]; function storeTokenToLocalStorage(token: TokenType) { localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(token)); @@ -16,7 +18,10 @@ function removeTokenFromLocalStorage() { const defaultTokenContext = {}; const TokenContext = createContext(defaultTokenContext); -const TokenProvider = ({ children }: TokenProps) => { +const TokenProvider = ({ locale, children }: TokenProps) => { + const router = useRouter(); + const pathname = usePathname(); + const [token, setToken] = useState({ access_token: "", user: null, @@ -28,13 +33,27 @@ const TokenProvider = ({ children }: TokenProps) => { removeTokenFromLocalStorage, }; + const restoreTokenFromLocalStorage = () => { + const tokenString = localStorage.getItem(LOCAL_STORAGE_KEY); + if (tokenString) { + const restoredToken = JSON.parse(tokenString); + setToken(restoredToken); + } else { + checkSignInPage(); + } + }; + + const checkSignInPage = () => { + if ( + privatePaths.some((path) => { + return path === pathname; + }) + ) { + router.push(`/account/signin`, { locale: locale }); + } + }; + useEffect(() => { - const restoreTokenFromLocalStorage = () => { - const tokenString = localStorage.getItem(LOCAL_STORAGE_KEY); - if (tokenString) { - setToken(JSON.parse(tokenString)); - } - }; restoreTokenFromLocalStorage(); }, []); diff --git a/frontend/src/app/[locale]/account/AccountPage.tsx b/frontend/src/app/[locale]/account/AccountPage.tsx new file mode 100644 index 0000000..aa1b0ad --- /dev/null +++ b/frontend/src/app/[locale]/account/AccountPage.tsx @@ -0,0 +1,46 @@ +"use client"; +import { useContext } from "react"; +import { Avatar, Card, CardHeader, CardBody, Divider } from "@nextui-org/react"; +import { TokenContext } from "../TokenProvider"; + +type AccountPageMessages = { + yourProjects: string; +}; + +type Props = { + messages: AccountPageMessages; + locale: string; +}; + +export default function AccountPage({ messages, locale }: Props) { + const context = useContext(TokenContext); + + return ( + <> + {context.token && context.token.user && ( + + + +
+

+ {context.token.user.username} +

+

+ {context.token.user.email} +

+
+
+ + +

{messages.yourProjects}

+
+
+ )} + + ); +} diff --git a/frontend/src/app/[locale]/account/authControl.ts b/frontend/src/app/[locale]/account/authControl.ts index 7fdb43c..7c0aaec 100644 --- a/frontend/src/app/[locale]/account/authControl.ts +++ b/frontend/src/app/[locale]/account/authControl.ts @@ -1,4 +1,5 @@ import { UserType } from "@/types/user"; +import { avatars } from "@/config/selection"; import Config from "@/config/config"; const apiServer = Config.apiServer; @@ -50,4 +51,9 @@ async function signIn(signInUser: UserType) { } } -export { signUp, signIn }; +function getRandomAvatarPath() { + const randomIndex = Math.floor(Math.random() * avatars.length); + return avatars[randomIndex]; +} + +export { signUp, signIn, getRandomAvatarPath }; diff --git a/frontend/src/app/[locale]/account/authPage.tsx b/frontend/src/app/[locale]/account/authPage.tsx index 6a6f5c4..5a29d15 100644 --- a/frontend/src/app/[locale]/account/authPage.tsx +++ b/frontend/src/app/[locale]/account/authPage.tsx @@ -6,7 +6,7 @@ import { Link } from "@/src/navigation"; import { ChevronRight, Eye, EyeOff } from "lucide-react"; import { UserType, AuthMessages } from "@/types/user"; import { roles } from "@/config/selection"; -import { signUp, signIn } from "./authControl"; +import { signUp, signIn, getRandomAvatarPath } from "./authControl"; import { isValidEmail, isValidPassword } from "./validate"; import { TokenContext } from "../TokenProvider"; import { useRouter } from "@/src/navigation"; @@ -58,6 +58,8 @@ export default function AuthPage({ isSignup, messages, locale }: Props) { } } + const initialavatarPath = getRandomAvatarPath(); + user.avatarPath = initialavatarPath; await submit(); }; @@ -85,7 +87,7 @@ export default function AuthPage({ isSignup, messages, locale }: Props) { }; return ( - +

{messages.title}

- -

{t('account')}

- {/* - - */} -
- - afgdsfg - -
- ); + return ; } diff --git a/frontend/src/app/[locale]/layout.tsx b/frontend/src/app/[locale]/layout.tsx index 722c970..7dfdd23 100644 --- a/frontend/src/app/[locale]/layout.tsx +++ b/frontend/src/app/[locale]/layout.tsx @@ -34,7 +34,10 @@ export default function RootLayout({ fontSans.variable )} > - +
{children}
diff --git a/frontend/src/app/[locale]/providers.tsx b/frontend/src/app/[locale]/providers.tsx index 3870280..5202799 100644 --- a/frontend/src/app/[locale]/providers.tsx +++ b/frontend/src/app/[locale]/providers.tsx @@ -12,16 +12,16 @@ import { TokenProps } from "@/types/user"; export interface ProvidersProps { children: React.ReactNode; themeProps?: ThemeProviderProps; - userProps?: TokenProps; + tokenProps?: TokenProps; } -export function Providers({ children, themeProps, userProps }: ProvidersProps) { +export function Providers({ children, themeProps, tokenProps }: ProvidersProps) { const router = useRouter(); return ( - {children} + {children} ); diff --git a/frontend/types/user.ts b/frontend/types/user.ts index f690d96..f8b4c1c 100644 --- a/frontend/types/user.ts +++ b/frontend/types/user.ts @@ -8,6 +8,7 @@ export type UserType = { } | null; export type TokenProps = { + locale: string; children?: React.ReactNode; };