Skip to content

Commit 20d2476

Browse files
authored
Merge pull request #1 from agpprastyo/fix/register-fe
implement registration form with validation and environment variable loading
2 parents 22bdbe9 + 8ed6873 commit 20d2476

8 files changed

Lines changed: 151 additions & 68 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ go.work
2222
go.work.sum
2323

2424
# env file
25-
.env
25+
.env
26+
.env.local

.idea/jsLibraryMappings.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Load environment variables from .env
22
ifneq (,$(wildcard .env))
3-
include .env
3+
include .env.local
44
export
55
endif
66

internal/server/server.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,16 @@ type App struct {
3030
JWTMaker *token.JWTMaker
3131
}
3232

33-
func InitApp() *App {
34-
err := godotenv.Load()
35-
if err != nil {
36-
panic("Error loading .env file")
33+
func loadEnv() {
34+
// Try to load .env.local first
35+
if err := godotenv.Load(".env.local"); err != nil {
36+
// Fallback to .env if .env.local does not exist
37+
_ = godotenv.Load(".env")
3738
}
39+
}
40+
41+
func InitApp() *App {
42+
loadEnv()
3843

3944
// Load configuration
4045
cfg := config.Load()

web/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@ dist-ssr
2222
*.njsproj
2323
*.sln
2424
*.sw?
25+
26+
.env
27+
.env.local
28+

web/src/components/login-form.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,7 @@ export function LoginForm({
8686
<Button type="submit" className="w-full" disabled={loading}>
8787
{loading ? "Logging in..." : "Login"}
8888
</Button>
89-
<Button variant="outline" className="w-full" type="button" disabled={loading}>
90-
Login with Google
91-
</Button>
89+
9290
</div>
9391
</div>
9492
<div className="mt-4 text-center text-sm">
Lines changed: 107 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import React, { useState } from "react"
2+
import { userRegister } from "@/lib/api/UserApi"
3+
import { useNavigate } from "react-router-dom"
14
import { cn } from "@/lib/utils"
25
import { Button } from "@/components/ui/button"
36
import {
@@ -12,64 +15,110 @@ import { Label } from "@/components/ui/label"
1215
import { Link } from "react-router-dom"
1316

1417
export function RegisterForm({
15-
className,
16-
...props
17-
}: React.ComponentProps<"div">) {
18-
return (
19-
<div className={cn("flex flex-col gap-6", className)} {...props}>
20-
<Card>
21-
<CardHeader>
22-
<CardTitle>Create an account</CardTitle>
23-
<CardDescription>
24-
Enter your details to register a new account
25-
</CardDescription>
26-
</CardHeader>
27-
<CardContent>
28-
<form>
29-
<div className="flex flex-col gap-6">
30-
<div className="grid gap-3">
31-
<Label htmlFor="username">Username</Label>
32-
<Input
33-
id="username"
34-
type="text"
35-
placeholder="yourusername"
36-
required
37-
/>
38-
</div>
39-
<div className="grid gap-3">
40-
<Label htmlFor="email">Email</Label>
41-
<Input
42-
id="email"
43-
type="email"
44-
placeholder="m@example.com"
45-
required
46-
/>
47-
</div>
48-
<div className="grid gap-3">
49-
<Label htmlFor="password">Password</Label>
50-
<Input id="password" type="password" required />
51-
</div>
52-
<div className="grid gap-3">
53-
<Label htmlFor="confirm-password">Confirm Password</Label>
54-
<Input id="confirm-password" type="password" required />
55-
</div>
56-
<div className="flex flex-col gap-3">
57-
<Button type="submit" className="w-full">
58-
Register
59-
</Button>
60-
</div>
61-
</div>
62-
<div className="mt-4 text-center text-sm">
63-
Already have an account?{" "}
18+
className,
19+
...props
20+
}: React.ComponentProps<"div">) {
21+
const [form, setForm] = useState({ username: "", email: "", password: "", confirmPassword: "" })
22+
const [error, setError] = useState<string | null>(null)
23+
const [loading, setLoading] = useState(false)
24+
const navigate = useNavigate()
6425

26+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
27+
setForm({ ...form, [e.target.id]: e.target.value })
28+
}
6529

66-
<Link to="/login" className="underline underline-offset-4">
67-
Login
68-
</Link>
69-
</div>
70-
</form>
71-
</CardContent>
72-
</Card>
73-
</div>
74-
)
30+
const handleSubmit = async (e: React.FormEvent) => {
31+
e.preventDefault()
32+
setError(null)
33+
if (form.password !== form.confirmPassword) {
34+
setError("Passwords do not match")
35+
return
36+
}
37+
setLoading(true)
38+
try {
39+
await userRegister({
40+
username: form.username,
41+
email: form.email,
42+
password: form.password,
43+
})
44+
navigate("/login")
45+
} catch (err: any) {
46+
setError(err.message)
47+
} finally {
48+
setLoading(false)
49+
}
50+
}
51+
52+
return (
53+
<div className={cn("flex flex-col gap-6", className)} {...props}>
54+
<Card>
55+
<CardHeader>
56+
<CardTitle>Create an account</CardTitle>
57+
<CardDescription>
58+
Enter your details to register a new account
59+
</CardDescription>
60+
</CardHeader>
61+
<CardContent>
62+
<form onSubmit={handleSubmit}>
63+
<div className="flex flex-col gap-6">
64+
<div className="grid gap-3">
65+
<Label htmlFor="username">Username</Label>
66+
<Input
67+
id="username"
68+
type="text"
69+
placeholder="yourusername"
70+
required
71+
value={form.username}
72+
onChange={handleChange}
73+
/>
74+
</div>
75+
<div className="grid gap-3">
76+
<Label htmlFor="email">Email</Label>
77+
<Input
78+
id="email"
79+
type="email"
80+
placeholder="m@example.com"
81+
required
82+
value={form.email}
83+
onChange={handleChange}
84+
/>
85+
</div>
86+
<div className="grid gap-3">
87+
<Label htmlFor="password">Password</Label>
88+
<Input
89+
id="password"
90+
type="password"
91+
required
92+
value={form.password}
93+
onChange={handleChange}
94+
/>
95+
</div>
96+
<div className="grid gap-3">
97+
<Label htmlFor="confirmPassword">Confirm Password</Label>
98+
<Input
99+
id="confirmPassword"
100+
type="password"
101+
required
102+
value={form.confirmPassword}
103+
onChange={handleChange}
104+
/>
105+
</div>
106+
{error && <div className="text-red-500 text-sm">{error}</div>}
107+
<div className="flex flex-col gap-3">
108+
<Button type="submit" className="w-full" disabled={loading}>
109+
{loading ? "Registering..." : "Register"}
110+
</Button>
111+
</div>
112+
</div>
113+
<div className="mt-4 text-center text-sm">
114+
Already have an account?{" "}
115+
<Link to="/login" className="underline underline-offset-4">
116+
Login
117+
</Link>
118+
</div>
119+
</form>
120+
</CardContent>
121+
</Card>
122+
</div>
123+
)
75124
}

web/src/lib/api/UserApi.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
11
import type {Login} from "@/types/user";
22

33

4-
const baseUrl = "http://localhost:8080";
4+
const baseUrl = "http://localhost:8081";
55
const api = `${baseUrl}/api/v1`;
66

77

8+
export const userRegister = async (
9+
data: { email: string; password: string; username: string }
10+
): Promise<any> => {
11+
const response = await fetch(`${api}/register`, {
12+
method: 'POST',
13+
headers: {
14+
'Content-Type': 'application/json',
15+
},
16+
credentials: 'include',
17+
body: JSON.stringify(data),
18+
});
19+
20+
if (!response.ok) {
21+
throw new Error('Registration failed');
22+
}
23+
24+
return await response.json();
25+
};
26+
27+
828
export const userLogin = async (
929
data: { email: string; password: string }
1030
): Promise<Login> => {

0 commit comments

Comments
 (0)