Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
.title {
text-align: center;
font-size: var(--text-2xl);
margin: var(--space-2xl) 0 var(--space-xl) 0;
}

.accountContainer {
display: flex;
gap: var(--space-2xl);
width: fit-content;
margin: 20px auto 20px auto;
border: 1px solid var(--color-base);
border-radius: 10px;
padding: var(--space-sm) var(--space-base);
}

@media screen and (min-width: 600px) {
.accountContainer {
display: flex;
gap: var(--space-2xl);
}
}

Comment on lines +17 to +23

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

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

The media query for min-width: 600px repeats the same display: flex and gap properties that are already defined in the base .accountContainer rule (lines 8-9). This is redundant and can be removed. Only properties that differ from the base should be included in media queries.

Suggested change
@media screen and (min-width: 600px) {
.accountContainer {
display: flex;
gap: var(--space-2xl);
}
}

Copilot uses AI. Check for mistakes.
@media screen and (max-width: 599px) {
.accountContainer {
display: flex;
gap: var(--space-lg);
flex-direction: column;
align-items: center;
}
}

.iconWrapper {
display: flex;
width: 150px;
gap: var(--space-sm);
}

.icon {
width: 50px;
height: 50px;
fill: var(--color-base);
}

.iconLabel {
display: flex;
align-items: center;
font-size: var(--text-xl);
}

.button {
font-size: var(--color-base);

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

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

The font-size property is incorrectly set to var(--color-base), which is a color value (#b09757). This should be a text size variable like var(--text-base) or var(--text-lg). This will cause the button text to render incorrectly.

Suggested change
font-size: var(--color-base);
font-size: var(--text-base);

Copilot uses AI. Check for mistakes.
color: var(--background-color-base);
background-color: var(--color-base);
border: none;
padding: 0 var(--space-sm);
border-radius: 10px;
cursor: pointer;
height: 50px;
}

.button:disabled {
background-color: var(--color-disabled);
cursor: not-allowed;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useEffect, useState, type FC } from 'react';
// better-auth
import { authClient } from '../../../../lib/auth';
// icons
import { FaDiscord, FaGoogle } from 'react-icons/fa6';
// css
import styles from './index.module.css';

type Provider = 'discord' | 'google';
type LinkedSocialAccount = {
provider: Provider;
isLinked: boolean;
};

const SocialLink: FC = () => {
const [linkedAccounts, setLinkedAccounts] = useState<LinkedSocialAccount[]>([]);

useEffect(() => {
const fetchSession = async () => {
const session = await authClient.listAccounts();
if (session.data) {
for (const account of session.data) {
setLinkedAccounts((prev) => [
...prev,
{ provider: account.providerId as 'discord' | 'google', isLinked: true },

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

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

The type assertion 'as "discord" | "google"' assumes that account.providerId will always be one of these two values. If the API returns other provider types or the providerId doesn't match exactly, this could lead to incorrect behavior. Consider validating the providerId before adding it to the state, or using a type guard to ensure type safety.

Copilot uses AI. Check for mistakes.
]);
}
Comment on lines +22 to +27

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

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

Calling setLinkedAccounts inside a loop will trigger multiple re-renders and may not capture all accounts correctly due to the asynchronous nature of state updates. The previous state may not include accounts added in earlier loop iterations. Consider building the complete array first and then setting state once, or use a functional update pattern that correctly accumulates all accounts.

Suggested change
for (const account of session.data) {
setLinkedAccounts((prev) => [
...prev,
{ provider: account.providerId as 'discord' | 'google', isLinked: true },
]);
}
const accounts: LinkedSocialAccount[] = session.data.map((account) => ({
provider: account.providerId as Provider,
isLinked: true,
}));
setLinkedAccounts(accounts);

Copilot uses AI. Check for mistakes.
} else {
console.log('No linked accounts found.');
Comment on lines +28 to +29

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

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

Using console.log in production code is not recommended. The codebase doesn't use console.log elsewhere in the routes directory. If this logging is needed for debugging, it should be removed before merging. If it's intentional, consider using a proper logging library or removing it entirely since an empty data array is a valid state that doesn't require logging.

Suggested change
} else {
console.log('No linked accounts found.');

Copilot uses AI. Check for mistakes.
}
};
fetchSession();
Comment on lines +18 to +32

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

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

The async function fetchSession inside useEffect lacks error handling. If authClient.listAccounts() fails, the error will be unhandled and could cause the component to fail silently. Consider adding a try-catch block and providing appropriate error feedback to the user, consistent with error handling patterns used elsewhere in the codebase.

Copilot uses AI. Check for mistakes.
}, []);

const handleDiscordLinkSocial = async () => {
await authClient.linkSocial({
provider: 'discord',
callbackURL: `${import.meta.env.VITE_REDIRECT_URL satisfies string}profile`,
});
};
const handleGoogleLinkSocial = async () => {
await authClient.linkSocial({
provider: 'google',
callbackURL: `${import.meta.env.VITE_REDIRECT_URL satisfies string}profile`,
});
};
Comment on lines +35 to +46

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

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

The async functions handleDiscordLinkSocial and handleGoogleLinkSocial lack error handling. If authClient.linkSocial fails, the error will be unhandled. Consider adding try-catch blocks and providing user feedback via toast notifications, consistent with error handling patterns used elsewhere in the codebase (see products/frontend/src/routes/Profile/index.tsx lines 22, 33).

Copilot uses AI. Check for mistakes.

return (
<>
<h2 className={styles.title}>アカウント連携</h2>
<div className={styles.accountContainer}>
<div className={styles.iconWrapper}>
<FaDiscord className={styles.icon} />
<p className={styles.iconLabel}>Discord</p>
</div>
{linkedAccounts.find((account) => account.provider === 'discord' && account.isLinked) ? (
<button className={styles.button} disabled={true}>
連携済み
</button>
) : (
<button className={styles.button} onClick={handleDiscordLinkSocial} disabled={false}>
連携する
</button>
Comment on lines +57 to +63

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

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

The disabled prop is explicitly set to true and false, which is redundant. In React/JSX, you can simply use disabled without a value for true, and omit the prop entirely for false. This would make the code cleaner and more idiomatic.

Copilot uses AI. Check for mistakes.
)}
</div>
<div className={styles.accountContainer}>
<div className={styles.iconWrapper}>
<FaGoogle className={styles.icon} />
<p className={styles.iconLabel}>Google</p>
</div>
{linkedAccounts.find((account) => account.provider === 'google' && account.isLinked) ? (
<button className={styles.button} disabled={true}>
連携済み
</button>
) : (
<button className={styles.button} onClick={handleGoogleLinkSocial} disabled={false}>
連携する
</button>
Comment on lines +72 to +78

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

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

The disabled prop is explicitly set to true and false, which is redundant. In React/JSX, you can simply use disabled without a value for true, and omit the prop entirely for false. This would make the code cleaner and more idiomatic.

Copilot uses AI. Check for mistakes.
)}
</div>
</>
);
};

export default SocialLink;
1 change: 1 addition & 0 deletions products/frontend/src/routes/Profile/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as SocialLink } from './SocialLink';
3 changes: 3 additions & 0 deletions products/frontend/src/routes/Profile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { useForm, type SubmitHandler } from 'react-hook-form';
import { FormButton, Loading, Error, Title } from '../../share';
// toast
import { toast } from 'react-hot-toast';
// components
import { SocialLink } from './components';
// css
import styles from './index.module.css';

Expand Down Expand Up @@ -91,6 +93,7 @@ const Profile: FC = () => {

<FormButton content="更新" onClick={handleSubmit(onSubmit)} disabled={userProfileMutation.isPending} />
</form>
<SocialLink />
</>
)}
</>
Expand Down
Loading