|
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
|
|
|
|
|
13
|
|
|
|
|
14
|
|
|
type User = { |
|
15
|
|
|
githubId: string; |
|
16
|
|
|
username: string; |
|
17
|
|
|
email: string; |
|
18
|
|
|
roles: string[]; |
|
19
|
|
|
createdAt: string; |
|
20
|
|
|
hasAcceptedTerms: boolean; |
|
21
|
|
|
avatarUrl?: string; |
|
22
|
|
|
accumulatedCost: number; |
|
23
|
|
|
balance: number; |
|
24
|
|
|
} |
|
25
|
|
|
|
|
26
|
2 |
|
const AdminUserOverviewPage: React.FC = () => { |
|
27
|
1 |
|
const { githubId } = useParams<{ githubId: string }>(); |
|
28
|
2 |
|
const { token } = useSelector((state: RootState) => state.auth); |
|
29
|
1 |
|
const [user, setUser] = useState<User | null>(null); |
|
30
|
1 |
|
const [loading, setLoading] = useState(true); |
|
31
|
1 |
|
const [isKund, setIsKund] = useState(false); |
|
32
|
1 |
|
const [isAdmin, setIsAdmin] = useState(false); |
|
33
|
1 |
|
const [createdAt, setCreatedAt] = useState(""); |
|
34
|
1 |
|
const [username, setUsername] = useState(""); |
|
35
|
1 |
|
const [email, setEmail] = useState(""); |
|
36
|
1 |
|
const [hasAcceptedTerms, setHasAcceptedTerms] = useState(false); |
|
37
|
1 |
|
const [avatarUrl, setAvatarUrl] = useState(""); |
|
38
|
1 |
|
const [isMonthlyPayment, setIsMonthlyPayment] = useState(false); |
|
39
|
1 |
|
const [accumulatedCost, setAccumulatedCost] = useState(0); |
|
40
|
1 |
|
const [balance, setBalance] = useState(0); |
|
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"].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
|
|
|
setUsername(user.username); |
|
79
|
|
|
setEmail(user.email); |
|
80
|
|
|
setCreatedAt(user.createdAt); |
|
81
|
|
|
setHasAcceptedTerms(user.hasAcceptedTerms); |
|
82
|
2 |
|
setAvatarUrl(user.avatarUrl ?? ""); |
|
83
|
|
|
setIsMonthlyPayment(user.isMonthlyPayment); |
|
84
|
|
|
setAccumulatedCost(user.accumulatedCost); |
|
85
|
|
|
setBalance(user.balance); |
|
86
|
|
|
|
|
87
|
|
|
} catch (error) { |
|
88
|
|
|
console.error('Failed to fetch user info:', error); |
|
89
|
|
|
} finally { |
|
90
|
|
|
setLoading(false); |
|
91
|
|
|
} |
|
92
|
|
|
}; |
|
93
|
|
|
|
|
94
|
2 |
|
if (githubId) { |
|
95
|
|
|
getUserInfo(); |
|
96
|
|
|
} |
|
97
|
|
|
}, [githubId]); |
|
98
|
|
|
|
|
99
|
2 |
|
if (loading) { |
|
100
|
1 |
|
return <div data-testid="admin-user-overview-page">Laddar användardata...</div>; |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
2 |
|
if (!user) { |
|
104
|
|
|
return <div data-testid="admin-user-overview-page">Ingen användare hittades.</div>; |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
|
|
return ( |
|
108
|
|
|
<div className="w-full max-w-4xl mx-auto p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700"> |
|
109
|
|
|
<AdminGate/> |
|
110
|
|
|
{/* <h1 className="text-2xl font-bold text-center mb-6 text-gray-900 dark:text-white">{user.username}</h1> |
|
111
|
|
|
<div className="space-y-4"> |
|
112
|
|
|
<p className="text-gray-600 dark:text-gray-400">Email: <b>{user.email}</b></p> |
|
113
|
|
|
<p className="font-normal text-gray-700 dark:text-gray-400 p-2">Skapat: <b>{user.createdAt || "No User"}</b></p> |
|
114
|
|
|
<p className="font-normal text-gray-700 dark:text-gray-400 p-2">Github Id: <b>{user.githubId || ":("}</b></p> |
|
115
|
|
|
<p className="text-gray-600 dark:text-gray-400">Roller: <b>{user.roles.join(', ')}</b></p> |
|
116
|
|
|
<p className="text-gray-600 dark:text-gray-400"> |
|
117
|
|
|
<b>{user.hasAcceptedTerms ? 'Godkänt användarvillkor' : 'Ej godkänt användarvillkor'}</b> |
|
118
|
|
|
</p> |
|
119
|
|
|
{user.avatarUrl && ( |
|
120
|
|
|
<img |
|
121
|
|
|
src={user.avatarUrl} |
|
122
|
|
|
alt={`${user.username}'s avatar`} |
|
123
|
|
|
className="w-24 h-24 rounded-full mt-4" |
|
124
|
|
|
/> |
|
125
|
|
|
)} |
|
126
|
|
|
|
|
127
|
|
|
|
|
128
|
|
|
<p> |
|
129
|
|
|
<Link to="/userlistpage" className="py-2 m-16 px-4 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"> |
|
130
|
|
|
Gå tillbaka |
|
131
|
|
|
</Link> |
|
132
|
|
|
</p> |
|
133
|
|
|
</div> */} |
|
134
|
|
|
|
|
135
|
|
|
|
|
136
|
|
|
<section className="bg-white dark:bg-gray-900"> |
|
137
|
|
|
<div className="max-w-2xl px-4 py-8 mx-auto lg:py-16"> |
|
138
|
|
|
<h2 className="mb-4 text-xl font-bold text-gray-900 dark:text-white">Användare: { username } </h2> |
|
139
|
|
|
<form action="#" onSubmit={(e) => updateUserInfo(e)}> |
|
140
|
|
|
<div className="grid gap-4 mb-4 sm:grid-cols-2 sm:gap-6 sm:mb-5"> |
|
141
|
|
|
<div className="sm:col-span-2"> |
|
142
|
|
|
<Label htmlFor="name">Användarnamn</Label> |
|
143
|
|
|
<TextInput color="blue" id="name" type="text" value={username} onChange={ (e)=> setUsername(e.target.value) } placeholder="användarnamn" required/> |
|
144
|
|
|
</div> |
|
145
|
|
|
|
|
146
|
|
|
<div className="sm:col-span-2"> |
|
147
|
|
|
<Label htmlFor="created" >Registrerad(readonly)</Label> |
|
148
|
|
|
<TextInput color="blue" id="created" type="text" value={createdAt} disabled required/> |
|
149
|
|
|
</div> |
|
150
|
|
|
|
|
151
|
|
|
<div className="w-full"> |
|
152
|
|
|
<Label htmlFor="email" >E-mail</Label> |
|
153
|
|
|
<TextInput color="blue" id="email" type="email" value={email} onChange={ (e)=> setEmail(e.target.value) } placeholder="[email protected]" required/> |
|
154
|
|
|
</div> |
|
155
|
|
|
|
|
156
|
|
|
<div className="w-full"> |
|
157
|
|
|
<Label htmlFor="githubid" >Github ID (readonly)</Label> |
|
158
|
|
|
<TextInput color="blue" id="githubid" type="text" value= {githubId} placeholder={user.githubId} required disabled/> |
|
159
|
|
|
</div> |
|
160
|
|
|
|
|
161
|
|
|
<div className="w-full"> |
|
162
|
|
|
<Label htmlFor="avatarurl" >Avatarurl</Label> |
|
163
|
2 |
|
<TextInput color="blue" id="avatarurl" type="url" value= {avatarUrl} placeholder={user.avatarUrl} onChange={ (e)=> setAvatarUrl(e.target.value || "") }/> |
|
164
|
|
|
<Card |
|
165
|
|
|
className="max-w-sm mx-auto mt-6 inline-block" |
|
166
|
|
|
imgAlt="user image" |
|
167
|
|
|
imgSrc={avatarUrl}/> |
|
168
|
|
|
</div> |
|
169
|
|
|
|
|
170
|
|
|
<div className="flex items-center gap-2"> |
|
171
|
|
|
<Checkbox id="kund" color="blue" checked={isKund} onChange={()=>setIsKund(!isKund)} /> |
|
172
|
|
|
<Label htmlFor="kund" className="flex">Kundbehörighet</Label> |
|
173
|
|
|
</div> |
|
174
|
|
|
<div className="flex items-center gap-2"> |
|
175
|
|
|
<Checkbox id="admin" color="blue" checked={isAdmin} onChange={()=>setIsAdmin(!isAdmin)} /> |
|
176
|
|
|
<Label htmlFor="admin">Adminbehörighet</Label> |
|
177
|
|
|
</div> |
|
178
|
|
|
<div className="flex items-center gap-2"> |
|
179
|
|
|
<ToggleSwitch color="blue" checked={isMonthlyPayment} label="Månatlig betalning" onChange={() => setIsMonthlyPayment(!isMonthlyPayment)} /> |
|
180
|
|
|
<ToggleSwitch color="teal" checked={hasAcceptedTerms} label="Godkända användarvillkor" onChange={() => setHasAcceptedTerms(!hasAcceptedTerms)} /> |
|
181
|
|
|
</div> |
|
182
|
|
|
<div className="w-full"> |
|
183
|
|
|
<Label htmlFor="ackkost" >Ackumulerad kostnad</Label> |
|
184
|
2 |
|
<TextInput color="blue" id="ackkost" type="text" value= {accumulatedCost} onChange={(e)=> setAccumulatedCost(parseFloat(e.target.value) || 0) } placeholder="" required/> |
|
185
|
|
|
</div> |
|
186
|
|
|
|
|
187
|
|
|
<div className="w-full"> |
|
188
|
|
|
<Label htmlFor=" balans" >Balans</Label> |
|
189
|
2 |
|
<TextInput color="blue" id="balans" type="text" value={balance} onChange={(e)=> setBalance(parseFloat(e.target.value) || 0) } placeholder="" required/> |
|
190
|
|
|
|
|
191
|
|
|
</div> |
|
192
|
|
|
|
|
193
|
|
|
</div> |
|
194
|
|
|
<div className="flex items-center space-x-4"> |
|
195
|
|
|
<Button type="submit" color="blue"> |
|
196
|
|
|
Uppdatera |
|
197
|
|
|
</Button> |
|
198
|
|
|
<Button color="failure"> |
|
199
|
|
|
Radera (inte implementerad än) |
|
200
|
|
|
</Button> |
|
201
|
|
|
<Button color="light"> |
|
202
|
|
|
<Link to="/userlistpage">Gå tillbaka</Link> |
|
203
|
|
|
</Button> |
|
204
|
|
|
</div> |
|
205
|
|
|
</form> |
|
206
|
|
|
</div> |
|
207
|
|
|
</section> |
|
208
|
|
|
</div> |
|
209
|
|
|
); |
|
210
|
|
|
}; |
|
211
|
|
|
|
|
212
|
|
|
export default AdminUserOverviewPage; |
|
213
|
|
|
|