add auth to backend and frontend
This commit is contained in:
+1
-1
@@ -20,7 +20,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/node": "^25.2.3",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"eslint": "^9.39.1",
|
||||
|
||||
+16
-13
@@ -6,18 +6,21 @@ import tseslint from 'typescript-eslint'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
rules: {
|
||||
'react-refresh/only-export-components': 'off',
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/node": "^25.2.3",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"eslint": "^9.39.1",
|
||||
|
||||
+14
-12
@@ -4,7 +4,7 @@ import LatestTemperature from "@/components/latest-temperature"
|
||||
import TemperatureHistory from "@/components/temperature-history"
|
||||
import TemperaturePlot from "@/components/temperature-plot"
|
||||
import RegisterForm from './components/register-form'
|
||||
// import { Button } from "@/components/ui/button"
|
||||
import { AuthProvider } from './contexts/auth-context'
|
||||
|
||||
|
||||
|
||||
@@ -12,18 +12,20 @@ import RegisterForm from './components/register-form'
|
||||
function App() {
|
||||
return (
|
||||
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
||||
<main className="min-h-screen bg-background p-6 w-full">
|
||||
<div>
|
||||
<RegisterForm />
|
||||
</div>
|
||||
<div className="w-full mx-auto space-y-2">
|
||||
<TemperaturePlot />
|
||||
<div className="grid grid-cols-4 gap-6">
|
||||
<LatestTemperature />
|
||||
<TemperatureHistory />
|
||||
<AuthProvider>
|
||||
<main className="min-h-screen bg-background p-6 w-full">
|
||||
<div>
|
||||
<RegisterForm />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<div className="w-full mx-auto space-y-2">
|
||||
<TemperaturePlot />
|
||||
<div className="grid grid-cols-4 gap-6">
|
||||
<LatestTemperature />
|
||||
<TemperatureHistory />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</AuthProvider>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
createContext,
|
||||
useState,
|
||||
useEffect,
|
||||
} from "react"
|
||||
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
type User = {
|
||||
id: number
|
||||
email: string
|
||||
create_at: string
|
||||
}
|
||||
|
||||
type AuthContextType = {
|
||||
user: User | null
|
||||
isAuthenticated: boolean
|
||||
login: (email: string, passwor: string) => Promise<void>
|
||||
logout: () => void
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export const AuthContext = createContext<AuthContextType | null>(null)
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
checkAuth()
|
||||
}, [])
|
||||
|
||||
async function checkAuth() {
|
||||
try {
|
||||
const res = await fetch("/api/me", {
|
||||
credentials: "include",
|
||||
})
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
setUser(data.user)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
setUser(null)
|
||||
}
|
||||
finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function login(email: string, password: string) {
|
||||
const res = await fetch("/api/login", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, password })
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const msg = await res.text()
|
||||
throw new Error(msg || "Invalid credentials")
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
setUser(data.user)
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
await fetch("api/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
})
|
||||
setUser(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, isAuthenticated: !!user, login, logout, isLoading }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { AuthContext } from "@/contexts/auth-context"
|
||||
import { useContext } from "react"
|
||||
|
||||
export function useAuth() {
|
||||
const ctx = useContext(AuthContext)
|
||||
if (!ctx) throw new Error("useAuth must be used within AuthProvider")
|
||||
return ctx
|
||||
}
|
||||
Reference in New Issue
Block a user