Skip to content
Open
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
189 changes: 187 additions & 2 deletions src/components/settings/pages/security-settings-content.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,188 @@
import React, { useState } from 'react';
import { createAuthClient } from 'better-auth/client';
Copy link

Choose a reason for hiding this comment

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

Importing from better-auth/client but existing codebase uses better-auth/react (see src/lib/auth/client.ts:3). Use the shared authClient from @/lib/auth/client instead of creating a new instance.

Suggested change
import { createAuthClient } from 'better-auth/client';
import { authClient } from '@/lib/auth/client';

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/settings/pages/security-settings-content.tsx
Line: 2:2

Comment:
Importing from `better-auth/client` but existing codebase uses `better-auth/react` (see `src/lib/auth/client.ts:3`). Use the shared `authClient` from `@/lib/auth/client` instead of creating a new instance.

```suggestion
import { authClient } from '@/lib/auth/client';
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { AlertCircle, LogOut, Shield, Lock } from 'lucide-react';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { forceLogout } from "@/lib/auth/logout";


export function SecuritySettingsContent() {
return <>Security coming soon...</>;
}
const [currentPassword, setCurrentPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);

const authClient = createAuthClient()
Copy link

Choose a reason for hiding this comment

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

Creates a new auth client instance on every render. Move outside component or memoize.

Suggested change
const authClient = createAuthClient()
const authClient = React.useMemo(() => createAuthClient(), []);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/settings/pages/security-settings-content.tsx
Line: 20:20

Comment:
Creates a new auth client instance on every render. Move outside component or memoize.

```suggestion
  const authClient = React.useMemo(() => createAuthClient(), []);
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah this is really important, you should just import it from where greptile mentioned


const handleLogout = async () => {
await forceLogout();
};

const handleRevokeAllSessions = async () => {
try {
setLoading(true);
await authClient.revokeOtherSessions();
setMessage({ type: 'success', text: 'All other sessions have been revoked' });
} catch (error) {
setMessage({ type: 'error', text: 'Failed to revoke sessions. Please try again.' });
} finally {
setLoading(false);
}
};

const handleChangePassword = async () => {
setMessage(null);

if (newPassword !== confirmPassword) {
setMessage({ type: 'error', text: 'New passwords do not match' });
return;
}

if (newPassword.length < 8) {
setMessage({ type: 'error', text: 'Password must be at least 8 characters long' });
return;
}

try {
setLoading(true);
await authClient.changePassword({
currentPassword,
newPassword,
revokeOtherSessions: false,
});
setMessage({ type: 'success', text: 'Password changed successfully' });
setCurrentPassword('');
setNewPassword('');
setConfirmPassword('');
} catch (error) {
setMessage({ type: 'error', text: 'Failed to change password. Please check your current password.' });
} finally {
setLoading(false);
}
};

return (
<div className="space-y-6 max-h-[calc(100vh-200px)] overflow-y-auto px-1">
<div>
<h3 className="text-lg font-medium">Security</h3>
<p className="text-sm text-muted-foreground">
Manage your account security and sessions
</p>
</div>
<Separator />

{message && (
<Alert variant={message.type === 'error' ? 'destructive' : 'default'}>
<AlertCircle className="h-4 w-4" />
<AlertDescription>{message.text}</AlertDescription>
</Alert>
)}

<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Lock className="h-5 w-5" />
Change Password
</CardTitle>
<CardDescription>
Update your password to keep your account secure
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="current-password">Current Password</Label>
<Input
Copy link
Collaborator

Choose a reason for hiding this comment

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

We have a way to do forms that isn't manual. See the login and signup pages for examples.

id="current-password"
type="password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
disabled={loading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="new-password">New Password</Label>
<Input
id="new-password"
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
disabled={loading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="confirm-password">Confirm New Password</Label>
<Input
id="confirm-password"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
disabled={loading}
/>
</div>
<Button onClick={handleChangePassword} disabled={loading}>
Copy link

Choose a reason for hiding this comment

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

Button is not disabled when required fields are empty. Users can submit with empty passwords.

Suggested change
<Button onClick={handleChangePassword} disabled={loading}>
<Button onClick={handleChangePassword} disabled={loading || !currentPassword || !newPassword || !confirmPassword}>
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/settings/pages/security-settings-content.tsx
Line: 128:128

Comment:
Button is not disabled when required fields are empty. Users can submit with empty passwords.

```suggestion
            <Button onClick={handleChangePassword} disabled={loading || !currentPassword || !newPassword || !confirmPassword}>
```

How can I resolve this? If you propose a fix, please make it concise.

{loading ? 'Updating...' : 'Update Password'}
</Button>
</div>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Shield className="h-5 w-5" />
Session Management
</CardTitle>
<CardDescription>
Manage your active sessions across all devices
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">Logout from all devices</p>
<p className="text-sm text-muted-foreground">
End all sessions except the current one
</p>
</div>
<Button
variant="outline"
onClick={handleRevokeAllSessions}
disabled={loading}
>
Revoke All Sessions
</Button>
</div>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<LogOut className="h-5 w-5" />
Logout
</CardTitle>
<CardDescription>
Sign out of your account on this device
</CardDescription>
</CardHeader>
<CardContent>
<Button
variant="destructive"
onClick={handleLogout}
disabled={loading}
className="w-full sm:w-auto"
>
<LogOut className="mr-2 h-4 w-4" />
Logout
</Button>
</CardContent>
</Card>
</div>
);
}
2 changes: 1 addition & 1 deletion src/components/settings/settings-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const SettingsDialog = NiceModal.create(() => {
<Dialog open={modal.visible} onOpenChange={modal.hide}>
<DialogContent
hideCloseButton
className="sm:max-w-[min(calc(100vw-2rem),_42rem)] md:h-[60vh] h-[85vh] p-0 gap-0 border overflow-hidden"
className="sm:max-w-[min(calc(100vw-2rem),_42rem)] md:h-[60vh] h-[85vh] p-0 gap-0 border overflow-y-auto"
>
<Tabs
defaultValue="profile"
Expand Down