Skip to content

Commit 2f87a11

Browse files
committed
settings delete front
1 parent 799c3c0 commit 2f87a11

6 files changed

Lines changed: 158 additions & 24 deletions

File tree

backend/src/main/java/com/rk/postguard/config/SecurityConfig.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, CorsConfigurat
3636
.cors(cors -> cors.configurationSource(corsConfigurationSource))
3737
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
3838
.authorizeHttpRequests(auth -> auth
39-
.requestMatchers("/api/v1/auth/**","/api/v1/analytics/**","/ping","/health","/actuator/**" ,"/ws/**").permitAll()
40-
.requestMatchers("/api/v1/users/**").authenticated()
39+
.requestMatchers("/api/v1/auth/**","/api/v1/users/**","/api/v1/analytics/**","/ping","/health","/actuator/**" ,"/ws/**").permitAll()
40+
// .requestMatchers(HttpMethod.DELETE, ).authenticated()
4141
.anyRequest().authenticated()
4242
)
4343
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

backend/src/main/java/com/rk/postguard/controllers/UserController.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
import com.rk.postguard.service.UserService;
44
import lombok.RequiredArgsConstructor;
55
import org.springframework.http.ResponseEntity;
6-
import org.springframework.security.core.annotation.AuthenticationPrincipal;
7-
import org.springframework.security.core.userdetails.UserDetails;
6+
import org.springframework.security.core.Authentication;
87
import org.springframework.web.bind.annotation.DeleteMapping;
98
import org.springframework.web.bind.annotation.RequestMapping;
109
import org.springframework.web.bind.annotation.RestController;
@@ -16,9 +15,8 @@ public class UserController {
1615
private final UserService userService;
1716

1817
@DeleteMapping("/me")
19-
public ResponseEntity<?> deleteAccount(@AuthenticationPrincipal UserDetails userDetails
20-
){
21-
String username = userDetails.getUsername();
18+
public ResponseEntity<?> deleteAccount(Authentication auth){
19+
String username = auth.getName();
2220
userService.deleteUser(username);
2321
return ResponseEntity.noContent().build();
2422
}

backend/src/main/java/com/rk/postguard/security/JwtAuthenticationFilter.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,30 @@
88
import lombok.RequiredArgsConstructor;
99
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
1010
import org.springframework.security.core.context.SecurityContextHolder;
11+
import org.springframework.security.core.userdetails.UserDetails;
12+
import org.springframework.security.core.userdetails.UserDetailsService;
1113
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
1214
import org.springframework.stereotype.Component;
1315
import org.springframework.util.StringUtils;
1416
import org.springframework.web.filter.OncePerRequestFilter;
1517

1618
import java.io.IOException;
17-
import java.util.ArrayList;
19+
1820

1921
@Component
2022
@RequiredArgsConstructor
2123
public class JwtAuthenticationFilter extends OncePerRequestFilter {
2224
private final JwtTokenProvider jwtTokenProvider;
23-
25+
private final UserDetailsService userDetailsService;
2426

2527
@Override
2628
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
2729
try{
2830
String jwt = getJwtFromRequest(request);
2931
if(StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)){
3032
String username = jwtTokenProvider.getUsernameFromToken(jwt);
31-
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
33+
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
34+
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, userDetails.getAuthorities());
3235
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
3336
SecurityContextHolder.getContext().setAuthentication(authentication);
3437
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.rk.postguard.service;
2+
3+
4+
import com.rk.postguard.entity.User;
5+
import com.rk.postguard.repositories.UserRepository;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.security.core.userdetails.UserDetails;
8+
import org.springframework.security.core.userdetails.UserDetailsService;
9+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
10+
import org.springframework.stereotype.Service;
11+
12+
@Service
13+
@RequiredArgsConstructor
14+
public class CustomUserDetailsService implements UserDetailsService {
15+
private final UserRepository userRepository;
16+
17+
@Override
18+
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
19+
20+
User user = userRepository.findByUsername(username)
21+
.orElseThrow(() ->
22+
new UsernameNotFoundException("User not found"));
23+
24+
return org.springframework.security.core.userdetails.User
25+
.builder()
26+
.username(user.getUsername())
27+
.password(user.getPasswordHash())
28+
.authorities("ROLE_USER")
29+
.build();
30+
}
31+
}

frontend/src/pages/SettingsPage.tsx

Lines changed: 109 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import { useEffect, useState } from "react";
22
import { useSettings } from "../hooks/useSettings";
3-
import { AlertCircle, ArrowLeft, Bell, Clock, Moon, RefreshCw, Save, Sun } from "lucide-react";
3+
import { AlertCircle, ArrowLeft, Bell, Clock, Moon, RefreshCw, Save, Sun, Trash2 } from "lucide-react";
44
import { motion } from 'framer-motion';
55
import { GlassCard } from "../components/ui/GlassCard";
66
import { AnimatedButton } from "../components/ui/AnimatedButton";
7-
import { useTheme } from '../context/ThemeContext';
7+
import { useTheme } from '../context/ThemeContext';
88
import { useNavigate } from "react-router-dom";
9+
import { usersApi } from "../services/api";
910

1011

1112
export const SettingsPage = () => {
1213
const navigate = useNavigate();
1314
const { settings, loading, saving, updateSettings } = useSettings();
14-
const { setTheme } = useTheme();
15+
const [showDeleteModal, setShowDeleteModal] = useState(false);
16+
const [deleteConfirmation, setDeleteConfirmation] = useState('');
17+
18+
const { setTheme } = useTheme();
1519
const [formData, setFormData] = useState({
1620
captureIntervalSeconds: 30,
1721
notificationsEnabled: true,
@@ -23,6 +27,17 @@ export const SettingsPage = () => {
2327
theme: 'dark' as 'dark' | 'light',
2428
});
2529

30+
const handleDeleteAccount = async () => {
31+
if (deleteConfirmation !== 'DELETE') return;
32+
33+
try {
34+
await usersApi.deleteAccount();
35+
localStorage.clear();
36+
navigate('/login');
37+
} catch (error) {
38+
console.error('Failed to delete account', error);
39+
}
40+
};
2641

2742

2843
useEffect(() => {
@@ -68,13 +83,13 @@ export const SettingsPage = () => {
6883

6984
return (
7085
<div className='min-h-screen p-6 flex flex-col gap-10'>
71-
<button
72-
onClick={() => navigate(-1)}
73-
className="flex items-center gap-2 text-slate-400 hover:text-white transition-colors mb-8 cursor-pointer"
74-
>
75-
<ArrowLeft className="w-4 h-4" />
76-
Back to Dashboard
77-
</button>
86+
<button
87+
onClick={() => navigate(-1)}
88+
className="flex items-center gap-2 text-slate-400 hover:text-white transition-colors mb-8 cursor-pointer"
89+
>
90+
<ArrowLeft className="w-4 h-4" />
91+
Back to Dashboard
92+
</button>
7893

7994
<motion.div
8095
initial={{ y: -20, opacity: 0 }}
@@ -186,8 +201,8 @@ export const SettingsPage = () => {
186201
key={level}
187202
onClick={() => setFormData({ ...formData, notificationSensitivity: level })}
188203
className={`px-4 py-3 rounded-xl transition-all ${formData.notificationSensitivity === level
189-
? 'bg-purple-600 text-white'
190-
: 'glass hover:glass-strong'
204+
? 'bg-purple-600 text-white'
205+
: 'glass hover:glass-strong'
191206
}`}
192207
>
193208
<span className="capitalize">{level}</span>
@@ -293,8 +308,8 @@ export const SettingsPage = () => {
293308
key={themeOption}
294309
onClick={() => handleThemeChange(themeOption)}
295310
className={`px-4 py-3 rounded-xl transition-all flex items-center justify-center gap-2 ${formData.theme === themeOption
296-
? 'bg-yellow-600 text-white'
297-
: 'glass hover:glass-strong'
311+
? 'bg-yellow-600 text-white'
312+
: 'glass hover:glass-strong'
298313
}`}
299314
>
300315
{themeOption === 'dark' ? <Moon className="w-4 h-4" /> : <Sun className="w-4 h-4" />}
@@ -305,6 +320,86 @@ export const SettingsPage = () => {
305320
</div>
306321
</GlassCard>
307322

323+
{/* Delete Account */}
324+
<GlassCard>
325+
<div className="flex items-center gap-3 mb-4">
326+
<div className="p-3 glass rounded-xl bg-red-500/20">
327+
<Trash2 className="w-6 h-6 text-red-400" />
328+
</div>
329+
<div>
330+
<h2 className="text-xl font-semibold text-red-400">Danger Zone</h2>
331+
<p className="text-sm text-slate-400">Irreversible account actions</p>
332+
</div>
333+
</div>
334+
335+
<div className="flex flex-col items-start gap-5 ">
336+
<p className="text-sm text-slate-400">
337+
Deleting your account will remove all stored posture data and analytics history. This cannot be undone.
338+
</p>
339+
<button
340+
onClick={() => setShowDeleteModal(true)}
341+
className=" px-6 py-2.5 bg-red-500/10 hover:bg-red-500/20 border border-red-500/30 rounded-xl text-red-400 transition-all font-medium"
342+
>
343+
Delete My Account
344+
</button>
345+
</div>
346+
</GlassCard>
347+
{/* Delete Confirmation Modal */}
348+
{showDeleteModal && (
349+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
350+
<motion.div
351+
initial={{ scale: 0.9, opacity: 0 }}
352+
animate={{ scale: 1, opacity: 1 }}
353+
className="glass-strong rounded-2xl p-6 max-w-md w-full"
354+
>
355+
<h3 className="text-xl font-semibold text-red-400 mb-4">
356+
Delete Account?
357+
</h3>
358+
359+
<div className="space-y-3 mb-6 text-sm text-slate-300">
360+
<p>This will permanently delete:</p>
361+
<ul className="list-disc list-inside space-y-1 text-slate-400">
362+
<li>All your posture data</li>
363+
<li>Analytics history</li>
364+
<li>Account settings</li>
365+
</ul>
366+
</div>
367+
368+
<div className="mb-6">
369+
<label className="block text-sm text-slate-400 mb-2">
370+
Type <span className="text-red-400 font-semibold">DELETE</span> to confirm:
371+
</label>
372+
<input
373+
type="text"
374+
value={deleteConfirmation}
375+
onChange={(e) => setDeleteConfirmation(e.target.value)}
376+
className="w-full px-4 py-2 bg-slate-800/50 border border-slate-700 rounded-xl focus:outline-none focus:border-red-500/50"
377+
placeholder="DELETE"
378+
/>
379+
</div>
380+
381+
<div className="flex gap-3">
382+
<button
383+
onClick={() => {
384+
setShowDeleteModal(false);
385+
setDeleteConfirmation('');
386+
}}
387+
className="flex-1 px-4 py-2 glass cursor-pointer rounded-xl hover:light transition-all"
388+
>
389+
Cancel
390+
</button>
391+
<button
392+
onClick={handleDeleteAccount}
393+
disabled={deleteConfirmation !== 'DELETE'}
394+
className="flex-1 px-4 py-2 cursor-pointer bg-red-500 hover:bg-red-600 disabled:bg-red-500/20 disabled:cursor-not-allowed rounded-xl transition-all"
395+
>
396+
Delete Account
397+
</button>
398+
</div>
399+
</motion.div>
400+
</div>
401+
)}
402+
308403
{/* Save Button */}
309404
<div className="lg:col-span-2">
310405
<div className="flex items-center justify-between p-6 glass-strong rounded-2xl">

frontend/src/services/api.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ export const authApi = {
3737
login: (username: string, password: string) =>
3838
api.post('/auth/login', { username, password }),
3939
}
40+
41+
export const usersApi = {
42+
deleteAccount: () => api.delete('/users/me')
43+
}
44+
45+
46+
4047
export const postureApi = {
4148
saveEvent: (event: PostureEvent) =>
4249
api.post('/posture/events', event),

0 commit comments

Comments
 (0)