proje-react-panel 1.5.0 → 1.6.0-test-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.cursor/rules.md +11 -1
  2. package/AUTH_LAYOUT_EXAMPLE.md +343 -0
  3. package/AUTH_LAYOUT_GUIDE.md +819 -0
  4. package/IMPLEMENTATION_GUIDE.md +899 -0
  5. package/dist/api/ApiConfig.d.ts +11 -0
  6. package/dist/api/AuthApi.d.ts +2 -5
  7. package/dist/api/CrudApi.d.ts +11 -12
  8. package/dist/components/list/cells/BooleanCell.d.ts +5 -2
  9. package/dist/components/list/cells/DateCell.d.ts +5 -2
  10. package/dist/components/list/cells/DefaultCell.d.ts +3 -2
  11. package/dist/components/list/cells/DownloadCell.d.ts +3 -2
  12. package/dist/components/list/cells/ImageCell.d.ts +3 -2
  13. package/dist/components/list/cells/UUIDCell.d.ts +5 -2
  14. package/dist/decorators/auth/DefaultLoginForm.d.ts +4 -0
  15. package/dist/decorators/list/Cell.d.ts +4 -0
  16. package/dist/decorators/list/List.d.ts +7 -2
  17. package/dist/index.cjs.js +15 -1
  18. package/dist/index.d.ts +5 -0
  19. package/dist/index.esm.js +15 -1
  20. package/dist/types/Login.d.ts +8 -0
  21. package/package.json +3 -1
  22. package/src/api/ApiConfig.ts +63 -0
  23. package/src/api/AuthApi.ts +8 -0
  24. package/src/api/CrudApi.ts +96 -60
  25. package/src/components/list/CellField.tsx +19 -10
  26. package/src/components/list/Datagrid.tsx +26 -12
  27. package/src/components/list/cells/BooleanCell.tsx +7 -2
  28. package/src/components/list/cells/DateCell.tsx +6 -2
  29. package/src/components/list/cells/DefaultCell.tsx +4 -4
  30. package/src/components/list/cells/DownloadCell.tsx +4 -2
  31. package/src/components/list/cells/ImageCell.tsx +4 -2
  32. package/src/components/list/cells/LinkCell.tsx +3 -2
  33. package/src/components/list/cells/UUIDCell.tsx +6 -2
  34. package/src/decorators/auth/DefaultLoginForm.ts +32 -0
  35. package/src/decorators/list/Cell.ts +4 -0
  36. package/src/decorators/list/List.ts +3 -2
  37. package/src/index.ts +25 -0
  38. package/src/store/store.ts +1 -1
  39. package/src/styles/components/button.scss +14 -0
  40. package/src/styles/index.scss +1 -1
  41. package/src/styles/list.scss +8 -1
  42. package/src/types/Login.ts +9 -0
  43. package/src/utils/logout.ts +2 -0
@@ -0,0 +1,8 @@
1
+ export interface LoginResponse<T> {
2
+ access_token: string;
3
+ admin: T;
4
+ }
5
+ export interface LoginForm {
6
+ username: string;
7
+ password: string;
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proje-react-panel",
3
- "version": "1.5.0",
3
+ "version": "1.6.0-test-1",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "author": "SEFA DEMİR",
@@ -33,6 +33,7 @@
33
33
  "@types/react": "^19.0.10",
34
34
  "@typescript-eslint/eslint-plugin": "^8.26.1",
35
35
  "@typescript-eslint/parser": "^8.29.1",
36
+ "axios": "^1.12.2",
36
37
  "class-transformer": "^0.5.1",
37
38
  "class-validator": "^0.14.1",
38
39
  "eslint": "^9.24.0",
@@ -61,6 +62,7 @@
61
62
  "zustand": "^5.0.3"
62
63
  },
63
64
  "peerDependencies": {
65
+ "axios": ">=1.0.0",
64
66
  "react": ">=19.0.0",
65
67
  "react-hook-form": ">=7.54.2",
66
68
  "react-router": "7.3.0",
@@ -0,0 +1,63 @@
1
+ import axios, { AxiosInstance } from 'axios';
2
+
3
+ let axiosInstance: AxiosInstance;
4
+
5
+ export interface ApiConfig {
6
+ baseUrl: string;
7
+ }
8
+
9
+ export function initApi(config: ApiConfig): void {
10
+ axiosInstance = axios.create({
11
+ baseURL: config.baseUrl,
12
+ headers: {
13
+ 'Content-Type': 'application/json',
14
+ },
15
+ });
16
+
17
+ axiosInstance.interceptors.response.use(
18
+ response => response,
19
+ error => {
20
+ if (error.response && error.response.status === 401) {
21
+ setAuthLogout();
22
+ window.location.href = '/login';
23
+ }
24
+ return Promise.reject(error);
25
+ }
26
+ );
27
+ }
28
+
29
+ export function initAuthToken(): void {
30
+ if (!axiosInstance) {
31
+ throw new Error('API not initialized. Call initApi first.');
32
+ }
33
+ const token = localStorage.getItem('token');
34
+ if (token) {
35
+ axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
36
+ }
37
+ }
38
+
39
+ export function setAuthToken(token: string): void {
40
+ if (!axiosInstance) {
41
+ throw new Error('API not initialized. Call initApi first.');
42
+ }
43
+ axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
44
+ localStorage.setItem('token', token);
45
+ }
46
+
47
+ export function setAuthLogout(): void {
48
+ if (!axiosInstance) {
49
+ throw new Error('API not initialized. Call initApi first.');
50
+ }
51
+ axiosInstance.defaults.headers.common['Authorization'] = null;
52
+ localStorage.removeItem('token');
53
+ }
54
+
55
+ export function getAxiosInstance(): AxiosInstance {
56
+ if (!axiosInstance) {
57
+ throw new Error('API not initialized. Call initApi first.');
58
+ }
59
+ return axiosInstance;
60
+ }
61
+
62
+ // Export the axios instance for external use
63
+ export { axiosInstance };
@@ -0,0 +1,8 @@
1
+ import { LoginForm, LoginResponse } from '../types/Login';
2
+ import { getAxiosInstance } from './ApiConfig';
3
+
4
+ export async function login<T>(data: LoginForm | FormData): Promise<LoginResponse<T>> {
5
+ const axiosInstance = getAxiosInstance();
6
+ const response = await axiosInstance.post<LoginResponse<T>>('/auth/login', data);
7
+ return response.data;
8
+ }
@@ -1,65 +1,101 @@
1
- interface FetchOptions {
2
- token: string;
3
- baseUrl: string;
4
- }
1
+ import { GetDataForList, PaginatedResponse, GetDataParams } from '../decorators/list/List';
2
+ import { OnSubmitFN } from '../decorators/form/Form';
3
+ import { GetDetailsDataFN } from '../decorators/details/Details';
4
+ import { getAxiosInstance } from './ApiConfig';
5
+ import { AxiosError } from 'axios';
5
6
 
6
- export const CrudApi = {
7
- getList: (options: FetchOptions, api: string) => {
8
- return fetch(`${options.baseUrl}/${api}`, {
9
- method: 'GET',
10
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${options.token}` },
11
- }).then(res => {
12
- if (res.ok) {
13
- return res.json();
14
- }
15
- throw res;
7
+ export function getAll<T>(endpoint: string): GetDataForList<T> {
8
+ return async (params: GetDataParams): Promise<PaginatedResponse<T>> => {
9
+ const axiosInstance = getAxiosInstance();
10
+ const { page = 1, limit = 10 } = params;
11
+ const response = await axiosInstance.get<{
12
+ data: T[];
13
+ total: number;
14
+ }>(`/${endpoint}`, {
15
+ params: { page, limit, ...(params.filters ?? {}) },
16
16
  });
17
- },
18
- //TODO: fix this
19
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
- create: (options: FetchOptions, api: string, data: any) => {
21
- const headers: HeadersInit = { Authorization: `Bearer ${options.token}` };
22
- // Don't set Content-Type for FormData
23
- if (!(data instanceof FormData)) {
24
- headers['Content-Type'] = 'application/json';
25
- }
17
+ return {
18
+ data: response.data.data,
19
+ total: response.data.total,
20
+ page,
21
+ limit,
22
+ };
23
+ };
24
+ }
25
+
26
+ export function getOne<T>(endpoint: string, key = 'id'): GetDetailsDataFN<T> {
27
+ return async (params: Record<string, string>): Promise<T> => {
28
+ const axiosInstance = getAxiosInstance();
29
+ const response = await axiosInstance.get<T>(`/${endpoint}/${params[key]}`);
30
+ return response.data;
31
+ };
32
+ }
26
33
 
27
- return fetch(`${options?.baseUrl ?? ''}/${api}`, {
28
- method: 'POST',
29
- headers,
30
- body: data instanceof FormData ? data : JSON.stringify(data),
31
- }).then(res => res.json());
32
- },
33
- //TODO: fix this
34
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
- details: (options: FetchOptions, api: string, id: any) => {
36
- return fetch(`${options?.baseUrl ?? ''}/${api}/${id}`, {
37
- method: 'GET',
38
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${options.token}` },
39
- }).then(res => {
40
- return res.json();
34
+ export function create<T>(endpoint: string): OnSubmitFN<T> {
35
+ return async (data: T | FormData): Promise<T> => {
36
+ const axiosInstance = getAxiosInstance();
37
+ const response = await axiosInstance.post<T>(`/${endpoint}`, data);
38
+ return response.data;
39
+ };
40
+ }
41
+
42
+ export function createFormData<T>(endpoint: string): OnSubmitFN<T> {
43
+ return async (data: T | FormData): Promise<T> => {
44
+ const axiosInstance = getAxiosInstance();
45
+ const response = await axiosInstance.post<T>(`/${endpoint}`, data, {
46
+ headers: {
47
+ 'Content-Type': 'multipart/form-data',
48
+ },
41
49
  });
42
- },
43
- //TODO: fix this
44
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
- edit: (options: FetchOptions, api: string, data: any) => {
46
- const headers: HeadersInit = { Authorization: `Bearer ${options.token}` };
47
- // Don't set Content-Type for FormData
48
- if (!(data instanceof FormData)) {
49
- headers['Content-Type'] = 'application/json';
50
- }
51
- return fetch(`${options?.baseUrl ?? ''}/${api}/${data.id}`, {
52
- method: 'PUT',
53
- headers,
54
- body: data instanceof FormData ? data : JSON.stringify(data),
55
- }).then(res => res.json());
56
- },
57
- delete: (options: FetchOptions, api: string, id: string) => {
58
- return fetch(`${options?.baseUrl ?? ''}/${api}/${id}`, {
59
- method: 'DELETE',
60
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${options.token}` },
61
- }).then(res => {
62
- return res.clone().json();
50
+ return response.data;
51
+ };
52
+ }
53
+
54
+ export function update<T>(endpoint: string, key = 'id'): OnSubmitFN<T> {
55
+ return async (data: T | FormData): Promise<T> => {
56
+ const axiosInstance = getAxiosInstance();
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ const id = (data as any)[key];
59
+ const response = await axiosInstance.put<T>(`/${endpoint}/${id}`, data);
60
+ return response.data;
61
+ };
62
+ }
63
+
64
+ export function updateFormData<T>(endpoint: string, key = 'id'): OnSubmitFN<T> {
65
+ return async (data: T | FormData): Promise<T> => {
66
+ const axiosInstance = getAxiosInstance();
67
+ const id = (data as any)[key];
68
+ const response = await axiosInstance.put<T>(`/${endpoint}/${id}`, data, {
69
+ headers: {
70
+ 'Content-Type': 'multipart/form-data',
71
+ },
63
72
  });
64
- },
65
- };
73
+ return response.data;
74
+ };
75
+ }
76
+
77
+ export function updateSimple<T>(endpoint: string): OnSubmitFN<T> {
78
+ return async (data: T | FormData): Promise<T> => {
79
+ const axiosInstance = getAxiosInstance();
80
+ const response = await axiosInstance.put<T>(`/${endpoint}`, data);
81
+ return response.data;
82
+ };
83
+ }
84
+
85
+ export function remove<T>(endpoint: string, key = 'id'): (data: T) => Promise<void> {
86
+ return async (data: T): Promise<void> => {
87
+ const axiosInstance = getAxiosInstance();
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ const id = (data as any)[key];
90
+ await axiosInstance
91
+ .delete<T>(`/${endpoint}/${id}`)
92
+ .then((res: any) => res.data)
93
+ .catch((err: AxiosError) => {
94
+ const messageError = err.response?.data as { message: string };
95
+ if (messageError?.message) {
96
+ throw new Error(messageError.message);
97
+ }
98
+ throw err;
99
+ });
100
+ };
101
+ }
@@ -20,31 +20,40 @@ export function CellField<T extends AnyClass>({
20
20
  }: CellFieldProps<T>): React.ReactElement {
21
21
  let render;
22
22
 
23
- const value = item[configuration.name];
24
-
25
23
  switch (configuration.type) {
26
24
  case 'boolean':
27
- render = <BooleanCell value={value} />;
25
+ render = <BooleanCell item={item} configuration={configuration} />;
28
26
  break;
29
27
  case 'date':
30
- render = <DateCell value={value} />;
28
+ render = <DateCell item={item} configuration={configuration} />;
31
29
  break;
32
30
  case 'image':
33
- render = <ImageCell value={value} configuration={configuration} />;
31
+ render = <ImageCell item={item} configuration={configuration} />;
34
32
  break;
35
33
  case 'uuid':
36
- render = <UUIDCell value={value} />;
34
+ render = <UUIDCell item={item} configuration={configuration} />;
37
35
  break;
38
36
  case 'download':
39
- render = <DownloadCell value={value} configuration={configuration} />;
37
+ render = <DownloadCell item={item} configuration={configuration} />;
40
38
  break;
41
39
  case 'link':
42
40
  render = <LinkCell item={item} configuration={configuration} />;
43
41
  break;
44
42
  default:
45
- render = <DefaultCell value={value} configuration={configuration} />;
43
+ render = <DefaultCell item={item} configuration={configuration} />;
46
44
  break;
47
45
  }
48
-
49
- return <td key={configuration.name}>{render}</td>;
46
+ const width = configuration.style?.width;
47
+ const minWidth = configuration.style?.minWidth;
48
+ return (
49
+ <td
50
+ key={configuration.name}
51
+ style={{
52
+ minWidth,
53
+ width,
54
+ }}
55
+ >
56
+ {render}
57
+ </td>
58
+ );
50
59
  }
@@ -24,10 +24,10 @@ export function Datagrid<T extends AnyClass>({
24
24
  }: DatagridProps<T>) {
25
25
  const cells = listPageMeta.cells;
26
26
  const listData = useAppStore(state => state.listData[listPageMeta.class.key]);
27
- const listGeneralCells = data?.[0]
28
- ? typeof listPageMeta.class.cells === 'function'
29
- ? listPageMeta.class.cells?.(data[0])
30
- : listPageMeta.class.cells
27
+ const listActions = data?.[0]
28
+ ? typeof listPageMeta.class.actions === 'function'
29
+ ? listPageMeta.class.actions?.(data[0])
30
+ : listPageMeta.class.actions
31
31
  : null;
32
32
 
33
33
  return (
@@ -39,20 +39,27 @@ export function Datagrid<T extends AnyClass>({
39
39
  <thead>
40
40
  <tr>
41
41
  {cells.map(cellOptions => (
42
- <th key={cellOptions.name}>{cellOptions.title ?? cellOptions.name}</th>
42
+ <th
43
+ key={cellOptions.name}
44
+ style={{ width: cellOptions.style?.width, minWidth: cellOptions.style?.minWidth }}
45
+ >
46
+ {cellOptions.title ?? cellOptions.name}
47
+ </th>
43
48
  ))}
44
- {(listGeneralCells?.details ||
45
- listGeneralCells?.edit ||
46
- listGeneralCells?.delete) && <th>Actions</th>}
49
+ {(listActions?.details ||
50
+ listActions?.edit ||
51
+ listActions?.delete ||
52
+ listActions?.customActions?.length) && <th style={{ width: '30px' }}>Actions</th>}
47
53
  </tr>
48
54
  </thead>
49
55
  <tbody>
50
56
  {data.map((item, index) => {
51
57
  const listCells = item
52
- ? typeof listPageMeta.class.cells === 'function'
53
- ? listPageMeta.class.cells?.(item)
54
- : listPageMeta.class.cells
58
+ ? typeof listPageMeta.class.actions === 'function'
59
+ ? listPageMeta.class.actions?.(item)
60
+ : listPageMeta.class.actions
55
61
  : null;
62
+ //TODO: memoize this
56
63
  const listDataItem = listPageMeta.class.primaryId
57
64
  ? (listData?.[item[listPageMeta.class.primaryId!] as string] as
58
65
  | Record<string, unknown>
@@ -73,7 +80,7 @@ export function Datagrid<T extends AnyClass>({
73
80
  );
74
81
  })}
75
82
  {(listCells?.details || listCells?.edit || listCells?.delete) && (
76
- <td>
83
+ <td style={{ width: '30px' }}>
77
84
  <div className="util-cell-actions">
78
85
  <p className="util-cell-actions-label">
79
86
  Actions <DownArrowIcon className="icon icon-down" />
@@ -118,6 +125,13 @@ export function Datagrid<T extends AnyClass>({
118
125
  </a>
119
126
  </li>
120
127
  )}
128
+ {listCells?.customActions?.map(action => (
129
+ <li key={action.label}>
130
+ <a onClick={() => action.onClick(item)} className="util-cell-link">
131
+ <span className="util-cell-label">{action.label}</span>
132
+ </a>
133
+ </li>
134
+ ))}
121
135
  </ul>
122
136
  </div>
123
137
  </td>
@@ -1,12 +1,17 @@
1
1
  import React from 'react';
2
2
  import CheckIcon from '../../../assets/icons/svg/check.svg';
3
3
  import CrossIcon from '../../../assets/icons/svg/cross.svg';
4
+ import { AnyClass } from '../../../types/AnyClass';
5
+ import { CellConfiguration } from '../../../decorators/list/Cell';
4
6
 
5
7
  interface BooleanCellProps {
6
- value: boolean;
8
+ item: AnyClass;
9
+ configuration: CellConfiguration;
7
10
  }
8
11
 
9
- export function BooleanCell({ value }: BooleanCellProps) {
12
+ export function BooleanCell({ item, configuration }: BooleanCellProps) {
13
+ const value = item[configuration.name];
14
+
10
15
  return value ? (
11
16
  <CheckIcon className="icon icon-true" />
12
17
  ) : (
@@ -1,10 +1,14 @@
1
1
  import React from 'react';
2
+ import { AnyClass } from '../../../types/AnyClass';
3
+ import { CellConfiguration } from '../../../decorators/list/Cell';
2
4
 
3
5
  interface DateCellProps {
4
- value: string | number | Date;
6
+ item: AnyClass;
7
+ configuration: CellConfiguration;
5
8
  }
6
9
 
7
- export function DateCell({ value }: DateCellProps) {
10
+ export function DateCell({ item, configuration }: DateCellProps) {
11
+ const value = item[configuration.name];
8
12
  if (!value) return <>-</>;
9
13
 
10
14
  const date = new Date(value);
@@ -1,13 +1,13 @@
1
1
  import React from 'react';
2
2
  import { CellConfiguration } from '../../../decorators/list/Cell';
3
+ import { AnyClass } from '../../../types/AnyClass';
3
4
 
4
5
  interface DefaultCellProps {
5
- //TODO: any is not a good solution, we need to find a better way to do this
6
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
- value: any;
6
+ item: AnyClass;
8
7
  configuration: CellConfiguration;
9
8
  }
10
9
 
11
- export function DefaultCell({ value, configuration }: DefaultCellProps): React.ReactElement {
10
+ export function DefaultCell({ item, configuration }: DefaultCellProps): React.ReactElement {
11
+ const value = item[configuration.name];
12
12
  return <>{value ? value.toString() : configuration.placeHolder}</>;
13
13
  }
@@ -1,13 +1,15 @@
1
1
  import React from 'react';
2
2
  import { CellConfiguration } from '../../../decorators/list/Cell';
3
3
  import { DownloadCellConfiguration } from '../../../decorators/list/cells/DownloadCell';
4
+ import { AnyClass } from '../../../types/AnyClass';
4
5
 
5
6
  interface DownloadCellProps {
6
- value: string;
7
+ item: AnyClass;
7
8
  configuration: CellConfiguration;
8
9
  }
9
10
 
10
- export function DownloadCell({ value, configuration }: DownloadCellProps): React.ReactElement {
11
+ export function DownloadCell({ item, configuration }: DownloadCellProps): React.ReactElement {
12
+ const value = item[configuration.name];
11
13
  if (!value) return <>-</>;
12
14
 
13
15
  const downloadConfiguration = configuration as DownloadCellConfiguration;
@@ -1,14 +1,16 @@
1
1
  import React from 'react';
2
2
  import { CellConfiguration } from '../../../decorators/list/Cell';
3
3
  import { ImageCellConfiguration } from '../../../decorators/list/cells/ImageCell';
4
+ import { AnyClass } from '../../../types/AnyClass';
4
5
 
5
6
  interface ImageCellProps {
6
- value: string;
7
+ item: AnyClass;
7
8
  configuration: CellConfiguration;
8
9
  }
9
10
 
10
- export function ImageCell({ value, configuration }: ImageCellProps) {
11
+ export function ImageCell({ item, configuration }: ImageCellProps) {
11
12
  const imageConfiguration = configuration as ImageCellConfiguration;
13
+ const value = item[configuration.name];
12
14
  if (!value) return <>-</>;
13
15
 
14
16
  return (
@@ -15,13 +15,14 @@ export function LinkCell<T>({ item, configuration }: LinkCellProps<T>) {
15
15
  return (
16
16
  <Link to={linkConfiguration.path ?? linkConfiguration.url ?? ''}>
17
17
  {linkConfiguration.onClick ? (
18
- <button
18
+ <a
19
+ className="util-cell-link"
19
20
  onClick={() => {
20
21
  linkConfiguration.onClick?.(item as T);
21
22
  }}
22
23
  >
23
24
  {value?.toString()}
24
- </button>
25
+ </a>
25
26
  ) : (
26
27
  value?.toString() || linkConfiguration.placeHolder
27
28
  )}
@@ -1,10 +1,14 @@
1
1
  import React from 'react';
2
+ import { AnyClass } from '../../../types/AnyClass';
3
+ import { CellConfiguration } from '../../../decorators/list/Cell';
2
4
 
3
5
  interface UUIDCellProps {
4
- value: string;
6
+ item: AnyClass;
7
+ configuration: CellConfiguration;
5
8
  }
6
9
 
7
- export function UUIDCell({ value }: UUIDCellProps) {
10
+ export function UUIDCell({ item, configuration }: UUIDCellProps) {
11
+ const value = item[configuration.name];
8
12
  if (!value || typeof value !== 'string' || value.length < 6) return <>-</>;
9
13
 
10
14
  return <>{`${value.slice(0, 3)}...${value.slice(-3)}`}</>;
@@ -0,0 +1,32 @@
1
+ import { MinLength } from 'class-validator';
2
+ import { Form } from '../form/Form';
3
+ import { Input } from '../form/Input';
4
+ import { LoginForm as LoginFormInterface, LoginResponse } from '../../types/Login';
5
+ import { setAuthToken } from '../../api/ApiConfig';
6
+ import { login } from '../../utils/login';
7
+ import { User } from '../../types/User';
8
+ import { authLogin } from '../..';
9
+
10
+ @Form<LoginFormInterface, LoginResponse<User>>({
11
+ onSubmit: authLogin,
12
+ onSubmitSuccess: (data: LoginResponse<User>) => {
13
+ setAuthToken(data.access_token);
14
+ login(data.admin as User, data.access_token, () => {
15
+ window.location.href = '/';
16
+ });
17
+ },
18
+ type: 'formData',
19
+ })
20
+ export class DefaultLoginForm {
21
+ @MinLength(3)
22
+ @Input({
23
+ label: 'Username',
24
+ })
25
+ username: string;
26
+
27
+ @Input({
28
+ label: 'Password',
29
+ inputType: 'password',
30
+ })
31
+ password: string;
32
+ }
@@ -22,6 +22,10 @@ export interface CellOptions {
22
22
  type?: CellTypes;
23
23
  placeHolder?: string;
24
24
  filter?: Filter | StaticSelectFilter;
25
+ style?: {
26
+ minWidth?: string;
27
+ width?: string;
28
+ };
25
29
  }
26
30
 
27
31
  export interface CellConfiguration extends Omit<CellOptions, 'type'> {
@@ -25,7 +25,8 @@ export interface ListHeaderOptions {
25
25
  create?: { path: string; label: string };
26
26
  }
27
27
 
28
- export interface ListCellOptions<T> {
28
+ export interface ListActionOptions<T> {
29
+ customActions?: { label: string; onClick: (item: T) => void; icon?: string }[];
29
30
  details?: { path: string; label: string };
30
31
  edit?: { path: string; label: string };
31
32
  delete?: { label: string; onRemoveItem?: (item: T) => Promise<void> };
@@ -34,7 +35,7 @@ export interface ListCellOptions<T> {
34
35
  export interface ListOptions<T> {
35
36
  getData: GetDataForList<T>;
36
37
  headers?: ListHeaderOptions;
37
- cells?: ((item: T) => ListCellOptions<T>) | ListCellOptions<T>;
38
+ actions?: ((item: T) => ListActionOptions<T>) | ListActionOptions<T>;
38
39
  primaryId?: string;
39
40
  key?: string;
40
41
  }
package/src/index.ts CHANGED
@@ -37,6 +37,31 @@ export { Layout } from './components/layout';
37
37
  export { Login } from './components/Login';
38
38
  export { login } from './utils/login';
39
39
  export { logout } from './utils/logout';
40
+ export { DefaultLoginForm } from './decorators/auth/DefaultLoginForm';
41
+
42
+ //API
43
+ export {
44
+ initApi,
45
+ initAuthToken,
46
+ setAuthToken,
47
+ setAuthLogout,
48
+ getAxiosInstance,
49
+ axiosInstance,
50
+ } from './api/ApiConfig';
51
+ export {
52
+ getAll,
53
+ getOne,
54
+ create,
55
+ createFormData,
56
+ update,
57
+ updateFormData,
58
+ updateSimple,
59
+ remove,
60
+ } from './api/CrudApi';
61
+ export { login as authLogin } from './api/AuthApi';
62
+
63
+ //TYPES
64
+ export type { LoginForm, LoginResponse } from './types/Login';
40
65
 
41
66
  //SERVICES
42
67
  export { updateDetailsData } from './services/DataService';
@@ -34,7 +34,7 @@ export const useAppStore = createWithEqualityFn<AppState>()(
34
34
  }),
35
35
  }),
36
36
  {
37
- name: 'app-store-1',
37
+ name: 'proje-panel-store',
38
38
  storage: createJSONStorage(() => localStorage),
39
39
  partialize: state => ({
40
40
  user: state.user,
@@ -0,0 +1,14 @@
1
+ .panel-button {
2
+ padding: 0.5rem 1rem;
3
+ border: 1px solid #444444;
4
+ border-radius: 4px;
5
+ background-color: cornflowerblue;
6
+ color: #ffffff;
7
+ font-size: 0.875rem;
8
+ font-weight: 500;
9
+ cursor: pointer;
10
+ transition: all 0.2s ease;
11
+ &:hover {
12
+ transform: scale(1.01);
13
+ }
14
+ }