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

プロフィール編集画面 #121

Merged
merged 21 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 24 additions & 22 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,31 @@ const config: StorybookConfig = {
autodocs: true,
},
webpackFinal: async (config) => {
/**
* FIXME 良い方法があればそっちに変更したい。
* SVGに関するルールを削除
*/
config.module.rules = config.module.rules.map((rule) => {
if (
rule &&
rule !== '...' &&
rule.test instanceof RegExp &&
rule.test.test('.svg')
) {
return undefined;
}
return rule;
});
if (config.module && config.module.rules) {
/**
* FIXME 良い方法があればそっちに変更したい。
* SVGに関するルールを削除
*/
config.module.rules = config.module.rules.map((rule) => {
if (
rule &&
rule !== '...' &&
rule.test instanceof RegExp &&
rule.test.test('.svg')
) {
return undefined;
}
return rule;
});

config.module.rules.push({
test: /\.svg$/,
issuer: {
and: [/\.(js|ts)x?$/],
},
use: ['@svgr/webpack'],
});
config.module.rules.push({
test: /\.svg$/,
issuer: {
and: [/\.(js|ts)x?$/],
},
use: ['@svgr/webpack'],
});
}
if (config.resolve?.alias) {
config.resolve.alias = {
...config.resolve.alias,
Expand Down
2 changes: 1 addition & 1 deletion .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Preview } from '@storybook/react';
import theme from '@/theme/theme';
import theme from '../src/theme/theme';
Copy link

@github-actions github-actions bot Dec 13, 2023

Choose a reason for hiding this comment

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

Image description CodeRabbit

インポートパスが変更されていますが、これは意図したものでしょうか? 以前のパスは絶対パスを使用していましたが、新しいパスは相対パスになっています。これにより、ファイル構造が変更された場合に影響を受ける可能性があります。

- import theme from '@/theme/theme';
+ import theme from '../src/theme/theme';

Copy link
Member Author

@takecchi takecchi Dec 13, 2023

Choose a reason for hiding this comment

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

意図した変更になります!

Choose a reason for hiding this comment

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

Image description CodeRabbit

@takecchi 理解しました、ありがとうございます。ただし、絶対パスを使用することで、ファイル構造の変更による影響を最小限に抑えることができます。そのため、可能であれば絶対パスを使用することをお勧めします。

import { withThemeFromJSXProvider } from '@storybook/addon-styling';
import { CssBaseline, ThemeProvider } from '@mui/material';
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
Expand Down
32 changes: 28 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
}
},
"dependencies": {
"@cuculus/cuculus-api": "^0.4.0",
"@cuculus/cuculus-api": "^0.4.1",
"@ducanh2912/next-pwa": "^9.7.1",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
Expand All @@ -32,6 +32,7 @@
"next": "^13.5.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-easy-crop": "^5.0.2",
"swr": "^2.2.1",
"virtua": "^0.17.4"
},
Expand Down
18 changes: 2 additions & 16 deletions src/app/(menu)/(public)/[username]/_components/ProfilePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,15 @@ type Props = {
};
export default function ProfilePage({ fallbackData }: Props) {
const { data, isLoading } = useUser(fallbackData.username, fallbackData);
const { data: authId, isLoading: authorizing } = useAuth();
const { data: authId } = useAuth();
if (!data) {
// FIXME 読み込み中
return <></>;
}
Comment on lines +15 to 19
Copy link

@github-actions github-actions bot Dec 13, 2023

Choose a reason for hiding this comment

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

Image description CodeRabbit

useAuth()フックから返されるisLoadingをチェックしていないようです。これにより、認証データがまだロード中の場合でもコンポーネントがレンダリングされ、予期しない結果を引き起こす可能性があります。authIdが利用可能であることを確認するために、isLoadingのチェックを追加することをお勧めします。

  const { data: authId } = useAuth();
   if (!data) {
+16:   const { data: authId, isLoading: authLoading } = useAuth();
+17:   if (!data || authLoading) {
     // FIXME 読み込み中
     return <></>;
   }

Copy link
Member Author

Choose a reason for hiding this comment

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

ロード中の場合、dataはundefinedで返却されるため、isLoadingの確認はしていません。

Choose a reason for hiding this comment

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

Image description CodeRabbit

@takecchi あなたの説明を理解しました。しかし、useAuth()フックが非同期であり、その結果がすぐに利用可能でない場合(つまり、ロード中の場合)でも、Reactはコンポーネントをレンダリングします。これは、authIdが未定義の状態でコンポーネントが一時的にレンダリングされることを意味します。

この一時的な未定義状態が問題を引き起こすかどうかは、authIdがどのように使用されているかによります。もしauthIdがレンダリングの初期段階で重要な役割を果たしているなら、isLoadingをチェックすることで、データが利用可能になるまでレンダリングを遅延させることが推奨されます。

ただし、あなたのコードの文脈では、authIdが未定義の状態で一時的にレンダリングされても問題ないのであれば、isLoadingのチェックは不要かもしれません。


return (
<PrimaryColumn columnName={data.name} showBack>
<ProfileCard
name={data.name}
username={data.username}
id={data.id}
protected={data._protected}
followingCount={data.followingCount}
followersCount={data.followersCount}
createdAt={data.createdAt}
bio={data.bio}
profileImageUrl={data.profileImageUrl}
url={data.url}
verified={data.verified}
authId={authId}
authorizing={authorizing}
/>
<ProfileCard authId={authId} {...data} />
Comment on lines 22 to +23
Copy link

@github-actions github-actions bot Dec 13, 2023

Choose a reason for hiding this comment

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

Image description CodeRabbit

新しいProfileCardコンポーネントへのプロパティの渡し方が変更されています。この変更は、コードの可読性と保守性を向上させるための良いステップです。ただし、ProfileCardコンポーネントが受け取るすべてのプロパティが適切に型付けされていることを確認してください。これにより、将来的なバグを防ぎ、開発者がコンポーネントの使用方法を理解するのを助けます。

Copy link
Member Author

Choose a reason for hiding this comment

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

型が正しいかという意味合いでいうと、tscで確認が取れるため問題ないかと。

Choose a reason for hiding this comment

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

Image description CodeRabbit

@takecchi ご返信いただきありがとうございます。TypeScriptの型チェックが行われているとのことで安心しました。私のコメントは、ProfileCardコンポーネントが受け取るプロパティが明示的に定義されているかどうかを確認することを推奨していました。これは、将来的なバグを防ぐだけでなく、他の開発者がコンポーネントの使用方法を理解するのに役立つからです。

しかし、あなたのコメントを読む限りでは、既に適切な型チェックが行われているようですので、特に問題はないと思います。引き続き良いコードの書き方を維持してください。

{!isLoading && <UserTimeline user={data} />}
</PrimaryColumn>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ type Story = StoryObj<typeof meta>;
export const NormalFollowButton: Story = {
args: {
userId: 123,
followStatus: 'NotFollowing',
isFollowing: true,
},
};
Original file line number Diff line number Diff line change
@@ -1,83 +1,21 @@
'use client';

import CapsuleButton from '@/app/_components/button/CapsuleButton';
import { ButtonTypeMap } from '@mui/material';
import { MouseEventHandler } from 'react';
import { OverridableStringUnion } from '@mui/types';
import { ButtonPropsVariantOverrides } from '@mui/material/Button';

// static class propertyにして文字列で持たせる?(interfaceどうするか)(enum使う?)
export type FollowStatus =
| 'NotFollowing'
| 'Following'
| 'Pending'
| 'Blocked'
| 'EditProfile';

interface Props {
type Props = {
userId: number;
followStatus: FollowStatus;
}

export function FollowButton({ followStatus }: Props) {
// TODO ボタン処理実装
const follow: MouseEventHandler<HTMLButtonElement> = () => {
// doPost(followActionUrl)
};

// TODO ボタン処理実装
const unfollow: MouseEventHandler<HTMLButtonElement> = () => {
// doDelete(followActionUrl);
};

// TODO ボタン処理実装
const cancelRequest: MouseEventHandler<HTMLButtonElement> = () => {
// doCancel(???);
};

const editProfile: MouseEventHandler<HTMLButtonElement> = () => {
// editProfile(???);
};
isFollowing: boolean;
};

const [color, enabled, text, onClick, variant] = ((): [
ButtonTypeMap['props']['color'],
boolean,
string,
MouseEventHandler<HTMLButtonElement> | undefined,
OverridableStringUnion<
'text' | 'outlined' | 'contained',
ButtonPropsVariantOverrides
>,
] => {
switch (followStatus) {
case 'NotFollowing':
return ['primary', true, 'フォロー', unfollow, 'contained'];
case 'Following':
return ['primary', true, 'フォロー中', follow, 'outlined'];
case 'Pending':
return ['secondary', true, '承認待ち', cancelRequest, 'outlined'];
case 'Blocked':
return [
'warning',
false,
'ブロックされています',
undefined,
'outlined',
];
case 'EditProfile':
return ['primary', true, 'プロフィールを編集', editProfile, 'outlined'];
default:
return ['error', false, '(invalid value)', undefined, 'outlined'];
}
})();
export function FollowButton({ isFollowing }: Props) {
const text = isFollowing ? 'フォロー中' : 'フォロー';

return (
<CapsuleButton
aria-label={text}
color={color}
disabled={!enabled}
onClick={onClick}
variant={variant}
color="primary"
// onClick={onClick}
variant={isFollowing ? 'outlined' : 'contained'}
>
{text}
</CapsuleButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use client';

import { styled } from '@mui/material';

const HeaderImage = styled('div')<{
image?: string;
}>`
display: block;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
aspect-ratio: 3 / 1;
background-color: ${({ theme }) => theme.palette.primary.light};
background-image: ${({ image }) => (image ? `url(${image})` : 'none')};
`;

export default HeaderImage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';

import { Avatar, styled } from '@mui/material';

const UserIcon = styled(Avatar)`
width: 120px;
height: 120px;

margin-top: -80px;
border-color: ${({ theme }) => theme.palette.background.paper};
border-style: solid;

${({ theme }) => theme.breakpoints.down('tablet')} {
width: 92px;
height: 92px;
margin-top: -61px;
}
`;

export default UserIcon;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import CapsuleButton from '@/app/_components/button/CapsuleButton';
import ProfileSettingModal from '@/app/(menu)/(public)/[username]/_components/layouts/ProfileSettingModal';
import { useState } from 'react';
import { useProfile } from '@/swr/client/auth';

export function EditProfileButton() {
const text = 'プロフィールを編集';
const [open, setOpen] = useState<boolean>(false);
const { data } = useProfile();

if (!data) {
return <></>;
}

return (
<>
<CapsuleButton
aria-label={text}
color="primary"
onClick={() => {
setOpen(true);
}}
variant="outlined"
>
{text}
</CapsuleButton>
<ProfileSettingModal
open={open}
bio={data.bio}
displayName={data.name}
src={data.profileImageUrl}
onClose={() => setOpen(false)}
/>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@ export const NormalProfileCard: Story = {
username: 'takecchi',
createdAt: new Date('2023-10-04T18:57:44.373Z'),
profileImageUrl: '/mock/profileAvatarImage.png',
protected: false,
_protected: false,
verified: false,
bio: 'こんにちは。',
url: '',
followersCount: 2,
followingCount: 1,
authId: undefined,
authorizing: false,
},
};
Loading
Loading