Skip to content

Commit ee62273

Browse files
hemanandrclaude
andcommitted
fix: temporary workaround for CreatedAtAction routing issue
- Changed from CreatedAtAction to Ok response for user creation - Avoids InvalidOperationException while preserving functionality - User creation works but returns 200 OK instead of 201 Created - TODO: Investigate proper routing configuration later 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 79082b3 commit ee62273

File tree

14 files changed

+696
-210
lines changed

14 files changed

+696
-210
lines changed

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"Bash(ping:*)",
1515
"Bash(findstr:*)",
1616
"Bash(npx eslint:*)",
17-
"Read(//c/ProgramData/ThingConnect.Pulse/logs/**)"
17+
"Read(//c/ProgramData/ThingConnect.Pulse/logs/**)",
18+
"WebSearch"
1819
],
1920
"deny": [],
2021
"ask": []

ThingConnect.Pulse.Server/Controllers/UserManagementController.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,9 @@ public async Task<ActionResult<UserInfoDto>> CreateUserAsync([FromBody] CreateUs
198198
IsActive = user.IsActive
199199
};
200200

201-
return CreatedAtAction(
202-
actionName: nameof(GetUserByIdAsync),
203-
controllerName: null, // Same controller
204-
routeValues: new { id = user.Id },
205-
value: userDto
206-
);
201+
// Return Ok for now to avoid routing issues
202+
// TODO: Fix location header generation
203+
return Ok(userDto);
207204
}
208205
catch (Exception ex)
209206
{

thingconnect.pulse.client/obj/Debug/package.g.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<PackageJsonDependenciesNextThemes Condition="$(PackageJsonDependenciesNextThemes) == ''">^0.4.6</PackageJsonDependenciesNextThemes>
2828
<PackageJsonDependenciesReact Condition="$(PackageJsonDependenciesReact) == ''">^19.1.1</PackageJsonDependenciesReact>
2929
<PackageJsonDependenciesReactDom Condition="$(PackageJsonDependenciesReactDom) == ''">^19.1.1</PackageJsonDependenciesReactDom>
30+
<PackageJsonDependenciesReactHookForm Condition="$(PackageJsonDependenciesReactHookForm) == ''">^7.62.0</PackageJsonDependenciesReactHookForm>
3031
<PackageJsonDependenciesReactIcons Condition="$(PackageJsonDependenciesReactIcons) == ''">^5.5.0</PackageJsonDependenciesReactIcons>
3132
<PackageJsonDependenciesReactRouterDom Condition="$(PackageJsonDependenciesReactRouterDom) == ''">^7.8.1</PackageJsonDependenciesReactRouterDom>
3233
<PackageJsonDependenciesZod Condition="$(PackageJsonDependenciesZod) == ''">^4.0.17</PackageJsonDependenciesZod>

thingconnect.pulse.client/package-lock.json

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

thingconnect.pulse.client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"next-themes": "^0.4.6",
2929
"react": "^19.1.1",
3030
"react-dom": "^19.1.1",
31+
"react-hook-form": "^7.62.0",
3132
"react-icons": "^5.5.0",
3233
"react-router-dom": "^7.8.1",
3334
"zod": "^4.0.17"

thingconnect.pulse.client/src/components/layout/Navigation.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Box, VStack, Text, Icon, Image, HStack, Badge, Button } from '@chakra-ui/react';
22
import { Link as RouterLink, useLocation } from 'react-router-dom';
3-
import { Wifi, Activity, LogOut } from 'lucide-react';
3+
import { Wifi, Activity, LogOut, Users } from 'lucide-react';
44
import thingConnectIcon from '@/assets/ThingConnectPulseLogo.svg';
55
import { Clock, Wrench, Settings, Info, Dashboard, Help } from '@/icons';
66
import { useAuth } from '@/features/auth/context/AuthContext';
@@ -11,15 +11,18 @@ interface NavigationProps {
1111
const navigationItems = [
1212
{ label: 'Dashboard', path: '/', icon: Dashboard },
1313
{ label: 'History', path: '/history', icon: Clock },
14-
{ label: 'Configuration', path: '/configuration', icon: Wrench },
15-
{ label: 'Settings', path: '/settings', icon: Settings },
14+
{ label: 'Configuration', path: '/configuration', icon: Wrench, adminOnly: true },
15+
{ label: 'User Management', path: '/users', icon: Users, adminOnly: true },
16+
{ label: 'Settings', path: '/settings', icon: Settings, adminOnly: true },
1617
{ label: 'Help', path: 'https://docs.thingconnect.io/pulse/', icon: Help, external: true },
1718
{ label: 'About', path: '/about', icon: Info },
1819
];
1920

2021
export function Navigation({ onItemClick }: NavigationProps) {
2122
const location = useLocation();
22-
const { logout } = useAuth();
23+
const { logout, user } = useAuth();
24+
25+
const isAdmin = user?.role === 'Administrator';
2326

2427
const isActiveRoute = (path: string) =>
2528
path === '/' ? location.pathname === '/' : location.pathname.startsWith(path);
@@ -51,7 +54,9 @@ export function Navigation({ onItemClick }: NavigationProps) {
5154
/>
5255
</Box>
5356
<VStack gap={2} p={4} flex='1' align='stretch' data-testid='navigation-items'>
54-
{navigationItems.map(item => {
57+
{navigationItems
58+
.filter(item => !item.adminOnly || isAdmin)
59+
.map(item => {
5560
const isActive = !item.external && isActiveRoute(item.path);
5661
const ItemContent = (
5762
<HStack

thingconnect.pulse.client/src/features/users/components/CreateUserModal.tsx

Lines changed: 45 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,15 @@ import {
66
DialogFooter,
77
Button,
88
Input,
9-
FormControl,
10-
FormLabel,
11-
FormErrorMessage,
129
VStack,
13-
Select,
1410
Alert,
15-
PasswordInput,
1611
} from '@chakra-ui/react';
1712
import { useState, useCallback } from 'react';
1813
import { useForm } from 'react-hook-form';
1914
import { LoadingButton } from '@/components/ui/LoadingButton';
15+
import { Field } from '@/components/ui/field';
16+
import { PasswordInput } from '@/components/form/PasswordInput';
17+
import { NativeSelectRoot, NativeSelectField } from '@/components/ui/native-select';
2018
import type { CreateUserRequest } from '@/api/types';
2119

2220
interface CreateUserModalProps {
@@ -44,6 +42,7 @@ export function CreateUserModal({
4442
formState: { errors, isSubmitting },
4543
reset,
4644
watch,
45+
setValue,
4746
} = useForm<FormData>({
4847
defaultValues: {
4948
username: '',
@@ -108,8 +107,11 @@ export function CreateUserModal({
108107
)}
109108

110109
{/* Username */}
111-
<FormControl isInvalid={!!errors.username}>
112-
<FormLabel>Username</FormLabel>
110+
<Field
111+
label="Username"
112+
errorText={errors.username?.message}
113+
invalid={!!errors.username}
114+
>
113115
<Input
114116
{...register('username', {
115117
required: 'Username is required',
@@ -121,14 +123,14 @@ export function CreateUserModal({
121123
placeholder="Enter username"
122124
disabled={isSubmitting || loading}
123125
/>
124-
{errors.username && (
125-
<FormErrorMessage>{errors.username.message}</FormErrorMessage>
126-
)}
127-
</FormControl>
126+
</Field>
128127

129128
{/* Email */}
130-
<FormControl isInvalid={!!errors.email}>
131-
<FormLabel>Email</FormLabel>
129+
<Field
130+
label="Email"
131+
errorText={errors.email?.message}
132+
invalid={!!errors.email}
133+
>
132134
<Input
133135
type="email"
134136
{...register('email', {
@@ -145,14 +147,14 @@ export function CreateUserModal({
145147
placeholder="Enter email address"
146148
disabled={isSubmitting || loading}
147149
/>
148-
{errors.email && (
149-
<FormErrorMessage>{errors.email.message}</FormErrorMessage>
150-
)}
151-
</FormControl>
150+
</Field>
152151

153152
{/* Password */}
154-
<FormControl isInvalid={!!errors.password}>
155-
<FormLabel>Password</FormLabel>
153+
<Field
154+
label="Password"
155+
errorText={errors.password?.message}
156+
invalid={!!errors.password}
157+
>
156158
<PasswordInput
157159
{...register('password', {
158160
required: 'Password is required',
@@ -168,14 +170,14 @@ export function CreateUserModal({
168170
placeholder="Enter password"
169171
disabled={isSubmitting || loading}
170172
/>
171-
{errors.password && (
172-
<FormErrorMessage>{errors.password.message}</FormErrorMessage>
173-
)}
174-
</FormControl>
173+
</Field>
175174

176175
{/* Confirm Password */}
177-
<FormControl isInvalid={!!errors.confirmPassword}>
178-
<FormLabel>Confirm Password</FormLabel>
176+
<Field
177+
label="Confirm Password"
178+
errorText={errors.confirmPassword?.message}
179+
invalid={!!errors.confirmPassword}
180+
>
179181
<PasswordInput
180182
{...register('confirmPassword', {
181183
required: 'Please confirm your password',
@@ -185,30 +187,26 @@ export function CreateUserModal({
185187
placeholder="Confirm password"
186188
disabled={isSubmitting || loading}
187189
/>
188-
{errors.confirmPassword && (
189-
<FormErrorMessage>{errors.confirmPassword.message}</FormErrorMessage>
190-
)}
191-
</FormControl>
190+
</Field>
192191

193192
{/* Role */}
194-
<FormControl isInvalid={!!errors.role}>
195-
<FormLabel>Role</FormLabel>
196-
<Select.Root
197-
{...register('role', { required: 'Role is required' })}
198-
disabled={isSubmitting || loading}
199-
>
200-
<Select.Trigger>
201-
<Select.ValueText placeholder="Select role" />
202-
</Select.Trigger>
203-
<Select.Content>
204-
<Select.Item value="User">User</Select.Item>
205-
<Select.Item value="Administrator">Administrator</Select.Item>
206-
</Select.Content>
207-
</Select.Root>
208-
{errors.role && (
209-
<FormErrorMessage>{errors.role.message}</FormErrorMessage>
210-
)}
211-
</FormControl>
193+
<Field
194+
label="Role"
195+
errorText={errors.role?.message}
196+
invalid={!!errors.role}
197+
>
198+
<NativeSelectRoot>
199+
<NativeSelectField
200+
placeholder="Select role"
201+
defaultValue="User"
202+
onChange={(e) => setValue('role', e.target.value as 'User' | 'Administrator')}
203+
_disabled={isSubmitting || loading ? { opacity: 0.6, cursor: 'not-allowed' } : {}}
204+
>
205+
<option value="User">User</option>
206+
<option value="Administrator">Administrator</option>
207+
</NativeSelectField>
208+
</NativeSelectRoot>
209+
</Field>
212210
</VStack>
213211

214212
<DialogFooter>

thingconnect.pulse.client/src/features/users/components/EditUserModal.tsx

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,16 @@ import {
66
DialogFooter,
77
Button,
88
Input,
9-
FormControl,
10-
FormLabel,
11-
FormErrorMessage,
129
VStack,
1310
Alert,
14-
Switch,
1511
HStack,
1612
Text,
13+
Switch,
1714
} from '@chakra-ui/react';
1815
import { useState, useCallback, useEffect } from 'react';
1916
import { useForm } from 'react-hook-form';
2017
import { LoadingButton } from '@/components/ui/LoadingButton';
18+
import { Field } from '@/components/ui/field';
2119
import type { UserInfo, UpdateUserRequest } from '@/api/types';
2220

2321
interface EditUserModalProps {
@@ -141,8 +139,11 @@ export function EditUserModal({
141139
)}
142140

143141
{/* Username */}
144-
<FormControl isInvalid={!!errors.username}>
145-
<FormLabel>Username</FormLabel>
142+
<Field
143+
label="Username"
144+
errorText={errors.username?.message}
145+
invalid={!!errors.username}
146+
>
146147
<Input
147148
{...register('username', {
148149
maxLength: {
@@ -153,14 +154,14 @@ export function EditUserModal({
153154
placeholder="Enter username"
154155
disabled={isSubmitting || loading}
155156
/>
156-
{errors.username && (
157-
<FormErrorMessage>{errors.username.message}</FormErrorMessage>
158-
)}
159-
</FormControl>
157+
</Field>
160158

161159
{/* Email */}
162-
<FormControl isInvalid={!!errors.email}>
163-
<FormLabel>Email</FormLabel>
160+
<Field
161+
label="Email"
162+
errorText={errors.email?.message}
163+
invalid={!!errors.email}
164+
>
164165
<Input
165166
type="email"
166167
{...register('email', {
@@ -176,43 +177,42 @@ export function EditUserModal({
176177
placeholder="Enter email address"
177178
disabled={isSubmitting || loading}
178179
/>
179-
{errors.email && (
180-
<FormErrorMessage>{errors.email.message}</FormErrorMessage>
181-
)}
182-
</FormControl>
180+
</Field>
183181

184182
{/* Active Status */}
185-
<FormControl>
183+
<Field label="Account Status">
186184
<HStack justify="space-between" w="full">
187185
<VStack align="start" gap={1}>
188-
<FormLabel mb={0}>Account Status</FormLabel>
189186
<Text fontSize="sm" color="gray.600" _dark={{ color: "gray.400" }}>
190187
{isActive ? 'User can log in' : 'User cannot log in'}
191188
</Text>
192189
</VStack>
193-
<Switch
190+
<Switch.Root
194191
checked={isActive}
195192
onCheckedChange={(details) => setValue('isActive', details.checked)}
196193
colorPalette="green"
197194
size="lg"
198195
disabled={isSubmitting || loading}
199-
/>
196+
>
197+
<Switch.Control>
198+
<Switch.Thumb />
199+
</Switch.Control>
200+
</Switch.Root>
200201
</HStack>
201-
</FormControl>
202+
</Field>
202203

203204
{/* Role Info (Read-only) */}
204-
<FormControl>
205-
<FormLabel>Role</FormLabel>
205+
<Field
206+
label="Role"
207+
helperText="Use the role change action in the user list to modify roles"
208+
>
206209
<Input
207210
value={user.role}
208211
disabled
209212
bg="gray.50"
210213
_dark={{ bg: "gray.700" }}
211214
/>
212-
<Text fontSize="sm" color="gray.600" _dark={{ color: "gray.400" }} mt={1}>
213-
Use the role change action in the user list to modify roles
214-
</Text>
215-
</FormControl>
215+
</Field>
216216
</VStack>
217217

218218
<DialogFooter>

0 commit comments

Comments
 (0)