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
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260708113003_Initial")]
[Migration("20260709175406_Initial")]
partial class Initial
{
/// <inheritdoc />

View file

@ -4,23 +4,8 @@ import { useNavigate } from 'react-router-dom'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Button } from '@/components/ui'
import { BasketItem, BillingCycle } from '@/proxy/order/models'
import { CustomTenantDto } from '@/proxy/config/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
}
import type { OrderSuccessData } from '@/store/client.model'
import type { BasketItem } from '@/proxy/order/models'
interface OrderSuccessProps {
orderId: string

View file

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

View file

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

@ -6,7 +6,7 @@ import { Injections, StoreModel } from '.'
export interface LocaleStoreModel {
currentLang: string
currentUiVersion: string | undefined
currentTenantName?: string
currentTenantName?: string
}
export interface LocaleStoreActions {
@ -21,7 +21,7 @@ export type LocaleModel = LocaleStoreModel & LocaleStoreActions
const initialState: LocaleStoreModel = {
currentLang: appConfig.locale,
currentUiVersion: appConfig.uiVersion,
currentTenantName: undefined
currentTenantName: undefined,
}
export const localeModel: LocaleModel = {
@ -41,5 +41,5 @@ export const localeModel: LocaleModel = {
}),
setTenantName: action((state, payload) => {
state.currentTenantName = payload
}),
}),
}

View file

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

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 {
items: BasketItem[];
subtotal: number;
vatTotal: number;
total: number;
globalBillingCycle: BillingCycle;
globalPeriod: number;
items: BasketItem[]
subtotal: number
vatTotal: number
total: number
globalBillingCycle: BillingCycle
globalPeriod: number
}
export const initialCartState: CartState = {
@ -15,29 +15,24 @@ export const initialCartState: CartState = {
vatTotal: 0,
total: 0,
globalBillingCycle: 'yearly',
globalPeriod: 1
};
// Key for localStorage
const CART_STORAGE_KEY = 'cartState';
globalPeriod: 1,
}
export const calculateItemPrice = (
product: ProductDto,
quantity: number,
billingCycle: BillingCycle,
period: number = 1
period: number = 1,
): number => {
const price = billingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice!;
const vatRate = product.vatRate ?? 0.2;
return roundMoney(price * quantity * period * (1 + vatRate));
};
const price = billingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice!
const vatRate = product.vatRate ?? 0.2
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 = (
totalPrice: number,
vatRate: number = 0.2,
): number => (vatRate > 0 ? roundMoney(totalPrice - totalPrice / (1 + vatRate)) : 0);
const calculateItemVatAmount = (totalPrice: number, vatRate: number = 0.2): number =>
vatRate > 0 ? roundMoney(totalPrice - totalPrice / (1 + vatRate)) : 0
const withItemVat = (
product: ProductDto,
@ -45,8 +40,8 @@ const withItemVat = (
billingCycle: BillingCycle,
period: number,
): BasketItem => {
const vatRate = product.vatRate ?? 0.2;
const totalPrice = calculateItemPrice(product, quantity, billingCycle, period);
const vatRate = product.vatRate ?? 0.2
const totalPrice = calculateItemPrice(product, quantity, billingCycle, period)
return {
product,
quantity,
@ -54,135 +49,128 @@ const withItemVat = (
vatRate,
vatAmount: calculateItemVatAmount(totalPrice, vatRate),
totalPrice,
};
};
}
}
const calculateCartTotals = (items: BasketItem[]) => {
const total = roundMoney(items.reduce((sum, item) => sum + item.totalPrice, 0));
const vatTotal = roundMoney(items.reduce((sum, item) => sum + (item.vatAmount ?? 0), 0));
const total = roundMoney(items.reduce((sum, item) => sum + item.totalPrice, 0))
const vatTotal = roundMoney(items.reduce((sum, item) => sum + (item.vatAmount ?? 0), 0))
return {
subtotal: roundMoney(total - vatTotal),
vatTotal,
total,
};
};
}
}
export const addItemToCart = (
cartState: CartState,
product: ProductDto,
quantity: number,
billingCycle: BillingCycle
billingCycle: BillingCycle,
): CartState => {
const isAlreadyInCart = isItemInCartState(cartState.items, product.id, billingCycle);
const isAlreadyInCart = isItemInCartState(cartState.items, product.id, billingCycle)
if (!product.isQuantityBased && isAlreadyInCart) {
return cartState;
return cartState
}
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) {
newItems = [...cartState.items];
newItems[existingItemIndex].quantity += quantity;
newItems = [...cartState.items]
newItems[existingItemIndex].quantity += quantity
newItems[existingItemIndex] = withItemVat(
product,
newItems[existingItemIndex].quantity,
billingCycle,
cartState.globalPeriod,
);
)
} else {
const actualQuantity = !product.isQuantityBased ? 1 : quantity;
const actualQuantity = !product.isQuantityBased ? 1 : quantity
newItems = [
...cartState.items,
withItemVat(product, actualQuantity, billingCycle, cartState.globalPeriod),
];
]
}
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) };
saveCartToLocalStorage(updatedState);
return updatedState;
};
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }
return updatedState
}
export const removeItemFromCart = (cartState: CartState, id: string): CartState => {
const newItems = cartState.items.filter(item => item.product.id !== id);
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) };
saveCartToLocalStorage(updatedState);
return updatedState;
};
const newItems = cartState.items.filter((item) => item.product.id !== id)
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }
return updatedState
}
export const updateCartItemQuantity = (cartState: CartState, id: string, quantity: number): CartState => {
const newItems = cartState.items.map(item => {
if (item.product.id === id) {
if (quantity <= 0) return null;
if (!item.product.isQuantityBased) return item;
export const updateCartItemQuantity = (
cartState: CartState,
id: string,
quantity: number,
): 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 item;
}).filter(Boolean) as BasketItem[];
return withItemVat(item.product, quantity, item.billingCycle, cartState.globalPeriod)
}
return item
})
.filter(Boolean) as BasketItem[]
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) };
saveCartToLocalStorage(updatedState);
return updatedState;
};
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }
return updatedState
}
export const clearCart = (): CartState => {
localStorage.removeItem(CART_STORAGE_KEY);
return initialCartState;
};
return initialCartState
}
export const setCartGlobalBillingCycle = (cartState: CartState, cycle: BillingCycle): CartState => {
const newItems = cartState.items.map(item =>
withItemVat(item.product, item.quantity, cycle, cartState.globalPeriod)
);
const updatedState = { ...cartState, globalBillingCycle: cycle, items: newItems, ...calculateCartTotals(newItems) };
saveCartToLocalStorage(updatedState);
return updatedState;
};
const newItems = cartState.items.map((item) =>
withItemVat(item.product, item.quantity, cycle, cartState.globalPeriod),
)
const updatedState = {
...cartState,
globalBillingCycle: cycle,
items: newItems,
...calculateCartTotals(newItems),
}
return updatedState
}
export const setCartGlobalPeriod = (cartState: CartState, period: number): CartState => {
const newItems = cartState.items.map(item =>
withItemVat(item.product, item.quantity, item.billingCycle, period)
);
const updatedState = { ...cartState, globalPeriod: period, items: newItems, ...calculateCartTotals(newItems) };
saveCartToLocalStorage(updatedState);
return updatedState;
};
const newItems = cartState.items.map((item) =>
withItemVat(item.product, item.quantity, item.billingCycle, period),
)
const updatedState = {
...cartState,
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(
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 => {
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),
};
};
export const isItemInCartState = (
items: BasketItem[],
productId: string,
billingCycle: BillingCycle,
): boolean => {
return items.some((item) => item.product.id === productId && item.billingCycle === billingCycle)
}

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import React, { useState, useRef, useEffect } from 'react'
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 { useLocalization } from '@/utils/hooks/useLocalization'
import { aiService } from '@/services/ai.service'
@ -18,9 +18,6 @@ import { APP_NAME } from '@/constants/app.constant'
import dayjs from 'dayjs'
import { AI_ASSISTANT } from '@/constants/permission.constant'
const CONVERSATIONS_STORAGE_KEY = 'AiConversations'
const ACTIVE_CONVERSATION_STORAGE_KEY = 'AiActiveConversationId'
const getConversationTitle = (message: string) =>
message.length > 42 ? `${message.slice(0, 42).trimEnd()}` : message
@ -75,12 +72,14 @@ const renderFormattedText = (value: string) => {
// Main Component
const Assistant = () => {
// Hooks
const [conversations, setConversations] = useState<Conversation[]>([])
const [activeConversationId, setActiveConversationId] = useState<string | null>(null)
const storedAiConversations = useStoreState((state) => state.admin.aiConversations)
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 [isConversationDrawerOpen, setIsConversationDrawerOpen] = useState(false)
const [, setMessages] = useState<Message[]>([])
const aiPosts: Message[] = []
const [input, setInput] = useState('')
const [loading, setLoading] = useState(false)
const [bot, setBot] = useState<AiBotDto[]>([])
@ -104,50 +103,12 @@ const Assistant = () => {
}
useEffect(() => {
if (messages.length === 0 && aiPosts.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)
if (conversations.length === 0) {
const conversation = createConversation()
setConversations([conversation])
setActiveConversationId(conversation.id)
} else if (!conversations.some((conversation) => conversation.id === activeConversationId)) {
setActiveConversationId(conversations[0].id)
}
setIsConversationsLoaded(true)
}, [])
@ -155,11 +116,11 @@ const Assistant = () => {
useEffect(() => {
if (!isConversationsLoaded) return
localStorage.setItem(CONVERSATIONS_STORAGE_KEY, JSON.stringify(conversations))
if (activeConversationId) {
localStorage.setItem(ACTIVE_CONVERSATION_STORAGE_KEY, activeConversationId)
}
}, [activeConversationId, conversations, isConversationsLoaded])
setAiConversations({
items: conversations,
aiActiveConversationId: activeConversationId,
})
}, [activeConversationId, conversations, isConversationsLoaded, setAiConversations])
const activeConversation = conversations.find(
(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 { Button, Dialog, Notification, toast, Checkbox } from '@/components/ui'
import {
@ -29,6 +29,7 @@ import {
filterNonLinkNodes,
} from '@/views/admin/listForm/wizard/WizardStep1'
import { MenuAddDialog } from '../shared/MenuAddDialog'
import { useStoreActions, useStoreState } from '@/store'
// ─── Types ────────────────────────────────────────────────────────────────────
@ -269,8 +270,6 @@ const WORKFLOW_COLUMNS: ColumnDefinition[] = [
},
]
const CREATE_TABLE_SCRIPT_STORAGE_KEY = 'sqlQueryManager.lastCreateTableScript'
// ─── T-SQL Generator ──────────────────────────────────────────────────────────
function colToSqlLine(col: ColumnDefinition, addComma = true): string {
@ -998,6 +997,12 @@ const SqlTableDesignerDialog = ({
initialTableData,
}: TableDesignerDialogProps) => {
const { translate } = useLocalization()
const lastCreateTableScript = useStoreState(
(state) => state.client.developerKit.lastCreateTableScript,
)
const setLastCreateTableScript = useStoreActions(
(actions) => actions.client.developerKit.setLastCreateTableScript,
)
const isEditMode = !!initialTableData
@ -1283,14 +1288,14 @@ const SqlTableDesignerDialog = ({
const clipboardText = await navigator.clipboard.readText()
if (/\bCREATE\s+TABLE\b/i.test(clipboardText)) {
script = clipboardText
localStorage.setItem(CREATE_TABLE_SCRIPT_STORAGE_KEY, clipboardText)
setLastCreateTableScript(clipboardText)
}
} catch {
// Clipboard read can fail due to browser permissions; fallback to saved script.
}
if (!script) {
script = localStorage.getItem(CREATE_TABLE_SCRIPT_STORAGE_KEY) || ''
script = lastCreateTableScript
}
if (!script.trim()) {

View file

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

View file

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

View file

@ -6,11 +6,10 @@ import { CustomTenantDto } from '@/proxy/config/models'
import { OrderDto } from '@/proxy/order/models'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { OrderService } from '@/services/order.service'
import { useStoreActions, useStoreState } from '@/store/store'
import {
CartState,
clearCart,
initialCartState,
loadCartFromLocalStorage,
removeItemFromCart,
updateCartItemQuantity,
} from '@/utils/cartUtils'
@ -19,32 +18,29 @@ import React, { useState, useEffect } from 'react'
import { Helmet } from 'react-helmet'
import { useNavigate } from 'react-router-dom'
const TENANT_DATA_STORAGE_KEY = 'tenantData'
const TENANT_FORM_DRAFT_STORAGE_KEY = 'tenantFormDraft'
const Payment: React.FC = () => {
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 [cartState, setCartState] = useState<CartState>(initialCartState)
const [cartState, setCartState] = useState<CartState>(storedCartState)
const { translate } = useLocalization()
useEffect(() => {
const tenantData = localStorage.getItem(TENANT_DATA_STORAGE_KEY) // ✅ güncellendi
if (tenantData) {
setTenant(JSON.parse(tenantData))
if (storedTenantData) {
setTenant(storedTenantData)
} else {
navigate(ROUTES_ENUM.public.checkout)
}
setCartState(loadCartFromLocalStorage())
}, [navigate])
}, [navigate, storedTenantData])
// Save cart state to localStorage whenever it changes
useEffect(() => {
localStorage.setItem('cartState', JSON.stringify(cartState))
}, [cartState])
persistCartState(cartState)
}, [cartState, persistCartState])
const handleCheckout = () => {
setIsCartOpen(false)
@ -99,18 +95,11 @@ const Payment: React.FC = () => {
const result = await orderService.createOrder(orderData)
if (result.success && result.orderId) {
localStorage.setItem('orderId', result.orderId) // ✅ güncellendi
localStorage.setItem(
'orderSuccessData',
JSON.stringify({
orderId: result.orderId,
...orderData,
}),
)
setOrderResult({
orderId: result.orderId,
...orderData,
})
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)
} else {
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 { BillingCycle } from '@/proxy/order/models'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { useStoreActions, useStoreState } from '@/store/store'
import {
CartState,
clearCart,
loadCartFromLocalStorage,
removeItemFromCart,
setCartGlobalBillingCycle,
setCartGlobalPeriod,
@ -23,15 +23,14 @@ const Products: React.FC = () => {
const [isCartOpen, setIsCartOpen] = useState(false)
const { translate } = useLocalization()
const [cartState, setCartState] = useState<CartState>(() => {
return loadCartFromLocalStorage()
})
const storedCartState = useStoreState((state) => state.client.cartState)
const { setCartState: persistCartState } = useStoreActions((actions) => actions.client)
const [cartState, setCartState] = useState<CartState>(storedCartState)
const navigate = useNavigate()
// Save cart state to localStorage whenever it changes
useEffect(() => {
localStorage.setItem('cartState', JSON.stringify(cartState))
}, [cartState])
persistCartState(cartState)
}, [cartState, persistCartState])
const handleCartClick = () => {
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 { APP_NAME } from '@/constants/app.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 { Helmet } from 'react-helmet'
import { useNavigate } from 'react-router-dom'
import { useStoreActions, useStoreState } from '@/store/store'
const Success: React.FC = () => {
const navigate = useNavigate()
const [orderId, setOrderId] = useState<string>('')
const [order, setOrder] = useState<OrderSuccessData | null>(null)
const storedOrder = useStoreState((state) => state.client.order)
const { clearResult, clearTenant } = useStoreActions((actions) => actions.client.order)
const [orderId] = useState(storedOrder.orderId || '')
const [order] = useState(storedOrder.orderSuccessData)
const { translate } = useLocalization()
useEffect(() => {
// Get order ID from local storage
const orderIdData = localStorage.getItem('orderId')
const orderSuccessData = localStorage.getItem('orderSuccessData')
if (orderIdData) {
setOrderId(orderIdData)
if (orderSuccessData) {
setOrder(JSON.parse(orderSuccessData))
}
// Clear order ID from local storage
localStorage.removeItem('orderId')
localStorage.removeItem('orderSuccessData')
if (orderId) {
clearResult()
clearTenant()
} else {
navigate(ROUTES_ENUM.public.home)
}
}, [navigate])
}, [clearResult, clearTenant, navigate, orderId])
const handleBackToShop = () => {
navigate(ROUTES_ENUM.public.home)
@ -62,7 +57,11 @@ const Success: React.FC = () => {
</div>
<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>
)