1
|
|
|
import React, { useState, useEffect } from 'react'; |
2
|
|
|
import { useParams } from 'react-router-dom'; |
3
|
|
|
import { API_URL, getHeader } from '../../helpers/config'; |
4
|
|
|
import { useSelector } from 'react-redux'; |
5
|
|
|
import { RootState } from '../../redux/store/store'; |
6
|
|
|
import axios, { AxiosError } from 'axios'; |
7
|
|
|
import { Link } from 'react-router-dom'; |
8
|
|
|
import { Button, ToggleSwitch, TextInput, Checkbox, Label, Card } from "flowbite-react"; |
9
|
|
|
import { toast } from 'react-toastify'; |
10
|
|
|
import AdminGate from '../../components/AdminGate'; |
11
|
|
|
|
12
|
|
|
type User = { |
13
|
|
|
githubId: string; |
14
|
|
|
username: string; |
15
|
|
|
email: string; |
16
|
|
|
roles: string[]; |
17
|
|
|
createdAt: string; |
18
|
|
|
hasAcceptedTerms: boolean; |
19
|
|
|
avatarUrl?: string; |
20
|
|
|
accumulatedCost: number; |
21
|
|
|
balance: number; |
22
|
|
|
} |
23
|
|
|
|
24
|
2 |
|
const AdminUserOverviewPage: React.FC = () => { |
25
|
1 |
|
const { githubId } = useParams<{ githubId: string }>(); |
26
|
2 |
|
const { token } = useSelector((state: RootState) => state.auth); |
27
|
1 |
|
const [user, setUser] = useState<User | null>(null); |
28
|
1 |
|
const [loading, setLoading] = useState(true); |
29
|
1 |
|
const [isKund, setIsKund] = useState(false); |
30
|
1 |
|
const [isAdmin, setIsAdmin] = useState(false); |
31
|
1 |
|
const [isDeleted, setIsDeleted] = useState(false); |
32
|
1 |
|
const [createdAt, setCreatedAt] = useState(""); |
33
|
1 |
|
const [username, setUsername] = useState(""); |
34
|
1 |
|
const [email, setEmail] = useState(""); |
35
|
1 |
|
const [hasAcceptedTerms, setHasAcceptedTerms] = useState(false); |
36
|
1 |
|
const [avatarUrl, setAvatarUrl] = useState(""); |
37
|
1 |
|
const [isMonthlyPayment, setIsMonthlyPayment] = useState(false); |
38
|
1 |
|
const [accumulatedCost, setAccumulatedCost] = useState(0); |
39
|
1 |
|
const [balance, setBalance] = useState(0); |
40
|
|
|
|
41
|
|
|
|
42
|
|
|
|
43
|
1 |
|
const updateUserInfo = async (e: React.FormEvent<HTMLFormElement>) => { |
44
|
|
|
e.preventDefault(); |
45
|
|
|
const updatedData = { |
46
|
|
|
'githubId': githubId, |
47
|
|
|
'username': username, |
48
|
|
|
'email': email, |
49
|
|
|
'roles': [isAdmin && "admin", isKund && "user", isDeleted && "inactive"].filter(Boolean), |
50
|
|
|
'hasAcceptedTerms': hasAcceptedTerms, |
51
|
|
|
'avatarUrl': avatarUrl, |
52
|
|
|
'isMonthlyPayment': isMonthlyPayment, |
53
|
|
|
'accumulatedCost': accumulatedCost, |
54
|
|
|
'balance': balance, |
55
|
|
|
}; |
56
|
|
|
try { |
57
|
|
|
const response = await axios.patch(`${API_URL}/users/${githubId}`, updatedData, getHeader(token)); |
58
|
|
|
console.log(response); |
59
|
|
|
toast.success("User was updated"); |
60
|
|
|
} catch(error) |
61
|
|
|
{ |
62
|
|
|
const axiosError = error as AxiosError; |
63
|
|
|
toast.error(`User was not updated ${axiosError.message}`); |
64
|
2 |
|
console.error("Error:", axiosError.response || axiosError.toJSON()); |
65
|
|
|
} |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
|
69
|
|
|
|
70
|
1 |
|
useEffect(() => { |
71
|
1 |
|
const getUserInfo = async () => { |
72
|
|
|
try { |
73
|
|
|
const response = await axios.get(`${API_URL}/users/${githubId}`, getHeader(token)); |
74
|
|
|
setUser(response.data); |
75
|
|
|
const user = response.data; |
76
|
|
|
setIsKund(user.roles.includes("user")); |
77
|
|
|
setIsAdmin(user.roles.includes("admin")); |
78
|
|
|
setIsDeleted(user.roles.includes("inactive")); |
79
|
|
|
setUsername(user.username); |
80
|
|
|
setEmail(user.email); |
81
|
|
|
setCreatedAt(user.createdAt); |
82
|
|
|
setHasAcceptedTerms(user.hasAcceptedTerms); |
83
|
2 |
|
setAvatarUrl(user.avatarUrl ?? ""); |
84
|
|
|
setIsMonthlyPayment(user.isMonthlyPayment); |
85
|
|
|
setAccumulatedCost(user.accumulatedCost); |
86
|
|
|
setBalance(user.balance); |
87
|
|
|
|
88
|
|
|
} catch (error) { |
89
|
|
|
console.error('Failed to fetch user info:', error); |
90
|
|
|
} finally { |
91
|
|
|
setLoading(false); |
92
|
|
|
} |
93
|
|
|
}; |
94
|
|
|
|
95
|
2 |
|
if (githubId) { |
96
|
|
|
getUserInfo(); |
97
|
|
|
} |
98
|
|
|
}, [githubId, token]); |
99
|
|
|
|
100
|
2 |
|
if (loading) { |
101
|
1 |
|
return <div data-testid="admin-user-overview-page">Laddar användardata...</div>; |
102
|
|
|
} |
103
|
|
|
|
104
|
2 |
|
if (!user) { |
105
|
|
|
return <div data-testid="admin-user-overview-page">Ingen användare hittades.</div>; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
return ( |
109
|
|
|
<div className="w-full max-w-4xl mx-auto p-6 bg-white border border-gray-200 rounded-lg shadow"> |
110
|
|
|
<AdminGate/> |
111
|
|
|
|
112
|
|
|
<section className="bg-white"> |
113
|
|
|
<div className="max-w-2xl px-4 py-8 mx-auto lg:py-16"> |
114
|
|
|
<h2 className="mb-4 text-xl font-bold text-gray-900">Användare: { username } |
115
|
|
|
{isDeleted && |
116
|
|
|
<span className="text-red-600 text-xl">(AVAKTIVERAD)</span>} |
117
|
|
|
</h2> |
118
|
|
|
<form action="#" onSubmit={(e) => updateUserInfo(e)}> |
119
|
|
|
<div className="grid gap-4 mb-4"> |
120
|
|
|
<div className=""> |
121
|
|
|
<Label htmlFor="name">Användarnamn</Label> |
122
|
|
|
<TextInput color="blue" id="name" type="text" value={username} onChange={ (e)=> setUsername(e.target.value) } placeholder="användarnamn" required/> |
123
|
|
|
</div> |
124
|
|
|
|
125
|
|
|
<div className=""> |
126
|
|
|
<Label htmlFor="created" >Registrerad(readonly)</Label> |
127
|
|
|
<TextInput color="blue" id="created" type="text" value={createdAt} disabled required/> |
128
|
|
|
</div> |
129
|
|
|
|
130
|
|
|
<div className="w-full"> |
131
|
|
|
<Label htmlFor="email" >E-mail</Label> |
132
|
|
|
<TextInput color="blue" id="email" type="email" value={email} onChange={ (e)=> setEmail(e.target.value) } placeholder="[email protected]" required/> |
133
|
|
|
</div> |
134
|
|
|
|
135
|
|
|
<div className="w-full"> |
136
|
|
|
<Label htmlFor="githubid" >Github ID (readonly)</Label> |
137
|
|
|
<TextInput color="blue" id="githubid" type="text" value= {githubId} placeholder={user.githubId} required disabled/> |
138
|
|
|
</div> |
139
|
|
|
|
140
|
|
|
<div className="w-full"> |
141
|
|
|
<Label htmlFor="avatarurl" >Avatarurl</Label> |
142
|
2 |
|
<TextInput color="blue" id="avatarurl" type="url" value= {avatarUrl} placeholder={user.avatarUrl} onChange={ (e)=> setAvatarUrl(e.target.value || "") }/> |
143
|
|
|
<Card |
144
|
|
|
className="max-w-sm mx-auto mt-6 inline-block" |
145
|
|
|
imgAlt="user image" |
146
|
|
|
imgSrc={avatarUrl}/> |
147
|
|
|
</div> |
148
|
|
|
|
149
|
|
|
<div className="flex items-center gap-2"> |
150
|
|
|
<Checkbox id="kund" color="blue" checked={isKund} onChange={()=>setIsKund(!isKund)} /> |
151
|
|
|
<Label htmlFor="kund" className="flex">Kundbehörighet</Label> |
152
|
|
|
</div> |
153
|
|
|
<div className="flex items-center gap-2"> |
154
|
|
|
<Checkbox id="admin" color="blue" checked={isAdmin} onChange={()=>setIsAdmin(!isAdmin)} /> |
155
|
|
|
<Label htmlFor="admin">Adminbehörighet</Label> |
156
|
|
|
</div> |
157
|
|
|
<div className="flex items-center gap-2"> |
158
|
|
|
<Checkbox id="delete" color="red" checked={isDeleted} onChange={()=>setIsDeleted(!isDeleted)} /> |
159
|
|
|
<Label htmlFor="delete" className="text-red-600 font-bold">Avaktiverad</Label> |
160
|
|
|
</div> |
161
|
|
|
<div className="flex items-center gap-2"> |
162
|
|
|
<ToggleSwitch color="blue" checked={isMonthlyPayment} label="Månatlig betalning" onChange={() => setIsMonthlyPayment(!isMonthlyPayment)} /> |
163
|
|
|
<ToggleSwitch color="teal" checked={hasAcceptedTerms} label="Godkända användarvillkor" onChange={() => setHasAcceptedTerms(!hasAcceptedTerms)} /> |
164
|
|
|
</div> |
165
|
|
|
<div className="w-full"> |
166
|
|
|
<Label htmlFor="ackkost" >Ackumulerad kostnad</Label> |
167
|
2 |
|
<TextInput color="blue" id="ackkost" type="text" value= {accumulatedCost} onChange={(e)=> setAccumulatedCost(parseFloat(e.target.value) || 0) } placeholder="" required/> |
168
|
|
|
</div> |
169
|
|
|
|
170
|
|
|
<div className="w-full"> |
171
|
|
|
<Label htmlFor=" balans" >Balans</Label> |
172
|
2 |
|
<TextInput color="blue" id="balans" type="text" value={balance} onChange={(e)=> setBalance(parseFloat(e.target.value) || 0) } placeholder="" required/> |
173
|
|
|
|
174
|
|
|
</div> |
175
|
|
|
|
176
|
|
|
</div> |
177
|
|
|
<div className="flex items-center space-x-4"> |
178
|
|
|
<Button type="submit" color="blue"> |
179
|
|
|
Uppdatera |
180
|
|
|
</Button> |
181
|
|
|
<Button color="light"> |
182
|
|
|
<Link to="/userlistpage">Gå tillbaka</Link> |
183
|
|
|
</Button> |
184
|
|
|
</div> |
185
|
|
|
</form> |
186
|
|
|
</div> |
187
|
|
|
</section> |
188
|
|
|
</div> |
189
|
|
|
); |
190
|
|
|
}; |
191
|
|
|
|
192
|
|
|
export default AdminUserOverviewPage; |
193
|
|
|
|