@campxdev/campx-web-utils 1.1.8 → 1.2.0

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/export.ts +9 -0
  2. package/package.json +7 -4
  3. package/src/App.css +38 -0
  4. package/src/App.tsx +16 -0
  5. package/src/AppContent.tsx +19 -0
  6. package/src/components/ActivityLog.tsx +96 -0
  7. package/src/components/ChangePassword.tsx +183 -0
  8. package/src/config/axios.ts +146 -0
  9. package/src/context/ConfirmDialogProvider.tsx +78 -0
  10. package/src/context/ErrorBoundary/ErrorBoundary.tsx +131 -0
  11. package/src/context/ErrorBoundary/Login.tsx +206 -0
  12. package/src/context/Providers.tsx +76 -0
  13. package/src/context/SnackbarProvider.tsx +42 -0
  14. package/src/context/application-store.ts +86 -0
  15. package/src/context/export.ts +5 -0
  16. package/src/hooks/export.ts +1 -0
  17. package/src/hooks/useConfirm.ts +57 -0
  18. package/src/index.css +13 -0
  19. package/src/index.tsx +19 -0
  20. package/src/layout/AppLayout/AppLayout.tsx +155 -0
  21. package/src/layout/AppLayout/components/HelpDocs.tsx +121 -0
  22. package/src/logo.svg +1 -0
  23. package/src/react-app-env.d.ts +1 -0
  24. package/src/reportWebVitals.ts +15 -0
  25. package/src/routes/main.tsx +32 -0
  26. package/src/selectors/BatchSelector.tsx +15 -0
  27. package/src/selectors/CourseSelector.tsx +16 -0
  28. package/src/selectors/DepartmentSelector.tsx +19 -0
  29. package/src/selectors/EmployeesSelector.tsx +20 -0
  30. package/src/selectors/FeeTypeSelector.tsx +15 -0
  31. package/src/selectors/HostelBlocksSelector.tsx +19 -0
  32. package/src/selectors/HostelFloorSelector.tsx +19 -0
  33. package/src/selectors/HostelRoomSelector.tsx +20 -0
  34. package/src/selectors/PrintFormatSelector.tsx +17 -0
  35. package/src/selectors/ProgramSelector.tsx +16 -0
  36. package/src/selectors/RegulationSelector.tsx +19 -0
  37. package/src/selectors/SemesterSelector.tsx +16 -0
  38. package/src/selectors/YearRangeSelector.tsx +26 -0
  39. package/src/selectors/export.ts +13 -0
  40. package/src/selectors/utils.tsx +39 -0
  41. package/src/setupTests.ts +5 -0
  42. package/src/utils/constants.ts +7 -0
  43. package/src/utils/functions.tsx +11 -0
package/export.ts ADDED
@@ -0,0 +1,9 @@
1
+ export * from './src/components/ChangePassword';
2
+ export * from './src/config/axios';
3
+ export * from './src/context/export';
4
+ export * from './src/hooks/export';
5
+ export * from './src/layout/AppLayout/AppLayout';
6
+ export * from './src/layout/AppLayout/components/HelpDocs';
7
+ export * from './src/selectors/export';
8
+ export * from './src/utils/constants';
9
+ export * from './src/utils/functions';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@campxdev/campx-web-utils",
3
- "version": "1.1.8",
3
+ "version": "1.2.0",
4
4
  "author": "CampX",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -14,11 +14,13 @@
14
14
  "module": "dist/esm/index.js",
15
15
  "types": "dist/index.d.ts",
16
16
  "files": [
17
- "dist"
17
+ "dist",
18
+ "src",
19
+ "export.ts"
18
20
  ],
19
21
  "private": false,
20
22
  "peerDependencies": {
21
- "@campxdev/react-blueprint": "2.1.4",
23
+ "@campxdev/react-blueprint": "2.2.0",
22
24
  "@emotion/react": "^11.14.0",
23
25
  "@emotion/styled": "^11.14.0",
24
26
  "@mui/icons-material": "^7.0.2",
@@ -29,7 +31,7 @@
29
31
  "react-router-dom": "^6.24.0"
30
32
  },
31
33
  "dependencies": {
32
- "@campxdev/react-blueprint": "2.1.4",
34
+ "@campxdev/react-blueprint": "2.2.0",
33
35
  "@hookform/resolvers": "^2.9.10",
34
36
  "@mui/x-date-pickers": "^8.1.0",
35
37
  "axios": "^1.7.2",
@@ -93,6 +95,7 @@
93
95
  "test": "craco test",
94
96
  "eject": "react-scripts eject",
95
97
  "build": "rollup -c --bundleConfigAsCjs",
98
+ "yalc:dev": "cp package.json package.json.backup && node -e \"const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('./package.json')); pkg.main='export.ts'; pkg.module='export.ts'; pkg.types='export.ts'; pkg.exports={'.':'./export.ts'}; delete pkg.scripts.prepare; fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2));\" && yalc publish --push --sig --no-scripts && mv package.json.backup package.json",
96
99
  "prepare": "npm run build"
97
100
  },
98
101
  "eslintConfig": {
package/src/App.css ADDED
@@ -0,0 +1,38 @@
1
+ .App {
2
+ text-align: center;
3
+ }
4
+
5
+ .App-logo {
6
+ height: 40vmin;
7
+ pointer-events: none;
8
+ }
9
+
10
+ @media (prefers-reduced-motion: no-preference) {
11
+ .App-logo {
12
+ animation: App-logo-spin infinite 20s linear;
13
+ }
14
+ }
15
+
16
+ .App-header {
17
+ background-color: #282c34;
18
+ min-height: 100vh;
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ justify-content: center;
23
+ font-size: calc(10px + 2vmin);
24
+ color: white;
25
+ }
26
+
27
+ .App-link {
28
+ color: #61dafb;
29
+ }
30
+
31
+ @keyframes App-logo-spin {
32
+ from {
33
+ transform: rotate(0deg);
34
+ }
35
+ to {
36
+ transform: rotate(360deg);
37
+ }
38
+ }
package/src/App.tsx ADDED
@@ -0,0 +1,16 @@
1
+ import { useRoutes } from 'react-router-dom';
2
+
3
+ import { Providers } from './context/export';
4
+ import { mainRoutes } from './routes/main';
5
+
6
+ export default function App() {
7
+ return (
8
+ <Providers>
9
+ <AppRouter />
10
+ </Providers>
11
+ );
12
+ }
13
+ const AppRouter = () => {
14
+ const app = useRoutes(mainRoutes);
15
+ return app;
16
+ };
@@ -0,0 +1,19 @@
1
+ import { Breadcrumbs, Icons, TextField } from '@campxdev/react-blueprint';
2
+ import { useState } from 'react';
3
+ import { CourseSelector } from './selectors/CourseSelector';
4
+
5
+ function AppContent() {
6
+ const [value, setValue] = useState('63b810ee995d6d64f4a248de');
7
+
8
+ return (
9
+ <>
10
+ <Breadcrumbs pathTrimCount={0} />
11
+ <TextField label="habdkhabs" />
12
+ <Icons.AppsIcon />
13
+
14
+ <CourseSelector onChange={() => {}} />
15
+ </>
16
+ );
17
+ }
18
+
19
+ export default AppContent;
@@ -0,0 +1,96 @@
1
+ import { ActivityLogView } from '@campxdev/react-blueprint';
2
+ import { Store } from 'pullstate';
3
+ import { useEffect, useState } from 'react';
4
+ import { useInfiniteQuery } from 'react-query';
5
+ import { axios } from '../config/axios';
6
+
7
+ function formatDate(date: Date): string {
8
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
9
+ }
10
+
11
+ export type ActivityLogType = {
12
+ activitiesData: any[];
13
+ lastTimestamp: number | null;
14
+ };
15
+
16
+ export type ActivityLogStoreType = {
17
+ [logType: string]: ActivityLogType;
18
+ };
19
+
20
+ export const ActivityLogStore = new Store<ActivityLogStoreType>({});
21
+
22
+ export default function ActivityLog({
23
+ apiEndpoint,
24
+ params,
25
+ }: {
26
+ apiEndpoint: string;
27
+ params: Record<string, any>;
28
+ }) {
29
+ const [fromDate, setFromDate] = useState<Date | null>(null);
30
+ const [toDate, setToDate] = useState<Date | null>(null);
31
+
32
+ const { activitiesData } = ActivityLogStore.useState(
33
+ (s) => s[params.logType] || { activitiesData: [], lastTimestamp: null },
34
+ );
35
+
36
+ const fetchActivities = async ({ pageParam = null }) => {
37
+ const response = await axios.get(apiEndpoint, {
38
+ params: {
39
+ ...params,
40
+ lastTimestamp: pageParam,
41
+ ...(fromDate && { fromDate: formatDate(fromDate) }),
42
+ ...(toDate && { toDate: formatDate(toDate) }),
43
+ },
44
+ });
45
+ return response.data;
46
+ };
47
+
48
+ const { fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
49
+ useInfiniteQuery(
50
+ ['activities', params.logType, fromDate, toDate],
51
+ fetchActivities,
52
+ {
53
+ getNextPageParam: (lastPage) =>
54
+ lastPage?.length
55
+ ? new Date(lastPage[lastPage.length - 1]?.timestamp).getTime()
56
+ : null,
57
+ onSuccess: (data) => {
58
+ ActivityLogStore.update((s) => {
59
+ const currentData = s[params.logType] || {
60
+ activitiesData: [],
61
+ lastTimestamp: null,
62
+ };
63
+ currentData.activitiesData = data.pages.flatMap(
64
+ (page) => page ?? [],
65
+ );
66
+ currentData.lastTimestamp = data.pages[data.pages.length - 1]
67
+ ?.timestamp
68
+ ? new Date(data.pages[data.pages.length - 1].timestamp).getTime()
69
+ : null;
70
+ s[params.logType] = currentData;
71
+ });
72
+ },
73
+ enabled: activitiesData.length === 0 || !!fromDate || !!toDate,
74
+ },
75
+ );
76
+
77
+ useEffect(() => {
78
+ if (fromDate || toDate) {
79
+ fetchNextPage();
80
+ }
81
+ }, [fromDate, toDate, fetchNextPage]);
82
+
83
+ return (
84
+ <ActivityLogView
85
+ activitiesData={activitiesData}
86
+ isFetchingNextPage={isFetchingNextPage}
87
+ fetchNextPage={fetchNextPage}
88
+ hasNextPage={hasNextPage}
89
+ fromDate={fromDate}
90
+ toDate={toDate}
91
+ isLoading={isLoading}
92
+ setFromDate={setFromDate}
93
+ setToDate={setToDate}
94
+ />
95
+ );
96
+ }
@@ -0,0 +1,183 @@
1
+ import {
2
+ CircularProgress,
3
+ IconButton,
4
+ InputAdornment,
5
+ MenuItem,
6
+ Stack,
7
+ styled,
8
+ } from '@mui/material';
9
+ import { useState } from 'react';
10
+ import { useForm } from 'react-hook-form';
11
+
12
+ import {
13
+ Button,
14
+ DialogButton,
15
+ FormControlWrapper,
16
+ TextField,
17
+ } from '@campxdev/react-blueprint';
18
+ import { Visibility, VisibilityOff } from '@mui/icons-material';
19
+ import { toast } from 'react-toastify';
20
+ import { axios } from '../config/axios';
21
+ import { isDevelopment } from '../utils/constants';
22
+
23
+ interface PasswordVisibilityState {
24
+ oldPassword: boolean;
25
+ newPassword: boolean;
26
+ confirmPassword: boolean;
27
+ }
28
+
29
+ interface FormData {
30
+ oldPassword: string;
31
+ newPassword: string;
32
+ confirmPassword: string;
33
+ }
34
+
35
+ interface Field {
36
+ label: string;
37
+ name: keyof PasswordVisibilityState;
38
+ }
39
+
40
+ interface ChangePasswordProps {
41
+ close: () => void;
42
+ }
43
+
44
+ export const StyledMenuItem = styled(MenuItem)(() => ({
45
+ display: 'flex',
46
+ alignItems: 'center',
47
+ gap: '5px',
48
+ '& .MuiListItemIcon-root': {
49
+ minWidth: '24px',
50
+ },
51
+ '& .MuiSvgIcon-root': {
52
+ height: '14px',
53
+ width: '14px',
54
+ },
55
+ }));
56
+
57
+ export function ChangePassword({ close }: ChangePasswordProps) {
58
+ const [showPassword, setShowPassword] = useState<PasswordVisibilityState>({
59
+ oldPassword: false,
60
+ newPassword: false,
61
+ confirmPassword: false,
62
+ });
63
+ const [isLoading, setIsLoading] = useState(false);
64
+
65
+ const { handleSubmit, control, reset } = useForm<FormData>();
66
+
67
+ const onSubmit = async (formData: any) => {
68
+ setIsLoading(true);
69
+ const { oldPassword, newPassword, confirmPassword } = formData;
70
+ if (newPassword === confirmPassword) {
71
+ try {
72
+ await axios.post(
73
+ isDevelopment
74
+ ? 'https://api.campx.dev/auth-server/auth/change-password'
75
+ : 'https://api.campx.in/auth-server/auth/change-password',
76
+ {
77
+ oldPassword,
78
+ newPassword,
79
+ },
80
+ );
81
+ toast.success('Password Changed Successfully');
82
+ setIsLoading(false);
83
+ reset();
84
+ close();
85
+ } catch (error) {
86
+ console.log(error, 'this is error');
87
+ // axiosErrorToast(error);
88
+ setIsLoading(false);
89
+ }
90
+ } else {
91
+ toast.error('New Password, Confirm Password must be same');
92
+ setIsLoading(false);
93
+ }
94
+ };
95
+
96
+ const fields: Field[] = [
97
+ { label: 'Old Password', name: 'oldPassword' },
98
+ { label: 'New Password', name: 'newPassword' },
99
+ { label: 'Confirm Password', name: 'confirmPassword' },
100
+ ];
101
+
102
+ return (
103
+ <form onSubmit={handleSubmit(onSubmit)}>
104
+ <FormControlWrapper control={control}>
105
+ <Stack gap={1} direction="column">
106
+ {fields.map((item) => {
107
+ return (
108
+ <>
109
+ <TextField
110
+ label={item.label}
111
+ name={item.name}
112
+ type={showPassword[item.name] ? 'text' : 'password'}
113
+ required
114
+ InputProps={{
115
+ endAdornment: (
116
+ <InputAdornment position="end">
117
+ <IconButton
118
+ size="small"
119
+ onClick={() =>
120
+ setShowPassword((prev: any) => ({
121
+ ...prev,
122
+ [item.name]: !prev[item.name],
123
+ }))
124
+ }
125
+ edge="end"
126
+ >
127
+ {showPassword[item.name] ? (
128
+ <Visibility />
129
+ ) : (
130
+ <VisibilityOff />
131
+ )}
132
+ </IconButton>
133
+ </InputAdornment>
134
+ ),
135
+ }}
136
+ />
137
+ </>
138
+ );
139
+ })}
140
+
141
+ <Stack direction={'row'} gap={2} sx={{ marginTop: '15px' }}>
142
+ <Button variant="outlined" onClick={close}>
143
+ Cancel
144
+ </Button>
145
+ <Button
146
+ type="submit"
147
+ variant="contained"
148
+ endIcon={
149
+ isLoading && (
150
+ <CircularProgress
151
+ style={{ color: 'white' }}
152
+ size="30px"
153
+ thickness={1.7}
154
+ />
155
+ )
156
+ }
157
+ >
158
+ Submit
159
+ </Button>
160
+ </Stack>
161
+ </Stack>
162
+ </FormControlWrapper>
163
+ </form>
164
+ );
165
+ }
166
+
167
+ export const ChangePasswordDialog: React.FC = () => (
168
+ <>
169
+ <DialogButton
170
+ anchor={({ open }) => (
171
+ <StyledMenuItem
172
+ onClick={() => {
173
+ open();
174
+ }}
175
+ >
176
+ Change Password
177
+ </StyledMenuItem>
178
+ )}
179
+ content={({ close }) => <ChangePassword close={close} />}
180
+ title="Change Password"
181
+ />
182
+ </>
183
+ );
@@ -0,0 +1,146 @@
1
+ import Axios from 'axios';
2
+ import Cookies from 'js-cookie';
3
+ import { ApplicationStore } from '../context/application-store';
4
+ import { institutionCode, isDevelopment, tenantCode } from '../utils/constants';
5
+
6
+ // Declare the extended axios types to include our custom property
7
+ declare module 'axios' {
8
+ export interface AxiosRequestConfig {
9
+ skipInstitutionCode?: boolean;
10
+ }
11
+ }
12
+
13
+ declare global {
14
+ interface ImportMeta {
15
+ env: {
16
+ VITE_APP_API_HOST?: string;
17
+ // Add other Vite environment variables as needed
18
+ };
19
+ }
20
+ }
21
+
22
+ // Get base URL from environment variables
23
+ const getBaseUrl = (): string => {
24
+ // For Vite
25
+ if (typeof import.meta !== 'undefined' && import.meta.env) {
26
+ return import.meta.env.VITE_APP_API_HOST || '';
27
+ }
28
+
29
+ // For Create React App
30
+ return process.env.REACT_APP_API_HOST || '';
31
+ };
32
+
33
+ // Format query parameters
34
+ const formatParams = (params: any) => {
35
+ return Object.fromEntries(
36
+ Object.entries(params ?? {}).map(([key, value]) => [
37
+ key,
38
+ value === '__empty__' ? '' : value,
39
+ ]),
40
+ );
41
+ };
42
+
43
+ // Create axios instance
44
+ export const axios = Axios.create({
45
+ baseURL: getBaseUrl(),
46
+ withCredentials: true,
47
+ headers: {
48
+ 'x-tenant-id': tenantCode,
49
+ 'x-institution-code': institutionCode,
50
+ },
51
+ });
52
+
53
+ // Request interceptor
54
+ axios.interceptors.request.use(
55
+ (config) => {
56
+ // Format query parameters
57
+ const params = formatParams(config?.params);
58
+
59
+ // Add session tokens from cookies if in development
60
+ const sessionKey = Cookies.get('campx_session_key');
61
+ const openPaymentsKey = Cookies.get('campx_open_payments_key');
62
+ const studentSessionKey = Cookies.get('campx_student_key');
63
+
64
+ if (isDevelopment && sessionKey) {
65
+ config.headers.set('campx_session_key', sessionKey);
66
+ }
67
+
68
+ if (isDevelopment && studentSessionKey) {
69
+ config.headers.set('campx_student_key', studentSessionKey);
70
+ }
71
+
72
+ if (openPaymentsKey) {
73
+ config.headers.set('campx_open_payments_key', openPaymentsKey);
74
+ }
75
+
76
+ // Skip sending X-institution-code if the flag is set
77
+ if (config.skipInstitutionCode) {
78
+ config.headers.delete('x-institution-code');
79
+ }
80
+
81
+ return {
82
+ ...config,
83
+ params,
84
+ };
85
+ },
86
+ (error) => {
87
+ return Promise.reject(error);
88
+ },
89
+ );
90
+
91
+ // Response interceptor
92
+ axios.interceptors.response.use(
93
+ (response) => {
94
+ // Check if the response has hideSnackbar property
95
+ const shouldHideSnackbar = response.data?.hideSnackbar === true;
96
+
97
+ // Handle successful responses
98
+ if (
99
+ !shouldHideSnackbar &&
100
+ response.config.method &&
101
+ ['put', 'post', 'delete', 'patch'].includes(response.config.method) &&
102
+ [200, 201, 202].includes(response.status)
103
+ ) {
104
+ // Use ApplicationStore for snackbar
105
+ ApplicationStore.update((s) => {
106
+ s.snackbar.open = true;
107
+ s.snackbar.message = response.data.message || 'Operation successful';
108
+ s.snackbar.severity = 'success';
109
+ });
110
+ }
111
+
112
+ return response;
113
+ },
114
+ (err) => {
115
+ // Check if the error response has hideSnackbar property
116
+ const shouldHideSnackbar = err.response?.data?.hideSnackbar === true;
117
+
118
+ // Handle error responses
119
+ if (!shouldHideSnackbar && [400, 422].includes(err.response?.status)) {
120
+ // Use ApplicationStore for error snackbar
121
+ ApplicationStore.update((s) => {
122
+ s.snackbar.open = true;
123
+ s.snackbar.message = err.response.data.message || 'Bad Request';
124
+ s.snackbar.severity = 'error';
125
+ });
126
+ }
127
+
128
+ if (
129
+ !shouldHideSnackbar &&
130
+ err.response?.config.method &&
131
+ ['put', 'post', 'delete', 'patch'].includes(err.response.config.method) &&
132
+ [404].includes(err.response.status)
133
+ ) {
134
+ // Use ApplicationStore for not found error snackbar
135
+ ApplicationStore.update((s) => {
136
+ s.snackbar.open = true;
137
+ s.snackbar.message = err.response.data.message || 'Not Found';
138
+ s.snackbar.severity = 'error';
139
+ });
140
+ }
141
+
142
+ return Promise.reject(err);
143
+ },
144
+ );
145
+
146
+ export default axios;
@@ -0,0 +1,78 @@
1
+ import { ConfirmDialog, ConfirmDialogType } from '@campxdev/react-blueprint';
2
+ import { createContext, ReactNode } from 'react';
3
+ import { ApplicationStore } from './application-store';
4
+
5
+ interface ConfirmStateProps {
6
+ showConfirmDialog: (
7
+ title: string,
8
+ message: string,
9
+ onConfirm: () => void,
10
+ onCancel: () => void,
11
+ type?: ConfirmDialogType,
12
+ confirmButtonText?: string,
13
+ cancelButtonText?: string,
14
+ ) => void;
15
+ }
16
+
17
+ const ConfirmContext = createContext<ConfirmStateProps>({
18
+ showConfirmDialog: () => {},
19
+ });
20
+
21
+ export const ConfirmDialogProvider = ({
22
+ children,
23
+ }: {
24
+ children: ReactNode;
25
+ }) => {
26
+ const confirmDialog = ApplicationStore.useState((s) => s.confirmDialog);
27
+
28
+ const showConfirmDialog = (
29
+ title: string,
30
+ message: string,
31
+ onConfirm: () => void,
32
+ onCancel: () => void,
33
+ type: ConfirmDialogType = 'confirm',
34
+ confirmButtonText?: string,
35
+ cancelButtonText?: string,
36
+ ) => {
37
+ ApplicationStore.update((s) => {
38
+ s.confirmDialog = {
39
+ isOpen: true,
40
+ title,
41
+ message,
42
+ onConfirm,
43
+ onCancel,
44
+ type,
45
+ confirmButtonText,
46
+ cancelButtonText,
47
+ };
48
+ });
49
+ };
50
+
51
+ const handleCloseDialog = () => {
52
+ ApplicationStore.update((s) => {
53
+ s.confirmDialog.isOpen = false;
54
+ });
55
+ };
56
+
57
+ return (
58
+ <ConfirmContext.Provider value={{ showConfirmDialog }}>
59
+ {children}
60
+ <ConfirmDialog
61
+ isOpen={confirmDialog.isOpen}
62
+ title={confirmDialog.title}
63
+ message={confirmDialog.message}
64
+ type={confirmDialog.type}
65
+ confirmButtonText={confirmDialog.confirmButtonText}
66
+ cancelButtonText={confirmDialog.cancelButtonText}
67
+ onConfirm={() => {
68
+ confirmDialog.onConfirm();
69
+ handleCloseDialog();
70
+ }}
71
+ onCancel={() => {
72
+ confirmDialog.onCancel();
73
+ handleCloseDialog();
74
+ }}
75
+ />
76
+ </ConfirmContext.Provider>
77
+ );
78
+ };