Skip to content

Commit dc77a1c

Browse files
authored
Merge pull request #599 from tekdi/main-learner
Main learner qa
2 parents d867438 + 7ab204a commit dc77a1c

File tree

135 files changed

+12303
-1839
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+12303
-1839
lines changed

Dockerfile.learner-web-app

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ COPY . .
1717

1818
# Build Specific Application
1919
# Build only specific applications
20-
RUN npx nx run-many --target=build --projects=learner-web-app,players
20+
RUN npx nx run-many --target=build --projects=learner-web-app,players,forget-password
2121

2222
# Install PM2 to manage multiple apps
2323
RUN npm install -g pm2
2424

2525
# Expose the ports for all apps
26-
EXPOSE 3003 4108
26+
EXPOSE 3003 4108 4109
2727

2828
# Command to run all apps using PM2
2929
CMD ["pm2-runtime", "ecosystem.learner-web-app.config.js"]

apps/admin-app-repo/src/utils/API/APIEndpoints.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export const API_ENDPOINTS = {
3939
issueCertificate: `${baseurl}/tracking/certificate/issue`,
4040
renderCertificate: `${baseurl}/tracking/certificate/render`,
4141
downloadCertificate: `${baseurl}/tracking/certificate/render-PDF`,
42+
framework : (frameworkId: string) => `${baseurl}/framework/v1/read/${frameworkId}`,
43+
actionObject : `${baseurl}/action/object/category/definition/v1/read?fields=objectMetadata,forms,name,label`,
4244

4345
};
4446

apps/learner-web-app/next.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ const nextConfig = {
1212
// See: https://github.com/gregberge/svgr
1313
svgr: false,
1414
},
15+
16+
typescript: {
17+
ignoreBuildErrors: true,
18+
},
19+
eslint: {
20+
ignoreDuringBuilds: true,
21+
},
22+
23+
//cross import support
24+
transpilePackages: ['@shared-lib-v2/*'],
25+
1526
images: {
1627
domains: ['program-image-dev.s3.ap-south-1.amazonaws.com'],
1728
},
5.12 KB
Loading
386 KB
Loading
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
import { validate } from 'uuid';
2+
import { NextResponse } from 'next/server';
3+
4+
export async function POST(req) {
5+
try {
6+
const body = await req.json();
7+
const { readForm } = body;
8+
9+
if (readForm && readForm.length > 0) {
10+
const fields = await fetchFormFields(readForm);
11+
12+
if (fields && fields.length > 0) {
13+
const { schema, uiSchema } = generateSchemaAndUISchema(fields);
14+
return NextResponse.json({ schema, uiSchema }, { status: 200 });
15+
} else {
16+
return NextResponse.json(
17+
{ error: 'Form Fields Not Found in API Call' },
18+
{ status: 500 }
19+
);
20+
}
21+
} else {
22+
return NextResponse.json(
23+
{ error: 'Form data is required' },
24+
{ status: 500 }
25+
);
26+
}
27+
} catch (error) {
28+
return NextResponse.json({ error: error.message }, { status: 500 });
29+
}
30+
}
31+
32+
const fetchFormFields = async (readForm) => {
33+
const axios = require('axios');
34+
let fields = [];
35+
for (let i = 0; i < readForm.length; i++) {
36+
let fetchUrl = readForm[i].fetchUrl;
37+
let header = readForm[i].header;
38+
let config = {
39+
method: 'get',
40+
maxBodyLength: Infinity,
41+
url: fetchUrl,
42+
headers: {
43+
Accept: '*/*',
44+
...header,
45+
},
46+
};
47+
48+
await axios
49+
.request(config)
50+
.then((response) => {
51+
fields = [...fields, ...response?.data?.result?.fields];
52+
// console.log('response', response?.data);
53+
})
54+
.catch((error) => {
55+
console.log('error', error);
56+
});
57+
}
58+
// console.log('field!!!', fields);
59+
return fields;
60+
};
61+
62+
function normalizePattern(pattern) {
63+
if (typeof pattern === 'string') {
64+
return pattern.startsWith('/') && pattern.endsWith('/')
65+
? pattern.slice(1, -1)
66+
: pattern;
67+
}
68+
return pattern;
69+
}
70+
71+
function generateSchemaAndUISchema(fields) {
72+
const schema = {
73+
type: 'object',
74+
properties: {},
75+
};
76+
const uiSchema = {};
77+
78+
fields.forEach((field) => {
79+
const {
80+
name,
81+
hint,
82+
label,
83+
placeholder,
84+
coreField,
85+
fieldId,
86+
type,
87+
options,
88+
pattern,
89+
validation,
90+
maxLength,
91+
minLength,
92+
//custom field attributes
93+
api,
94+
extra,
95+
} = field;
96+
let isRequired = false;
97+
const schemaField = {
98+
type: 'string',
99+
title: label,
100+
coreField,
101+
fieldId,
102+
field_type: type,
103+
};
104+
105+
if (pattern) {
106+
schemaField.pattern = normalizePattern(pattern);
107+
}
108+
if (validation?.maxSelections) {
109+
schemaField.maxSelection = parseInt(validation.maxSelections, 10);
110+
}
111+
if (validation?.isMultiSelect) {
112+
schemaField.isMultiSelect = validation.isMultiSelect;
113+
schemaField.uniqueItems = validation.isMultiSelect;
114+
schemaField.type = 'array';
115+
}
116+
if (validation?.isRequired) {
117+
schemaField.isRequired = validation.isRequired;
118+
isRequired = validation.isRequired;
119+
}
120+
if (validation?.minValue || validation?.minValue == 0) {
121+
schemaField.minValue = validation.minValue;
122+
}
123+
if (validation?.maxValue) {
124+
schemaField.maxValue = validation.maxValue;
125+
}
126+
if (maxLength) {
127+
schemaField.maxLength = parseInt(maxLength, 10);
128+
}
129+
if (minLength) {
130+
schemaField.minLength = parseInt(minLength, 10);
131+
}
132+
if (isRequired) {
133+
schema.required = schema.required || [];
134+
schema.required.push(name);
135+
}
136+
// Handling UI Schema (including hint and placeholder)
137+
uiSchema[name] = {
138+
'ui:widget': 'text', // default widget
139+
};
140+
141+
if (placeholder) {
142+
uiSchema[name]['ui:placeholder'] = placeholder;
143+
}
144+
145+
if (hint) {
146+
uiSchema[name]['ui:help'] = hint;
147+
}
148+
149+
if (type === 'radio') {
150+
schemaField.enum = options?.map((opt) => opt.value);
151+
schemaField.enumNames = options?.map((opt) => opt.label);
152+
uiSchema[name] = {
153+
'ui:widget': 'CustomRadioWidget',
154+
'ui:options': {
155+
hideError: true, // ✅ hides automatic error rendering
156+
},
157+
};
158+
} else if (type === 'drop_down' || type === 'checkbox') {
159+
if (schemaField?.isMultiSelect === true) {
160+
schemaField.items = {
161+
type: 'string',
162+
enum: options?.map((opt) => opt.value) || ['Select'],
163+
enumNames: options?.map((opt) => opt.label) || ['Select'],
164+
};
165+
} else {
166+
schemaField.enum = options?.map((opt) => opt.value) || ['Select'];
167+
schemaField.enumNames = options?.map((opt) => opt.label) || ['Select'];
168+
}
169+
uiSchema[name] = {
170+
'ui:widget':
171+
type === 'checkbox'
172+
? //? 'checkboxes'
173+
'CustomCheckboxWidget'
174+
: schemaField?.isMultiSelect === true
175+
? 'CustomMultiSelectWidget'
176+
: 'CustomSingleSelectWidget',
177+
'ui:options': {
178+
multiple: schemaField?.isMultiSelect === true ? true : false,
179+
uniqueItems: schemaField?.isMultiSelect === true ? true : false,
180+
hideError: schemaField?.isMultiSelect === true ? false : true,
181+
},
182+
};
183+
} else if (type === 'date') {
184+
let minDateCount = 0;
185+
let maxDateCount = 100;
186+
if (schemaField?.minValue) {
187+
minDateCount = schemaField?.minValue;
188+
}
189+
if (schemaField?.minValue) {
190+
maxDateCount = schemaField?.maxValue;
191+
}
192+
193+
//set dates
194+
// Get current date
195+
const currentDate = new Date();
196+
197+
// Calculate min and max date range based on age limit
198+
const maxDate = new Date(
199+
currentDate.getFullYear() - minDateCount,
200+
currentDate.getMonth(),
201+
currentDate.getDate()
202+
)
203+
.toISOString()
204+
.split('T')[0]; // 18 years ago
205+
206+
const minDate = new Date(
207+
currentDate.getFullYear() - maxDateCount,
208+
currentDate.getMonth(),
209+
currentDate.getDate()
210+
)
211+
.toISOString()
212+
.split('T')[0]; // 50 years ago
213+
214+
schemaField.format = 'date';
215+
216+
uiSchema[name] = {
217+
'ui:widget': 'CustomDateWidget',
218+
'ui:options': {
219+
minValue: minDate, // Default minValue (optional)
220+
maxValue: maxDate, // Default maxValue (optional)
221+
hideError: true,
222+
},
223+
};
224+
} else if (type === 'dateTime') {
225+
schemaField.format = 'date-time';
226+
uiSchema[name] = { 'ui:widget': 'dateTime' };
227+
} else {
228+
uiSchema[name] = {
229+
'ui:widget': 'CustomTextFieldWidget',
230+
'ui:options': { validateOnBlur: true, hideError: true },
231+
};
232+
}
233+
234+
//Our custom RJSF field attributes
235+
if (api) {
236+
schemaField.api = api;
237+
if (schemaField?.isMultiSelect === true) {
238+
schemaField.items = {
239+
type: 'string',
240+
enum: ['Select'],
241+
enumNames: ['Select'],
242+
};
243+
} else {
244+
schemaField.enum = ['Select'];
245+
schemaField.enumNames = ['Select'];
246+
}
247+
}
248+
249+
if (extra) {
250+
schemaField.extra = extra;
251+
}
252+
253+
schema.properties[name] = schemaField;
254+
});
255+
256+
//form order schema
257+
let originalOrder = Object.keys(schema.properties);
258+
const finalUiOrder = reorderUiOrder(originalOrder, preferredOrder);
259+
uiSchema['ui:order'] = finalUiOrder;
260+
261+
return { schema, uiSchema };
262+
}
263+
264+
const reorderUiOrder = (originalOrder, preferredOrder) => {
265+
const preferredSet = new Set(preferredOrder);
266+
267+
const ordered = [
268+
...preferredOrder.filter((field) => originalOrder.includes(field)),
269+
...originalOrder.filter((field) => !preferredSet.has(field)),
270+
];
271+
272+
return ordered;
273+
};
274+
const preferredOrder = [
275+
'state',
276+
'district',
277+
'block',
278+
'village',
279+
'center',
280+
'parentId',
281+
'batch',
282+
'board',
283+
'medium',
284+
'grade',
285+
];

apps/learner-web-app/src/app/content-details/[identifier]/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
// pages/content-details/[identifier].tsx
22

3-
'use client';
43
import React from 'react';
54
import Layout from '../../../components/Layout';
65
// import { useRouter } from 'next/navigation';
76
import dynamic from 'next/dynamic';
87
import { gredientStyle } from '@learner/utils/style';
98
import { Box } from '@mui/material';
9+
import { getMetadata } from '@learner/utils/API/metabaseService';
1010

11+
export async function generateMetadata({ params }: any) {
12+
return await getMetadata(params.identifier);
13+
}
1114
const ContentEnrollDetails = dynamic(() => import('@ContentEnrollDetails'), {
1215
ssr: false,
1316
});

apps/learner-web-app/src/app/content/FilterComponent.tsx

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)