Add missing lib/ (API client, auth, models) and logo asset
lib/ was never committed despite every page importing from it - a fresh clone couldn't build. public/logo-encap.jpg was likewise missing even though app/login/page.tsx already references it. Also gitignore .idea/, which shouldn't be tracked. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017hQcuUQFue2bFvrYw2UQhK
This commit is contained in:
parent
d0560df6c9
commit
cb0c8dc140
3
.gitignore
vendored
3
.gitignore
vendored
@ -36,6 +36,9 @@ yarn-error.log*
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
175
lib/api.ts
Normal file
175
lib/api.ts
Normal file
@ -0,0 +1,175 @@
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:8080/api';
|
||||
import { LoginResponse, User, Association } from "./models";
|
||||
|
||||
// Store token in localStorage (we'll improve this later)
|
||||
export function setAuthToken(token: string) {
|
||||
localStorage.setItem('auth_token', token);
|
||||
}
|
||||
|
||||
export function getAuthToken(): string | null {
|
||||
return localStorage.getItem('auth_token');
|
||||
}
|
||||
|
||||
export function removeAuthToken() {
|
||||
localStorage.removeItem('auth_token');
|
||||
}
|
||||
|
||||
// Helper to add Authorization header
|
||||
function getHeaders(): HeadersInit {
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
const token = getAuthToken();
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
export async function login(email: string, password: string): Promise<LoginResponse> {
|
||||
const response = await fetch(`${API_BASE_URL}/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Invalid credentials');
|
||||
}
|
||||
|
||||
const data: LoginResponse = await response.json();
|
||||
setAuthToken(data.token); // Store token
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getAssociation(id: string): Promise<Association> {
|
||||
const response = await fetch(`${API_BASE_URL}/associations/${id}`, {
|
||||
headers: getHeaders(),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
removeAuthToken();
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
throw new Error('Failed to fetch association');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function getAssociations(): Promise<Association[]> {
|
||||
const response = await fetch(`${API_BASE_URL}/associations`, {
|
||||
headers: getHeaders(),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
removeAuthToken();
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
throw new Error('Failed to fetch associations');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function createAssociation(association: Omit<Association, 'id' | 'created' | 'translit'>): Promise<Association> {
|
||||
const response = await fetch(`${API_BASE_URL}/associations`, {
|
||||
method: 'POST',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(association),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to create association');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function updateAssociation(id: string, association: Association): Promise<Association> {
|
||||
const response = await fetch(`${API_BASE_URL}/associations/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(association),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
removeAuthToken();
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
throw new Error('Failed to update association');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function downloadAttestato(id: string, associationName: string): Promise<void> {
|
||||
const response = await fetch(`${API_BASE_URL}/associations/${id}/attestato`, {
|
||||
headers: getHeaders(),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
removeAuthToken();
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
throw new Error('Failed to generate attestato');
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `attestato-${associationName}.pdf`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export async function deleteAssociation(id: string): Promise<void> {
|
||||
const response = await fetch(`${API_BASE_URL}/associations/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: getHeaders(),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
removeAuthToken();
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
throw new Error('Failed to delete association');
|
||||
}
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
removeAuthToken();
|
||||
}
|
||||
|
||||
export async function verifyToken(): Promise<boolean> {
|
||||
try {
|
||||
const token = getAuthToken();
|
||||
|
||||
if (!token) return false;
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/associations`, {
|
||||
headers: getHeaders(),
|
||||
});
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
removeAuthToken();
|
||||
return false;
|
||||
}
|
||||
|
||||
return response.ok;
|
||||
|
||||
} catch (error) {
|
||||
console.error("verifyToken error:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
30
lib/auth.ts
Normal file
30
lib/auth.ts
Normal file
@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { logout as clearAuthToken } from "./api";
|
||||
import type { User } from "./models";
|
||||
|
||||
export type StoredUser = Pick<User, "email" | "role">;
|
||||
|
||||
export function useStoredUser() {
|
||||
const [user, setUser] = useState<StoredUser | null>(null);
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const email = localStorage.getItem("user_email");
|
||||
const role = localStorage.getItem("user_role") as StoredUser["role"] | null;
|
||||
if (email && role) {
|
||||
setUser({ email, role });
|
||||
}
|
||||
setReady(true);
|
||||
}, []);
|
||||
|
||||
return { user, ready };
|
||||
}
|
||||
|
||||
export function clearStoredUser() {
|
||||
clearAuthToken();
|
||||
localStorage.removeItem("user_role");
|
||||
localStorage.removeItem("user_email");
|
||||
localStorage.removeItem("association_id");
|
||||
}
|
||||
37
lib/models.ts
Normal file
37
lib/models.ts
Normal file
@ -0,0 +1,37 @@
|
||||
export interface Associate {
|
||||
name: string;
|
||||
surname: string;
|
||||
birthDate: string;
|
||||
address: string;
|
||||
fiscalCode: string;
|
||||
italianCitizen: boolean;
|
||||
role: 'PRESIDENT' | 'VICE_PRESIDENT' | 'SECRETARY' | 'ASSOCIATE';
|
||||
residencePermitNumber?: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id?: string;
|
||||
email: string;
|
||||
password?: string;
|
||||
role: 'CLIENT' | 'MANAGER' | 'ADMIN';
|
||||
}
|
||||
|
||||
export interface Association {
|
||||
id?: string;
|
||||
runtsId?: string;
|
||||
name: string;
|
||||
address: string;
|
||||
created?: string;
|
||||
isShown?: boolean;
|
||||
vatNumber?: string;
|
||||
translit?: string;
|
||||
associates?: Associate[];
|
||||
users?: User[];
|
||||
ratificationCode?: string;
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
token: string;
|
||||
user: User;
|
||||
association: Association;
|
||||
}
|
||||
6
lib/utils.ts
Normal file
6
lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
BIN
public/logo-encap.jpg
Normal file
BIN
public/logo-encap.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
Loading…
Reference in New Issue
Block a user