Disscount Statistika
diff --git a/frontend/src/components/custom/app-sidebar.tsx b/frontend/src/components/custom/app-sidebar.tsx
index 261ca3e..46b387b 100644
--- a/frontend/src/components/custom/app-sidebar.tsx
+++ b/frontend/src/components/custom/app-sidebar.tsx
@@ -83,11 +83,12 @@ export const AppSidebar = memo(function AppSidebar() {
diff --git a/frontend/src/components/custom/footer.tsx b/frontend/src/components/custom/footer.tsx
index adc9f75..175322e 100644
--- a/frontend/src/components/custom/footer.tsx
+++ b/frontend/src/components/custom/footer.tsx
@@ -24,11 +24,12 @@ export default function FooterSection() {
{/* App logo */}
diff --git a/frontend/src/components/custom/header/forms/account-details-modal.tsx b/frontend/src/components/custom/header/forms/account-details-modal.tsx
index d521f23..06836a7 100644
--- a/frontend/src/components/custom/header/forms/account-details-modal.tsx
+++ b/frontend/src/components/custom/header/forms/account-details-modal.tsx
@@ -18,13 +18,6 @@ import {
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import {
Form,
@@ -36,7 +29,8 @@ import {
FormMessage,
} from "@/components/ui/form";
import { userRequestSchema, UserRequest } from "@/lib/api/schemas/auth-user";
-import { authService, userService } from "@/lib/api";
+import { userService } from "@/lib/api";
+import { authClient } from "@/lib/auth-client";
import { useUser } from "@/context/user-context";
interface IAccountDetailsModalProps {
@@ -49,19 +43,19 @@ export default function AccountDetailsModal({
onOpenChange,
}: IAccountDetailsModalProps) {
const [selectedFile, setSelectedFile] = useState(null);
+ const [isLoggingOutAll, setIsLoggingOutAll] = useState(false);
+ const [isDeleting, setIsDeleting] = useState(false);
const { user, isLoading: isUserLoading, setUser, logout } = useUser();
const queryClient = useQueryClient();
// Update mutations
const updateUserMutation = userService.useUpdateCurrentUser();
const deleteUserMutation = userService.useDeleteCurrentUser();
- const logoutAllMutation = authService.useLogoutAll();
const form = useForm({
resolver: zodResolver(userRequestSchema),
defaultValues: {
username: user?.username,
- stayLoggedInDays: user?.stayLoggedInDays || 30,
notificationsPush: user?.notificationsPush ?? true,
notificationsEmail: user?.notificationsEmail ?? true,
},
@@ -85,7 +79,6 @@ export default function AccountDetailsModal({
updateUserMutation.mutate(
{
username: data.username,
- stayLoggedInDays: data.stayLoggedInDays,
notificationsPush: data.notificationsPush,
notificationsEmail: data.notificationsEmail,
},
@@ -126,46 +119,61 @@ export default function AccountDetailsModal({
onOpenChange(false);
};
- const handleDeleteUser = () => {
+ const handleDeleteUser = async () => {
if (
- confirm(
+ !confirm(
"Jeste li sigurni da želite obrisati svoj račun? Ova akcija se ne može poništiti."
)
) {
- deleteUserMutation.mutate(undefined, {
- onSuccess: () => {
- toast.success("Račun je uspješno obrisan!");
- logout();
- onOpenChange(false);
- },
- onError: (error) => {
- toast.error(error.message || "Greška pri brisanju računa");
- },
- });
+ return;
+ }
+
+ setIsDeleting(true);
+ try {
+ // 1) Backend anonymizes the profile (strips PII, keeps business data).
+ // Runs first, while we still have a valid session/JWT.
+ await deleteUserMutation.mutateAsync();
+
+ // 2) Delete the better-auth identity so the email can be re-used.
+ const { error } = await authClient.deleteUser();
+ if (error) {
+ throw new Error(error.message || "Brisanje identiteta nije uspjelo");
+ }
+
+ toast.success("Račun je uspješno obrisan!");
+ onOpenChange(false);
+ // 3) Clear local state (signOut is a no-op now that the user is gone).
+ logout();
+ } catch (error) {
+ toast.error((error as Error)?.message || "Greška pri brisanju računa");
+ } finally {
+ setIsDeleting(false);
}
};
- const handleLogoutAll = () => {
- if (confirm("Jeste li sigurni da se želite odjaviti sa svih uređaja?")) {
- logoutAllMutation.mutate(undefined, {
- onSuccess: () => {
- toast.success("Odjavljen si sa svih uređaja!");
- setUser(null);
- // Clear all React Query caches
- queryClient.clear();
- onOpenChange(false);
- },
- onError: (error) => {
- toast.error(error.message || "Greška pri odjavi sa svih uređaja");
- },
- });
+ const handleLogoutAll = async () => {
+ if (!confirm("Jeste li sigurni da se želite odjaviti sa svih ostalih uređaja?"))
+ return;
+
+ setIsLoggingOutAll(true);
+ const { error } = await authClient.revokeOtherSessions();
+ setIsLoggingOutAll(false);
+
+ if (error) {
+ toast.error(error.message || "Greška pri odjavi sa svih uređaja");
+ return;
}
+
+ toast.success("Odjavljen si sa svih ostalih uređaja!");
+ // Drop cached per-user queries; the current session stays active.
+ queryClient.clear();
+ onOpenChange(false);
};
const isLoading =
updateUserMutation.isPending ||
- deleteUserMutation.isPending ||
- logoutAllMutation.isPending ||
+ isDeleting ||
+ isLoggingOutAll ||
isUserLoading;
return (
@@ -249,34 +257,6 @@ export default function AccountDetailsModal({
- (
-
- Ostani prijavljen
-
-
-
- )}
- />
-
diff --git a/frontend/src/components/custom/header/forms/auth-modal.tsx b/frontend/src/components/custom/header/forms/auth-modal.tsx
index eb64961..851a360 100644
--- a/frontend/src/components/custom/header/forms/auth-modal.tsx
+++ b/frontend/src/components/custom/header/forms/auth-modal.tsx
@@ -14,6 +14,7 @@ import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { LoginForm } from "@/components/custom/header/forms/login-form";
import { SignUpForm } from "@/components/custom/header/forms/signup-form";
+import { signIn } from "@/lib/auth-client";
import { GoogleIcon } from "@daveyplate/better-auth-ui";
interface IAuthModalProps {
@@ -36,9 +37,17 @@ export function AuthModal({ isOpen, onOpenChange }: IAuthModalProps) {
setShowOnboarding(true);
};
- const handleGoogleSignIn = () => {
- // TODO: Implement Google OAuth flow
- toast.info("Google prijava će biti dodana uskoro");
+ const handleGoogleSignIn = async () => {
+ // Redirects to Google and back to the app; the session is then picked up
+ // by the user context. callbackURL is where the user lands afterwards.
+ const { error } = await signIn.social({
+ provider: "google",
+ callbackURL: "/",
+ });
+
+ if (error) {
+ toast.error("Greška pri Google prijavi");
+ }
};
const switchToSignUp = () => {
diff --git a/frontend/src/components/custom/header/forms/login-form.tsx b/frontend/src/components/custom/header/forms/login-form.tsx
index b814d46..dbe6b0a 100644
--- a/frontend/src/components/custom/header/forms/login-form.tsx
+++ b/frontend/src/components/custom/header/forms/login-form.tsx
@@ -4,14 +4,13 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Loader2 } from "lucide-react";
import { toast } from "sonner";
-import { isAxiosError } from "axios";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { loginRequestSchema, LoginRequest } from "@/lib/api/schemas/auth-user";
import { cn } from "@/lib/utils";
-import { authService } from "@/lib/api";
+import { signIn } from "@/lib/auth-client";
import { useUser } from "@/context/user-context";
import {
Form,
@@ -28,128 +27,102 @@ interface ILoginFormProps {
}
export function LoginForm({ onSuccess }: ILoginFormProps) {
- const loginMutation = authService.useLogin();
const { handleUserLogin } = useUser();
const form = useForm({
resolver: zodResolver(loginRequestSchema),
defaultValues: {
- usernameOrEmail: "",
+ email: "",
password: "",
},
});
- const onSubmit = (data: LoginRequest) => {
- // clear previous root error and start login
+ const onSubmit = async (data: LoginRequest) => {
form.clearErrors("root");
- loginMutation.mutate(
- {
- usernameOrEmail: data.usernameOrEmail,
- password: data.password,
- },
- {
- onSuccess: async (response) => {
- toast.success("Prijava uspješna!");
- form.reset();
- // Use handleUserLogin to set user and fetch shopping lists/digital cards
- await handleUserLogin(response.user);
- onSuccess?.();
- },
- onError: (error: unknown) => {
- let status = 0;
- let serverMessage: string | undefined;
- if (isAxiosError(error)) {
- status = error.response?.status ?? 0;
- serverMessage =
- (error.response?.data as { message?: string })?.message ||
- error.message;
- } else {
- serverMessage = (error as Error)?.message || "Unknown error";
- }
+ const { error } = await signIn.email({
+ email: data.email,
+ password: data.password,
+ });
- if (status >= 400 && status < 500) {
- // 4xx -> show root form error
- const message =
- status === 401
- ? "Neispravno korisničko ime ili email i lozinka"
- : serverMessage || "Provjeri unesene podatke.";
- form.setError("root", { type: "server", message });
- } else {
- toast.error(serverMessage || "Greška pri prijavi");
- }
- },
- }
- );
+ if (error) {
+ const message =
+ error.status === 401 || error.status === 403
+ ? "Neispravan email ili lozinka"
+ : error.message || "Greška pri prijavi";
+ form.setError("root", { type: "server", message });
+ return;
+ }
+
+ toast.success("Prijava uspješna!");
+ form.reset();
+ await handleUserLogin();
+ onSuccess?.();
};
return (
- <>
-