Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add demo profile #21

Open
wants to merge 3 commits into
base: dev/[email protected]
Choose a base branch
from
Open

add demo profile #21

wants to merge 3 commits into from

Conversation

d-ben-b
Copy link

@d-ben-b d-ben-b commented Dec 9, 2024

Summary

簡單說一下前因後果,這支PR的目的
實作UserPage
Profile Edit

Uploaded Exam List

Starred Exam List
的三個功能,api為demo的方式呈現

How Has This Been Tested?

附上如何測試的以及測試結果
image
image
image
This pull request introduces a new profile layout and functionality for editing user profiles, along with necessary updates to dependencies. The changes include creating new components for the profile page, sidebar, and form handling, as well as adding new dependencies for form validation and management.

New components and layout:

Form handling and UI components:

  • frontend/ui/Form.tsx: Added form-related components (Form, FormField, FormItem, FormLabel, FormControl, FormMessage) for structured form creation.
  • frontend/ui/input.tsx: Created an Input component using React.forwardRef for handling input fields.

Dependency updates:

  • frontend/package.json: Added @hookform/resolvers, react-hook-form, and zod dependencies to support form validation and management.
  • Updated lock file to include the new dependencies and their resolutions.

Task:

如果這隻 PR 是為了修某個 Jira 的 ticket, 把 link 貼在這

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced ProfileLayout, ProfilePage, and ProfileSidebar components for enhanced profile management.
    • Added ProfileEdit component for user profile editing with validation.
    • Implemented reusable form components: Form, FormField, FormItem, FormLabel, FormControl, and FormMessage.
    • Added Input component for flexible input handling.
  • Dependencies

    • Updated package.json to include @hookform/resolvers, react-hook-form, and zod for form management and validation.

Copy link

coderabbitai bot commented Dec 9, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

This pull request introduces several new components for the profile section of the frontend application. Notable additions include ProfileLayout, ProfilePage, ProfileEdit, and ProfileSidebar, which collectively enhance user interaction with profile management features. The changes also include a set of reusable form components and an input component, facilitating form handling and validation. Additionally, the package.json file has been updated to include new dependencies necessary for form management and validation.

Changes

File Change Summary
frontend/app/profile/layout.tsx Added new component ProfileLayout that includes a sidebar and main content area.
frontend/app/profile/page.tsx Introduced ProfilePage component that encapsulates profile editing functionality.
frontend/components/ProfileEdit.tsx Added ProfileEdit component for editing user profiles with validation using react-hook-form and zod.
frontend/components/ProfileSidebar.tsx Created ProfileSidebar component with navigation links for profile management.
frontend/package.json Updated dependencies: added @hookform/resolvers, react-hook-form, and zod.
frontend/ui/Form.tsx Introduced several form-related components: Form, FormField, FormItem, FormLabel, FormControl, FormMessage.
frontend/ui/Input.tsx Added Input component for creating flexible input fields with ref forwarding.

Possibly related PRs

  • File card #13: The ProfileSidebar component in the main PR may relate to the ExamCard component's navigation functionality, as both involve user interface components that facilitate navigation within the application.

Suggested reviewers

  • owenowenisme

Poem

🐰 In the profile's cozy nook,
New layouts and forms take a look.
With a sidebar to guide the way,
Editing profiles is here to stay!
So hop along, let’s celebrate,
A new journey that feels just great! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (11)
frontend/app/profile/layout.tsx (2)

11-14: Enhance accessibility with semantic HTML elements

Consider using semantic HTML elements to improve accessibility and SEO:

-    <div className="flex">
+    <div className="flex" role="main">
       <ProfileSidebar />
-      <main className="flex-1 p-4">{children}</main>
+      <article className="flex-1 p-4">{children}</article>
     </div>

11-14: Consider adding responsive design support

The current layout might not work well on mobile devices where a full-width sidebar isn't ideal.

Consider:

  1. Making the sidebar collapsible on mobile
  2. Adding media queries to adjust layout for different screen sizes
  3. Using a hamburger menu for mobile navigation

Would you like me to provide an example implementation of these responsive features?

frontend/ui/input.tsx (2)

6-6: Maintain consistent language in comments

The comment is in Chinese while the codebase appears to be primarily in English.

-// 使用 React.forwardRef 來轉發 ref
+// Using React.forwardRef to forward the ref to the input element

4-4: Enhance type safety for className prop

Consider creating a more specific interface for the Input component's props.

-interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
+interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
+  // Add specific prop types here
+  error?: boolean;
+  helperText?: string;
+}
frontend/components/ProfileSidebar.tsx (2)

12-25: Implement i18n for text content

The navigation text is hard-coded in Chinese. Consider implementing internationalization for better maintainability and future language support.

Example using a translation hook:

+import { useTranslations } from 'next-intl';
+
 export default function ProfileSidebar() {
+  const t = useTranslations('profile');
   return (
     // ...
-          編輯個人資料
+          {t('edit_profile')}
     // ...
-          上傳的考題清單
+          {t('uploaded_exams')}
     // ...
-          收藏的考題清單
+          {t('starred_exams')}

6-6: Consider making the width configurable

The sidebar width is hard-coded to w-64. Consider making it configurable through props for better flexibility.

+interface ProfileSidebarProps {
+  width?: 'sm' | 'md' | 'lg';
+}
+
-export default function ProfileSidebar() {
+export default function ProfileSidebar({ width = 'md' }: ProfileSidebarProps) {
+  const widthClasses = {
+    sm: 'w-48',
+    md: 'w-64',
+    lg: 'w-80'
+  };
+
   return (
-    <aside className="w-64 bg-gray-800 p-4 text-white">
+    <aside className={`${widthClasses[width]} bg-gray-800 p-4 text-white`}>
frontend/ui/Form.tsx (2)

7-17: Add aria-labels for better accessibility

FormField and FormItem components should include ARIA attributes to improve accessibility for screen readers.

export const FormField: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
-  return <div>{children}</div>;
+  return <div role="group" aria-label="form field">{children}</div>;
};

export const FormItem: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
-  return <div>{children}</div>;
+  return <div role="group" aria-label="form item">{children}</div>;
};

35-39: Add role="alert" for error messages

FormMessage component should include the role="alert" attribute to improve accessibility for error messages.

export const FormMessage: React.FC<{ children?: React.ReactNode }> = ({
  children,
}) => {
-  return <p className="text-sm text-red-500">{children}</p>;
+  return <p role="alert" className="text-sm text-red-500">{children}</p>;
};
frontend/package.json (1)

Update react-hook-form to latest version 7.54.0

The dependency analysis shows:

  • react-hook-form can be safely updated from 7.53.2 to 7.54.0
    • Compatible with the project's React version (18.2.0)
    • Compatible with @hookform/resolvers which requires ^7.0.0
  • zod 3.23.8 is secure (above the vulnerable version range)
  • @hookform/resolvers is at the latest version (3.9.1)
🔗 Analysis chain

Line range hint 26-40: Verify compatibility and check for security updates

The newly added dependencies have fixed versions:

Let's verify these versions for compatibility and security updates.


Update dependencies to address security vulnerability and version mismatch

  • react-hook-form can be updated from 7.53.2 to 7.54.0
  • zod version 3.23.8 is secure as it's higher than the vulnerable version range (<= 3.22.2)
  • @hookform/resolvers is at the latest version (3.9.1)

Let's verify the compatibility between these packages before recommending the update:


Let me check the peer dependencies directly from the npm registry:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for latest versions and security advisories

# Check latest versions
echo "Checking latest versions..."
for pkg in "@hookform/resolvers" "react-hook-form" "zod"; do
  echo "Package: $pkg"
  curl -s "https://registry.npmjs.org/$pkg" | jq -r '.["dist-tags"].latest'
done

# Check for security advisories
echo "Checking security advisories..."
for pkg in "@hookform/resolvers" "react-hook-form" "zod"; do
  echo "Package: $pkg"
  gh api graphql -f query='{
    securityVulnerabilities(first:5, ecosystem:NPM, package:"'$pkg'") {
      nodes {
        advisory {
          summary
          severity
          publishedAt
        }
        vulnerableVersionRange
        firstPatchedVersion {
          identifier
        }
      }
    }
  }'
done

Length of output: 2393


Script:

#!/bin/bash
# Check package.json files in node_modules for peer dependencies
echo "Checking @hookform/resolvers peer dependencies..."
cat node_modules/@hookform/resolvers/package.json | jq '.peerDependencies'

echo "Checking react-hook-form peer dependencies..."
cat node_modules/react-hook-form/package.json | jq '.peerDependencies'

echo "Checking zod peer dependencies..."
cat node_modules/zod/package.json | jq '.peerDependencies'

Length of output: 705


Script:

#!/bin/bash
# Check peer dependencies from npm registry
echo "Checking @hookform/[email protected] peer dependencies..."
curl -s "https://registry.npmjs.org/@hookform/resolvers/3.9.1" | jq '.peerDependencies'

echo "Checking [email protected] peer dependencies..."
curl -s "https://registry.npmjs.org/react-hook-form/7.54.0" | jq '.peerDependencies'

echo "Checking [email protected] peer dependencies..."
curl -s "https://registry.npmjs.org/zod/3.23.8" | jq '.peerDependencies'

Length of output: 651

frontend/components/ProfileEdit.tsx (2)

18-36: Consider adding more comprehensive validation rules

The schema validation could be enhanced with additional rules:

  • Email domain validation
  • File type validation for avatar
  • Username reserved words check
const ProfileSchema = z.object({
  email: z.string()
    .email({ message: '無效的電子郵件地址' })
+   .refine((email) => {
+     const domain = email.split('@')[1];
+     return !['example.com', 'test.com'].includes(domain);
+   }, { message: '不允許使用測試域名' }),
  avatar: z
    .instanceof(FileList)
    .or(z.undefined())
    .refine(
      (fileList) =>
        !fileList ||
        fileList.length === 0 ||
-       (fileList[0] && fileList[0].size <= 5 * 1024 * 1024),
+       (fileList[0] && 
+         fileList[0].size <= 5 * 1024 * 1024 &&
+         ['image/jpeg', 'image/png', 'image/gif'].includes(fileList[0].type)),
-     { message: '檔案大小不得超過 5MB' },
+     { message: '檔案必須是 JPG、PNG 或 GIF 格式,且大小不得超過 5MB' },
    ),
  username: z
    .string()
    .min(1, { message: '使用者名稱為必填項目' })
    .max(20, { message: '使用者名稱不得超過 20 個字元' })
    .regex(/^[a-zA-Z0-9_]+$/, { message: '使用者名稱僅允許字母、數字和底線' })
+   .refine(
+     (username) => !['admin', 'root', 'system'].includes(username.toLowerCase()),
+     { message: '此使用者名稱已被保留' }
+   ),
});

147-176: Add error simulation to mock functions

The mock API functions should simulate error scenarios for better testing.

const mockUploadAvatar = (file: File): Promise<void> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
+     // Simulate random errors
+     if (Math.random() < 0.2) {
+       reject(new Error('Failed to upload avatar'));
+       return;
+     }
      console.log('Avatar uploaded:', file);
      resolve();
    }, 2000);
  });
};

const mockUpdateUsername = (username: string): Promise<void> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
+     // Simulate conflict errors
+     if (username.toLowerCase() === 'taken') {
+       reject(new Error('Username already taken'));
+       return;
+     }
      console.log('Username updated to:', username);
      resolve();
    }, 1000);
  });
};

const mockFetchUserData = (): Promise<{ email: string; username: string }> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
+     // Simulate authentication errors
+     if (Math.random() < 0.1) {
+       reject(new Error('Authentication failed'));
+       return;
+     }
      resolve({
        email: '[email protected]',
        username: 'user_name',
      });
    }, 1000);
  });
};
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ba8775b and 69e25e3.

⛔ Files ignored due to path filters (1)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • frontend/app/profile/layout.tsx (1 hunks)
  • frontend/app/profile/page.tsx (1 hunks)
  • frontend/components/ProfileEdit.tsx (1 hunks)
  • frontend/components/ProfileSidebar.tsx (1 hunks)
  • frontend/package.json (2 hunks)
  • frontend/ui/Form.tsx (1 hunks)
  • frontend/ui/input.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/app/profile/page.tsx

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.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (2)
frontend/components/ProfileEdit.tsx (2)

98-112: Add avatar preview functionality

Consider adding an avatar preview to improve user experience when uploading profile pictures.

+ const [avatarPreview, setAvatarPreview] = useState<string | null>(null);
+
+ const handleAvatarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+   const file = e.target.files?.[0];
+   if (file) {
+     const reader = new FileReader();
+     reader.onloadend = () => {
+       setAvatarPreview(reader.result as string);
+     };
+     reader.readAsDataURL(file);
+   } else {
+     setAvatarPreview(null);
+   }
+ };

  <FormField>
    <FormItem>
      <FormLabel>Avatar</FormLabel>
+     {avatarPreview && (
+       <div className="mt-2">
+         <img
+           src={avatarPreview}
+           alt="Avatar preview"
+           className="h-20 w-20 rounded-full object-cover"
+         />
+       </div>
+     )}
      <FormControl>
        <input
          type="file"
          accept="image/*"
-         {...register('avatar')}
+         {...register('avatar', {
+           onChange: handleAvatarChange
+         })}
          className="mt-1 block w-full rounded border border-gray-300 p-2"
        />
      </FormControl>
      <FormMessage>{errors.avatar?.message}</FormMessage>
    </FormItem>
  </FormField>

139-169: Enhance mock API implementations

The current mock implementations don't adequately simulate real-world scenarios. Consider adding error cases, input validation, and variable network conditions.

  const mockUploadAvatar = (file: File): Promise<void> => {
    return new Promise((resolve, reject) => {
+     // Simulate network conditions
+     const latency = Math.random() * 2000 + 1000;
+     // Simulate random failures
+     const shouldFail = Math.random() < 0.1;
+     
+     // Validate file size (e.g., max 5MB)
+     if (file.size > 5 * 1024 * 1024) {
+       reject(new Error('File size exceeds 5MB limit'));
+       return;
+     }
+     
      setTimeout(() => {
+       if (shouldFail) {
+         reject(new Error('Network error'));
+         return;
+       }
        console.log('Avatar uploaded:', file);
        resolve();
-     }, 2000);
+     }, latency);
    });
  };

  const mockUpdateUsername = (username: string): Promise<void> => {
    return new Promise((resolve, reject) => {
+     // Simulate network conditions
+     const latency = Math.random() * 1000 + 500;
+     // Simulate random failures
+     const shouldFail = Math.random() < 0.1;
+     
+     // Validate username format
+     if (!/^[a-zA-Z0-9_]+$/.test(username)) {
+       reject(new Error('Invalid username format'));
+       return;
+     }
+     
      setTimeout(() => {
+       if (shouldFail) {
+         reject(new Error('Network error'));
+         return;
+       }
        console.log('Username updated to:', username);
        resolve();
-     }, 1000);
+     }, latency);
    });
  };
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 69e25e3 and 3bc537a.

📒 Files selected for processing (2)
  • frontend/components/ProfileEdit.tsx (1 hunks)
  • frontend/components/ProfileSidebar.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/components/ProfileSidebar.tsx

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.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (5)
frontend/ui/Form.tsx (3)

3-5: Consider enhancing Form component with additional props

The Form component could be more flexible by accepting and forwarding common form props like onSubmit, className, etc.

-export const Form: React.FC<{ children: React.ReactNode }> = ({ children }) => {
-  return <form>{children}</form>;
+export const Form: React.FC<React.ComponentPropsWithoutRef<'form'>> = ({
+  children,
+  ...props
+}) => {
+  return <form {...props}>{children}</form>;
};

7-17: Consider consolidating or differentiating FormField and FormItem

Both components are identical div wrappers. Consider either:

  1. Consolidating them into a single component
  2. Adding distinct roles and styling to differentiate their purposes
 export const FormField: React.FC<{ children: React.ReactNode }> = ({
   children,
+  ...props
 }) => {
-  return <div>{children}</div>;
+  return <div role="group" {...props}>{children}</div>;
 };

 export const FormItem: React.FC<{ children: React.ReactNode }> = ({
   children,
+  ...props
 }) => {
-  return <div>{children}</div>;
+  return <div className="form-item space-y-2" {...props}>{children}</div>;
 };

44-44: Translate comment to English for consistency

-  role = 'alert', // 默認值設置為 "alert"
+  role = 'alert', // Default value set to "alert"
frontend/components/ProfileEdit.tsx (2)

81-109: Add retry mechanism for failed updates

While the error handling is good, consider adding a retry mechanism for failed API calls.

+const MAX_RETRIES = 3;
+const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
+
+const withRetry = async <T>(
+  fn: () => Promise<T>,
+  retries = MAX_RETRIES
+): Promise<T> => {
+  try {
+    return await fn();
+  } catch (error) {
+    if (retries > 0) {
+      await wait(1000);
+      return withRetry(fn, retries - 1);
+    }
+    throw error;
+  }
+};

 const onSubmit = async (data: ProfileFormData) => {
   try {
     const validatedData = ProfileSchema.parse(data);
     let updates = [];

     if (data.avatar && data.avatar.length > 0) {
-      updates.push(mockUploadAvatar(data.avatar[0]));
+      updates.push(withRetry(() => mockUploadAvatar(data.avatar[0])));
     }

-    updates.push(mockUpdateUsername(data.username));
+    updates.push(withRetry(() => mockUpdateUsername(data.username)));

     await Promise.all(updates);
     // ...

187-217: Add type definitions for mock API functions

Consider adding type definitions to better document the mock API interface.

+interface UserData {
+  email: string;
+  username: string;
+}
+
+interface ApiResponse<T> {
+  data: T;
+  status: number;
+}

-const mockUploadAvatar = (file: File): Promise<void> => {
+const mockUploadAvatar = (file: File): Promise<ApiResponse<void>> => {
   return new Promise((resolve) => {
     setTimeout(() => {
       console.log('Avatar uploaded:', file);
-      resolve();
+      resolve({ data: undefined, status: 200 });
     }, 2000);
   });
 };

-const mockUpdateUsername = (username: string): Promise<void> => {
+const mockUpdateUsername = (username: string): Promise<ApiResponse<void>> => {
   // ... similar changes ...
 };

-const mockFetchUserData = (): Promise<{ email: string; username: string }> => {
+const mockFetchUserData = (): Promise<ApiResponse<UserData>> => {
   // ... similar changes ...
 };
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3bc537a and 4477baa.

📒 Files selected for processing (5)
  • frontend/app/profile/layout.tsx (1 hunks)
  • frontend/components/ProfileEdit.tsx (1 hunks)
  • frontend/components/ProfileSidebar.tsx (1 hunks)
  • frontend/ui/Form.tsx (1 hunks)
  • frontend/ui/input.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/app/profile/layout.tsx
  • frontend/ui/input.tsx
  • frontend/components/ProfileSidebar.tsx
🔇 Additional comments (2)
frontend/ui/Form.tsx (1)

19-31: LGTM! Accessible label implementation

The FormLabel component correctly implements accessibility with htmlFor prop and appropriate styling.

frontend/components/ProfileEdit.tsx (1)

1-39: LGTM! Well-structured schema validation

The imports are well-organized and the Zod schema implements comprehensive validation for email, avatar, and username fields.

Comment on lines +58 to +79
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
let mounted = true;
const fetchUserData = async () => {
try {
setIsLoading(true);
const userData = await mockFetchUserData();
if (!mounted) return;
setValue('email', userData.email);
setValue('username', userData.username);
} catch (error) {
console.error('Failed to fetch user data:', error);
// Use proper error notification system
} finally {
if (mounted) setIsLoading(false);
}
};
fetchUserData();
return () => {
mounted = false;
};
}, [setValue]);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error state handling for failed data fetches

While the loading state is handled well, there's no UI feedback for fetch errors.

 export default function ProfileEdit() {
+  const [error, setError] = useState<string | null>(null);
   // ...
   const fetchUserData = async () => {
     try {
       setIsLoading(true);
+      setError(null);
       const userData = await mockFetchUserData();
       if (!mounted) return;
       setValue('email', userData.email);
       setValue('username', userData.username);
     } catch (error) {
       console.error('Failed to fetch user data:', error);
+      if (mounted) {
+        setError('Failed to load profile data. Please try again later.');
+      }
     } finally {
       if (mounted) setIsLoading(false);
     }
   };
   // ...
+  if (error) {
+    return <div role="alert" className="text-red-500">{error}</div>;
+  }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +111 to +184
return (
<Form>
<form
onSubmit={handleSubmit(onSubmit)}
className="space-y-4"
aria-busy={isSubmitting}
>
{/* Email 欄位(僅顯示,不可編輯) */}
<FormField>
<FormItem>
<FormLabel htmlFor="email">Email</FormLabel>
<FormControl>
<Input
id="email"
type="email"
{...register('email')}
disabled
aria-readonly="true"
aria-label="Email address"
className="mt-1 block w-full rounded border border-gray-300 p-2 text-black"
/>
</FormControl>
<FormMessage role="alert">{errors.email?.message}</FormMessage>
</FormItem>
</FormField>

{/* Avatar 上傳欄位 */}
<FormField>
<FormItem>
<FormLabel htmlFor="avatar">Avatar</FormLabel>
<FormControl>
<input
type="file"
accept="image/*"
{...register('avatar')}
disabled={isSubmitting}
className="mt-1 block w-full rounded border border-gray-300 p-2"
/>
</FormControl>
<FormMessage>{errors.avatar?.message}</FormMessage>
</FormItem>
</FormField>

{/* Username 編輯欄位 */}
<FormField>
<FormItem>
<FormLabel htmlFor="username">Username</FormLabel>
<FormControl>
<Input
className="mt-1 block w-full rounded border border-gray-300 p-2 text-black"
type="text"
{...register('username')}
disabled={isSubmitting}
placeholder="輸入您的使用者名稱"
/>
</FormControl>
<FormMessage>{errors.username?.message}</FormMessage>
</FormItem>
</FormField>

{/* 提交按鈕 */}
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? (
<>
<span className="loading-spinner" aria-hidden="true" />
<span>儲存中...</span>
</>
) : (
'儲存變更'
)}
</Button>
</form>
</Form>
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add loading state UI for initial data fetch

While the form submission loading state is handled, the initial data fetch loading state isn't visible to users.

 return (
+  {isLoading ? (
+    <div role="status" className="animate-pulse space-y-4">
+      <div className="h-10 bg-gray-200 rounded"></div>
+      <div className="h-10 bg-gray-200 rounded"></div>
+      <div className="h-10 bg-gray-200 rounded"></div>
+    </div>
+  ) : (
   <Form>
     <form
       onSubmit={handleSubmit(onSubmit)}
       className="space-y-4"
       aria-busy={isSubmitting}
     >
       {/* ... form fields ... */}
     </form>
   </Form>
+  )}
 );

Committable suggestion skipped: line range outside the PR's diff.

} from '@/ui/Form';

// Define the validation schema using Zod
const ProfileSchema = z.object({
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit pick: zod schema 等 zod相關的東西 可以放入 module/zod/xxxSchema.ts之中,未來會比較好管理所有驗證的部分

${pathname === path ? 'bg-gray-700' : 'hover:bg-gray-700'}
`;

return (
Copy link
Collaborator

Choose a reason for hiding this comment

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

這個也希望可以把資料抽到別的檔案 然後以xxx.map的方式來引入

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.

2 participants