localStorage kaldırıldı, herşey [EasyPeasyStore][0] taşındı

This commit is contained in:
Sedat Öztürk 2026-07-09 20:55:07 +03:00
parent 904f8ed447
commit 7f7c6fab36
19 changed files with 440 additions and 431 deletions

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations namespace Sozsoft.Platform.Migrations
{ {
[DbContext(typeof(PlatformDbContext))] [DbContext(typeof(PlatformDbContext))]
[Migration("20260708113003_Initial")] [Migration("20260709175406_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />

View file

@ -4,23 +4,8 @@ import { useNavigate } from 'react-router-dom'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { Button } from '@/components/ui' import { Button } from '@/components/ui'
import { BasketItem, BillingCycle } from '@/proxy/order/models' import type { OrderSuccessData } from '@/store/client.model'
import { CustomTenantDto } from '@/proxy/config/models' import type { BasketItem } from '@/proxy/order/models'
export interface OrderSuccessData {
orderId: string
tenant: CustomTenantDto
items: BasketItem[]
billingCycle: BillingCycle
period: number
licenseStartTime?: string
licenseEndTime?: string
subtotal: number
vatTotal: number
commission: number
total: number
installment?: number
}
interface OrderSuccessProps { interface OrderSuccessProps {
orderId: string orderId: string

View file

@ -17,7 +17,7 @@ import {
FaCheckCircle, FaCheckCircle,
FaSitemap, FaSitemap,
} from 'react-icons/fa' } from 'react-icons/fa'
import React, { useEffect, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
@ -30,6 +30,8 @@ import {
getCountries, getCountries,
getDistricts, getDistricts,
} from '@/services/location.service' } from '@/services/location.service'
import { useStoreActions, useStoreState } from '@/store/store'
import type { TenantFormDraft } from '@/store/client.model'
interface LocationOption<T> { interface LocationOption<T> {
label: string label: string
@ -38,8 +40,6 @@ interface LocationOption<T> {
} }
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
const TENANT_DATA_STORAGE_KEY = 'tenantData'
const TENANT_FORM_DRAFT_STORAGE_KEY = 'tenantFormDraft'
const DEFAULT_PHONE_FORMAT = '000 000 000 000 000' const DEFAULT_PHONE_FORMAT = '000 000 000 000 000'
const TURKEY_PHONE_FORMAT = '(000) 000-0000' const TURKEY_PHONE_FORMAT = '(000) 000-0000'
const REQUIRED_NEW_CUSTOMER_FIELDS: Array<keyof CustomTenantDto> = [ const REQUIRED_NEW_CUSTOMER_FIELDS: Array<keyof CustomTenantDto> = [
@ -169,11 +169,6 @@ const getUniqueLocationOptions = <T,>(
}, []) }, [])
} }
type TenantFormDraft = Partial<CustomTenantDto> & {
isExisting?: boolean
foundTenantName?: string
}
interface TenantFormProps { interface TenantFormProps {
onSubmit: (tenant: CustomTenantDto) => void onSubmit: (tenant: CustomTenantDto) => void
onBack: () => void onBack: () => void
@ -196,22 +191,25 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
const [foundTenantName, setFoundTenantName] = useState('') const [foundTenantName, setFoundTenantName] = useState('')
const navigate = useNavigate() const navigate = useNavigate()
const { translate } = useLocalization() const { translate } = useLocalization()
const { tenantData, tenantFormDraft } = useStoreState(
(state) => state.client.order,
)
const { setTenantFormDraft, clearTenant } = useStoreActions(
(actions) => actions.client.order,
)
const hasRestoredTenantData = useRef(false)
useEffect(() => { useEffect(() => {
const savedDraft = localStorage.getItem(TENANT_FORM_DRAFT_STORAGE_KEY) if (hasRestoredTenantData.current) return
const savedTenant = localStorage.getItem(TENANT_DATA_STORAGE_KEY) hasRestoredTenantData.current = true
const savedData = savedDraft || savedTenant
const savedData: TenantFormDraft | null = tenantFormDraft || tenantData
if (!savedData) return if (!savedData) return
try { setIsExisting(savedData.isExisting ?? true)
const parsed = JSON.parse(savedData) as TenantFormDraft setFoundTenantName(savedData.foundTenantName || savedData.name || '')
setIsExisting(parsed.isExisting ?? true) setFormData(savedData)
setFoundTenantName(parsed.foundTenantName || parsed.name || '') }, [tenantData, tenantFormDraft])
setFormData(parsed)
} catch (error) {
console.error('Müşteri form taslağı okunamadı:', error)
}
}, [])
useEffect(() => { useEffect(() => {
const loadCountries = async () => { const loadCountries = async () => {
@ -259,15 +257,12 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
useEffect(() => { useEffect(() => {
if (Object.keys(formData).length === 0) return if (Object.keys(formData).length === 0) return
localStorage.setItem( setTenantFormDraft({
TENANT_FORM_DRAFT_STORAGE_KEY, ...formData,
JSON.stringify({ isExisting,
...formData, foundTenantName,
isExisting, })
foundTenantName, }, [formData, foundTenantName, isExisting, setTenantFormDraft])
}),
)
}, [formData, foundTenantName, isExisting])
useEffect(() => { useEffect(() => {
const loadLocationOptions = async () => { const loadLocationOptions = async () => {
@ -399,8 +394,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
setPhoneError('') setPhoneError('')
setDistricts([]) setDistricts([])
setFormData(nextIsExisting ? {} : getDefaultNewCustomerData()) setFormData(nextIsExisting ? {} : getDefaultNewCustomerData())
localStorage.removeItem(TENANT_DATA_STORAGE_KEY) clearTenant()
localStorage.removeItem(TENANT_FORM_DRAFT_STORAGE_KEY)
} }
const resetExistingTenantDetails = (name: string) => { const resetExistingTenantDetails = (name: string) => {
@ -679,8 +673,8 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<FaPhone className="w-4 h-4 text-gray-500" /> <FaPhone className="w-4 h-4 text-gray-500" />
<span className="font-medium">{translate('::blog.categories.mobile')}:</span> <span className="font-medium">{translate('::Abp.Identity.User.UserInformation.PhoneNumber')}:</span>
<span>{formData.mobileNumber}</span> <span>{formData.phoneNumber}</span>
</div> </div>
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">

View file

@ -4,16 +4,32 @@ import { Injections } from './store'
import { GridOptionsEditDto } from '../proxy/form/models' import { GridOptionsEditDto } from '../proxy/form/models'
import setNull from '../utils/setNull' import setNull from '../utils/setNull'
import { ListState } from '@/proxy/admin/list-form/models' import { ListState } from '@/proxy/admin/list-form/models'
import { Message } from '@/proxy/ai/models' import { Conversation } from '@/proxy/ai/models'
export type DashboardLayout = 'default' | 'wide-center' | 'balanced' | 'wide-left' | 'wide-right'
export interface DashboardState {
dashboardLayout: DashboardLayout
hiddenWidgetIds: string[]
widgetOrder: {
left: string[]
center: string[]
right: string[]
}
}
export interface AiConversationsState {
items: Conversation[]
aiActiveConversationId: string | null
}
export interface AdminStoreModel { export interface AdminStoreModel {
lists: { lists: {
values: GridOptionsEditDto | undefined values: GridOptionsEditDto | undefined
states: ListState[] states: ListState[]
} }
messages: { dashboard: DashboardState
aiPosts: Message[] aiConversations: AiConversationsState
}
} }
export interface AdminStoreActions { export interface AdminStoreActions {
@ -27,21 +43,36 @@ export interface AdminStoreActions {
> >
setStates: Action<AdminStoreModel['lists'], ListState> setStates: Action<AdminStoreModel['lists'], ListState>
} }
messages: { dashboard: {
addAiPost: Action<AdminStoreModel['messages'], Message> setDashboardLayout: Action<DashboardState, DashboardLayout>
setAiPosts: Action<AdminStoreModel['messages'], Message[]> setHiddenWidgetIds: Action<DashboardState, string[]>
setWidgetOrder: Action<DashboardState, DashboardState['widgetOrder']>
reset: Action<DashboardState>
} }
setAiConversations: Action<AdminStoreModel, AiConversationsState>
} }
export type AdminModel = AdminStoreModel & AdminStoreActions export type AdminModel = AdminStoreModel & AdminStoreActions
const initialDashboardState: DashboardState = {
dashboardLayout: 'default',
hiddenWidgetIds: [],
widgetOrder: {
left: [],
center: [],
right: [],
},
}
const initialState: AdminStoreModel = { const initialState: AdminStoreModel = {
lists: { lists: {
values: undefined, values: undefined,
states: [], states: [],
}, },
messages: { dashboard: initialDashboardState,
aiPosts: [], aiConversations: {
items: [],
aiActiveConversationId: null,
}, },
} }
@ -73,14 +104,25 @@ export const adminModel: AdminModel = {
actions.setListFormValues(result.data) actions.setListFormValues(result.data)
}), }),
}, },
messages: { dashboard: {
...initialState.messages, ...initialState.dashboard,
addAiPost: action((state, payload) => { setDashboardLayout: action((state, payload) => {
state.aiPosts = [...state.aiPosts, payload] state.dashboardLayout = payload
}), }),
setHiddenWidgetIds: action((state, payload) => {
setAiPosts: action((state, payload) => { state.hiddenWidgetIds = payload
state.aiPosts = payload }),
setWidgetOrder: action((state, payload) => {
state.widgetOrder = payload
}),
reset: action((state) => {
state.dashboardLayout = initialDashboardState.dashboardLayout
state.hiddenWidgetIds = initialDashboardState.hiddenWidgetIds
state.widgetOrder = initialDashboardState.widgetOrder
}), }),
}, },
aiConversations: initialState.aiConversations,
setAiConversations: action((state, payload) => {
state.aiConversations = payload
}),
} }

View file

@ -0,0 +1,122 @@
import type { Action } from 'easy-peasy'
import { action } from 'easy-peasy'
import type { CustomTenantDto } from '@/proxy/config/models'
import type { OrderDto } from '@/proxy/order/models'
import type { FileItem } from '@/types/fileManagement'
import { initialCartState, type CartState } from '../utils/cartUtils'
export type TenantFormDraft = Partial<CustomTenantDto> & {
isExisting?: boolean
foundTenantName?: string
}
export type OrderSuccessData = OrderDto & {
orderId: string
}
export interface OrderState {
tenantData: CustomTenantDto | null
tenantFormDraft: TenantFormDraft | null
orderId: string | null
orderSuccessData: OrderSuccessData | null
}
export interface FileManagerClipboard {
operation: 'copy' | 'cut'
items: FileItem[]
sourceFolder?: string
}
export interface FileManagerState {
clipboard: FileManagerClipboard | null
}
export interface DeveloperKitState {
lastCreateTableScript: string
}
export interface ClientStoreModel {
cartState: CartState
order: OrderState
fileManager: FileManagerState
developerKit: DeveloperKitState
}
export interface ClientStoreActions {
setCartState: Action<ClientStoreModel, CartState>
order: {
setTenantData: Action<OrderState, CustomTenantDto>
setTenantFormDraft: Action<OrderState, TenantFormDraft>
setOrderResult: Action<OrderState, OrderSuccessData>
clearTenant: Action<OrderState>
clearResult: Action<OrderState>
}
fileManager: {
setClipboard: Action<FileManagerState, FileManagerClipboard>
clearClipboard: Action<FileManagerState>
}
developerKit: {
setLastCreateTableScript: Action<DeveloperKitState, string>
}
}
export type ClientModel = ClientStoreModel & ClientStoreActions
const initialState: ClientStoreModel = {
cartState: initialCartState,
order: {
tenantData: null,
tenantFormDraft: null,
orderId: null,
orderSuccessData: null,
},
fileManager: {
clipboard: null,
},
developerKit: {
lastCreateTableScript: '',
},
}
export const clientModel: ClientModel = {
...initialState,
setCartState: action((state, payload) => {
state.cartState = payload
}),
order: {
...initialState.order,
setTenantData: action((state, payload) => {
state.tenantData = payload
}),
setTenantFormDraft: action((state, payload) => {
state.tenantFormDraft = payload
}),
setOrderResult: action((state, payload) => {
state.orderId = payload.orderId
state.orderSuccessData = payload
}),
clearTenant: action((state) => {
state.tenantData = null
state.tenantFormDraft = null
}),
clearResult: action((state) => {
state.orderId = null
state.orderSuccessData = null
}),
},
fileManager: {
...initialState.fileManager,
setClipboard: action((state, payload) => {
state.clipboard = payload
}),
clearClipboard: action((state) => {
state.clipboard = null
}),
},
developerKit: {
...initialState.developerKit,
setLastCreateTableScript: action((state, payload) => {
state.lastCreateTableScript = payload
}),
},
}

View file

@ -21,7 +21,7 @@ export type LocaleModel = LocaleStoreModel & LocaleStoreActions
const initialState: LocaleStoreModel = { const initialState: LocaleStoreModel = {
currentLang: appConfig.locale, currentLang: appConfig.locale,
currentUiVersion: appConfig.uiVersion, currentUiVersion: appConfig.uiVersion,
currentTenantName: undefined currentTenantName: undefined,
} }
export const localeModel: LocaleModel = { export const localeModel: LocaleModel = {

View file

@ -5,6 +5,7 @@ import { AbpConfigModel, abpConfigModel } from './abpConfig.model'
import { AdminModel, adminModel } from './admin.model' import { AdminModel, adminModel } from './admin.model'
import { AuthModel, authModel } from './auth.model' import { AuthModel, authModel } from './auth.model'
import { BaseModel, baseModel } from './base.model' import { BaseModel, baseModel } from './base.model'
import { ClientModel, clientModel } from './client.model'
import { LocaleModel, localeModel } from './locale.model' import { LocaleModel, localeModel } from './locale.model'
import { ThemeModel, themeModel } from './theme.model' import { ThemeModel, themeModel } from './theme.model'
import { refreshToken } from '../services/auth.service' import { refreshToken } from '../services/auth.service'
@ -17,6 +18,7 @@ export interface StoreModel {
auth: AuthModel auth: AuthModel
base: BaseModel base: BaseModel
locale: LocaleModel locale: LocaleModel
client: ClientModel
admin: AdminModel admin: AdminModel
} }
@ -38,11 +40,11 @@ export const store = createStore<StoreModel>(
auth: authModel, auth: authModel,
base: baseModel, base: baseModel,
locale: localeModel, locale: localeModel,
client: clientModel,
admin: adminModel, admin: adminModel,
}, },
{ {
allow: ['auth', 'theme', 'locale', 'admin'], allow: ['auth', 'theme', 'locale', 'client', 'admin'],
storage: 'localStorage',
}, },
), ),
{ {

View file

@ -1,12 +1,12 @@
import { BillingCycle, BasketItem, ProductDto } from "@/proxy/order/models"; import { BillingCycle, BasketItem, ProductDto } from '@/proxy/order/models'
export interface CartState { export interface CartState {
items: BasketItem[]; items: BasketItem[]
subtotal: number; subtotal: number
vatTotal: number; vatTotal: number
total: number; total: number
globalBillingCycle: BillingCycle; globalBillingCycle: BillingCycle
globalPeriod: number; globalPeriod: number
} }
export const initialCartState: CartState = { export const initialCartState: CartState = {
@ -15,29 +15,24 @@ export const initialCartState: CartState = {
vatTotal: 0, vatTotal: 0,
total: 0, total: 0,
globalBillingCycle: 'yearly', globalBillingCycle: 'yearly',
globalPeriod: 1 globalPeriod: 1,
}; }
// Key for localStorage
const CART_STORAGE_KEY = 'cartState';
export const calculateItemPrice = ( export const calculateItemPrice = (
product: ProductDto, product: ProductDto,
quantity: number, quantity: number,
billingCycle: BillingCycle, billingCycle: BillingCycle,
period: number = 1 period: number = 1,
): number => { ): number => {
const price = billingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice!; const price = billingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice!
const vatRate = product.vatRate ?? 0.2; const vatRate = product.vatRate ?? 0.2
return roundMoney(price * quantity * period * (1 + vatRate)); return roundMoney(price * quantity * period * (1 + vatRate))
}; }
const roundMoney = (value: number): number => Math.round(value * 100) / 100; const roundMoney = (value: number): number => Math.round(value * 100) / 100
const calculateItemVatAmount = ( const calculateItemVatAmount = (totalPrice: number, vatRate: number = 0.2): number =>
totalPrice: number, vatRate > 0 ? roundMoney(totalPrice - totalPrice / (1 + vatRate)) : 0
vatRate: number = 0.2,
): number => (vatRate > 0 ? roundMoney(totalPrice - totalPrice / (1 + vatRate)) : 0);
const withItemVat = ( const withItemVat = (
product: ProductDto, product: ProductDto,
@ -45,8 +40,8 @@ const withItemVat = (
billingCycle: BillingCycle, billingCycle: BillingCycle,
period: number, period: number,
): BasketItem => { ): BasketItem => {
const vatRate = product.vatRate ?? 0.2; const vatRate = product.vatRate ?? 0.2
const totalPrice = calculateItemPrice(product, quantity, billingCycle, period); const totalPrice = calculateItemPrice(product, quantity, billingCycle, period)
return { return {
product, product,
quantity, quantity,
@ -54,135 +49,128 @@ const withItemVat = (
vatRate, vatRate,
vatAmount: calculateItemVatAmount(totalPrice, vatRate), vatAmount: calculateItemVatAmount(totalPrice, vatRate),
totalPrice, totalPrice,
}; }
}; }
const calculateCartTotals = (items: BasketItem[]) => { const calculateCartTotals = (items: BasketItem[]) => {
const total = roundMoney(items.reduce((sum, item) => sum + item.totalPrice, 0)); const total = roundMoney(items.reduce((sum, item) => sum + item.totalPrice, 0))
const vatTotal = roundMoney(items.reduce((sum, item) => sum + (item.vatAmount ?? 0), 0)); const vatTotal = roundMoney(items.reduce((sum, item) => sum + (item.vatAmount ?? 0), 0))
return { return {
subtotal: roundMoney(total - vatTotal), subtotal: roundMoney(total - vatTotal),
vatTotal, vatTotal,
total, total,
}; }
}; }
export const addItemToCart = ( export const addItemToCart = (
cartState: CartState, cartState: CartState,
product: ProductDto, product: ProductDto,
quantity: number, quantity: number,
billingCycle: BillingCycle billingCycle: BillingCycle,
): CartState => { ): CartState => {
const isAlreadyInCart = isItemInCartState(cartState.items, product.id, billingCycle); const isAlreadyInCart = isItemInCartState(cartState.items, product.id, billingCycle)
if (!product.isQuantityBased && isAlreadyInCart) { if (!product.isQuantityBased && isAlreadyInCart) {
return cartState; return cartState
} }
const existingItemIndex = cartState.items.findIndex( const existingItemIndex = cartState.items.findIndex(
item => item.product.id === product.id && item.billingCycle === billingCycle (item) => item.product.id === product.id && item.billingCycle === billingCycle,
); )
let newItems; let newItems
if (existingItemIndex > -1) { if (existingItemIndex > -1) {
newItems = [...cartState.items]; newItems = [...cartState.items]
newItems[existingItemIndex].quantity += quantity; newItems[existingItemIndex].quantity += quantity
newItems[existingItemIndex] = withItemVat( newItems[existingItemIndex] = withItemVat(
product, product,
newItems[existingItemIndex].quantity, newItems[existingItemIndex].quantity,
billingCycle, billingCycle,
cartState.globalPeriod, cartState.globalPeriod,
); )
} else { } else {
const actualQuantity = !product.isQuantityBased ? 1 : quantity; const actualQuantity = !product.isQuantityBased ? 1 : quantity
newItems = [ newItems = [
...cartState.items, ...cartState.items,
withItemVat(product, actualQuantity, billingCycle, cartState.globalPeriod), withItemVat(product, actualQuantity, billingCycle, cartState.globalPeriod),
]; ]
} }
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }; const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }
saveCartToLocalStorage(updatedState); return updatedState
return updatedState; }
};
export const removeItemFromCart = (cartState: CartState, id: string): CartState => { export const removeItemFromCart = (cartState: CartState, id: string): CartState => {
const newItems = cartState.items.filter(item => item.product.id !== id); const newItems = cartState.items.filter((item) => item.product.id !== id)
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }; const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }
saveCartToLocalStorage(updatedState); return updatedState
return updatedState; }
};
export const updateCartItemQuantity = (cartState: CartState, id: string, quantity: number): CartState => { export const updateCartItemQuantity = (
const newItems = cartState.items.map(item => { cartState: CartState,
if (item.product.id === id) { id: string,
if (quantity <= 0) return null; quantity: number,
if (!item.product.isQuantityBased) return item; ): CartState => {
const newItems = cartState.items
.map((item) => {
if (item.product.id === id) {
if (quantity <= 0) return null
if (!item.product.isQuantityBased) return item
return withItemVat(item.product, quantity, item.billingCycle, cartState.globalPeriod); return withItemVat(item.product, quantity, item.billingCycle, cartState.globalPeriod)
} }
return item; return item
}).filter(Boolean) as BasketItem[]; })
.filter(Boolean) as BasketItem[]
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }; const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }
saveCartToLocalStorage(updatedState); return updatedState
return updatedState; }
};
export const clearCart = (): CartState => { export const clearCart = (): CartState => {
localStorage.removeItem(CART_STORAGE_KEY); return initialCartState
return initialCartState; }
};
export const setCartGlobalBillingCycle = (cartState: CartState, cycle: BillingCycle): CartState => { export const setCartGlobalBillingCycle = (cartState: CartState, cycle: BillingCycle): CartState => {
const newItems = cartState.items.map(item => const newItems = cartState.items.map((item) =>
withItemVat(item.product, item.quantity, cycle, cartState.globalPeriod) withItemVat(item.product, item.quantity, cycle, cartState.globalPeriod),
); )
const updatedState = { ...cartState, globalBillingCycle: cycle, items: newItems, ...calculateCartTotals(newItems) }; const updatedState = {
saveCartToLocalStorage(updatedState); ...cartState,
return updatedState; globalBillingCycle: cycle,
}; items: newItems,
...calculateCartTotals(newItems),
}
return updatedState
}
export const setCartGlobalPeriod = (cartState: CartState, period: number): CartState => { export const setCartGlobalPeriod = (cartState: CartState, period: number): CartState => {
const newItems = cartState.items.map(item => const newItems = cartState.items.map((item) =>
withItemVat(item.product, item.quantity, item.billingCycle, period) withItemVat(item.product, item.quantity, item.billingCycle, period),
); )
const updatedState = { ...cartState, globalPeriod: period, items: newItems, ...calculateCartTotals(newItems) }; const updatedState = {
saveCartToLocalStorage(updatedState); ...cartState,
return updatedState; globalPeriod: period,
}; items: newItems,
...calculateCartTotals(newItems),
}
return updatedState
}
export const isItemInCart = (cartState: CartState, productId: string, billingCycle: BillingCycle): boolean => { export const isItemInCart = (
cartState: CartState,
productId: string,
billingCycle: BillingCycle,
): boolean => {
return cartState.items.some( return cartState.items.some(
item => item.product.id === productId && item.billingCycle === billingCycle (item) => item.product.id === productId && item.billingCycle === billingCycle,
); )
}; }
export const isItemInCartState = (items: BasketItem[], productId: string, billingCycle: BillingCycle): boolean => { export const isItemInCartState = (
return items.some( items: BasketItem[],
item => item.product.id === productId && item.billingCycle === billingCycle productId: string,
); billingCycle: BillingCycle,
}; ): boolean => {
return items.some((item) => item.product.id === productId && item.billingCycle === billingCycle)
// Utility: Save to localStorage }
const saveCartToLocalStorage = (cartState: CartState) => {
localStorage.setItem(CART_STORAGE_KEY, JSON.stringify(cartState));
};
// Utility: Load from localStorage
export const loadCartFromLocalStorage = (): CartState => {
const saved = localStorage.getItem(CART_STORAGE_KEY);
if (!saved) return initialCartState;
const parsed = JSON.parse(saved) as CartState;
const period = parsed.globalPeriod || 1;
const items = (parsed.items || []).map(item =>
withItemVat(item.product, item.quantity, item.billingCycle, period)
);
return {
...initialCartState,
...parsed,
items,
...calculateCartTotals(items),
};
};

View file

@ -1,7 +1,7 @@
import { useState, useEffect, useCallback, useRef, type MouseEvent } from 'react' import { useState, useEffect, useCallback, useRef, type MouseEvent } from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { Button, Input, Select, toast, Notification, Spinner } from '@/components/ui' import { Button, Input, Select, toast, Notification, Spinner } from '@/components/ui'
import { useStoreState } from '@/store' import { useStoreActions, useStoreState } from '@/store'
import { import {
FaFolder, FaFolder,
FaCloudUploadAlt, FaCloudUploadAlt,
@ -50,6 +50,13 @@ const FileManager = () => {
const authTenantId = useStoreState((state) => state.auth.tenant?.tenantId) const authTenantId = useStoreState((state) => state.auth.tenant?.tenantId)
const authTenantName = useStoreState((state) => state.auth.tenant?.tenantName) const authTenantName = useStoreState((state) => state.auth.tenant?.tenantName)
const fileManagerClipboard = useStoreState((state) => state.client.fileManager.clipboard)
const setFileManagerClipboard = useStoreActions(
(actions) => actions.client.fileManager.setClipboard,
)
const clearFileManagerClipboard = useStoreActions(
(actions) => actions.client.fileManager.clearClipboard,
)
const isHostContext = !authTenantId const isHostContext = !authTenantId
// State // State
@ -447,22 +454,7 @@ const FileManager = () => {
} }
} }
// Clipboard state for paste button const hasClipboardData = !!fileManagerClipboard
const [hasClipboardData, setHasClipboardData] = useState(false)
useEffect(() => {
const checkClipboard = () => {
setHasClipboardData(!!localStorage.getItem('fileManager_clipboard'))
}
// Check initially
checkClipboard()
// Check periodically (in case another tab changes it)
const interval = setInterval(checkClipboard, 1000)
return () => clearInterval(interval)
}, [])
// Keyboard shortcuts // Keyboard shortcuts
useEffect(() => { useEffect(() => {
@ -477,14 +469,12 @@ const FileManager = () => {
e.preventDefault() e.preventDefault()
if (selectedItems.length > 0) { if (selectedItems.length > 0) {
copySelectedItems() copySelectedItems()
setHasClipboardData(true)
} }
break break
case 'x': case 'x':
e.preventDefault() e.preventDefault()
if (selectedItems.length > 0) { if (selectedItems.length > 0) {
cutSelectedItems() cutSelectedItems()
setHasClipboardData(true)
} }
break break
case 'v': case 'v':
@ -616,16 +606,11 @@ const FileManager = () => {
} }
if (copyableItems.length > 0) { if (copyableItems.length > 0) {
// Store in local storage or context for paste operation setFileManagerClipboard({
localStorage.setItem( operation: 'copy',
'fileManager_clipboard', items: copyableItems,
JSON.stringify({ sourceFolder: currentFolderId,
operation: 'copy', })
items: copyableItems,
sourceFolder: currentFolderId,
}),
)
setHasClipboardData(true)
toast.push( toast.push(
<Notification title="Copied" type="success"> <Notification title="Copied" type="success">
{copyableItems.length} item(s) copied to clipboard {copyableItems.length} item(s) copied to clipboard
@ -651,16 +636,11 @@ const FileManager = () => {
} }
if (cuttableItems.length > 0) { if (cuttableItems.length > 0) {
// Store in local storage or context for paste operation setFileManagerClipboard({
localStorage.setItem( operation: 'cut',
'fileManager_clipboard', items: cuttableItems,
JSON.stringify({ sourceFolder: currentFolderId,
operation: 'cut', })
items: cuttableItems,
sourceFolder: currentFolderId,
}),
)
setHasClipboardData(true)
toast.push( toast.push(
<Notification title="Cut" type="success"> <Notification title="Cut" type="success">
{cuttableItems.length} item(s) cut to clipboard {cuttableItems.length} item(s) cut to clipboard
@ -671,8 +651,7 @@ const FileManager = () => {
} }
const pasteItems = async () => { const pasteItems = async () => {
const clipboardData = localStorage.getItem('fileManager_clipboard') if (!fileManagerClipboard) {
if (!clipboardData) {
toast.push( toast.push(
<Notification title="Clipboard Empty" type="info"> <Notification title="Clipboard Empty" type="info">
No items in clipboard No items in clipboard
@ -683,8 +662,8 @@ const FileManager = () => {
} }
try { try {
const clipboard = JSON.parse(clipboardData) const clipboard = fileManagerClipboard
const itemIds = clipboard.items.map((item: FileItemType) => item.id) const itemIds = clipboard.items.map((item) => item.id)
if (clipboard.operation === 'copy') { if (clipboard.operation === 'copy') {
setLoading(true) setLoading(true)
@ -724,9 +703,7 @@ const FileManager = () => {
try { try {
await fileManagementService.moveItems(itemIds, currentFolderId, selectedTenant?.id) await fileManagementService.moveItems(itemIds, currentFolderId, selectedTenant?.id)
await fetchItems(currentFolderId) await fetchItems(currentFolderId)
// Clipboard'ı temizle clearFileManagerClipboard()
localStorage.removeItem('fileManager_clipboard')
setHasClipboardData(false)
toast.push( toast.push(
<Notification title={translate('::App.Platform.Success')} type="success"> <Notification title={translate('::App.Platform.Success')} type="success">
{itemIds.length} item(s) moved successfully {itemIds.length} item(s) moved successfully
@ -1129,18 +1106,15 @@ const FileManager = () => {
onRename={openRenameModal} onRename={openRenameModal}
onMove={(item) => { onMove={(item) => {
// Move işlemi için öğeyi cut olarak clipboard'a koy // Move işlemi için öğeyi cut olarak clipboard'a koy
cutSelectedItems() if (selectedItems.includes(item.id)) {
if (!selectedItems.includes(item.id)) { cutSelectedItems()
} else {
setSelectedItems([item.id]) setSelectedItems([item.id])
localStorage.setItem( setFileManagerClipboard({
'fileManager_clipboard', operation: 'cut',
JSON.stringify({ items: [item],
operation: 'cut', sourceFolder: currentFolderId,
items: [item], })
sourceFolder: currentFolderId,
}),
)
setHasClipboardData(true)
toast.push( toast.push(
<Notification title="Cut" type="success"> <Notification title="Cut" type="success">
Item ready to move. Navigate to target folder and paste. Item ready to move. Navigate to target folder and paste.

View file

@ -106,8 +106,6 @@ export const editingOptionNewRowPositionOptions = [
export const stateStoringTypeOptions = [ export const stateStoringTypeOptions = [
{ value: 'custom', label: 'Database' }, { value: 'custom', label: 'Database' },
{ value: 'localStorage', label: 'Local Storage' },
{ value: 'sessionStorage', label: 'Session Storage' },
] ]
export const editingOptionStartEditActionOptions = [ export const editingOptionStartEditActionOptions = [

View file

@ -1,6 +1,6 @@
import React, { useState, useRef, useEffect } from 'react' import React, { useState, useRef, useEffect } from 'react'
import { FaComment, FaPaperPlane, FaPlus, FaRobot, FaTrash } from 'react-icons/fa' import { FaComment, FaPaperPlane, FaPlus, FaRobot, FaTrash } from 'react-icons/fa'
import { useStoreState } from '@/store' import { useStoreActions, useStoreState } from '@/store'
import { Avatar, Button, Drawer, Dropdown, ScrollBar } from '@/components/ui' import { Avatar, Button, Drawer, Dropdown, ScrollBar } from '@/components/ui'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { aiService } from '@/services/ai.service' import { aiService } from '@/services/ai.service'
@ -18,9 +18,6 @@ import { APP_NAME } from '@/constants/app.constant'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { AI_ASSISTANT } from '@/constants/permission.constant' import { AI_ASSISTANT } from '@/constants/permission.constant'
const CONVERSATIONS_STORAGE_KEY = 'AiConversations'
const ACTIVE_CONVERSATION_STORAGE_KEY = 'AiActiveConversationId'
const getConversationTitle = (message: string) => const getConversationTitle = (message: string) =>
message.length > 42 ? `${message.slice(0, 42).trimEnd()}` : message message.length > 42 ? `${message.slice(0, 42).trimEnd()}` : message
@ -75,12 +72,14 @@ const renderFormattedText = (value: string) => {
// Main Component // Main Component
const Assistant = () => { const Assistant = () => {
// Hooks // Hooks
const [conversations, setConversations] = useState<Conversation[]>([]) const storedAiConversations = useStoreState((state) => state.admin.aiConversations)
const [activeConversationId, setActiveConversationId] = useState<string | null>(null) const { setAiConversations } = useStoreActions((actions) => actions.admin)
const [conversations, setConversations] = useState<Conversation[]>(storedAiConversations.items)
const [activeConversationId, setActiveConversationId] = useState<string | null>(
storedAiConversations.aiActiveConversationId,
)
const [isConversationsLoaded, setIsConversationsLoaded] = useState(false) const [isConversationsLoaded, setIsConversationsLoaded] = useState(false)
const [isConversationDrawerOpen, setIsConversationDrawerOpen] = useState(false) const [isConversationDrawerOpen, setIsConversationDrawerOpen] = useState(false)
const [, setMessages] = useState<Message[]>([])
const aiPosts: Message[] = []
const [input, setInput] = useState('') const [input, setInput] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [bot, setBot] = useState<AiBotDto[]>([]) const [bot, setBot] = useState<AiBotDto[]>([])
@ -104,50 +103,12 @@ const Assistant = () => {
} }
useEffect(() => { useEffect(() => {
if (messages.length === 0 && aiPosts.length > 0) { if (conversations.length === 0) {
setMessages(aiPosts) // artık doğrudan Message[]
}
}, [aiPosts])
useEffect(() => {
try {
const saved = localStorage.getItem(CONVERSATIONS_STORAGE_KEY)
const parsed = saved ? JSON.parse(saved) : []
const savedConversations = Array.isArray(parsed)
? parsed.filter(
(conversation): conversation is Conversation =>
conversation &&
typeof conversation.id === 'string' &&
Array.isArray(conversation.messages),
)
: []
if (savedConversations.length > 0) {
const savedActiveConversationId = localStorage.getItem(ACTIVE_CONVERSATION_STORAGE_KEY)
setConversations(savedConversations)
setActiveConversationId(
savedConversations.some((conversation) => conversation.id === savedActiveConversationId)
? savedActiveConversationId
: savedConversations[0].id,
)
} else {
const legacyPosts = JSON.parse(localStorage.getItem('AiPosts') || '[]')
const conversation = createConversation()
if (Array.isArray(legacyPosts) && legacyPosts.length > 0) {
conversation.messages = legacyPosts
const firstQuestion = legacyPosts.find((message) => message.role === 'user')?.content
conversation.title = firstQuestion
? getConversationTitle(String(firstQuestion))
: conversation.title
}
setConversations([conversation])
setActiveConversationId(conversation.id)
}
} catch (error) {
console.warn('AI conversations localStorage parse error', error)
const conversation = createConversation() const conversation = createConversation()
setConversations([conversation]) setConversations([conversation])
setActiveConversationId(conversation.id) setActiveConversationId(conversation.id)
} else if (!conversations.some((conversation) => conversation.id === activeConversationId)) {
setActiveConversationId(conversations[0].id)
} }
setIsConversationsLoaded(true) setIsConversationsLoaded(true)
}, []) }, [])
@ -155,11 +116,11 @@ const Assistant = () => {
useEffect(() => { useEffect(() => {
if (!isConversationsLoaded) return if (!isConversationsLoaded) return
localStorage.setItem(CONVERSATIONS_STORAGE_KEY, JSON.stringify(conversations)) setAiConversations({
if (activeConversationId) { items: conversations,
localStorage.setItem(ACTIVE_CONVERSATION_STORAGE_KEY, activeConversationId) aiActiveConversationId: activeConversationId,
} })
}, [activeConversationId, conversations, isConversationsLoaded]) }, [activeConversationId, conversations, isConversationsLoaded, setAiConversations])
const activeConversation = conversations.find( const activeConversation = conversations.find(
(conversation) => conversation.id === activeConversationId, (conversation) => conversation.id === activeConversationId,

View file

@ -1,24 +0,0 @@
import { useStoreActions } from '@/store'
import { useEffect } from 'react'
const LoadAiPostsFromLocalStorage = () => {
const setAiPosts = useStoreActions((actions) => actions.admin.messages.setAiPosts)
useEffect(() => {
const saved = localStorage.getItem('AiPosts')
if (saved) {
try {
const parsed = JSON.parse(saved)
if (Array.isArray(parsed) && parsed.every((p) => 'role' in p && 'content' in p)) {
setAiPosts(parsed)
}
} catch (err) {
console.warn('aiPosts localStorage parse error', err)
}
}
}, [])
return null
}
export default LoadAiPostsFromLocalStorage

View file

@ -1,4 +1,4 @@
import React, { useState, useCallback, useMemo, useEffect } from 'react' import React, { useState, useCallback, useMemo, useEffect } from 'react'
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
import { Button, Dialog, Notification, toast, Checkbox } from '@/components/ui' import { Button, Dialog, Notification, toast, Checkbox } from '@/components/ui'
import { import {
@ -29,6 +29,7 @@ import {
filterNonLinkNodes, filterNonLinkNodes,
} from '@/views/admin/listForm/wizard/WizardStep1' } from '@/views/admin/listForm/wizard/WizardStep1'
import { MenuAddDialog } from '../shared/MenuAddDialog' import { MenuAddDialog } from '../shared/MenuAddDialog'
import { useStoreActions, useStoreState } from '@/store'
// ─── Types ──────────────────────────────────────────────────────────────────── // ─── Types ────────────────────────────────────────────────────────────────────
@ -269,8 +270,6 @@ const WORKFLOW_COLUMNS: ColumnDefinition[] = [
}, },
] ]
const CREATE_TABLE_SCRIPT_STORAGE_KEY = 'sqlQueryManager.lastCreateTableScript'
// ─── T-SQL Generator ────────────────────────────────────────────────────────── // ─── T-SQL Generator ──────────────────────────────────────────────────────────
function colToSqlLine(col: ColumnDefinition, addComma = true): string { function colToSqlLine(col: ColumnDefinition, addComma = true): string {
@ -998,6 +997,12 @@ const SqlTableDesignerDialog = ({
initialTableData, initialTableData,
}: TableDesignerDialogProps) => { }: TableDesignerDialogProps) => {
const { translate } = useLocalization() const { translate } = useLocalization()
const lastCreateTableScript = useStoreState(
(state) => state.client.developerKit.lastCreateTableScript,
)
const setLastCreateTableScript = useStoreActions(
(actions) => actions.client.developerKit.setLastCreateTableScript,
)
const isEditMode = !!initialTableData const isEditMode = !!initialTableData
@ -1283,14 +1288,14 @@ const SqlTableDesignerDialog = ({
const clipboardText = await navigator.clipboard.readText() const clipboardText = await navigator.clipboard.readText()
if (/\bCREATE\s+TABLE\b/i.test(clipboardText)) { if (/\bCREATE\s+TABLE\b/i.test(clipboardText)) {
script = clipboardText script = clipboardText
localStorage.setItem(CREATE_TABLE_SCRIPT_STORAGE_KEY, clipboardText) setLastCreateTableScript(clipboardText)
} }
} catch { } catch {
// Clipboard read can fail due to browser permissions; fallback to saved script. // Clipboard read can fail due to browser permissions; fallback to saved script.
} }
if (!script) { if (!script) {
script = localStorage.getItem(CREATE_TABLE_SCRIPT_STORAGE_KEY) || '' script = lastCreateTableScript
} }
if (!script.trim()) { if (!script.trim()) {

View file

@ -22,7 +22,8 @@ import { intranetService } from '@/services/intranet.service'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import useLocale from '@/utils/hooks/useLocale' import useLocale from '@/utils/hooks/useLocale'
import { currentLocalDate } from '@/utils/dateUtils' import { currentLocalDate } from '@/utils/dateUtils'
import { useStoreState } from '@/store/store' import { useStoreActions, useStoreState } from '@/store/store'
import type { DashboardLayout } from '@/store/admin.model'
import Button from '@/components/ui/Button' import Button from '@/components/ui/Button'
import { LuX } from 'react-icons/lu' import { LuX } from 'react-icons/lu'
import type { DashboardWidgetColumn, DashboardWidgetDefinition } from './dashboardWidget' import type { DashboardWidgetColumn, DashboardWidgetDefinition } from './dashboardWidget'
@ -31,9 +32,6 @@ dayjs.extend(relativeTime)
dayjs.extend(isBetween) dayjs.extend(isBetween)
dayjs.extend(localizedFormat) dayjs.extend(localizedFormat)
const WIDGET_ORDER_KEY = 'dashboard-widget-order'
const WIDGET_VISIBILITY_KEY = 'dashboard-hidden-widgets'
const DASHBOARD_LAYOUT_KEY = 'dashboard-layout'
const dashboardColumns: DashboardWidgetColumn[] = ['left', 'center', 'right'] const dashboardColumns: DashboardWidgetColumn[] = ['left', 'center', 'right']
type DashboardWidgetModule = { dashboardWidget?: DashboardWidgetDefinition } type DashboardWidgetModule = { dashboardWidget?: DashboardWidgetDefinition }
@ -75,7 +73,7 @@ const layoutPresets = [
}, },
] as const ] as const
type DashboardLayoutId = (typeof layoutPresets)[number]['id'] type DashboardLayoutId = DashboardLayout
const columnSpanClasses: Record<number, string> = { const columnSpanClasses: Record<number, string> = {
2: 'lg:col-span-2', 2: 'lg:col-span-2',
@ -92,27 +90,15 @@ const IntranetDashboard: React.FC = () => {
const [selectedSurvey, setSelectedSurvey] = useState<SurveyDto | null>(null) const [selectedSurvey, setSelectedSurvey] = useState<SurveyDto | null>(null)
const [showSurveyModal, setShowSurveyModal] = useState(false) const [showSurveyModal, setShowSurveyModal] = useState(false)
const [isDesignMode, setIsDesignMode] = useState(false) const [isDesignMode, setIsDesignMode] = useState(false)
const [hiddenWidgetIds, setHiddenWidgetIds] = useState<string[]>(() => { const { dashboardLayout: selectedLayout, hiddenWidgetIds, widgetOrder } = useStoreState(
try { (state) => state.admin.dashboard,
const savedIds: unknown = JSON.parse(localStorage.getItem(WIDGET_VISIBILITY_KEY) || '[]') )
return Array.isArray(savedIds) const {
? savedIds.filter((id): id is string => typeof id === 'string') setDashboardLayout,
: [] setHiddenWidgetIds,
} catch { setWidgetOrder,
return [] reset: resetDashboard,
} } = useStoreActions((actions) => actions.admin.dashboard)
})
const [selectedLayout, setSelectedLayout] = useState<DashboardLayoutId>(() => {
const savedLayout = localStorage.getItem(DASHBOARD_LAYOUT_KEY)
return layoutPresets.some((layout) => layout.id === savedLayout)
? (savedLayout as DashboardLayoutId)
: 'default'
})
const [widgetOrder, setWidgetOrder] = useState<Record<DashboardWidgetColumn, string[]>>({
left: [],
center: [],
right: [],
})
const [intranetDashboard, setIntranetDashboard] = useState<IntranetDashboardDto>() const [intranetDashboard, setIntranetDashboard] = useState<IntranetDashboardDto>()
const { translate } = useLocalization() const { translate } = useLocalization()
@ -212,10 +198,10 @@ const IntranetDashboard: React.FC = () => {
useEffect(() => { useEffect(() => {
if (!grantedPolicies) return if (!grantedPolicies) return
const savedOrder = localStorage.getItem(WIDGET_ORDER_KEY) const hasSavedOrder = dashboardColumns.some((column) => widgetOrder[column].length > 0)
if (savedOrder) { if (hasSavedOrder) {
try { try {
const parsed = JSON.parse(savedOrder) as Record<string, unknown[]> const parsed = widgetOrder
const order: Record<DashboardWidgetColumn, string[]> = { const order: Record<DashboardWidgetColumn, string[]> = {
left: [...new Set((parsed.left || []) as string[])], left: [...new Set((parsed.left || []) as string[])],
center: [...new Set((parsed.center || []) as string[])], center: [...new Set((parsed.center || []) as string[])],
@ -230,8 +216,7 @@ const IntranetDashboard: React.FC = () => {
}) })
setWidgetOrder(order) setWidgetOrder(order)
} catch (error) { } catch {
console.error('Widget order parse error:', error)
initializeDefaultOrder() initializeDefaultOrder()
} }
} else { } else {
@ -241,22 +226,17 @@ const IntranetDashboard: React.FC = () => {
const saveWidgetOrder = (newOrder: Record<DashboardWidgetColumn, string[]>) => { const saveWidgetOrder = (newOrder: Record<DashboardWidgetColumn, string[]>) => {
setWidgetOrder(newOrder) setWidgetOrder(newOrder)
localStorage.setItem(WIDGET_ORDER_KEY, JSON.stringify(newOrder))
} }
const setWidgetVisibility = (widgetId: string, visible: boolean) => { const setWidgetVisibility = (widgetId: string, visible: boolean) => {
setHiddenWidgetIds((current) => { const next = visible
const next = visible ? hiddenWidgetIds.filter((id) => id !== widgetId)
? current.filter((id) => id !== widgetId) : [...new Set([...hiddenWidgetIds, widgetId])]
: [...new Set([...current, widgetId])] setHiddenWidgetIds(next)
localStorage.setItem(WIDGET_VISIBILITY_KEY, JSON.stringify(next))
return next
})
} }
const selectLayout = (layoutId: DashboardLayoutId) => { const selectLayout = (layoutId: DashboardLayoutId) => {
setSelectedLayout(layoutId) setDashboardLayout(layoutId)
localStorage.setItem(DASHBOARD_LAYOUT_KEY, layoutId)
} }
const activeLayout = const activeLayout =
@ -524,11 +504,7 @@ const IntranetDashboard: React.FC = () => {
{isDesignMode && ( {isDesignMode && (
<Button <Button
onClick={() => { onClick={() => {
localStorage.removeItem(WIDGET_ORDER_KEY) resetDashboard()
localStorage.removeItem(WIDGET_VISIBILITY_KEY)
localStorage.removeItem(DASHBOARD_LAYOUT_KEY)
setHiddenWidgetIds([])
setSelectedLayout('default')
initializeDefaultOrder() initializeDefaultOrder()
}} }}
variant="plain" variant="plain"

View file

@ -3,10 +3,10 @@ import { TenantForm } from '@/components/orders/TenantForm'
import { APP_NAME } from '@/constants/app.constant' import { APP_NAME } from '@/constants/app.constant'
import { CustomTenantDto } from '@/proxy/config/models' import { CustomTenantDto } from '@/proxy/config/models'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { useStoreActions, useStoreState } from '@/store/store'
import { import {
CartState, CartState,
clearCart, clearCart,
loadCartFromLocalStorage,
removeItemFromCart, removeItemFromCart,
updateCartItemQuantity, updateCartItemQuantity,
} from '@/utils/cartUtils' } from '@/utils/cartUtils'
@ -15,23 +15,22 @@ import React, { useState, useEffect } from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
const TENANT_DATA_STORAGE_KEY = 'tenantData'
const Checkout: React.FC = () => { const Checkout: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const [isCartOpen, setIsCartOpen] = useState(false) const [isCartOpen, setIsCartOpen] = useState(false)
const { translate } = useLocalization() const { translate } = useLocalization()
const [cartState, setCartState] = useState<CartState>(() => { const storedCartState = useStoreState((state) => state.client.cartState)
return loadCartFromLocalStorage() const { setCartState: persistCartState } = useStoreActions((actions) => actions.client)
}) const { setTenantData } = useStoreActions((actions) => actions.client.order)
const [cartState, setCartState] = useState<CartState>(storedCartState)
useEffect(() => { useEffect(() => {
localStorage.setItem('cartState', JSON.stringify(cartState)) persistCartState(cartState)
}, [cartState]) }, [cartState, persistCartState])
const handleTenantSubmit = (tenantData: CustomTenantDto) => { const handleTenantSubmit = (tenantData: CustomTenantDto) => {
localStorage.setItem(TENANT_DATA_STORAGE_KEY, JSON.stringify(tenantData)) setTenantData(tenantData)
navigate(ROUTES_ENUM.public.payment) navigate(ROUTES_ENUM.public.payment)
} }

View file

@ -6,11 +6,10 @@ import { CustomTenantDto } from '@/proxy/config/models'
import { OrderDto } from '@/proxy/order/models' import { OrderDto } from '@/proxy/order/models'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { OrderService } from '@/services/order.service' import { OrderService } from '@/services/order.service'
import { useStoreActions, useStoreState } from '@/store/store'
import { import {
CartState, CartState,
clearCart, clearCart,
initialCartState,
loadCartFromLocalStorage,
removeItemFromCart, removeItemFromCart,
updateCartItemQuantity, updateCartItemQuantity,
} from '@/utils/cartUtils' } from '@/utils/cartUtils'
@ -19,32 +18,29 @@ import React, { useState, useEffect } from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
const TENANT_DATA_STORAGE_KEY = 'tenantData'
const TENANT_FORM_DRAFT_STORAGE_KEY = 'tenantFormDraft'
const Payment: React.FC = () => { const Payment: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const [tenant, setTenant] = useState<CustomTenantDto | null>(null) const storedCartState = useStoreState((state) => state.client.cartState)
const storedTenantData = useStoreState((state) => state.client.order.tenantData)
const { setCartState: persistCartState } = useStoreActions((actions) => actions.client)
const { setOrderResult } = useStoreActions((actions) => actions.client.order)
const [tenant, setTenant] = useState<CustomTenantDto | null>(storedTenantData)
const [isCartOpen, setIsCartOpen] = useState(false) const [isCartOpen, setIsCartOpen] = useState(false)
const [cartState, setCartState] = useState<CartState>(initialCartState) const [cartState, setCartState] = useState<CartState>(storedCartState)
const { translate } = useLocalization() const { translate } = useLocalization()
useEffect(() => { useEffect(() => {
const tenantData = localStorage.getItem(TENANT_DATA_STORAGE_KEY) // ✅ güncellendi if (storedTenantData) {
setTenant(storedTenantData)
if (tenantData) {
setTenant(JSON.parse(tenantData))
} else { } else {
navigate(ROUTES_ENUM.public.checkout) navigate(ROUTES_ENUM.public.checkout)
} }
setCartState(loadCartFromLocalStorage()) }, [navigate, storedTenantData])
}, [navigate])
// Save cart state to localStorage whenever it changes
useEffect(() => { useEffect(() => {
localStorage.setItem('cartState', JSON.stringify(cartState)) persistCartState(cartState)
}, [cartState]) }, [cartState, persistCartState])
const handleCheckout = () => { const handleCheckout = () => {
setIsCartOpen(false) setIsCartOpen(false)
@ -99,18 +95,11 @@ const Payment: React.FC = () => {
const result = await orderService.createOrder(orderData) const result = await orderService.createOrder(orderData)
if (result.success && result.orderId) { if (result.success && result.orderId) {
localStorage.setItem('orderId', result.orderId) // ✅ güncellendi setOrderResult({
localStorage.setItem( orderId: result.orderId,
'orderSuccessData', ...orderData,
JSON.stringify({ })
orderId: result.orderId,
...orderData,
}),
)
setCartState(clearCart()) setCartState(clearCart())
localStorage.removeItem(TENANT_DATA_STORAGE_KEY) // ✅ temizlik
localStorage.removeItem(TENANT_FORM_DRAFT_STORAGE_KEY)
localStorage.removeItem('cartState') // ✅ temizlik
navigate(ROUTES_ENUM.public.success) navigate(ROUTES_ENUM.public.success)
} else { } else {
alert('Sipariş oluşturulurken hata oluştu: ' + result.error) alert('Sipariş oluşturulurken hata oluştu: ' + result.error)

View file

@ -4,10 +4,10 @@ import { ProductCatalog } from '@/components/orders/ProductCatalog'
import { APP_NAME } from '@/constants/app.constant' import { APP_NAME } from '@/constants/app.constant'
import { BillingCycle } from '@/proxy/order/models' import { BillingCycle } from '@/proxy/order/models'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { useStoreActions, useStoreState } from '@/store/store'
import { import {
CartState, CartState,
clearCart, clearCart,
loadCartFromLocalStorage,
removeItemFromCart, removeItemFromCart,
setCartGlobalBillingCycle, setCartGlobalBillingCycle,
setCartGlobalPeriod, setCartGlobalPeriod,
@ -23,15 +23,14 @@ const Products: React.FC = () => {
const [isCartOpen, setIsCartOpen] = useState(false) const [isCartOpen, setIsCartOpen] = useState(false)
const { translate } = useLocalization() const { translate } = useLocalization()
const [cartState, setCartState] = useState<CartState>(() => { const storedCartState = useStoreState((state) => state.client.cartState)
return loadCartFromLocalStorage() const { setCartState: persistCartState } = useStoreActions((actions) => actions.client)
}) const [cartState, setCartState] = useState<CartState>(storedCartState)
const navigate = useNavigate() const navigate = useNavigate()
// Save cart state to localStorage whenever it changes
useEffect(() => { useEffect(() => {
localStorage.setItem('cartState', JSON.stringify(cartState)) persistCartState(cartState)
}, [cartState]) }, [cartState, persistCartState])
const handleCartClick = () => { const handleCartClick = () => {
setIsCartOpen(true) setIsCartOpen(true)

View file

@ -1,4 +1,4 @@
import { OrderSuccess, OrderSuccessData } from '@/components/orders/OrderSuccess' import { OrderSuccess } from '@/components/orders/OrderSuccess'
import { Loading } from '@/components/shared' import { Loading } from '@/components/shared'
import { APP_NAME } from '@/constants/app.constant' import { APP_NAME } from '@/constants/app.constant'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
@ -6,29 +6,24 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useStoreActions, useStoreState } from '@/store/store'
const Success: React.FC = () => { const Success: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const [orderId, setOrderId] = useState<string>('') const storedOrder = useStoreState((state) => state.client.order)
const [order, setOrder] = useState<OrderSuccessData | null>(null) const { clearResult, clearTenant } = useStoreActions((actions) => actions.client.order)
const [orderId] = useState(storedOrder.orderId || '')
const [order] = useState(storedOrder.orderSuccessData)
const { translate } = useLocalization() const { translate } = useLocalization()
useEffect(() => { useEffect(() => {
// Get order ID from local storage if (orderId) {
const orderIdData = localStorage.getItem('orderId') clearResult()
const orderSuccessData = localStorage.getItem('orderSuccessData') clearTenant()
if (orderIdData) {
setOrderId(orderIdData)
if (orderSuccessData) {
setOrder(JSON.parse(orderSuccessData))
}
// Clear order ID from local storage
localStorage.removeItem('orderId')
localStorage.removeItem('orderSuccessData')
} else { } else {
navigate(ROUTES_ENUM.public.home) navigate(ROUTES_ENUM.public.home)
} }
}, [navigate]) }, [clearResult, clearTenant, navigate, orderId])
const handleBackToShop = () => { const handleBackToShop = () => {
navigate(ROUTES_ENUM.public.home) navigate(ROUTES_ENUM.public.home)
@ -62,7 +57,11 @@ const Success: React.FC = () => {
</div> </div>
<div className="container mx-auto px-4 pt-12"> <div className="container mx-auto px-4 pt-12">
<OrderSuccess orderId={orderId} order={order} onBackToShop={handleBackToShop} /> <OrderSuccess
orderId={orderId}
order={order}
onBackToShop={handleBackToShop}
/>
</div> </div>
</div> </div>
) )