Implement redirect with toast notification on profile save
- Add gotoHomeWithMessage function to navigationActions - Redirect to home page after successful profile save - Display success message in toast notification instead of in-form - Check for profileSaved URL parameter on home page mount - Clean up URL parameter after showing toast - Move success message translation to home-page section - Remove unused success state and message from AccountForm - Remove useEffect import from AccountForm User experience: After saving profile, users are redirected to the familiar home screen and see a toast notification confirming the save. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,11 @@ export async function gotoHome({year, month}: YearMonth) {
|
|||||||
await gotoUrl(path);
|
await gotoUrl(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function gotoHomeWithMessage(locale: string, message: string) {
|
||||||
|
const path = `/${locale}?${message}=true`;
|
||||||
|
await gotoUrl(path);
|
||||||
|
}
|
||||||
|
|
||||||
export async function gotoUrl(path: string) {
|
export async function gotoUrl(path: string) {
|
||||||
console.log(path)
|
console.log(path)
|
||||||
revalidatePath(path, "page");
|
revalidatePath(path, "page");
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import { withUser } from '@/app/lib/auth';
|
|||||||
import { AuthenticatedUser } from '../types/next-auth';
|
import { AuthenticatedUser } from '../types/next-auth';
|
||||||
import { unstable_noStore as noStore } from 'next/cache';
|
import { unstable_noStore as noStore } from 'next/cache';
|
||||||
import { IntlTemplateFn } from '@/app/i18n';
|
import { IntlTemplateFn } from '@/app/i18n';
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations, getLocale } from "next-intl/server";
|
||||||
import { revalidatePath } from 'next/cache';
|
import { revalidatePath } from 'next/cache';
|
||||||
|
import { gotoHomeWithMessage } from './navigationActions';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
errors?: {
|
errors?: {
|
||||||
@@ -93,8 +94,7 @@ export const updateUserProfile = withUser(async (user: AuthenticatedUser, prevSt
|
|||||||
|
|
||||||
revalidatePath('/account');
|
revalidatePath('/account');
|
||||||
|
|
||||||
return {
|
// Get current locale and redirect to home with success message
|
||||||
message: null,
|
const locale = await getLocale();
|
||||||
success: true,
|
await gotoHomeWithMessage(locale, 'profileSaved');
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, useEffect } from "react";
|
import { FC } from "react";
|
||||||
import { UserProfile } from "../lib/db-types";
|
import { UserProfile } from "../lib/db-types";
|
||||||
import { updateUserProfile } from "../lib/actions/userProfileActions";
|
import { updateUserProfile } from "../lib/actions/userProfileActions";
|
||||||
import { useFormState, useFormStatus } from "react-dom";
|
import { useFormState, useFormStatus } from "react-dom";
|
||||||
@@ -15,11 +15,10 @@ export type AccountFormProps = {
|
|||||||
type FormFieldsProps = {
|
type FormFieldsProps = {
|
||||||
profile: UserProfile | null;
|
profile: UserProfile | null;
|
||||||
errors: any;
|
errors: any;
|
||||||
success: boolean;
|
|
||||||
message: string | null;
|
message: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormFields: FC<FormFieldsProps> = ({ profile, errors, success, message }) => {
|
const FormFields: FC<FormFieldsProps> = ({ profile, errors, message }) => {
|
||||||
const { pending } = useFormStatus();
|
const { pending } = useFormStatus();
|
||||||
const t = useTranslations("account-form");
|
const t = useTranslations("account-form");
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
@@ -118,16 +117,11 @@ const FormFields: FC<FormFieldsProps> = ({ profile, errors, success, message })
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="general-error" aria-live="polite" aria-atomic="true">
|
<div id="general-error" aria-live="polite" aria-atomic="true">
|
||||||
{message && !success && (
|
{message && (
|
||||||
<p className="mt-2 text-sm text-red-500">
|
<p className="mt-2 text-sm text-red-500">
|
||||||
{message}
|
{message}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{success && (
|
|
||||||
<p className="mt-2 text-sm text-success">
|
|
||||||
{t("success-message")}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-4">
|
<div className="pt-4">
|
||||||
@@ -147,18 +141,10 @@ const FormFields: FC<FormFieldsProps> = ({ profile, errors, success, message })
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AccountForm: FC<AccountFormProps> = ({ profile }) => {
|
export const AccountForm: FC<AccountFormProps> = ({ profile }) => {
|
||||||
const initialState = { message: null, errors: {}, success: false };
|
const initialState = { message: null, errors: {} };
|
||||||
const [state, dispatch] = useFormState(updateUserProfile, initialState);
|
const [state, dispatch] = useFormState(updateUserProfile, initialState);
|
||||||
const t = useTranslations("account-form");
|
const t = useTranslations("account-form");
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (state.success) {
|
|
||||||
// Show success message or toast notification
|
|
||||||
// For now, we'll just log it
|
|
||||||
console.log("Profile updated successfully");
|
|
||||||
}
|
|
||||||
}, [state.success]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card card-compact card-bordered min-w-[20em] max-w-[90em] bg-base-100 shadow-s my-1">
|
<div className="card card-compact card-bordered min-w-[20em] max-w-[90em] bg-base-100 shadow-s my-1">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
@@ -167,7 +153,6 @@ export const AccountForm: FC<AccountFormProps> = ({ profile }) => {
|
|||||||
<FormFields
|
<FormFields
|
||||||
profile={profile}
|
profile={profile}
|
||||||
errors={state.errors}
|
errors={state.errors}
|
||||||
success={state.success ?? false}
|
|
||||||
message={state.message ?? null}
|
message={state.message ?? null}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import { LocationCard } from "./LocationCard";
|
|||||||
import { PrintButton } from "./PrintButton";
|
import { PrintButton } from "./PrintButton";
|
||||||
import { BillingLocation, YearMonth } from "../lib/db-types";
|
import { BillingLocation, YearMonth } from "../lib/db-types";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { ToastContainer } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
const getNextYearMonth = (yearMonth:YearMonth) => {
|
const getNextYearMonth = (yearMonth:YearMonth) => {
|
||||||
const {year, month} = yearMonth;
|
const {year, month} = yearMonth;
|
||||||
@@ -38,6 +39,7 @@ export const MonthLocationList:React.FC<MonthLocationListProps > = ({
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const search = useSearchParams();
|
const search = useSearchParams();
|
||||||
|
const t = useTranslations("home-page");
|
||||||
|
|
||||||
const initialMonth = search.get('month')
|
const initialMonth = search.get('month')
|
||||||
|
|
||||||
@@ -45,6 +47,18 @@ export const MonthLocationList:React.FC<MonthLocationListProps > = ({
|
|||||||
initialMonth ? parseInt(initialMonth as string) : -1 // no month is expanded
|
initialMonth ? parseInt(initialMonth as string) : -1 // no month is expanded
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check for profile saved success message
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (search.get('profileSaved') === 'true') {
|
||||||
|
toast.success(t("profile-saved-message"), { theme: "dark" });
|
||||||
|
// Clean up URL parameter
|
||||||
|
const params = new URLSearchParams(search.toString());
|
||||||
|
params.delete('profileSaved');
|
||||||
|
const newSearch = params.toString();
|
||||||
|
router.replace(newSearch ? `?${newSearch}` : '/');
|
||||||
|
}
|
||||||
|
}, [search, router, t]);
|
||||||
|
|
||||||
if(!availableYears || !months) {
|
if(!availableYears || !months) {
|
||||||
const currentYearMonth:YearMonth = {
|
const currentYearMonth:YearMonth = {
|
||||||
year: new Date().getFullYear(),
|
year: new Date().getFullYear(),
|
||||||
|
|||||||
@@ -73,7 +73,8 @@
|
|||||||
"table-header-barcode": "2D Barcode",
|
"table-header-barcode": "2D Barcode",
|
||||||
"empty-state-title": "No Barcode Data Found",
|
"empty-state-title": "No Barcode Data Found",
|
||||||
"empty-state-message": "No bills with 2D barcodes found for {yearMonth}"
|
"empty-state-message": "No bills with 2D barcodes found for {yearMonth}"
|
||||||
}
|
},
|
||||||
|
"profile-saved-message": "Profile updated successfully"
|
||||||
},
|
},
|
||||||
"bill-delete-form": {
|
"bill-delete-form": {
|
||||||
"text": "Please confirm deletion of bill \"<strong>{bill_name}</strong>\" at \"<strong>{location_name}</strong>\".",
|
"text": "Please confirm deletion of bill \"<strong>{bill_name}</strong>\" at \"<strong>{location_name}</strong>\".",
|
||||||
@@ -143,7 +144,6 @@
|
|||||||
"iban-placeholder": "Enter your IBAN",
|
"iban-placeholder": "Enter your IBAN",
|
||||||
"save-button": "Save",
|
"save-button": "Save",
|
||||||
"cancel-button": "Cancel",
|
"cancel-button": "Cancel",
|
||||||
"success-message": "Profile updated successfully",
|
|
||||||
"validation": {}
|
"validation": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,8 @@
|
|||||||
"table-header-barcode": "2D Barkod",
|
"table-header-barcode": "2D Barkod",
|
||||||
"empty-state-title": "Nema Podataka o Barkodovima",
|
"empty-state-title": "Nema Podataka o Barkodovima",
|
||||||
"empty-state-message": "Nema računa s 2D barkodovima za {yearMonth}"
|
"empty-state-message": "Nema računa s 2D barkodovima za {yearMonth}"
|
||||||
}
|
},
|
||||||
|
"profile-saved-message": "Profil uspješno ažuriran"
|
||||||
},
|
},
|
||||||
"bill-delete-form": {
|
"bill-delete-form": {
|
||||||
"text": "Molim potvrdi brisanje računa \"<strong>{bill_name}</strong>\" koji pripada nekretnini \"<strong>{location_name}</strong>\".",
|
"text": "Molim potvrdi brisanje računa \"<strong>{bill_name}</strong>\" koji pripada nekretnini \"<strong>{location_name}</strong>\".",
|
||||||
@@ -142,7 +143,6 @@
|
|||||||
"iban-placeholder": "Unesite svoj IBAN",
|
"iban-placeholder": "Unesite svoj IBAN",
|
||||||
"save-button": "Spremi",
|
"save-button": "Spremi",
|
||||||
"cancel-button": "Odbaci",
|
"cancel-button": "Odbaci",
|
||||||
"success-message": "Profil uspješno ažuriran",
|
|
||||||
"validation": {}
|
"validation": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user