Skip to content

Conversation

@sin-hyunjin
Copy link
Contributor

@sin-hyunjin sin-hyunjin commented Nov 15, 2023

Issues 번호 :

Closes #50

변경, 추가된 코드(설명 등)

코드 주의점

Summary by CodeRabbit

  • New Features

    • Introduced new API endpoints for advice, diary management, emotion analysis, summary generation, and user profile operations.
    • Added a new page for diary entry creation with form fields and image upload preview.
    • Implemented a new component for uploading and previewing multiple images.
    • Integrated user session management with useSession and Recoil state management.
    • Added a new RefreshToken component to handle token refresh every 30 minutes.
    • Created a new textState atom for state management with Recoil.
  • Enhancements

    • Updated error handling in NextAuth to provide more descriptive errors.
    • Modified API routes to use dynamic URLs from environment variables.
    • Improved user feedback by updating error messages in login flow.
    • Enhanced the diary layout with updated styling.
    • Optimized random string generation in hooks for better performance.
  • Bug Fixes

    • Fixed an issue with JWT token verification logic to correctly handle sign-out scenarios.
    • Corrected the axios post request data in the calendar page.
  • Style

    • Updated CSS styles for buttons and toggles in the icon test suite.
  • Documentation

    • Added comments for clarity in the NextAuth error handling process.
  • Refactor

    • Refactored the configuration for code formatting to align with project standards.
    • Moved the useRef import in the login component for better code organization.
  • Chores

    • Adjusted the project's Prettier configuration for consistent code styling.
  • Revert

    • Removed unused NavBar import from the write page component.

@coderabbitai
Copy link

coderabbitai bot commented Nov 15, 2023

Walkthrough

The project underwent a comprehensive update, enhancing various API endpoints and integrating new features. Changes include adjustments to JWT handling, the introduction of new API routes for diary management, emotion analysis, and image uploads, as well as updates to error handling and response formatting. The integration of state management with Recoil and session management improvements with NextAuth are also notable. These updates aim to refine the application's functionality and user experience.

Changes

File Path Change Summary
.prettierrc.js Updated code formatter configuration
src/app/api/.../route.ts Added new API endpoint handlers and updated existing ones
src/app/api/user/.../route.ts Enhanced user API with new methods and JWT token checks
src/app/api/.../route.ts Updated Axios calls to use dynamic URLs from environment variables
src/app/components/... Integrated Recoil state management and updated NextAuth session handling
src/app/diary/... Adjusted diary layout and state management
src/app/hooks/hooks.ts Modified random string generation length
src/app/icontest/... Added new components for diary entry creation and image upload handling
src/app/lib/... Updated JWT verification logic
src/app/signin2/... Implemented new sign-in and user registration features

Assessment against linked issues (Beta)

Objective Addressed Explanation
Implement JWT feature (Feature/43 jwt)
Update API endpoints to handle new features
Integrate state management with Recoil
Improve session management with NextAuth
Enhance user experience with new diary and image upload features

Poem

As winter whispers, code hops along, 🐇❄️
Through the digital burrows, where bytes belong.
With each commit and push, the project grows,
A garden of syntax, in binary rows.


Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • If you reply to a review comment from CodeRabbit, the bot will automatically respond.
  • To engage with CodeRabbit bot directly around the specific lines of code in the PR, mention @coderabbitai in your review comment
  • Note: Review comments are made on code diffs or files, not on the PR overview.
  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Note: For conversation with the bot, please use the review comments on code diffs or files.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json


const hashPassword = crypto.createHash('sha512').update(body.password + salt).digest('hex');
// 비밀번호 뒤에 salt를 붙여서 암호화.
const hashPassword = crypto.createHash('sha512').update(password + salt).digest('hex');

Check failure

Code scanning / CodeQL

Use of password hash with insufficient computational effort

Password from [a call to get](1) is hashed insecurely. Password from [an access to password](2) is hashed insecurely.
return NextResponse.json({result:'뭔가가 이상함.'});
}
const salt = chk[0].user_salt;
const db_pw = chk[0].user_password;

Check failure

Code scanning / CodeQL

Use of password hash with insufficient computational effort

Password from [an access to password](1) is hashed insecurely.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 19

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 3784ce0 and 2e3e951.
Files ignored due to filter (7)
  • package-lock.json
  • package.json
  • public/imgs/1699932284800_yi862zgmllcb4h7KzeLzGq3LFaGkwIxP.png
  • public/imgs/1699932377524_q28ucKw2qv7zceIO.png
  • public/imgs/1699932690871_6ziy3nprERU56YTy.png
  • public/imgs/스크린샷 2023-09-11 100559.png
  • public/imgs/스크린샷 2023-09-15 085650.png
Files selected for processing (27)
  • .prettierrc.js (1 hunks)
  • src/app/api/advice/route.ts (1 hunks)
  • src/app/api/auth/[...nextauth]/route.ts (2 hunks)
  • src/app/api/diary/route.ts (1 hunks)
  • src/app/api/emotion/route.ts (2 hunks)
  • src/app/api/login/route.ts (3 hunks)
  • src/app/api/summary/route.ts (1 hunks)
  • src/app/api/test/route.ts (1 hunks)
  • src/app/api/trans/route.ts (1 hunks)
  • src/app/api/user/route.ts (1 hunks)
  • src/app/api/user/token/route.ts (1 hunks)
  • src/app/calendar/page.tsx (1 hunks)
  • src/app/components/Providers.tsx (1 hunks)
  • src/app/components/RefreshToken.tsx (2 hunks)
  • src/app/diary/_components/DiaryLayout.tsx (1 hunks)
  • src/app/diary/page.tsx (2 hunks)
  • src/app/hooks/hooks.ts (1 hunks)
  • src/app/icontest/contenttest/page.tsx (1 hunks)
  • src/app/icontest/formtest/page.tsx (1 hunks)
  • src/app/icontest/icon.css (1 hunks)
  • src/app/icontest/page.tsx (1 hunks)
  • src/app/lib/atoms/atom.ts (1 hunks)
  • src/app/lib/jwt.ts (1 hunks)
  • src/app/signin2/_components/LoginPage.tsx (1 hunks)
  • src/app/signin2/page.tsx (1 hunks)
  • src/app/signin2/test/page.tsx (1 hunks)
  • src/app/write/page.tsx (1 hunks)
Files skipped from review due to trivial changes (8)
  • .prettierrc.js
  • src/app/api/auth/[...nextauth]/route.ts
  • src/app/api/summary/route.ts
  • src/app/api/trans/route.ts
  • src/app/diary/_components/DiaryLayout.tsx
  • src/app/lib/atoms/atom.ts
  • src/app/signin2/_components/LoginPage.tsx
  • src/app/write/page.tsx
Additional comments: 21
src/app/hooks/hooks.ts (1)
  • 5-8: Reducing the length of the random string from 32 to 16 characters will decrease the entropy of the generated string, which could potentially affect the security if these strings are used in sensitive contexts such as tokens, session identifiers, etc. Ensure that this change does not compromise the security requirements of the system.
src/app/components/Providers.tsx (1)
  • 2-23: The integration of RecoilRoot within SessionProvider is correctly implemented. This setup ensures that the Recoil state management is available throughout the application while still maintaining the session context provided by next-auth. It's important to ensure that the rest of the application is adapted to use Recoil where state management is needed.
src/app/api/user/token/route.ts (3)
  • 4-5: The console log here seems to be for debugging purposes. Ensure that it is intended to be part of the production code. If not, it should be removed to avoid unnecessary logging in production.
-    console.log('fetched...')
  • 15-15: The comment indicates that the current time is being set to 1 minute earlier. This could be for token expiration leeway. Verify that this adjustment aligns with the intended token expiration policy.

  • 18-18: The check for decoded being equal to 'signout' is a specific business logic. Ensure that this behavior is documented and that other parts of the system are aware of this special 'signout' token case.

src/app/icontest/page.tsx (1)
  • 1-19: The React component Page is correctly using the useState hook to manage the toggle state and a click event handler to update it. The use of template literals to conditionally apply the 'toggle' class is also correct. The import of the CSS file is standard practice for styling components in React.

However, the "use client" directive at the top of the file is not standard. If this is intended to be a Next.js specific directive for client-side only components, ensure that it is supported by the version of Next.js being used and that it is correctly implemented.

// Correct usage of useState and event handling
const [toggle, setToggle] = useState(false);
// ...
<div className={`btn ${toggle ? 'toggle' : ''}`} onClick={() => setToggle(!toggle)}>
src/app/api/login/route.ts (3)
  • 1-4: The import of NextResponse and the use of NextResponse.json is a good practice for handling HTTP responses in Next.js API routes. It ensures consistency and leverages built-in helpers for JSON responses.

  • 15-15: The error message is in a language other than English. While this may be intentional for localization, ensure that this aligns with the application's language policy and that appropriate localization mechanisms are in place if the application is intended for an international audience.

  • 31-31: The error message for an incorrect password is clear and user-friendly. However, ensure that the language used is consistent with the rest of the application.

src/app/calendar/page.tsx (1)
  • 6-31: The removal of the date formatting logic before the axios post request could potentially change the behavior of the API call. Ensure that the backend API endpoint at /api/test is expecting an empty object and that no date information is required for the request to be processed correctly. If the date is still needed, the removed code should be restored or the backend should be updated accordingly.
-        // const dateValue = Array.isArray(value) ? (value as Date[])[0] : (value as Date);
-        const response = await axios.post(`${process.env.NEXT_PUBLIC_BASE_URL}/api/test`,
-        {
-          data: {
-          }
-        });
+        const dateValue = Array.isArray(value) ? value[0] : value;
+        const response = await axios.post(`${process.env.NEXT_PUBLIC_BASE_URL}/api/test`, { date: dateValue });
src/app/diary/page.tsx (1)
  • 63-69: The removal of the class name binding from the JSX elements is a visual change. Ensure that this change is intentional and that the corresponding CSS rules are updated to match the new class names if necessary. Also, verify that the removal of the class name binding does not affect the styling and functionality of the pagination elements.
src/app/api/user/route.ts (1)
  • 119-123: The OPTIONS method seems to be for testing purposes and logs the user ID. Ensure that this method does not expose any sensitive information and is protected or removed before deploying to production.
src/app/api/emotion/route.ts (3)
  • 4-8: The reqBody type has been updated to expect an array of strings for the text property. This change will require all client-side code that sends requests to this endpoint to send an array of strings instead of a single string. Ensure that all such instances are updated to match this new expectation.

  • 19-25: The use of an environment variable process.env.MODEL_URL is a good practice for maintaining flexibility and configurability of the application. However, ensure that this environment variable is properly set in all deployment configurations to prevent runtime errors.

  • 25-25: The response is being directly passed as result.result which assumes that the axios response data has a property result containing the desired data. Ensure that the external service is indeed returning data in this format. Additionally, consider adding error handling for cases where res.data does not match the expected structure to prevent runtime errors.

src/app/icontest/icon.css (1)
  • 1-46: The CSS changes introduced here seem to be for a toggle button, where the .circle element moves and changes color when the .toggle class is applied. The removal of the ::after pseudo-element in the toggled state suggests a visual change in the button's appearance when activated. Ensure that these changes align with the design specifications and that they have been tested across different browsers for compatibility.
  • The use of absolute positioning for .circle (line 18) and the transformation on line 25 to vertically center the element is a common technique, but it's important to test this in the context of the full UI to ensure it works as expected.
  • The transition on line 26 indicates a smooth animation effect when the toggle state changes. Verify that the duration and easing of the transition are consistent with the UX design.
  • The .toggle class on line 40-42 changes the position and color of the .circle, which is a clear indication of a state change. This is a good use of CSS to reflect dynamic states in the UI.
  • The display: none; on line 45 for the ::after pseudo-element when .toggle is applied suggests that some visual element is removed from the circle when toggled. This should be confirmed to match the intended design.

Overall, the CSS changes seem appropriate for a toggle button. However, it's important to ensure that these styles are scoped appropriately to avoid conflicts with other elements in the application and that they are tested for cross-browser compatibility.

src/app/signin2/page.tsx (1)
  • 1-78: - The use client directive at the top of the file is not a standard directive in Next.js or React. If it's meant to be a custom directive or comment, it should be clearly marked as such to avoid confusion.
src/app/components/RefreshToken.tsx (3)
  • 1-7: The use of "use client"; is not standard and might be a typo or a custom directive. If it's meant to be a directive for client-side rendering, Next.js handles this automatically, and such a directive is unnecessary. Verify if this is intended and remove it if it's not serving any purpose.

  • 31-36: The use of setInterval for refreshing the access token is a good approach. However, ensure that the interval is cleared when the component is unmounted to prevent memory leaks. The current implementation correctly clears the interval, which is good practice.

  • 37-37: The dependency array for useEffect only includes session, which is correct if the refreshAccess function does not depend on any other variables that could change over time. If there are other dependencies, they should be included in the array to ensure the effect is run when they change.

src/app/signin2/test/page.tsx (1)
  • 1-90: The component seems to be handling user registration with image upload. Here are some points to consider:
  • The use client directive at the top is non-standard and should be removed unless it's part of a custom DSL or framework that requires it.
  • The console.log(session); on line 15 should be removed or replaced with a more robust logging mechanism before deploying to production to avoid exposing session details.
  • The handleImg function on lines 16-23 creates a new object URL every time a file is selected but only revokes the previous URL if a new file is selected. This could potentially lead to memory leaks if the user selects files multiple times without submitting the form.
  • The handleSubmit function on lines 32-58 does not handle errors from the axios.put call. It's important to add error handling to inform the user if the registration fails and to handle any exceptions that may occur.
  • The input fields for the password on line 63 should use type="password" instead of type="text" to hide the input from view.
  • The className values like 'border' and 'w-[60px] h-[60px] rounded-[50%]' suggest that Tailwind CSS is being used. Ensure that these classes are defined in your Tailwind configuration.
  • The Image component from next/image on lines 77-83 is used for displaying the profile image preview. Ensure that the src attribute is properly sanitized to prevent any security vulnerabilities.
  • The imgReset function on lines 24-30 revokes the object URL and clears the image preview, which is good for resetting the state, but it should also handle any necessary cleanup if the component is unmounted to prevent memory leaks.
  • The page function should be named with a capital letter (Page) to follow React component naming conventions and to avoid potential issues with case-sensitive file systems or tooling.
  • The export default page on line 90 should be updated to match the corrected component name.

Here's a diff with some suggested changes:

-"use client"
+
import axios from 'axios';
import { useSession } from 'next-auth/react';
import Image from 'next/image';
import React, { useRef, useState } from 'react'

-const page = () => {
+const Page = () => {
-    console.log(session);
+    // Consider using a more robust logging mechanism or remove this line.
-                <input type="text" ref={pwRef} className='border' /><br />
+                <input type="password" ref={pwRef} className='border' /><br />


</blockquote></details></blockquote></details>



</details>

Comment on lines 6 to +17
import 'react-datepicker/dist/react-datepicker.css';
import { ko } from 'date-fns/esm/locale'
import DiaryLayout from './_components/DiaryLayout';
import { useRecoilState } from 'recoil'
import { textState } from '@/app/lib/atoms/atom'


const Diary = () => {
const [startDate, setStartDate] = useState<Date>(new Date());
const [endDate, setEndDate] = useState<Date>(new Date());
const [test, setText] = useRecoilState(textState);
console.log(test);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The introduction of Recoil for state management is a significant change. Ensure that the textState atom is properly defined and that its usage here aligns with the intended application state management strategy. Also, consider removing the console.log statement before merging to production to avoid exposing application state unnecessarily.

-    console.log(test);

Commitable suggestion

[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
import 'react-datepicker/dist/react-datepicker.css';
import { ko } from 'date-fns/esm/locale'
import DiaryLayout from './_components/DiaryLayout';
import { useRecoilState } from 'recoil'
import { textState } from '@/app/lib/atoms/atom'
const Diary = () => {
const [startDate, setStartDate] = useState<Date>(new Date());
const [endDate, setEndDate] = useState<Date>(new Date());
const [test, setText] = useRecoilState(textState);
console.log(test);
import 'react-datepicker/dist/react-datepicker.css';
import { ko } from 'date-fns/esm/locale'
import DiaryLayout from './_components/DiaryLayout';
import { useRecoilState } from 'recoil'
import { textState } from '@/app/lib/atoms/atom'
const Diary = () => {
const [startDate, setStartDate] = useState<Date>(new Date());
const [endDate, setEndDate] = useState<Date>(new Date());
const [test, setText] = useRecoilState(textState);

Comment on lines 24 to 33
`;

// 쿼리 실행
await queryPromise(sql, [user_id, emotion_img, user_pw, user_name, provider]);

const result = await queryPromise(sql, [user_id, emotion_img, user_pw, user_name, provider]);
console.log('test------------------------')
// 성공적인 응답 반환
const result = { emotion_img };
console.log(result);

// NextResponse 생성자를 사용하여 응답 반환
return new NextResponse(JSON.stringify(result), {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code correctly logs the result of the database query. However, it's generally not a good practice to leave console logs in production code as it can lead to unintentional information disclosure and clutter the server logs. Consider removing the console log or using a more sophisticated logging mechanism that can be toggled for development and production environments.

- console.log('test------------------------')
- console.log(result);

Commitable suggestion

[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
`;
// 쿼리 실행
await queryPromise(sql, [user_id, emotion_img, user_pw, user_name, provider]);
const result = await queryPromise(sql, [user_id, emotion_img, user_pw, user_name, provider]);
console.log('test------------------------')
// 성공적인 응답 반환
const result = { emotion_img };
console.log(result);
// NextResponse 생성자를 사용하여 응답 반환
return new NextResponse(JSON.stringify(result), {
`;
// 쿼리 실행
const result = await queryPromise(sql, [user_id, emotion_img, user_pw, user_name, provider]);
// 성공적인 응답 반환
// NextResponse 생성자를 사용하여 응답 반환
return new NextResponse(JSON.stringify(result), {

accessToken,
refreshToken
};
return new Response(JSON.stringify(rst))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The response for a successful login attempt should also use NextResponse.json for consistency with the error handling responses.

-        return new Response(JSON.stringify(rst))
+        return NextResponse.json(rst)

Commitable suggestion

[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
return new Response(JSON.stringify(rst))
return NextResponse.json(rst)

Comment on lines 36 to 46
export const verifyJwt = (token: string) => {
try {
const secret_key = process.env.SECRET_KEY
const decoded = jwt.verify(token, secret_key!)
const decoded = jwt.verify(token, secret_key!);
const currentTime = Math.floor(Date.now() / 1000) - 60000; // 갱신할 시간.
return decoded as JwtPayload
} catch(err) {
console.log(err);
return null;
console.log('에러에러에러');
return "signout";
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verifyJwt function has been updated to include a currentTime variable, which seems to be intended for token refresh logic. However, the variable is not used within the function, which could be an oversight. If the intention was to check the token's expiration against the current time, this logic is missing. Additionally, returning a string "signout" on error is a significant change in the function's contract and could lead to unexpected behavior if the calling code expects a JwtPayload object or null to indicate failure. The calling code should be reviewed to ensure it can handle this new return type properly.

Consider refactoring to use the currentTime variable for expiration checks and to return a more consistent type, such as null or an object with an error property, to indicate failure.

-        const currentTime = Math.floor(Date.now() / 1000) - 60000; // 갱신할 시간.
+        const currentTime = Math.floor(Date.now() / 1000);
+        if (decoded.exp && currentTime > decoded.exp) {
+            // Token has expired
+            return null; // or { error: "Token has expired" }
+        }

Also, the comment // 갱신할 시간. is not in English, which may not be consistent with the rest of the codebase if English is the standard language for comments. It should be translated to maintain consistency.


Commitable suggestion

[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
export const verifyJwt = (token: string) => {
try {
const secret_key = process.env.SECRET_KEY
const decoded = jwt.verify(token, secret_key!)
const decoded = jwt.verify(token, secret_key!);
const currentTime = Math.floor(Date.now() / 1000) - 60000; // 갱신할 시간.
return decoded as JwtPayload
} catch(err) {
console.log(err);
return null;
console.log('에러에러에러');
return "signout";
}
}
export const verifyJwt = (token: string) => {
try {
const secret_key = process.env.SECRET_KEY
const decoded = jwt.verify(token, secret_key!);
const currentTime = Math.floor(Date.now() / 1000);
if (decoded.exp && currentTime > decoded.exp) {
// Token has expired
return null; // or { error: "Token has expired" }
}
return decoded as JwtPayload
} catch(err) {
console.log('에러에러에러');
return "signout";
}
}

Comment on lines 17 to +31
export const POST = async(req: Request) => {
const body: reqBody = await req.json();
// 쿼리 가져옴.

try {
let sql = 'SELECT user_id from tb_user WHERE user_id = ?'
let sql = 'SELECT user_id from tb_user WHERE user_id = ?';
const chk = await queryPromise(sql, [body.user_id]);
if(chk.length >= 1) {
// 아이디가 이미 있다.
return new Response(JSON.stringify({"result":"exists"}))
// 아이디 존재.
return NextResponse.json({result:'이미 있는 아이디예요.'});
}
sql = 'INSERT INTO tb_user VALUES(?,?,?,?,?,?,?,?,?)'
return NextResponse.json({result:'가입할 수 있는 아이디예요.'})
} catch(err: any) {
console.log(err);
return NextResponse.json({result:'에러에러에러.'})
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The POST method is correctly checking for the existence of a user ID and returning appropriate JSON responses. However, the error message in line 30 is generic and not informative for debugging purposes. Consider logging more specific error details or using a structured error response.

Comment on lines +134 to +174
export const PUT = async(req: Request) => {
const data = await req.formData();
const images: any[] = [];
data.forEach((v, k) => {
images.push(v);
})
const imgs: any[] = [];

const uploadImages = async () => {
for (const v of images) {
const fb = new FormData();
fb.append('image', v);

try {
const result = await axios.post(
'https://api.imgur.com/3/upload',
fb,
{
headers: {
'Authorization': `Client-ID ${process.env.IMGUR_KEY}`,
'Accept': 'application/json'
}
}
);
imgs.push(result.data.data.link);
} catch (error) {
console.log(error);
return 0;
}
}
return imgs;
};

const result = await uploadImages();
if(result === 0) {
return NextResponse.json({result:'error'});
}
console.log(result);

return NextResponse.json({result:imgs})
} No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • The PUT function is designed to upload multiple images to Imgur, but there is no check to ensure that the images are valid or that the upload was successful before returning the URLs.
  • The function uploadImages is defined within the PUT function, which could be extracted to a separate utility function for reusability and clarity.
  • The error handling in the uploadImages function returns 0 in case of an error, which is not a standard way to handle errors in asynchronous functions. It would be better to throw an error and handle it appropriately.
  • The FormData object is being used without checking if the incoming data contains files, which could lead to errors if the data is not as expected.
  • The console.log statements should be replaced with a proper logging mechanism.

Comment on lines +25 to +27
} else if(result.result === 'signout') {
alert('다시 로그인해 주세요.');
signOut();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using alert for notifying the user is not recommended in a production environment as it is considered disruptive. Consider using a more user-friendly notification system that is integrated into your application's UI.

- alert('다시 로그인해 주세요.');
+ // Replace with a user-friendly notification system

Commitable suggestion

[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
} else if(result.result === 'signout') {
alert('다시 로그인해 주세요.');
signOut();
} else if(result.result === 'signout') {
// Replace with a user-friendly notification system
signOut();

Comment on lines +1 to +91
"use client";

import React, { useState, useRef } from 'react'
import Image from 'next/image'
import axios from 'axios'
// 이미지 여러 개 올리기 하자...
const page = () => {
const [images, setImages] = useState<File[]>([]);
const [previews, setPreviews] = useState<string[]>([]);
const imgRef = useRef<HTMLInputElement>(null);

const handleImageView = (e: React.ChangeEvent<HTMLInputElement>) => {
const newImages = [...images];
const newPreviews = [...previews];
for (let i=0; i < e.target.files!.length; i++) {
if (newImages.length < 3) {
const file = e.target.files![i];
newImages.push(file)
const reader = new FileReader();
reader.onload = (e) => {
newPreviews.push(e.target!.result as string);
setPreviews(newPreviews);
};
reader.readAsDataURL(file);
}
}
setImages(newImages);
};

const handleDeletePreview = (index: number) => {
const newImages = [...images];
const newPreviews = [...previews];
newImages.splice(index, 1);
newPreviews.splice(index, 1);
setImages(newImages);
setPreviews(newPreviews);
};

const send = async() => {
const formData = new FormData();
images.forEach((img) => {
if( img instanceof File && img.size > 0) {
formData.append('img', img)
};
});
const result = await axios.put(
'/api/diary',
formData,
{
headers: {
'Content-Type':'multipart/form-data'
}
}
);
console.log(result);
};
return (
<div>
{
previews?.map((prev, index) =>(
<div key={index}>
<Image
src={prev}
width={200}
height={300}
alt={`${prev}-${index}`}
/>
<button
onClick={() => handleDeletePreview(index)}
>
X
</button>
</div>
))
}
<input
ref={imgRef}
type='file'
multiple
accept='image/*'
className='border'
onChange={(e) => handleImageView(e)}
/>
<button onClick={send}>
gogo
</button>
</div>
);
}

export default page
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The component for uploading and previewing images seems to be well-structured and functional. However, there are a few areas that could be improved for better performance and code quality:

  • The handleImageView function currently reads all the selected files into memory to create previews. This could be a performance issue if the user selects very large images. Consider limiting the size of the images that can be uploaded or handling the previews in a way that does not require reading the entire file into memory.
  • The send function uses axios.put to send the form data to the server. Ensure that the server endpoint at /api/diary is expecting a PUT request with form data and that it is properly authenticated and authorized to handle image uploads.
  • The send function does not handle errors from the axios request. It would be good to add error handling to provide feedback to the user if the upload fails.
  • The send function logs the result to the console, which is fine for debugging but should be removed or replaced with a more user-friendly notification when the feature goes into production.
  • The page component is not following the naming convention for React components, which should be capitalized. Consider renaming page to Page or something more descriptive.
  • The use client directive at the beginning of the file is not standard. If it's meant to indicate that this component should only run in the client environment, consider using dynamic imports or other Next.js features to ensure this.

Here's a diff with some suggested changes:

- const page = () => {
+ const ImageUploadPreviewPage = () => {

...

- const send = async() => {
+ const sendImages = async() => {
    try {
      const formData = new FormData();
      images.forEach((img) => {
        if( img instanceof File && img.size > 0) {
          formData.append('img', img)
        };
      });
      const result = await axios.put(
        '/api/diary',
        formData,
        {
          headers: {
            'Content-Type':'multipart/form-data'
          }
        }
      );
      // Handle success, e.g., show a notification to the user
    } catch (error) {
      // Handle error, e.g., show an error message to the user
    }
  };

...

- <button onClick={send}>
+ <button onClick={sendImages}>
    gogo
  </button>

...

- export default page
+ export default ImageUploadPreviewPage

Commitable suggestion

[!IMPORTANT]
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
"use client";
import React, { useState, useRef } from 'react'
import Image from 'next/image'
import axios from 'axios'
// 이미지 여러 개 올리기 하자...
const page = () => {
const [images, setImages] = useState<File[]>([]);
const [previews, setPreviews] = useState<string[]>([]);
const imgRef = useRef<HTMLInputElement>(null);
const handleImageView = (e: React.ChangeEvent<HTMLInputElement>) => {
const newImages = [...images];
const newPreviews = [...previews];
for (let i=0; i < e.target.files!.length; i++) {
if (newImages.length < 3) {
const file = e.target.files![i];
newImages.push(file)
const reader = new FileReader();
reader.onload = (e) => {
newPreviews.push(e.target!.result as string);
setPreviews(newPreviews);
};
reader.readAsDataURL(file);
}
}
setImages(newImages);
};
const handleDeletePreview = (index: number) => {
const newImages = [...images];
const newPreviews = [...previews];
newImages.splice(index, 1);
newPreviews.splice(index, 1);
setImages(newImages);
setPreviews(newPreviews);
};
const send = async() => {
const formData = new FormData();
images.forEach((img) => {
if( img instanceof File && img.size > 0) {
formData.append('img', img)
};
});
const result = await axios.put(
'/api/diary',
formData,
{
headers: {
'Content-Type':'multipart/form-data'
}
}
);
console.log(result);
};
return (
<div>
{
previews?.map((prev, index) =>(
<div key={index}>
<Image
src={prev}
width={200}
height={300}
alt={`${prev}-${index}`}
/>
<button
onClick={() => handleDeletePreview(index)}
>
X
</button>
</div>
))
}
<input
ref={imgRef}
type='file'
multiple
accept='image/*'
className='border'
onChange={(e) => handleImageView(e)}
/>
<button onClick={send}>
gogo
</button>
</div>
);
}
export default page
"use client";
import React, { useState, useRef } from 'react'
import Image from 'next/image'
import axios from 'axios'
// 이미지 여러 개 올리기 하자...
const ImageUploadPreviewPage = () => {
const [images, setImages] = useState<File[]>([]);
const [previews, setPreviews] = useState<string[]>([]);
const imgRef = useRef<HTMLInputElement>(null);
const handleImageView = (e: React.ChangeEvent<HTMLInputElement>) => {
const newImages = [...images];
const newPreviews = [...previews];
for (let i=0; i < e.target.files!.length; i++) {
if (newImages.length < 3) {
const file = e.target.files![i];
newImages.push(file)
const reader = new FileReader();
reader.onload = (e) => {
newPreviews.push(e.target!.result as string);
setPreviews(newPreviews);
};
reader.readAsDataURL(file);
}
}
setImages(newImages);
};
const handleDeletePreview = (index: number) => {
const newImages = [...images];
newPreviews.splice(index, 1);
setImages(newImages);
setPreviews(newPreviews);
};
const sendImages = async() => {
try {
const formData = new FormData();
images.forEach((img) => {
if( img instanceof File && img.size > 0) {
formData.append('img', img)
};
});
const result = await axios.put(
'/api/diary',
formData,
{
headers: {
'Content-Type':'multipart/form-data'
}
}
);
// Handle success, e.g., show a notification to the user
} catch (error) {
// Handle error, e.g., show an error message to the user
}
};
return (
<div>
{
previews?.map((prev, index) =>(
<div key={index}>
<Image
src={prev}
width={200}
height={300}
alt={`${prev}-${index}`}
/>
<button
onClick={() => handleDeletePreview(index)}
>
X
</button>
</div>
))
}
<input
ref={imgRef}
type='file'
multiple
accept='image/*'
className='border'
onChange={(e) => handleImageView(e)}
/>
<button onClick={sendImages}>
gogo
</button>
</div>
);
}
export default ImageUploadPreviewPage

Comment on lines +1 to +114
'use client'

import React, { useState, useRef } from 'react'
import Image from 'next/image'
import axios from 'axios'
import { useSession } from 'next-auth/react'
const page = () => {
const { data: session } = useSession();
// 제목
const titleRef = useRef<HTMLInputElement>(null);

// 내용
const contentRef = useRef<HTMLTextAreaElement>(null);

// 날씨
const weatherRef = useRef<HTMLInputElement>(null);

// 이미지 제목
const imgTitRef = useRef<HTMLInputElement>(null);

// 이미지
const imgRef = useRef<HTMLInputElement>(null);

// 이미지 주소 (미리보기 보여주기 위함)
const [imgUrl, setImgUrl] = useState("");

const handleImageView = (e: React.ChangeEvent<{files: FileList | null}>) => {
if (e.target.files && e.target.files.length > 0) {
const file = e.target.files[0];
URL.revokeObjectURL(imgUrl);
// 이전 미리보기를 지움.

setImgUrl(prev => URL.createObjectURL(file));
}
}
const imgReset = () => {
// 미리보기 안 보이게.
if (imgRef.current) {
imgRef.current.value = '';
URL.revokeObjectURL(imgUrl);
setImgUrl(prev => '');
}
}
const send = async() => {
if(!session) return;
// 작성.
if(!titleRef.current) {
alert("제목 입력.");
return;
}
if(!contentRef.current) {
alert("내용 입력.");
return;
}
if(!weatherRef.current) {
alert('날씨 입력.');
return;
}
const formData = new FormData();
formData.append("title", titleRef.current.value);
formData.append("content", contentRef.current.value);
formData.append('id', session?.user?.id as string);
formData.append('name', session.user?.name as string);
formData.append('weather', weatherRef.current.value);
if(imgRef.current && imgRef.current.files && imgRef.current.files.length > 0 && imgTitRef.current) {
formData.append('img', imgRef.current.files[0])
formData.append('imgTit', imgTitRef.current.value)
};
const result = await axios.post(
"/api/diary",
formData,
{
headers: {
"Content-Type":"multipart/form-data",
"Authorization":`mlru ${session?.accessToken}`
}
}
);
console.log(result.data);
imgReset();
};
return (
<div>
일기 작성 테스트<br />

<input type='text' className='border' ref={titleRef} /><br />
<br />
<textarea ref={contentRef} name="diary" id="diary" className='border'></textarea>
<br />
<input ref={weatherRef} type='text' className='border' />
<br />
<input ref={imgTitRef} type='text' className='border' />
<br />
<input ref={imgRef} type='file' className='border' onChange={(e) => handleImageView(e)}/>
<br />
{
imgUrl && (
<>
<Image
src={imgUrl}
alt='preview'
width={200}
height={300}
/>
<button onClick={imgReset} className='border'>지우기</button>
<br />
</>
)
}
<br />
<button className='border' onClick={send}>gogogogogo</button>
</div>
)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The component seems well-structured and includes proper handling of image previews and form data submission. However, there are a few areas that could be improved:

  • The use of alert for user notifications is not considered a best practice for user experience. Consider using a more user-friendly way to display messages, such as a modal or a toast notification.
  • The send function could be improved by adding error handling for the axios.post call. This would allow you to handle any errors that occur during the API request and provide feedback to the user.
  • The Authorization header in the axios.post call is using a custom scheme mlru. Verify that this is the intended scheme and that the backend is configured to handle it.
  • The imgReset function revokes the object URL but does not check if imgUrl is a non-empty string before calling URL.revokeObjectURL(imgUrl). This could potentially lead to an unnecessary call if imgUrl is already an empty string.
  • The handleImageView function creates a new object URL but does not handle the cleanup of this URL if the component unmounts before the user resets the image. Consider adding a cleanup effect using useEffect to revoke any created object URLs when the component unmounts.
  • The send function directly accesses the DOM elements to get their values. While this works, it's not the React way of handling form inputs. Consider using state hooks to manage form input values and changes.
  • The send function checks for the existence of session but does not handle the case where session is null. This could lead to unexpected behavior if the session is not established.
  • The send function assumes that session.user.id and session.user.name are present. Ensure that these values are always available in the session object, or add checks to handle cases where they might be undefined.

Here's an example of how you might refactor the send function to include error handling:

const send = async () => {
  if (!session) {
    // Handle the case where there is no session
    return;
  }
  // ... rest of the validation logic

  try {
    const result = await axios.post("/api/diary", formData, {
      headers: {
        "Content-Type": "multipart/form-data",
        "Authorization": `Bearer ${session.accessToken}`, // Assuming Bearer token is used
      },
    });
    console.log(result.data);
    imgReset();
    // Handle success (e.g., show success message to user)
  } catch (error) {
    console.error(error);
    // Handle error (e.g., show error message to user)
  }
};

Comment on lines +32 to +58
const handleSubmit = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.preventDefault();
if (idRef.current && pwRef.current && nameRef.current) {
const formData = new FormData();
formData.append('user_id', idRef.current.value);
formData.append('password', pwRef.current.value);
formData.append('user_name', nameRef.current.value);
if (imgRef.current && imgRef.current.files && imgRef.current.files.length > 0) {
formData.append('img', imgRef.current.files[0]);
}
const result = await axios.put(
"/api/user",
formData,
{
headers: {
"Content-Type": "multipart/form-data"
}
}
);
if (result.statusText === 'OK') {
console.log('가입에 성공했어요.');
imgReset();
} else {
console.log(result.data.result)
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  •        try {
              const result = await axios.put(
                  "/api/user",
                  formData,
                  {
                      headers: {
                          "Content-Type": "multipart/form-data"
                      }
                  }
              );
              if (result.statusText === 'OK') {
                  console.log('가입에 성공했어요.');
                  imgReset();
              } else {
                  console.log(result.data.result)
              }
    
  •        } catch (error) {
    
  •            console.error('Registration failed:', error);
    
  •            // Handle the error appropriately here
    
  •        }
    

-export default page
+export default Page





<!-- This is an auto-generated comment by CodeRabbit -->

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants