diff --git a/.gitignore b/.gitignore index a5d7b00..18d4021 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,8 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files .vscode/* -!.vscode/extensions.json -.idea +node_modules +**/.env +**/.env.* .DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# Terraform -**/.terraform/* -*.tfstate -*.tfstate.* \ No newline at end of file +.dapr +**/**/secrets.json +.prod \ No newline at end of file diff --git a/.mattrbld/collections/profile.json b/.mattrbld/collections/profile.json deleted file mode 100644 index f49abf5..0000000 --- a/.mattrbld/collections/profile.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "allowedTypes": null, - "dir": "/data/profile", - "disableComments": false, - "disablePreview": false, - "draftByDefault": false, - "schemas": [ - "/.mattrbld/schemas/Profile.json" - ], - "linkable": false, - "maxSize": null, - "permissions": { - "everybody": [ - "everything" - ] - }, - "type": "json", - "urlTemplate": "" -} \ No newline at end of file diff --git a/.mattrbld/collections/projects.json b/.mattrbld/collections/projects.json deleted file mode 100644 index 43f5909..0000000 --- a/.mattrbld/collections/projects.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "allowedTypes": null, - "dir": "/data/projects", - "disableComments": false, - "disablePreview": false, - "draftByDefault": false, - "schemas": [ - "/.mattrbld/schemas/Projects.json" - ], - "linkable": false, - "maxSize": null, - "permissions": { - "everybody": [ - "everything" - ] - }, - "type": "json", - "urlTemplate": "" -} \ No newline at end of file diff --git a/.mattrbld/config.json b/.mattrbld/config.json deleted file mode 100644 index b74dac6..0000000 --- a/.mattrbld/config.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "autoquotes": null, - "brandColors": [], - "corsProxy": "http://localhost:9999", - "name": "noahspan-root", - "sidebar": [ - { - "label": "The sidebar has not yet been configured for this project" - }, - { - "icon": "wrench-and-driver", - "label": "Configure now", - "target": { - "name": "Project.Settings", - "query": { - "tab": "sidebar" - } - }, - "onlyPrivileged": true - }, - { - "separator": true - }, - { - "label": "Dashboard", - "icon": "grid", - "target": { - "name": "Project" - }, - "protected": true - }, - { - "label": "Media Library", - "icon": "image-stack", - "target": { - "name": "Project.MediaLibrary" - }, - "protected": true - }, - { - "label": "Settings", - "icon": "settings", - "target": { - "name": "Project.Settings" - }, - "onlyPrivileged": true, - "protected": true - }, - { - "separator": true - }, - { - "icon": "user", - "label": "Profile", - "target": { - "name": "Project.Collection", - "params": { - "path": "/.mattrbld/collections/profile.json" - } - } - }, - { - "icon": "repo", - "label": "Projects", - "target": { - "name": "Project.Collection", - "params": { - "path": "/.mattrbld/collections/projects.json" - } - } - } - ], - "slugifyOptions": null, - "customRoles": [], - "draftsDir": null, - "previewUrl": null, - "languages": [], - "media": { - "advanced": false, - "customFields": null, - "dir": null, - "maxSize": null, - "outputPath": null, - "permissions": { - "everybody": [ - "everything" - ] - } - } -} \ No newline at end of file diff --git a/.mattrbld/schemas/Profile.json b/.mattrbld/schemas/Profile.json deleted file mode 100644 index ab71b4b..0000000 --- a/.mattrbld/schemas/Profile.json +++ /dev/null @@ -1,905 +0,0 @@ -{ - "fields": [ - { - "type": "rows", - "default": null, - "icon": "rows", - "key": "experiences", - "label": "Experiences", - "options": { - "compact": true, - "allowEditing": true, - "itemLabel": "Row" - }, - "tab": "Experiences", - "validation": { - "max": null, - "min": null, - "unit": "rows" - }, - "value": [ - { - "type": "group", - "displayField": null, - "icon": "group", - "key": "experience", - "label": "Experience", - "tab": "Experiences", - "value": [ - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "jobTitle", - "label": "JobTitle", - "localised": false, - "options": { - "wrapping": true, - "multiline": false - }, - "tab": "Experiences", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "company", - "label": "Company", - "localised": false, - "options": { - "wrapping": true, - "multiline": false - }, - "tab": "Experiences", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "location", - "label": "Location", - "localised": false, - "options": { - "wrapping": true, - "multiline": false - }, - "tab": "Experiences", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "startDate", - "label": "StartDate", - "localised": false, - "options": { - "wrapping": true, - "multiline": false - }, - "tab": "Experiences", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "endDate", - "label": "EndDate", - "localised": false, - "options": { - "wrapping": true, - "multiline": false - }, - "tab": "Experiences", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "rich text", - "default": null, - "icon": "text", - "key": "details", - "label": "Details", - "localised": false, - "options": { - "outputFormat": "markdown", - "blockFormats": [ - "blockquote", - "heading", - "hr", - "orderedList", - "unorderedList", - "image" - ], - "inlineFormats": [ - "em", - "strong", - "link" - ], - "formatOptions": [ - "allowQuoteFooters", - "allowNestedLists", - "allowImageCaptions" - ], - "inputRuleOptions": [ - "autoquotes", - "dashes", - "ellipsis", - "noDoubleCaps", - "noDoubleSpace" - ], - "minHeading": 2, - "maxHeading": 4, - "codeLangs": [ - "html", - "css", - "javascript", - "markdown" - ], - "allowRaw": false, - "urlTemplate": "", - "urlSuffix": "", - "linkOptions": [ - "forceBlankTarget", - "allowExternal", - "allowInternal" - ] - }, - "tab": "Experiences", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - } - ], - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - } - ], - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "rows", - "default": null, - "icon": "rows", - "key": "education", - "label": "Education", - "options": { - "compact": true, - "allowEditing": true, - "itemLabel": "Row" - }, - "tab": "Education", - "validation": { - "max": null, - "min": null, - "unit": "rows" - }, - "value": [ - { - "type": "group", - "displayField": null, - "icon": "group", - "key": "Education", - "label": "Education", - "tab": "Education", - "value": [ - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "school", - "label": "School", - "localised": false, - "options": { - "wrapping": true, - "multiline": false - }, - "tab": "Education", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "location", - "label": "Location", - "localised": false, - "options": { - "wrapping": true, - "multiline": false - }, - "tab": "Education", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "degree", - "label": "Degree", - "localised": false, - "options": { - "wrapping": true, - "multiline": false - }, - "tab": "Education", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "fieldOfStudy", - "label": "Field of Study", - "localised": false, - "options": { - "wrapping": true, - "multiline": false - }, - "tab": "Education", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - } - ], - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - } - ], - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "rich text", - "default": null, - "icon": "text", - "key": "details", - "label": "Details", - "localised": false, - "options": { - "outputFormat": "markdown", - "blockFormats": [ - "blockquote", - "heading", - "hr", - "orderedList", - "unorderedList", - "image" - ], - "inlineFormats": [ - "em", - "strong", - "link" - ], - "formatOptions": [ - "allowQuoteFooters", - "allowNestedLists", - "allowImageCaptions" - ], - "inputRuleOptions": [ - "autoquotes", - "dashes", - "ellipsis", - "noDoubleCaps", - "noDoubleSpace" - ], - "minHeading": 2, - "maxHeading": 4, - "codeLangs": [ - "html", - "css", - "javascript", - "markdown" - ], - "allowRaw": false, - "urlTemplate": "", - "urlSuffix": "", - "linkOptions": [ - "forceBlankTarget", - "allowExternal", - "allowInternal" - ] - }, - "tab": "About", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "firstName", - "label": "First Name", - "localised": false, - "options": { - "wrapping": false, - "multiline": false - }, - "tab": "Intro", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "lastName", - "label": "Last Name", - "localised": false, - "options": { - "wrapping": false, - "multiline": false - }, - "tab": "Intro", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "link", - "default": null, - "icon": "link", - "key": "photoUrl", - "label": "Photo URL", - "localised": false, - "options": { - "type": "external", - "byFilePath": false, - "urlSuffix": "/", - "urlTemplate": "" - }, - "tab": "Intro", - "validation": { - "required": false - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "title", - "label": "Title", - "localised": false, - "options": { - "wrapping": false, - "multiline": false - }, - "tab": "Intro", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "location", - "label": "Location", - "localised": false, - "options": { - "wrapping": false, - "multiline": false - }, - "tab": "Intro", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "rows", - "default": null, - "icon": "rows", - "key": "skills", - "label": "Skills", - "options": { - "compact": true, - "allowEditing": true, - "itemLabel": "Row" - }, - "tab": "Skills", - "validation": { - "max": null, - "min": null, - "unit": "rows" - }, - "value": [ - { - "type": "group", - "displayField": null, - "icon": "group", - "key": "skillGroup", - "label": "Skill Group", - "tab": "Skills", - "value": [ - { - "type": "select", - "default": null, - "icon": "dropdown", - "key": "category", - "label": "Category", - "options": { - "filterable": false, - "removable": false, - "placeholder": "Select a value…", - "options": [ - "Experienced", - "Familiar" - ] - }, - "tab": "Skills", - "validation": { - "required": false - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "select", - "default": null, - "icon": "dropdown", - "key": "group", - "label": "Group", - "options": { - "filterable": false, - "removable": false, - "placeholder": "Select a value…", - "options": [ - "Agile Methodologies", - "Cloud", - "Containerization", - "Continuous Integration and Continuous Delivery", - "Database", - "Front-End Web Development", - "Infrastructure as Code", - "Messaging", - "Programming Languages", - "Test Automation" - ] - }, - "tab": "Skills", - "validation": { - "required": false - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "rows", - "default": null, - "icon": "rows", - "key": "skills", - "label": "Skill", - "options": { - "compact": true, - "allowEditing": true, - "itemLabel": "Row" - }, - "tab": "Skills", - "validation": { - "max": null, - "min": null, - "unit": "rows" - }, - "value": [ - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "skill", - "label": "Skill", - "localised": false, - "options": { - "wrapping": true, - "multiline": false - }, - "tab": "Skills", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - } - ], - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - } - ], - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - } - ], - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "rich text", - "default": null, - "icon": "text", - "key": "summary", - "label": "Summary", - "localised": false, - "options": { - "outputFormat": "markdown", - "blockFormats": [ - "blockquote", - "heading", - "hr", - "orderedList", - "unorderedList", - "image" - ], - "inlineFormats": [ - "em", - "strong", - "link" - ], - "formatOptions": [ - "allowQuoteFooters", - "allowNestedLists", - "allowImageCaptions" - ], - "inputRuleOptions": [ - "autoquotes", - "dashes", - "ellipsis", - "noDoubleCaps", - "noDoubleSpace" - ], - "minHeading": 2, - "maxHeading": 4, - "codeLangs": [ - "html", - "css", - "javascript", - "markdown" - ], - "allowRaw": false, - "urlTemplate": "", - "urlSuffix": "", - "linkOptions": [ - "forceBlankTarget", - "allowExternal", - "allowInternal" - ] - }, - "tab": "About", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - } - ], - "tabs": [ - { - "label": "Intro", - "groupAs": "intro" - }, - { - "label": "About", - "groupAs": "about" - }, - { - "label": "Skills", - "groupAs": null - }, - { - "label": "Experiences", - "groupAs": null - }, - { - "label": "Education", - "groupAs": null - } - ] -} \ No newline at end of file diff --git a/.mattrbld/schemas/Projects.json b/.mattrbld/schemas/Projects.json deleted file mode 100644 index 55f3505..0000000 --- a/.mattrbld/schemas/Projects.json +++ /dev/null @@ -1,214 +0,0 @@ -{ - "fields": [ - { - "type": "rows", - "default": null, - "icon": "rows", - "key": "projects", - "label": "Projects", - "options": { - "compact": true, - "allowEditing": true, - "itemLabel": "Row" - }, - "tab": "Untitled Tab", - "validation": { - "max": null, - "min": null, - "unit": "rows" - }, - "value": [ - { - "type": "group", - "displayField": null, - "icon": "group", - "key": "project", - "label": "Project", - "tab": "Untitled Tab", - "value": [ - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "repoName", - "label": "Repo Name", - "localised": false, - "options": { - "wrapping": false, - "multiline": false - }, - "tab": "Untitled Tab", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "title", - "label": "Title", - "localised": false, - "options": { - "wrapping": true, - "multiline": false - }, - "tab": "Untitled Tab", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "text", - "default": null, - "icon": "text-input", - "key": "iconName", - "label": "Icon Name", - "localised": false, - "options": { - "wrapping": true, - "multiline": false - }, - "tab": "Untitled Tab", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - }, - { - "type": "rich text", - "default": null, - "icon": "text", - "key": "summary", - "label": "Summary", - "localised": false, - "options": { - "outputFormat": "markdown", - "blockFormats": [ - "blockquote", - "heading", - "hr", - "orderedList", - "unorderedList", - "image" - ], - "inlineFormats": [ - "em", - "strong", - "link" - ], - "formatOptions": [ - "allowQuoteFooters", - "allowNestedLists", - "allowImageCaptions" - ], - "inputRuleOptions": [ - "autoquotes", - "dashes", - "ellipsis", - "noDoubleCaps", - "noDoubleSpace" - ], - "minHeading": 2, - "maxHeading": 4, - "codeLangs": [ - "html", - "css", - "javascript", - "markdown" - ], - "allowRaw": false, - "urlTemplate": "", - "urlSuffix": "", - "linkOptions": [ - "forceBlankTarget", - "allowExternal", - "allowInternal" - ] - }, - "tab": "Untitled Tab", - "validation": { - "enforceMinMax": true, - "max": null, - "min": null, - "regex": null, - "regexError": null, - "required": false, - "unit": "length" - }, - "value": null, - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - } - ], - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - } - ], - "version": 1, - "visibility": { - "hidden": false, - "showByValue": { - "field": null - } - } - } - ], - "tabs": [ - { - "label": "Untitled Tab", - "groupAs": null - } - ] -} \ No newline at end of file diff --git a/.mattrbld/users/noahspannbauer-icloud-com.json b/.mattrbld/users/noahspannbauer-icloud-com.json deleted file mode 100644 index bb16983..0000000 --- a/.mattrbld/users/noahspannbauer-icloud-com.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "email": "noahspannbauer@icloud.com", - "id": "noahspannbauer-icloud-com", - "name": "Noah", - "role": "owner" -} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9c065b5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM node:18-slim AS base + +FROM base AS api +WORKDIR api +COPY ./.prod/api . +EXPOSE 3000 +CMD ["node", "dist/main.js"] + +FROM base AS app +WORKDIR /app +COPY ./.prod/app . +RUN npm i -g serve +EXPOSE 8080 +CMD [ "serve", "-s", "dist", "-p", "8080" ] \ No newline at end of file diff --git a/api/.funcignore b/api/.funcignore deleted file mode 100644 index 5179222..0000000 --- a/api/.funcignore +++ /dev/null @@ -1,7 +0,0 @@ -*.js.map -*.ts -.git* -.vscode -local.settings.json -test -tsconfig.json \ No newline at end of file diff --git a/api/host.json b/api/host.json deleted file mode 100644 index d2059a4..0000000 --- a/api/host.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "version": "2.0" -} diff --git a/api/local.settings.json b/api/local.settings.json deleted file mode 100644 index 3603ddd..0000000 --- a/api/local.settings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "", - "AZURE_STORAGE_CONNECTION_STRING": "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;TableEndpoint=http://localhost:10002/devstoreaccount1;", - "FUNCTIONS_WORKER_RUNTIME": "node", - "GITHUB_API_USERNAME": "noahspannbauer", - "GITHUB_API_PAT": "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" - }, - "Host": { - "CORS": "*" - } -} diff --git a/api/main/function.json b/api/main/function.json deleted file mode 100644 index 1e5c89e..0000000 --- a/api/main/function.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "route": "{*segments}" - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ], - "scriptFile": "../dist/main/index.js" -} diff --git a/api/main/index.ts b/api/main/index.ts deleted file mode 100644 index 71f010c..0000000 --- a/api/main/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Context, HttpRequest } from '@azure/functions'; -import { AzureHttpAdapter } from '@nestjs/azure-func-http'; -import { createApp } from '../src/main.azure'; - -export default function(context: Context, req: HttpRequest): void { - AzureHttpAdapter.handle(createApp, context, req); -} diff --git a/api/main/sample.dat b/api/main/sample.dat deleted file mode 100644 index 26aac46..0000000 --- a/api/main/sample.dat +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "Azure" -} \ No newline at end of file diff --git a/api/package.json b/api/package.json index 7037a2f..81f9157 100644 --- a/api/package.json +++ b/api/package.json @@ -17,8 +17,7 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json", - "start:azure": "npm run build && func host start" + "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@azure/functions": "^1.0.3", diff --git a/api/proxies.json b/api/proxies.json deleted file mode 100644 index b385252..0000000 --- a/api/proxies.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/proxies", - "proxies": {} -} diff --git a/api/src/app.module.ts b/api/src/app.module.ts index 115ceb1..0e4d247 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -2,10 +2,12 @@ import { Module } from '@nestjs/common'; import { GitHubApiModule } from './github/github.module'; import { APP_FILTER } from '@nestjs/core'; import { HttpExceptionFilter } from './filters/http-exception.filter'; +import { ProjectModule } from './project/project.module'; @Module({ imports: [ - GitHubApiModule + GitHubApiModule, + ProjectModule ], providers: [ { diff --git a/api/src/github/github-content.interface.ts b/api/src/github/github-content.interface.ts deleted file mode 100644 index e62b49e..0000000 --- a/api/src/github/github-content.interface.ts +++ /dev/null @@ -1,20 +0,0 @@ -export interface Content { - name: string - path: string - sha: string - size: number - url: string - html_url: string - git_url: string - download_url: string - type: string - content: string - encoding: string - _links: Links -} - -interface Links { - self: string - git: string - html: string -} \ No newline at end of file diff --git a/api/src/github/github-repo.interface.ts b/api/src/github/github-repo.interface.ts deleted file mode 100644 index 2e56c11..0000000 --- a/api/src/github/github-repo.interface.ts +++ /dev/null @@ -1,120 +0,0 @@ -export interface Repo { - id: number - node_id: string - name: string - full_name: string - private: boolean - owner: Owner - html_url: string - description?: string - fork: boolean - url: string - forks_url: string - keys_url: string - collaborators_url: string - teams_url: string - hooks_url: string - issue_events_url: string - events_url: string - assignees_url: string - branches_url: string - tags_url: string - blobs_url: string - git_tags_url: string - git_refs_url: string - trees_url: string - statuses_url: string - languages_url: string - stargazers_url: string - contributors_url: string - subscribers_url: string - subscription_url: string - commits_url: string - git_commits_url: string - comments_url: string - issue_comment_url: string - contents_url: string - compare_url: string - merges_url: string - archive_url: string - downloads_url: string - issues_url: string - pulls_url: string - milestones_url: string - notifications_url: string - labels_url: string - releases_url: string - deployments_url: string - created_at: string - updated_at: string - pushed_at: string - git_url: string - ssh_url: string - clone_url: string - svn_url: string - homepage?: string - size: number - stargazers_count: number - watchers_count: number - language?: string - has_issues: boolean - has_projects: boolean - has_downloads: boolean - has_wiki: boolean - has_pages: boolean - has_discussions: boolean - forks_count: number - mirror_url: any - archived: boolean - disabled: boolean - open_issues_count: number - license?: License - allow_forking: boolean - is_template: boolean - web_commit_signoff_required: boolean - topics: any[] - visibility: string - forks: number - open_issues: number - watchers: number - default_branch: string - permissions: Permissions -} - -interface Owner { - login: string - id: number - node_id: string - avatar_url: string - gravatar_id: string - url: string - html_url: string - followers_url: string - following_url: string - gists_url: string - starred_url: string - subscriptions_url: string - organizations_url: string - repos_url: string - events_url: string - received_events_url: string - type: string - user_view_type: string - site_admin: boolean -} - -interface License { - key: string - name: string - spdx_id: string - url: string - node_id: string -} - -interface Permissions { - admin: boolean - maintain: boolean - push: boolean - triage: boolean - pull: boolean -} diff --git a/api/src/github/github.controller.ts b/api/src/github/github.controller.ts deleted file mode 100644 index 40721d1..0000000 --- a/api/src/github/github.controller.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Controller, Get, HttpException, Query } from '@nestjs/common'; -import { GitHubApiService } from './github.service'; -import { AxiosError } from 'axios' -import { Content } from './github-content.interface'; -import { Repo } from './github-repo.interface'; - -@Controller('github') -export class GitHubApiController { - constructor(private readonly githubApiService: GitHubApiService) {} - - @Get('readme') - async getReadMe(@Query() query: any): Promise { - try { - const repoName = query['repoName']; - - return await this.githubApiService.getReadMeContent(repoName) - } catch (error) { - const axiosError = error as AxiosError; - - throw new HttpException(axiosError.message, axiosError.status) - } - } - - @Get('repos') - async getRepos(): Promise { - try { - return await this.githubApiService.getRepos(); - } catch (error) { - const axiosError = error as AxiosError - - throw new HttpException(axiosError.message, axiosError.status) - } - } -} - diff --git a/api/src/github/github.module.ts b/api/src/github/github.module.ts deleted file mode 100644 index f050b17..0000000 --- a/api/src/github/github.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Module } from '@nestjs/common'; -import { GitHubApiController } from './github.controller'; -import { GitHubApiService } from './github.service'; -import { ConfigModule } from '@nestjs/config'; -import configuration from '../config/configuration'; -import { HttpModule } from '@nestjs/axios'; - -@Module({ - imports: [ - ConfigModule.forRoot({ - load: [configuration] - }), - HttpModule - ], - controllers: [GitHubApiController], - providers: [GitHubApiService], -}) -export class GitHubApiModule {} diff --git a/api/src/github/github.service.ts b/api/src/github/github.service.ts deleted file mode 100644 index 8c68fa0..0000000 --- a/api/src/github/github.service.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { HttpService } from '@nestjs/axios'; -import { AxiosResponse } from 'axios'; -import { Repo } from './github-repo.interface'; -import { Content } from './github-content.interface'; - -@Injectable() -export class GitHubApiService { - constructor( - private readonly configService: ConfigService, - private readonly httpService: HttpService - ) {} - - async getReadMeContent(repoName: string): Promise { - const githubApiUsername = this.configService.get('githubApiUsername') - const response: AxiosResponse = await this.httpService.axiosRef.get(`https://api.github.com/repos/${githubApiUsername}/${repoName}/contents/README.md`, { - auth: { - username: this.configService.get('githubApiUsername'), - password: this.configService.get('githubApiPassword') - } - }) - const content: Content = response.data - - return content; - } - - async getRepos(): Promise { - const response: AxiosResponse = await this.httpService.axiosRef.get('https://api.github.com/user/repos', { - auth: { - username: this.configService.get('githubApiUsername'), - password: this.configService.get('githubApiPassword') - } - }) - const repos: Repo[] = response.data - - return repos; - } -} diff --git a/api/src/main.azure.ts b/api/src/main.azure.ts deleted file mode 100644 index b0a02ed..0000000 --- a/api/src/main.azure.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { INestApplication, InternalServerErrorException } from '@nestjs/common'; -import { NestFactory } from '@nestjs/core'; -import { AppModule } from './app.module'; -import { HttpService } from '@nestjs/axios' -import { HttpExceptionFilter } from './filters/http-exception.filter'; - -export async function createApp(): Promise { - const httpService = new HttpService(); - const app = await NestFactory.create(AppModule); - - app.setGlobalPrefix('api'); - app.useGlobalFilters(new HttpExceptionFilter()); - httpService.axiosRef.interceptors.response.use( - (response) => { - return response; - }, - (error) => { - console.error('Internal server error exception', error); - - throw new InternalServerErrorException(); - } - ); - - await app.init(); - return app; -} diff --git a/api/src/main.ts b/api/src/main.ts index 3541ee6..50361e2 100644 --- a/api/src/main.ts +++ b/api/src/main.ts @@ -1,8 +1,26 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; +import { HttpService } from '@nestjs/axios'; +import { HttpExceptionFilter } from './filters/http-exception.filter'; +import { InternalServerErrorException } from '@nestjs/common'; async function bootstrap() { + const httpService = new HttpService(); const app = await NestFactory.create(AppModule); + + app.enableCors(); + app.setGlobalPrefix('api'); + app.useGlobalFilters(new HttpExceptionFilter()); + httpService.axiosRef.interceptors.response.use( + (response) => { + return response; + }, + (error) => { + console.error('Internal server error exception', error); + + throw new InternalServerErrorException(); + } + ); await app.listen(3000); } diff --git a/api/src/project/project.controller.ts b/api/src/project/project.controller.ts new file mode 100644 index 0000000..13d8b15 --- /dev/null +++ b/api/src/project/project.controller.ts @@ -0,0 +1,92 @@ +import { + Body, + Controller, + Delete, + Get, + HttpException, + Param, + Post, + Put +} from '@nestjs/common'; +import { ProjectDto } from './project.dto'; +import { Project } from './project.entity'; +import { ProjectService } from './project.service'; +import { CustomError } from '@noahspan/noahspan-modules'; + +@Controller('projects') +export class ProjectController { + constructor(private readonly projectService: ProjectService) {} + + @Get(':partitionKey/:rowKey') + async find( + @Param('partitionKey') partitionKey: string, + @Param('rowKey') rowKey: string + ) { + try { + return await this.projectService.find(partitionKey, rowKey); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + @Get() + async findAll() { + try { + return await this.projectService.findAll(); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + @Post() + async create(@Body() projectDto: ProjectDto) { + try { + const project = new Project(); + + Object.assign(project, projectDto); + + return await this.projectService.create(project); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + @Put(':partitionKey/:rowKey') + async update( + @Param('partitionKey') partitionKey: string, + @Param('rowKey') rowKey: string, + @Body() projectDto: ProjectDto + ) { + try { + const project = new Project(); + + Object.assign(project, projectDto); + + return await this.projectService.update(partitionKey, rowKey, project); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + @Delete(':partitionKey/:rowKey') + async delete( + @Param('partitionKey') partitionKey: string, + @Param('rowKey') rowKey: string + ) { + try { + return await this.projectService.delete(partitionKey, rowKey); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } +} diff --git a/api/src/project/project.dto.ts b/api/src/project/project.dto.ts new file mode 100644 index 0000000..e2ad22d --- /dev/null +++ b/api/src/project/project.dto.ts @@ -0,0 +1,6 @@ +export class ProjectDto { + rowKey: string; + displayName: string; + icon: string; + summary: string; +} \ No newline at end of file diff --git a/api/src/project/project.entity.ts b/api/src/project/project.entity.ts new file mode 100644 index 0000000..d22b3f3 --- /dev/null +++ b/api/src/project/project.entity.ts @@ -0,0 +1,7 @@ +export class Project { + partitionKey: string; + rowKey: string; + displayName: string; + icon: string; + summary: string; +} \ No newline at end of file diff --git a/api/src/project/project.module.ts b/api/src/project/project.module.ts new file mode 100644 index 0000000..0fb7515 --- /dev/null +++ b/api/src/project/project.module.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; +import { ProjectController } from './project.controller'; +import { ProjectService } from './project.service'; +import { AzureTableStorageModule } from '@noahspan/azure-database'; +import { Project } from './project.entity'; +import { ConfigModule, ConfigService } from '@nestjs/config'; + +@Module({ + imports: [ + AzureTableStorageModule.forRootAsync({ + imports: [ConfigModule], + useFactory: async (configService: ConfigService) => { + return { + connectionString: configService.get('azureStorageConnectionString') + }; + }, + inject: [ConfigService] + }), + AzureTableStorageModule.forFeature(Project, { + createTableIfNotExists: true, + table: 'projects' + }), + ], + controllers: [ProjectController], + providers: [ProjectService] +}) +export class ProjectModule {} diff --git a/api/src/project/project.service.ts b/api/src/project/project.service.ts new file mode 100644 index 0000000..334888d --- /dev/null +++ b/api/src/project/project.service.ts @@ -0,0 +1,38 @@ +import { InjectRepository, Repository } from '@noahspan/azure-database'; +import { Injectable } from '@nestjs/common'; +import { Project } from './project.entity'; +import { v4 as uuidv4 } from 'uuid'; + +@Injectable() +export class ProjectService { + constructor( + @InjectRepository(Project) + private readonly projectRepository: Repository + ) {} + + async find(partitionKey: string, rowKey: string): Promise { + return await this.projectRepository.find(partitionKey, rowKey); + } + + async findAll(): Promise { + return await this.projectRepository.findAll(); + } + + async create(project: Project): Promise { + project.partitionKey = 'project'; + + return await this.projectRepository.create(project); + } + + async update( + partitionKey: string, + rowKey: string, + project: Project + ): Promise { + return await this.projectRepository.update(partitionKey, rowKey, project); + } + + async delete(partitionKey: string, rowKey: string): Promise { + await this.projectRepository.delete(partitionKey, rowKey); + } +} diff --git a/app/src/App.css b/app/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/app/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/app/src/App.tsx b/app/src/App.tsx deleted file mode 100644 index 3d7ded3..0000000 --- a/app/src/App.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' - -function App() { - const [count, setCount] = useState(0) - - return ( - <> - -

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ) -} - -export default App diff --git a/app/src/components/siteNav/SiteNav.tsx b/app/src/components/siteNav/SiteNav.tsx deleted file mode 100644 index 0c892d6..0000000 --- a/app/src/components/siteNav/SiteNav.tsx +++ /dev/null @@ -1,140 +0,0 @@ -// import { useEffect, useState } from 'react'; -// import { ISiteNavProps } from './ISiteNavProps'; -// import { useNavigate } from 'react-router-dom'; -// import { useAppContext } from '../../hooks/appContext/UseAppContext'; -// import { -// Icon, -// MenuItem, -// Navbar, -// SignOutIcon, -// Spinner, -// Typography -// } from '@noahspan/noahspan-components'; -// import { useIsAuthenticated, useMsal } from '@azure/msal-react'; -// import { InteractionStatus } from '@azure/msal-browser'; -// import { useAccessToken } from '../../hooks/accessToken/UseAcessToken'; -// import { useHttpClient } from '../../hooks/httpClient/UseHttpClient'; -// import { AxiosInstance, AxiosResponse } from 'axios'; -// import { User } from '@microsoft/microsoft-graph-types'; -// import { EventMessage, EventPayload, EventType } from '@azure/msal-browser'; - -// type EventPayloadExtended = EventPayload & { accessToken: string }; - -// const SiteNav: React.FC = () => { -// const [loading, setLoading] = useState(false); -// const [userPhoto, setUserPhoto] = useState(); -// const httpClient: AxiosInstance = useHttpClient(); -// const appContext = useAppContext(); -// const isAuthenticated = useIsAuthenticated(); -// const { getAccessToken } = useAccessToken(); -// const { instance } = useMsal(); -// const navigate = useNavigate(); -// const pages = [ -// { -// name: 'Logbook', -// url: '/' -// }, -// { -// name: 'Pilots', -// url: '/pilots' -// } -// ]; -// const handleSignIn = () => { -// instance.loginRedirect({ -// scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`] -// }); -// }; -// const handleSignOut = () => { -// instance.logoutRedirect(); -// }; -// const getUserProfile = async (accessToken: string): Promise => { -// try { -// const response: AxiosResponse = await httpClient.get(`api/userProfile`, { -// headers: { -// Authorization: accessToken -// } -// }); -// const userProfile: User = response.data; - -// return userProfile; -// } catch (error) { -// throw new Error(); -// } -// }; -// const getUserPhoto = async (accessToken: string): Promise => { -// try { -// const response: AxiosResponse = await httpClient.get(`api/userPhoto`, { -// headers: { -// Authorization: accessToken -// }, -// responseType: 'arraybuffer' -// }); -// const arrayBufferView = new Uint8Array(response.data); -// const blob = new Blob([arrayBufferView], { type: 'image/png' }); -// const imageUrl = window.URL.createObjectURL(blob); - -// return imageUrl; -// } catch (error) { -// console.log(error); -// throw new Error(); -// } -// }; -// const handlePageClick = (url: string) => { -// navigate(url); -// }; - -// const Settings = () => { -// return ( -// -// -// -// Sign Out -// -// -// ); -// }; - -// useEffect(() => { -// const setUserProfile = async () => { -// try { -// setLoading(true); - -// const accessToken: string = await getAccessToken(); -// const userProfile = await getUserProfile(accessToken); -// const userPhoto = await getUserPhoto(accessToken); - -// setUserPhoto(userPhoto); - -// appContext.dispatch({ -// type: 'SET_USER_PROFILE', -// payload: userProfile -// }); -// } catch (error) { -// console.log(error); -// } finally { -// setLoading(false); -// } -// }; - -// if ( -// isAuthenticated && -// Object.keys(appContext.state.userProfile).length === 0 -// ) { -// setUserProfile(); -// } -// }, [isAuthenticated]); - -// return ( -// } -// pages={pages} -// settings={} -// userPhoto={userPhoto} -// /> -// ); -// }; - -// export default SiteNav;å \ No newline at end of file diff --git a/app/src/index.css b/app/src/index.css deleted file mode 100644 index 6119ad9..0000000 --- a/app/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/app/src/main.tsx b/app/src/main.tsx deleted file mode 100644 index 32938e4..0000000 --- a/app/src/main.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' -import { BrowserRouter } from 'react-router-dom' - -createRoot(document.getElementById('root')!).render( - - - - - , -) diff --git a/app/src/vite-env.d.ts b/app/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/app/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/app/vite.config.ts b/app/vite.config.ts deleted file mode 100644 index 8b0f57b..0000000 --- a/app/vite.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [react()], -}) diff --git a/app/.gitignore b/app2/.gitignore similarity index 100% rename from app/.gitignore rename to app2/.gitignore diff --git a/app/README.md b/app2/README.md similarity index 100% rename from app/README.md rename to app2/README.md diff --git a/app/eslint.config.js b/app2/eslint.config.js similarity index 100% rename from app/eslint.config.js rename to app2/eslint.config.js diff --git a/app/index.html b/app2/index.html similarity index 89% rename from app/index.html rename to app2/index.html index e4b78ea..77fc50f 100644 --- a/app/index.html +++ b/app2/index.html @@ -4,7 +4,7 @@ - Vite + React + TS + Noah Spannbauer
diff --git a/app/package.json b/app2/package.json similarity index 89% rename from app/package.json rename to app2/package.json index 630be3c..a319b4d 100644 --- a/app/package.json +++ b/app2/package.json @@ -1,5 +1,5 @@ { - "name": "app", + "name": "app2", "private": true, "version": "0.0.0", "type": "module", @@ -12,15 +12,16 @@ "dependencies": { "@azure/msal-browser": "^4.2.1", "@azure/msal-react": "^3.0.4", - "@noahspan/noahspan-components": "^1.0.0", + "@microsoft/microsoft-graph-types": "^2.40.0", + "@noahspan/noahspan-components": "^1.3.1", "axios": "^1.7.9", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-hook-form": "^7.54.2", "react-router-dom": "^7.1.5" }, "devDependencies": { "@eslint/js": "^9.19.0", - "@microsoft/microsoft-graph-types": "^2.40.0", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", diff --git a/app2/public/noahspan-logo.png b/app2/public/noahspan-logo.png new file mode 100644 index 0000000..c4cce0c Binary files /dev/null and b/app2/public/noahspan-logo.png differ diff --git a/app/public/vite.svg b/app2/public/vite.svg similarity index 100% rename from app/public/vite.svg rename to app2/public/vite.svg diff --git a/app2/src/App.tsx b/app2/src/App.tsx new file mode 100644 index 0000000..a351b1b --- /dev/null +++ b/app2/src/App.tsx @@ -0,0 +1,25 @@ +import { Route, Routes } from 'react-router-dom'; +import Projects from './components/projects/Projects'; +import SiteNav from './components/siteNav/SiteNav'; +import { useAppContext } from './hooks/appContext/UseAppContext'; +import { IPublicClientApplication } from '@azure/msal-browser'; +import { MsalProvider } from '@azure/msal-react'; + +interface AppProps { + pca: IPublicClientApplication +} + +const App = ({ pca }: AppProps) => { + const appContext = useAppContext(); + + return ( + + + + } /> + + + ) +} + +export default App diff --git a/app/src/assets/react.svg b/app2/src/assets/react.svg similarity index 100% rename from app/src/assets/react.svg rename to app2/src/assets/react.svg diff --git a/app2/src/auth/msalConfig.ts b/app2/src/auth/msalConfig.ts new file mode 100644 index 0000000..24c7b80 --- /dev/null +++ b/app2/src/auth/msalConfig.ts @@ -0,0 +1,70 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { LogLevel } from '@azure/msal-browser'; + +/** + * Configuration object to be passed to MSAL instance on creation. + * For a full list of MSAL.js configuration parameters, visit: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md + */ + +export const msalConfig = { + auth: { + clientId: import.meta.env.VITE_CLIENT_ID, // This is the ONLY mandatory field that you need to supply. + authority: `https://login.microsoftonline.com/${import.meta.env.VITE_TENANT_ID}`, // Replace the placeholder with your tenant subdomain + redirectUri: import.meta.env.VITE_REDIRECT_URL, // Points to window.location.origin. You must register this URI on Microsoft Entra admin center/App Registration. + postLogoutRedirectUri: '/', // Indicates the page to navigate after logout. + navigateToLoginRequestUrl: false, // If "true", will navigate back to the original request location before processing the auth code response. + }, + cache: { + cacheLocation: 'sessionStorage', // Configures cache location. "sessionStorage" is more secure, but "localStorage" gives you SSO between tabs. + storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge + }, + system: { + loggerOptions: { + loggerCallback: (level: any, message: any, containsPii: any) => { + if (containsPii) { + return; + } + switch (level) { + case LogLevel.Error: + console.error(message); + return; + case LogLevel.Info: + console.info(message); + return; + case LogLevel.Verbose: + console.debug(message); + return; + case LogLevel.Warning: + console.warn(message); + return; + default: + return; + } + }, + }, + }, +}; + +/** + * Scopes you add here will be prompted for user consent during sign-in. + * By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request. + * For more information about OIDC scopes, visit: + * https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes + */ +export const loginRequest = { + scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`], +}; + +/** + * An optional silentRequest object can be used to achieve silent SSO + * between applications by providing a "login_hint" property. + */ +// export const silentRequest = { +// scopes: ["openid", "profile"], +// loginHint: "example@domain.net" +// }; \ No newline at end of file diff --git a/app2/src/components/actionMenu/ActionMenu.tsx b/app2/src/components/actionMenu/ActionMenu.tsx new file mode 100644 index 0000000..c411075 --- /dev/null +++ b/app2/src/components/actionMenu/ActionMenu.tsx @@ -0,0 +1,61 @@ +import { useState } from 'react'; +import { ActionMenuProps } from './ActionMenuProps.interface'; +import { + Icon, + IconButton, + ListItemIcon, + ListItemText, + Menu, + MenuItem, +} from '@noahspan/noahspan-components'; +import { FormMode } from '../../enums/formMode'; + +const ActionMenu = ({ id, onDelete, onOpenCloseForm }: ActionMenuProps) => { + const [anchorElAction, setAnchorElAction] = useState( + null + ); + + const onOpenActionMenu = (event: React.MouseEvent) => { + setAnchorElAction(event.currentTarget); + }; + + const onCloseActionMenu = () => { + setAnchorElAction(null); + }; + + return ( +
+ + + + + onOpenCloseForm(FormMode.EDIT, id)}> + + + + Edit + + onOpenCloseForm(FormMode.VIEW, id)}> + + + + View + +
+ onDelete(id)}> + + + + Delete + +
+
+ ); +}; + +export default ActionMenu; diff --git a/app2/src/components/actionMenu/ActionMenuProps.interface.tsx b/app2/src/components/actionMenu/ActionMenuProps.interface.tsx new file mode 100644 index 0000000..98309a4 --- /dev/null +++ b/app2/src/components/actionMenu/ActionMenuProps.interface.tsx @@ -0,0 +1,7 @@ +import { FormMode } from '../../enums/formMode'; + +export interface ActionMenuProps { + id: string; + onDelete: (entryId: string) => void; + onOpenCloseForm: (formMode: FormMode, id: string) => void; +} diff --git a/app2/src/components/confirmationDialog/ConfirmationDialog.tsx b/app2/src/components/confirmationDialog/ConfirmationDialog.tsx new file mode 100644 index 0000000..cf0b008 --- /dev/null +++ b/app2/src/components/confirmationDialog/ConfirmationDialog.tsx @@ -0,0 +1,51 @@ +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Icon, + Spinner +} from '@noahspan/noahspan-components'; +import { ConfirmationDialogProps } from './ConfirmationDialogProps.interface'; + +const ConfirmationDialog = ({ + contentText, + isLoading, + isOpen, + onCancel, + onConfirm, + title +}: ConfirmationDialogProps) => { + return ( + + {title} + + {!isLoading && {contentText}} + {isLoading && } + + + + + + + ); +}; + +export default ConfirmationDialog; diff --git a/app2/src/components/confirmationDialog/ConfirmationDialogProps.interface.ts b/app2/src/components/confirmationDialog/ConfirmationDialogProps.interface.ts new file mode 100644 index 0000000..af258d3 --- /dev/null +++ b/app2/src/components/confirmationDialog/ConfirmationDialogProps.interface.ts @@ -0,0 +1,8 @@ +export interface ConfirmationDialogProps { + contentText: string; + isLoading: boolean; + isOpen: boolean; + onCancel: () => void; + onConfirm: () => void; + title: string; +} diff --git a/app2/src/components/profile/Profile.tsx b/app2/src/components/profile/Profile.tsx new file mode 100644 index 0000000..f533aa0 --- /dev/null +++ b/app2/src/components/profile/Profile.tsx @@ -0,0 +1,108 @@ +// import { +// Box, +// Container, +// Grid, +// Typography +// } from '@noahspan/noahspan-components'; +// import profile from '../../../data/profile/profile.json'; + +// const getSkills = (category: string) => { +// let skillsString: string = ''; + +// for (const skill of profile.skills) { +// console.log(skill) +// if (skill.category === category) { +// skillsString += `${skill.group} (` +// console.log(skillsString) +// for (const skillName of skill.skills) { +// skillsString += `${skillName}, ` +// } + +// skillsString += '), '; +// } +// } +// console.log(skillsString) +// return skillsString +// } + +// const Profile = () => { +// return ( +// +// +// +// +// {`${profile.intro.firstName} ${profile.intro.lastName}`} +// +// +// {profile.intro.title} +// +// +// {profile.intro.location} +// +// +// Summary +// +// +// {profile.about.details} +// +// +// Skills +// +// +// Experienced +// +// +// {getSkills('Experienced')} +// +// +// Familiar +// +// +// {getSkills('Familiar')} +// +// +// Experience +// +// {profile.experiences.length > 0 && profile.experiences.map((experience) => { +// return ( +// <> +// +// {experience.jobTitle} +// +// +// {`${experience.startDate}-${experience.endDate}`} +// +// +// {experience.company} +// +// +// {experience.location} +// +// +// ) +// })} +// {profile.experiences.length > 0 && profile.education.map((education) => { +// return ( +// <> +// +// {education.school} +// +// +// {education.location} +// +// +// {education.degree} +// +// +// {education.fieldOfStudy} +// +// +// ) +// })} +// +// +// +// ) +// } + +// export default Profile; \ No newline at end of file diff --git a/app2/src/components/projectForm/ProjectForm.tsx b/app2/src/components/projectForm/ProjectForm.tsx new file mode 100644 index 0000000..32a700f --- /dev/null +++ b/app2/src/components/projectForm/ProjectForm.tsx @@ -0,0 +1,354 @@ +import { useEffect, useReducer } from 'react'; +import { + Alert, + Button, + Drawer, + Grid, + Icon, + IconButton, + IconName, + TextField, + Typography +} from '@noahspan/noahspan-components'; +import { useForm, Controller, FormProvider } from 'react-hook-form'; +import { initialState, reducer } from './reducer'; +import { ProjectFormProps } from './ProjectFormProps.interface'; +import { FormMode } from '../../enums/formMode'; +import { useIsAuthenticated } from '@azure/msal-react'; +import { useAccessToken } from '../../hooks/accessToken/UseAccessToken'; +import { useHttpClient } from '../../hooks/httpClient/UseHttpClient'; +import { AxiosError, AxiosInstance, AxiosResponse } from 'axios'; + +const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectFormProps) => { + const [state, dispatch] = useReducer(reducer, initialState); + const httpClient: AxiosInstance = useHttpClient(); + const { getAccessToken } = useAccessToken(); + const isAuthenticated = useIsAuthenticated(); + const defaultValues = { + partitionKey: 'project', + rowKey: '', + title: '', + subTitle: '', + iconName: '', + summary: '', + repoName: '', + siteUrl: '', + order: '' + } + const methods = useForm({ + defaultValues: defaultValues + }); + const watchRepoName = methods.watch(['repoName']) + + const onCancel = () => { + methods.reset(defaultValues); + dispatch({ type: 'SET_IS_DISABLED', payload: false }); + onOpenClose(FormMode.CANCEL); + }; + + const onSubmit = async (data: unknown) => { + console.log(data) + try { + dispatch({ type: 'SET_IS_LOADING', payload: true }); + + const accessToken: string = await getAccessToken(); + + if (!projectId) { + await httpClient.post(`api/projects`, data, { + headers: { + Authorization: accessToken + } + }); + } else { + await httpClient.put(`api/projects/project/${projectId}`, data, { + headers: { + Authorization: accessToken + } + }); + } + + methods.reset(defaultValues); + dispatch({ type: 'SET_IS_DISABLED', payload: false }); + onOpenClose(FormMode.CANCEL); + } catch (error) { + const axiosError = error as AxiosError; + + dispatch({ type: 'SET_ERROR', payload: axiosError.message }); + } finally { + dispatch({ type: 'SET_IS_LOADING', payload: false }); + } + }; + + useEffect(() => { + if (watchRepoName.length > 0) { + methods.setValue('rowKey', watchRepoName[0]) + } + }, [watchRepoName]) + + useEffect(() => { + if (mode === FormMode.VIEW) { + dispatch({ type: 'SET_IS_DISABLED', payload: true }); + } + }, [mode]); + + useEffect(() => { + const getProject = async () => { + try { + dispatch({ type: 'SET_IS_LOADING', payload: true }); + + const config = isAuthenticated + ? { headers: { Authorization: await getAccessToken() } } + : {}; + const response: AxiosResponse = await httpClient.get( + `api/projects/project/${projectId}`, + config + ); + const entry = response.data; + + methods.reset(entry); + } catch (error) { + const axiosError = error as AxiosError; + + dispatch({ type: 'SET_ERROR', payload: axiosError.message }); + } finally { + dispatch({ type: 'SET_IS_LOADING', payload: false }); + } + }; + + if (projectId && isDrawerOpen) { + getProject(); + } + }, [projectId]); + + return ( + + +
+ + + {`${mode.toString().toLowerCase().charAt(0).toUpperCase() + mode.toString().slice(1).toLowerCase()} Project`} + + + + + + + {state.error && ( + + + dispatch({ type: 'SET_ERROR', payload: undefined }) + } + severity="error" + sx={{ width: '100%' }} + > + {state.error} + + + )} + + Order + + + ( + + )} + /> + + + Title + + + ( + + )} + /> + + + Sub Title + + + ( + + )} + /> + + + Icon Name + + + ( + + )} + /> + + + Repo Name + + + ( + + )} + /> + + + Site URL + + + ( + + )} + /> + + + Summary + + + ( + + )} + /> + + + + {mode.toString() !== FormMode.VIEW && ( + + )} + + +
+
+
+ ) +} + +export default ProjectForm; \ No newline at end of file diff --git a/app2/src/components/projectForm/ProjectFormProps.interface.ts b/app2/src/components/projectForm/ProjectFormProps.interface.ts new file mode 100644 index 0000000..9af6324 --- /dev/null +++ b/app2/src/components/projectForm/ProjectFormProps.interface.ts @@ -0,0 +1,8 @@ +import { FormMode } from "../../enums/formMode"; + +export interface ProjectFormProps { + projectId?: string; + isDrawerOpen: boolean; + mode: FormMode; + onOpenClose: (mode: FormMode) => void; +} \ No newline at end of file diff --git a/app2/src/components/projectForm/ProjectFormState.interface.ts b/app2/src/components/projectForm/ProjectFormState.interface.ts new file mode 100644 index 0000000..78666d8 --- /dev/null +++ b/app2/src/components/projectForm/ProjectFormState.interface.ts @@ -0,0 +1,5 @@ +export interface ProjectFormState { + error: string | undefined; + isDisabled: boolean; + isLoading: boolean; +} \ No newline at end of file diff --git a/app2/src/components/projectForm/reducer.ts b/app2/src/components/projectForm/reducer.ts new file mode 100644 index 0000000..e32c58f --- /dev/null +++ b/app2/src/components/projectForm/reducer.ts @@ -0,0 +1,41 @@ +import { ProjectFormState } from "./ProjectFormState.interface" + +type Action = +| { type: 'SET_ERROR'; payload: string | undefined } +| { type: 'SET_IS_DISABLED'; payload: boolean } +| { type: 'SET_IS_LOADING'; payload: boolean } + +export const initialState: ProjectFormState = { + error: undefined, + isDisabled: false, + isLoading: true, +}; + +export const reducer = ( + state: ProjectFormState, + action: Action +): ProjectFormState => { + switch (action.type) { + case 'SET_ERROR': { + return { + ...state, + error: action.payload + }; + } + case 'SET_IS_DISABLED': { + return { + ...state, + isDisabled: action.payload + }; + } + case 'SET_IS_LOADING': { + return { + ...state, + isLoading: action.payload + }; + } + default: { + return state; + } + } +}; \ No newline at end of file diff --git a/app2/src/components/projects/Project.interface.ts b/app2/src/components/projects/Project.interface.ts new file mode 100644 index 0000000..6d5ab82 --- /dev/null +++ b/app2/src/components/projects/Project.interface.ts @@ -0,0 +1,11 @@ +export interface Project { + partitionKey: string; + rowKey: string; + iconName: string; + order: string; + repoName: string; + siteUrl: string; + subTitle: string; + summary: string; + title: string; +} \ No newline at end of file diff --git a/app2/src/components/projects/Projects.tsx b/app2/src/components/projects/Projects.tsx new file mode 100644 index 0000000..828e148 --- /dev/null +++ b/app2/src/components/projects/Projects.tsx @@ -0,0 +1,248 @@ +import { Project } from './Project.interface'; +import { + Box, + Button, + Card, + CardActions, + CardContent, + CardHeader, + Container, + Grid, + Icon, + IconName, + Typography +} from '@noahspan/noahspan-components'; +import { useIsAuthenticated } from '@azure/msal-react'; +import { FormMode } from '../../enums/formMode'; +import { useEffect, useReducer } from 'react'; +import { initialState, reducer } from './reducer'; +import { AxiosError, AxiosInstance, AxiosResponse } from 'axios'; +import { useHttpClient } from '../../hooks/httpClient/UseHttpClient'; +import { useAccessToken } from '../../hooks/accessToken/UseAccessToken'; +import ProjectForm from '../projectForm/ProjectForm'; +import ConfirmationDialog from '../confirmationDialog/ConfirmationDialog'; +import ActionMenu from '../actionMenu/ActionMenu'; + +const Projects = () => { + const [state, dispatch] = useReducer(reducer, initialState) + const httpClient: AxiosInstance = useHttpClient(); + const isAuthenticated = useIsAuthenticated(); + const { getAccessToken } = useAccessToken(); + + const findIconByName: IconName = (name: string) => { + const keys = Object.keys(IconName); + + for (const key of keys) { + if (IconName[key] === name) { + return key + } + } + + return undefined; + } + + const getProjects = async (): Promise => { + try { + const config = isAuthenticated + ? { headers: { Authorization: await getAccessToken() } } + : {}; + const response: AxiosResponse = await httpClient.get( + `api/projects`, + config + ); + const projects: Project[] = response.data; + + projects.sort((a, b) => Number(a.order) - Number(b.order)) + + console.log(projects) + + return projects; + } catch (error) { + throw new Error('broken'); + } + }; + + const onOpenCloseProjectForm = (mode: FormMode, projectId?: string) => { + switch (mode) { + case FormMode.ADD: + case FormMode.EDIT: + case FormMode.VIEW: + dispatch({ + type: 'SET_OPEN_CLOSE_PROJECT_FORM', + payload: { + formMode: mode, + selectedProjectId: projectId, + isFormOpen: true + } + }); + + break; + case FormMode.CANCEL: + dispatch({ + type: 'SET_OPEN_CLOSE_PROJECT_FORM', + payload: { + formMode: mode, + selectedProjectId: undefined, + isFormOpen: false + } + }); + + break; + } + }; + + const onDeleteProject = (entryId: string) => { + dispatch({ + type: 'SET_DELETE', + payload: { isConfirmationDialogOpen: true, selectedProjectId: entryId } + }); + }; + + const onConfirmationDialogConfirm = async () => { + try { + dispatch({ type: 'SET_IS_CONFIRMATION_DIALOG_LOADING', payload: true }); + + const token = await getAccessToken(); + const config = isAuthenticated + ? { headers: { Authorization: `${token}` } } + : {}; + + // await httpClient.delete(`api/logs/log/${state.selectedEntryId}`, config); + + dispatch({ + type: 'SET_DELETE', + payload: { isConfirmationDialogOpen: false, selectedProjectId: undefined } + }); + // await getProjects(); + } catch (error) { + const axiosError = error as AxiosError; + + dispatch({ + type: 'SET_ERROR', + payload: `Loading of logbook entries failed with the following message: ${axiosError.message}` + }); + } finally { + dispatch({ type: 'SET_IS_CONFIRMATION_DIALOG_LOADING', payload: false }); + } + }; + + const onConfirmationDialogCancel = () => { + dispatch({ + type: 'SET_DELETE', + payload: { isConfirmationDialogOpen: false, selectedProjectId: undefined } + }); + }; + + useEffect(() => { + const loadProjects = async () => { + try { + const projects = await getProjects(); + + dispatch({ type: 'SET_PROJECTS', payload: projects }) + } catch (error) { + console.log(error); + } + } + + loadProjects() + }, []); + + return ( + + + + + Projects + + + {isAuthenticated && + + } + + {state.projects.length > 0 && state.projects.map((project: Project) => { + return ( + + + : null} + avatar={} + subheader={project.subTitle} + title={project.title} + slotProps={{ + subheader: { + fontSize: '16px' + }, + title: { + fontSize: '24px', + } + }} + /> + + {project.summary} + + + + + {project.repoName !== '' && + + } + + + {project.siteUrl !== '' && + + } + + + + + + + ) + })} + + {state.isFormOpen && + onOpenCloseProjectForm(mode)} + projectId={state.selectedProjectId} + /> + } + {state.isConfirmDialogOpen && ( + + )} + + + ) +} + +export default Projects; \ No newline at end of file diff --git a/app2/src/components/projects/ProjectsState.interface.ts b/app2/src/components/projects/ProjectsState.interface.ts new file mode 100644 index 0000000..eedefe0 --- /dev/null +++ b/app2/src/components/projects/ProjectsState.interface.ts @@ -0,0 +1,13 @@ +import { FormMode } from '../../enums/formMode'; +import { Project } from './Project.interface'; + +export interface ProjectsState { + projects: Project[]; + formMode: FormMode; + error: string | undefined; + isConfirmDialogLoading: boolean; + isConfirmDialogOpen: boolean; + isFormOpen: boolean; + isLoading: boolean; + selectedProjectId: string | undefined; +} \ No newline at end of file diff --git a/app2/src/components/projects/reducer.ts b/app2/src/components/projects/reducer.ts new file mode 100644 index 0000000..0197218 --- /dev/null +++ b/app2/src/components/projects/reducer.ts @@ -0,0 +1,92 @@ +import { FormMode } from '../../enums/formMode'; +import { ProjectsState } from './ProjectsState.interface' +import { Project } from './Project.interface'; + +type Action = + | { + type: 'SET_DELETE'; + payload: { + isConfirmationDialogOpen: boolean; + selectedProjectId: string | undefined; + }; + } + | { type: 'SET_PROJECTS'; payload: Project[] } + | { type: 'SET_ERROR'; payload: string | undefined } + | { type: 'SET_FORM_MODE'; payload: FormMode } + | { type: 'SET_IS_CONFIRMATION_DIALOG_LOADING'; payload: boolean } + | { type: 'SET_IS_LOADING'; payload: boolean } + | { + type: 'SET_OPEN_CLOSE_PROJECT_FORM'; + payload: { + formMode: FormMode; + selectedProjectId: string | undefined; + isFormOpen: boolean; + }; + }; + +export const initialState: ProjectsState = { + projects: [], + error: undefined, + formMode: FormMode.CANCEL, + isConfirmDialogLoading: false, + isConfirmDialogOpen: false, + isFormOpen: false, + isLoading: false, + selectedProjectId: undefined +}; + +export const reducer = ( + state: ProjectsState, + action: Action +): ProjectsState => { + switch (action.type) { + case 'SET_DELETE': { + return { + ...state, + isConfirmDialogOpen: action.payload.isConfirmationDialogOpen, + selectedProjectId: action.payload.selectedProjectId + }; + } + case 'SET_PROJECTS': { + return { + ...state, + projects: action.payload + }; + } + case 'SET_ERROR': { + return { + ...state, + error: action.payload + }; + } + case 'SET_FORM_MODE': { + return { + ...state, + formMode: action.payload + }; + } + case 'SET_IS_CONFIRMATION_DIALOG_LOADING': { + return { + ...state, + isConfirmDialogLoading: action.payload + }; + } + case 'SET_IS_LOADING': { + return { + ...state, + isLoading: action.payload + }; + } + case 'SET_OPEN_CLOSE_PROJECT_FORM': { + return { + ...state, + formMode: action.payload.formMode, + isFormOpen: action.payload.isFormOpen, + selectedProjectId: action.payload.selectedProjectId + }; + } + default: { + return state; + } + } +}; diff --git a/app2/src/components/siteNav/SiteNav.tsx b/app2/src/components/siteNav/SiteNav.tsx new file mode 100644 index 0000000..5d6418a --- /dev/null +++ b/app2/src/components/siteNav/SiteNav.tsx @@ -0,0 +1,130 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useAppContext } from '../../hooks/appContext/UseAppContext'; +import { + Icon, + MenuItem, + Navbar, + Spinner, + Typography +} from '@noahspan/noahspan-components'; +import { useIsAuthenticated, useMsal } from '@azure/msal-react'; +import { useAccessToken } from '../../hooks/accessToken/UseAccessToken'; +import { useHttpClient } from '../../hooks/httpClient/UseHttpClient'; +import { AxiosInstance, AxiosResponse } from 'axios'; +import { User } from '@microsoft/microsoft-graph-types'; + +const SiteNav = () => { + const [loading, setLoading] = useState(false); + const [userPhoto, setUserPhoto] = useState(); + const httpClient: AxiosInstance = useHttpClient(); + const appContext = useAppContext(); + const isAuthenticated = useIsAuthenticated(); + const { getAccessToken } = useAccessToken(); + const { instance } = useMsal(); + const navigate = useNavigate(); + const pages = [ + { + name: 'Projects', + url: '/' + } + ]; + const handleSignIn = () => { + instance.loginRedirect({ + scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`] + }); + }; + const handleSignOut = () => { + instance.logoutRedirect(); + }; + const getUserProfile = async (accessToken: string): Promise => { + try { + const response: AxiosResponse = await httpClient.get(`api/userProfile`, { + headers: { + Authorization: accessToken + } + }); + const userProfile: User = response.data; + + return userProfile; + } catch (error) { + throw new Error(); + } + }; + const getUserPhoto = async (accessToken: string): Promise => { + try { + const response: AxiosResponse = await httpClient.get(`api/userPhoto`, { + headers: { + Authorization: accessToken + }, + responseType: 'arraybuffer' + }); + const arrayBufferView = new Uint8Array(response.data); + const blob = new Blob([arrayBufferView], { type: 'image/png' }); + const imageUrl = window.URL.createObjectURL(blob); + + return imageUrl; + } catch (error) { + console.log(error); + throw new Error(); + } + }; + const handlePageClick = (url: string) => { + navigate(url); + }; + + const Settings = () => { + return ( + + + + Sign Out + + + ); + }; + + useEffect(() => { + const setUserProfile = async () => { + try { + setLoading(true); + + const accessToken: string = await getAccessToken(); + const userProfile = await getUserProfile(accessToken); + const userPhoto = await getUserPhoto(accessToken); + + setUserPhoto(userPhoto); + + appContext.dispatch({ + type: 'SET_USER_PROFILE', + payload: userProfile + }); + } catch (error) { + console.log(error); + } finally { + setLoading(false); + } + }; + + if ( + isAuthenticated && + Object.keys(appContext.state.userProfile).length === 0 + ) { + setUserProfile(); + } + }, [isAuthenticated]); + + return ( + } + pages={pages} + settings={} + userPhoto={userPhoto} + /> + ); +}; + +export default SiteNav; \ No newline at end of file diff --git a/app/src/context/appContext/AppContext.tsx b/app2/src/context/appContext/AppContext.tsx similarity index 100% rename from app/src/context/appContext/AppContext.tsx rename to app2/src/context/appContext/AppContext.tsx diff --git a/app/src/context/appContext/AppContextProps.interface.ts b/app2/src/context/appContext/AppContextProps.interface.ts similarity index 100% rename from app/src/context/appContext/AppContextProps.interface.ts rename to app2/src/context/appContext/AppContextProps.interface.ts diff --git a/app/src/context/appContext/AppContextProvider.tsx b/app2/src/context/appContext/AppContextProvider.tsx similarity index 100% rename from app/src/context/appContext/AppContextProvider.tsx rename to app2/src/context/appContext/AppContextProvider.tsx diff --git a/app/src/context/appContext/AppContextProviderProps.interface.ts b/app2/src/context/appContext/AppContextProviderProps.interface.ts similarity index 100% rename from app/src/context/appContext/AppContextProviderProps.interface.ts rename to app2/src/context/appContext/AppContextProviderProps.interface.ts diff --git a/app/src/context/appContext/AppContextState.interface.ts b/app2/src/context/appContext/AppContextState.interface.ts similarity index 100% rename from app/src/context/appContext/AppContextState.interface.ts rename to app2/src/context/appContext/AppContextState.interface.ts diff --git a/app/src/context/appContext/reducer.ts b/app2/src/context/appContext/reducer.ts similarity index 100% rename from app/src/context/appContext/reducer.ts rename to app2/src/context/appContext/reducer.ts diff --git a/app2/src/enums/formMode.ts b/app2/src/enums/formMode.ts new file mode 100644 index 0000000..f1eff1f --- /dev/null +++ b/app2/src/enums/formMode.ts @@ -0,0 +1,6 @@ +export enum FormMode { + ADD = 'ADD', + EDIT = 'EDIT', + VIEW = 'VIEW', + CANCEL = 'CANCEL' +} \ No newline at end of file diff --git a/app2/src/hooks/accessToken/UseAccessToken.tsx b/app2/src/hooks/accessToken/UseAccessToken.tsx new file mode 100644 index 0000000..336d9e5 --- /dev/null +++ b/app2/src/hooks/accessToken/UseAccessToken.tsx @@ -0,0 +1,32 @@ +import { + AuthenticationResult, + InteractionRequiredAuthError +} from '@azure/msal-browser'; +import { useMsal } from '@azure/msal-react'; + +export const useAccessToken = () => { + const { accounts, instance } = useMsal(); + const getAccessToken = async () => { + const tokenRequest = { + account: accounts[0], + scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`] + }; + + try { + const response: AuthenticationResult = + await instance.acquireTokenSilent(tokenRequest); + + return `Bearer ${response.accessToken}`; + } catch (error) { + if (error instanceof InteractionRequiredAuthError) { + await instance.acquireTokenRedirect(tokenRequest); + } + + throw error; + } + }; + + return { + getAccessToken + }; +}; diff --git a/app2/src/hooks/appContext/UseAppContext.tsx b/app2/src/hooks/appContext/UseAppContext.tsx new file mode 100644 index 0000000..abb557a --- /dev/null +++ b/app2/src/hooks/appContext/UseAppContext.tsx @@ -0,0 +1,11 @@ +import { useContext } from 'react'; +import { AppContext } from '../../context/appContext/AppContext'; + +export const useAppContext = () => { + const { state, dispatch } = useContext(AppContext); + + return { + state, + dispatch + }; +}; \ No newline at end of file diff --git a/app/src/hooks/httpClient/UseHttpClient.tsx b/app2/src/hooks/httpClient/UseHttpClient.tsx similarity index 100% rename from app/src/hooks/httpClient/UseHttpClient.tsx rename to app2/src/hooks/httpClient/UseHttpClient.tsx diff --git a/app/src/projects/Projects.tsx b/app2/src/index.css similarity index 100% rename from app/src/projects/Projects.tsx rename to app2/src/index.css diff --git a/app2/src/main.tsx b/app2/src/main.tsx new file mode 100644 index 0000000..0dc723a --- /dev/null +++ b/app2/src/main.tsx @@ -0,0 +1,39 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' +import { BrowserRouter } from 'react-router-dom'; +import { AuthenticationResult, EventMessage, EventType, PublicClientApplication } from '@azure/msal-browser'; +import { msalConfig } from './auth/msalConfig.ts'; +import AppContextProvider from './context/appContext/AppContextProvider.tsx'; + +const msalInstance: PublicClientApplication = new PublicClientApplication(msalConfig); + +msalInstance.initialize().then(() => { + const accounts = msalInstance.getAllAccounts(); + + if (accounts.length > 0) { + msalInstance.setActiveAccount(accounts[0]); + } + + msalInstance.addEventCallback((event: EventMessage) => { + if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) { + const payload = event.payload as AuthenticationResult; + const account = payload.account; + + msalInstance.setActiveAccount(account) + } + }) + + createRoot(document.getElementById('root')!).render( + + + + + + + + ) +}) + + diff --git a/app2/src/vite-env.d.ts b/app2/src/vite-env.d.ts new file mode 100644 index 0000000..9bf12a3 --- /dev/null +++ b/app2/src/vite-env.d.ts @@ -0,0 +1,12 @@ +/// + +interface ImportMetaEnv { + readonly VITE_API_URL: string; + readonly VITE_CLIENT_ID: string; + readonly VITE_TENANT_ID: string; + readonly VITE_REDIRECT_URL: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} \ No newline at end of file diff --git a/app/tsconfig.app.json b/app2/tsconfig.app.json similarity index 100% rename from app/tsconfig.app.json rename to app2/tsconfig.app.json diff --git a/app/tsconfig.json b/app2/tsconfig.json similarity index 100% rename from app/tsconfig.json rename to app2/tsconfig.json diff --git a/app/tsconfig.node.json b/app2/tsconfig.node.json similarity index 100% rename from app/tsconfig.node.json rename to app2/tsconfig.node.json diff --git a/app2/vite.config.ts b/app2/vite.config.ts new file mode 100644 index 0000000..dc1eec9 --- /dev/null +++ b/app2/vite.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + preview: { + port: 8080, + strictPort: true + }, + server: { + port: 8080, + strictPort: true, + host: '0.0.0.0' + } +}); diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..7dcd6df --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,11 @@ +version: '3.8' + +services: + azurite: + container_name: azurite + image: mcr.microsoft.com/azure-storage/azurite:3.33.0 + ports: + - '10000:10000' + - '10001:10001' + - '10002:10002' + command: 'azurite --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --loose' \ No newline at end of file diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore new file mode 100644 index 0000000..007a994 --- /dev/null +++ b/infrastructure/.gitignore @@ -0,0 +1,4 @@ +# Terraform +.terraform/* +*.tfstate +*.tfstate.* \ No newline at end of file diff --git a/infrastructure/app_config.tf b/infrastructure/app_config.tf index a0aec55..6740cef 100644 --- a/infrastructure/app_config.tf +++ b/infrastructure/app_config.tf @@ -1,30 +1,30 @@ -data "azurerm_client_config" "current" {} +# data "azurerm_client_config" "current" {} -data "azurerm_user_assigned_identity" "user_assigned_identity" { - name = "noahspanflyingdevuser" - resource_group_name = azurerm_resource_group.resource_group.name -} +# data "azurerm_user_assigned_identity" "user_assigned_identity" { +# name = "noahspanflyingdevuser" +# resource_group_name = azurerm_resource_group.resource_group.name +# } -resource "azurerm_app_configuration" "app_configuration" { - name = "noahspan-appconfig" - resource_group_name = azurerm_resource_group.resource_group.name - location = azurerm_resource_group.resource_group.location +# resource "azurerm_app_configuration" "app_configuration" { +# name = "noahspan-appconfig" +# resource_group_name = azurerm_resource_group.resource_group.name +# location = azurerm_resource_group.resource_group.location - identity { - type = "UserAssigned" - identity_ids = [ - data.azurerm_user_assigned_identity.user_assigned_identity.id - ] - } -} +# identity { +# type = "UserAssigned" +# identity_ids = [ +# data.azurerm_user_assigned_identity.user_assigned_identity.id +# ] +# } +# } -resource "azurerm_role_assignment" "role_assignment" { - scope = azurerm_app_configuration.app_configuration.id - role_definition_name = "App Configuration Data Owner" - principal_id = data.azurerm_client_config.current.object_id -} +# resource "azurerm_role_assignment" "role_assignment" { +# scope = azurerm_app_configuration.app_configuration.id +# role_definition_name = "App Configuration Data Owner" +# principal_id = data.azurerm_client_config.current.object_id +# } # resource "azurerm_app_configuration_feature" "flying-pilots" { # configuration_store_id = azurerm_app_configuration.app_configuration.id diff --git a/infrastructure/config.tf b/infrastructure/config.tf new file mode 100644 index 0000000..ca88f8a --- /dev/null +++ b/infrastructure/config.tf @@ -0,0 +1,4 @@ +module "environment" { + source = "./config" + environment = var.WORKSPACE +} \ No newline at end of file diff --git a/infrastructure/config/main.tf b/infrastructure/config/main.tf new file mode 100644 index 0000000..1a4ffa4 --- /dev/null +++ b/infrastructure/config/main.tf @@ -0,0 +1,52 @@ +locals { + container_app_app_name = { + test = "root-app-test" + prod = "root-app-prod" + } + + container_app_app_container_image = { + test = "noahspan/root-app:v0.0.1" + prod = "noahspan/root-app:v0.0.1" + } + + container_app_app_container_name = { + test = "root-app-test" + prod = "root-app-prod" + } + + container_app_api_name = { + test = "root-api-test" + prod = "root-api-prod" + } + + container_app_api_container_image = { + test = "noahspan/root-api:v0.0.1" + prod = "noahspan/root-api:v0.0.1" + } + + container_app_api_container_name = { + test = "root-api-test" + prod = "root-api-prod" + } + + container_app_api_dapr_app_id = { + test = "rootapitest" + prod = "rootpapiprod" + } + + container_app_environment_name = { + test = "root-test" + prod = "root-prod" + } + + log_analytics_workspace_name = { + test = "root-log-analytics-workspace-test" + prod = "root-log-analytics-workspace-prod" + } + + + storage_account_name = { + test = "noahspanroottest" + prod = "noahspanrootprod" + } +} \ No newline at end of file diff --git a/infrastructure/config/outputs.tf b/infrastructure/config/outputs.tf new file mode 100644 index 0000000..29fa8dd --- /dev/null +++ b/infrastructure/config/outputs.tf @@ -0,0 +1,39 @@ +output "container_app_app_name" { + value = local.container_app_app_name[var.environment] +} + +output "container_app_app_container_image" { + value = local.container_app_app_container_image[var.environment] +} + +output "container_app_app_container_name" { + value = local.container_app_app_container_name[var.environment] +} + +output "container_app_api_name" { + value = local.container_app_api_name[var.environment] +} + +output "container_app_api_container_image" { + value = local.container_app_api_container_image[var.environment] +} + +output "container_app_api_container_name" { + value = local.container_app_api_container_name[var.environment] +} + +output "container_app_api_dapr_app_id" { + value = local.container_app_api_dapr_app_id[var.environment] +} + +output "container_app_environment_name" { + value = local.container_app_environment_name[var.environment] +} + +output "log_analytics_workspace_name" { + value = local.log_analytics_workspace_name[var.environment] +} + +output "storage_account_name" { + value = local.storage_account_name[var.environment] +} \ No newline at end of file diff --git a/infrastructure/config/variables.tf b/infrastructure/config/variables.tf new file mode 100644 index 0000000..9b5503e --- /dev/null +++ b/infrastructure/config/variables.tf @@ -0,0 +1 @@ +variable "environment" {} \ No newline at end of file diff --git a/infrastructure/dns.tf b/infrastructure/dns.tf index a5b40f2..681efb4 100644 --- a/infrastructure/dns.tf +++ b/infrastructure/dns.tf @@ -4,46 +4,46 @@ # validation_type = "dns-txt-token" # } -resource "azurerm_dns_zone" "dns_zone" { - name = var.DOMAIN_NAME - resource_group_name = azurerm_resource_group.resource_group.name -} +# resource "azurerm_dns_zone" "dns_zone" { +# name = var.DOMAIN_NAME +# resource_group_name = azurerm_resource_group.resource_group.name +# } -resource "azurerm_dns_txt_record" "dns_txt_record" { - name = var.WEBSITE_NAME == var.DOMAIN_NAME ? "@" : split(".", var.WEBSITE_NAME)[0] - zone_name = azurerm_dns_zone.dns_zone.name - resource_group_name = azurerm_resource_group.resource_group.name - ttl = 14400 - record { - value = azurerm_static_web_app_custom_domain.static_web_app_custom_domain.validation_token == "" ? "validated" : azurerm_static_web_app_custom_domain.static_web_app_custom_domain.validation_token - } +# resource "azurerm_dns_txt_record" "dns_txt_record" { +# name = var.WEBSITE_NAME == var.DOMAIN_NAME ? "@" : split(".", var.WEBSITE_NAME)[0] +# zone_name = azurerm_dns_zone.dns_zone.name +# resource_group_name = azurerm_resource_group.resource_group.name +# ttl = 14400 +# record { +# value = azurerm_static_web_app_custom_domain.static_web_app_custom_domain.validation_token == "" ? "validated" : azurerm_static_web_app_custom_domain.static_web_app_custom_domain.validation_token +# } - # email - record { - value = var.EMAIL_TXT_VALUE == "" ? "validated" : var.EMAIL_TXT_VALUE - } +# # email +# record { +# value = var.EMAIL_TXT_VALUE == "" ? "validated" : var.EMAIL_TXT_VALUE +# } - record { - value = var.EMAIL_SPF_VALUE == "" ? "validated" : var.EMAIL_SPF_VALUE - } -} +# record { +# value = var.EMAIL_SPF_VALUE == "" ? "validated" : var.EMAIL_SPF_VALUE +# } +# } # email -resource "azurerm_dns_mx_record" "dns_mx_record" { - name = "@" - zone_name = azurerm_dns_zone.dns_zone.name - resource_group_name = azurerm_resource_group.resource_group.name - ttl = 14400 +# resource "azurerm_dns_mx_record" "dns_mx_record" { +# name = "@" +# zone_name = azurerm_dns_zone.dns_zone.name +# resource_group_name = azurerm_resource_group.resource_group.name +# ttl = 14400 - record { - preference = 10 - exchange = "mx01.mail.icloud.com." - } +# record { +# preference = 10 +# exchange = "mx01.mail.icloud.com." +# } - record { - preference = 10 - exchange = "mx02.mail.icloud.com." - } -} +# record { +# preference = 10 +# exchange = "mx02.mail.icloud.com." +# } +# } diff --git a/infrastructure/main.tf b/infrastructure/main.tf index 764520b..e03e45e 100644 --- a/infrastructure/main.tf +++ b/infrastructure/main.tf @@ -8,38 +8,198 @@ # subdomain_name = var.SUBDOMAIN_NAME # } -resource "azurerm_static_web_app" "static_web_app" { - name = var.STATIC_WEB_APP_NAME - resource_group_name = azurerm_resource_group.resource_group.name - location = var.REGION -} +# resource "azurerm_static_web_app" "static_web_app" { +# name = var.STATIC_WEB_APP_NAME +# resource_group_name = azurerm_resource_group.resource_group.name +# location = var.REGION +# } -resource "azurerm_static_web_app_custom_domain" "static_web_app_custom_domain" { - static_web_app_id = azurerm_static_web_app.static_web_app.id - domain_name = var.WEBSITE_NAME - validation_type = "dns-txt-token" -} +# resource "azurerm_static_web_app_custom_domain" "static_web_app_custom_domain" { +# static_web_app_id = azurerm_static_web_app.static_web_app.id +# domain_name = var.WEBSITE_NAME +# validation_type = "dns-txt-token" +# } -resource "azurerm_dns_a_record" "dns_a_record" { - name = var.WEBSITE_NAME == var.DOMAIN_NAME ? "@" : split(".", var.WEBSITE_NAME)[0] - zone_name = azurerm_dns_zone.dns_zone.name - resource_group_name = azurerm_resource_group.resource_group.name - ttl = 14400 - target_resource_id = azurerm_static_web_app.static_web_app.id -} +# resource "azurerm_dns_a_record" "dns_a_record" { +# name = var.WEBSITE_NAME == var.DOMAIN_NAME ? "@" : split(".", var.WEBSITE_NAME)[0] +# zone_name = azurerm_dns_zone.dns_zone.name +# resource_group_name = azurerm_resource_group.resource_group.name +# ttl = 14400 +# target_resource_id = azurerm_static_web_app.static_web_app.id +# } -resource "azurerm_dns_cname_record" "dns_cname_record" { - name = "www" - zone_name = azurerm_dns_zone.dns_zone.name - resource_group_name = azurerm_resource_group.resource_group.name - ttl = 14400 - record = azurerm_static_web_app.static_web_app.default_host_name -} +# resource "azurerm_dns_cname_record" "dns_cname_record" { +# name = "www" +# zone_name = azurerm_dns_zone.dns_zone.name +# resource_group_name = azurerm_resource_group.resource_group.name +# ttl = 14400 +# record = azurerm_static_web_app.static_web_app.default_host_name +# } -resource "azurerm_static_web_app_custom_domain" "static_web_app_custom_domain_www" { - static_web_app_id = azurerm_static_web_app.static_web_app.id - domain_name = "www.${var.DOMAIN_NAME}" - validation_type = "cname-delegation" +# resource "azurerm_static_web_app_custom_domain" "static_web_app_custom_domain_www" { +# static_web_app_id = azurerm_static_web_app.static_web_app.id +# domain_name = "www.${var.DOMAIN_NAME}" +# validation_type = "cname-delegation" - depends_on = [ azurerm_dns_cname_record.dns_cname_record ] -} \ No newline at end of file +# depends_on = [ azurerm_dns_cname_record.dns_cname_record ] +# } + + +# data "azurerm_client_config" "current" { +# } + +# resource "azurerm_log_analytics_workspace" "log_analytics_workspace" { +# name = module.environment.log_analytics_workspace_name +# location = data.azurerm_resource_group.resource_group.location +# resource_group_name = data.azurerm_resource_group.resource_group.name +# sku = "PerGB2018" +# retention_in_days = 30 +# } + +# resource "azurerm_container_app_environment" "container_app_environment" { +# name = module.environment.container_app_environment_name +# location = data.azurerm_resource_group.resource_group.location +# resource_group_name = data.azurerm_resource_group.resource_group.name +# log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id +# } + +# resource "azurerm_container_app" "container_app_api" { +# name = module.environment.container_app_api_name +# container_app_environment_id = azurerm_container_app_environment.container_app_environment.id +# resource_group_name = data.azurerm_resource_group.resource_group.name +# revision_mode = "Single" + +# registry { +# server = "index.docker.io" +# username = var.DOCKER_IO_USERNAME +# password_secret_name = "docker-io-password" +# } + +# template { +# min_replicas = 0 +# max_replicas = 2 + +# container { +# name = module.environment.container_app_api_container_name +# image = module.environment.container_app_api_container_image +# cpu = 0.25 +# memory = "0.5Gi" + +# env { +# name = "AZURE_STORAGE_CONNECTION_STRING" +# secret_name = "azure-storage-connection-string" +# } + +# env { +# name = "CLIENT_ID" +# secret_name = "client-id" +# } + +# env { +# name = "CLIENT_SECRET" +# secret_name = "client-secret" +# } + +# env { +# name = "TENANT_ID" +# secret_name = "tenant-id" +# } +# } +# } + +# ingress { +# allow_insecure_connections = false +# external_enabled = true +# target_port = 3000 +# transport = "auto" + +# traffic_weight { +# latest_revision = true +# percentage = 100 +# } +# } + +# secret { +# name = "docker-io-password" +# value = var.DOCKER_IO_PASSWORD +# } + +# secret { +# name = "azure-storage-connection-string" +# value = azurerm_storage_account.storage_account.primary_connection_string +# } + +# secret { +# name = "client-id" +# value = var.CLIENT_ID +# } + +# secret { +# name = "client-secret" +# value = var.CLIENT_SECRET +# } + +# secret { +# name = "tenant-id" +# value = var.TENANT_ID +# } + +# lifecycle { +# ignore_changes = [ template[0].container[0].image ] +# } +# } + +# resource "azurerm_container_app" "container_app_app" { +# name = module.environment.container_app_app_name +# container_app_environment_id = azurerm_container_app_environment.container_app_environment.id +# resource_group_name = data.azurerm_resource_group.resource_group.name +# revision_mode = "Single" + +# registry { +# server = "index.docker.io" +# username = var.DOCKER_IO_USERNAME +# password_secret_name = "docker-io-password" +# } + +# template { +# min_replicas = 0 +# max_replicas = 2 + +# container { +# name = module.environment.container_app_app_container_name +# image = module.environment.container_app_app_container_image +# cpu = 0.25 +# memory = "0.5Gi" +# } +# } + +# ingress { +# allow_insecure_connections = false +# external_enabled = true +# target_port = 8080 +# transport = "auto" + +# traffic_weight { +# latest_revision = true +# percentage = 100 +# } +# } + +# secret { +# name = "docker-io-password" +# value = var.DOCKER_IO_PASSWORD +# } + +# lifecycle { +# ignore_changes = [ template[0].container[0].image ] +# } +# } + +# resource "azurerm_container_app_custom_domain" "custom_domain" { +# name = trimsuffix(trimprefix(azurerm_dns_txt_record.dns_txt_record.fqdn, "asuid."), ".") +# container_app_id = azurerm_container_app.container_app_app.id + +# lifecycle { +# ignore_changes = [ certificate_binding_type, container_app_environment_certificate_id ] +# } +# } \ No newline at end of file diff --git a/infrastructure/modules/static_web_app/outputs.tf b/infrastructure/modules/static_web_app/outputs.tf index 3fec2d3..0754524 100644 --- a/infrastructure/modules/static_web_app/outputs.tf +++ b/infrastructure/modules/static_web_app/outputs.tf @@ -1,11 +1,11 @@ -output "id" { - value = azurerm_static_web_app.static_web_app.id -} +# output "id" { +# value = azurerm_static_web_app.static_web_app.id +# } -output "api_key" { - value = azurerm_static_web_app.static_web_app.api_key -} +# output "api_key" { +# value = azurerm_static_web_app.static_web_app.api_key +# } -output "default_host_name" { - value = azurerm_static_web_app.static_web_app.default_host_name -} \ No newline at end of file +# output "default_host_name" { +# value = azurerm_static_web_app.static_web_app.default_host_name +# } \ No newline at end of file diff --git a/infrastructure/modules/static_web_app/variables.tf b/infrastructure/modules/static_web_app/variables.tf index dd13bbc..a3dc9c9 100644 --- a/infrastructure/modules/static_web_app/variables.tf +++ b/infrastructure/modules/static_web_app/variables.tf @@ -1,24 +1,24 @@ -variable "region" { +# variable "region" { -} +# } -variable "resource_group_name" { +# variable "resource_group_name" { -} +# } -variable "static_web_app_name" { +# variable "static_web_app_name" { -} +# } -variable "custom_domain_name_count" { +# variable "custom_domain_name_count" { -} +# } -variable "domain_name" { +# variable "domain_name" { -} +# } -variable "subdomain_name" { +# variable "subdomain_name" { -} +# } diff --git a/infrastructure/outputs.tf b/infrastructure/outputs.tf index 28251c7..a26f319 100644 --- a/infrastructure/outputs.tf +++ b/infrastructure/outputs.tf @@ -1,7 +1,7 @@ -output "api_key" { - value = azurerm_static_web_app.static_web_app.api_key - sensitive = true -} +# output "api_key" { +# value = azurerm_static_web_app.static_web_app.api_key +# sensitive = true +# } # output "default_host_name" { # value = azurerm_static_web_app.static_web_app.default_host_name @@ -12,6 +12,6 @@ output "api_key" { # sensitive = false # } -output "name_servers" { - value = azurerm_dns_zone.dns_zone.name_servers -} \ No newline at end of file +# output "name_servers" { +# value = azurerm_dns_zone.dns_zone.name_servers +# } \ No newline at end of file diff --git a/infrastructure/resource_group.tf b/infrastructure/resource_group.tf index a8abc53..cbaf81a 100644 --- a/infrastructure/resource_group.tf +++ b/infrastructure/resource_group.tf @@ -1,4 +1,3 @@ -resource "azurerm_resource_group" "resource_group" { +data "azurerm_resource_group" "resource_group" { name = var.RESOURCE_GROUP_NAME - location = var.REGION } \ No newline at end of file diff --git a/infrastructure/storage_account.tf b/infrastructure/storage_account.tf new file mode 100644 index 0000000..b188d1d --- /dev/null +++ b/infrastructure/storage_account.tf @@ -0,0 +1,12 @@ +resource "azurerm_storage_account" "storage_account" { + name = module.environment.storage_account_name + resource_group_name = data.azurerm_resource_group.resource_group.name + location = data.azurerm_resource_group.resource_group.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_table" "projects_table" { + name = "projects" + storage_account_name = azurerm_storage_account.storage_account.name +} \ No newline at end of file diff --git a/infrastructure/variables.tf b/infrastructure/variables.tf index 393e5bc..76a21dd 100644 --- a/infrastructure/variables.tf +++ b/infrastructure/variables.tf @@ -1,31 +1,46 @@ -variable "EMAIL_TXT_VALUE" { +variable "API_SUBDOMAIN_NAME" { type = string } -variable "EMAIL_SPF_VALUE" { +variable "APP_SUBDOMAIN_NAME" { type = string } +variable "CLIENT_ID" { + type = string +} + +variable "CLIENT_SECRET" { + type = string + sensitive = true +} + +variable "DOCKER_IO_PASSWORD" { + type = string + sensitive = true +} + +variable "DOCKER_IO_USERNAME" { + type = string +} + +variable "DOMAIN_NAME" { + type = string +} + variable "REGION" { - type = string + type = string } variable "RESOURCE_GROUP_NAME" { type = string } -variable "STATIC_WEB_APP_NAME" { +variable "TENANT_ID" { type = string } -variable "CUSTOM_DOMAIN_NAME_COUNT" { - type = number -} - -variable "DOMAIN_NAME" { +variable "WORKSPACE" { type = string } -variable "WEBSITE_NAME" { - type = string -} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1660a95..aa41f01 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -120,7 +120,7 @@ importers: specifier: ^5.1.3 version: 5.7.3 - app: + app2: dependencies: '@azure/msal-browser': specifier: ^4.2.1 @@ -128,9 +128,12 @@ importers: '@azure/msal-react': specifier: ^3.0.4 version: 3.0.4(@azure/msal-browser@4.2.1)(react@19.0.0) + '@microsoft/microsoft-graph-types': + specifier: ^2.40.0 + version: 2.40.0 '@noahspan/noahspan-components': - specifier: ^1.0.0 - version: 1.0.0(@mui/system@6.4.3(@emotion/react@11.14.0(@types/react@19.0.8)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(less@4.2.2)(moment@2.30.1)(postcss@8.5.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.4(prettier@3.5.0))(typescript@5.7.3)(webpack@5.97.1(esbuild@0.18.20)) + specifier: ^1.3.1 + version: 1.3.1(@mui/system@6.4.3(@emotion/react@11.14.0(@types/react@19.0.8)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(less@4.2.2)(moment@2.30.1)(postcss@8.5.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.4(prettier@3.5.0))(typescript@5.7.3)(webpack@5.97.1(esbuild@0.18.20)) axios: specifier: ^1.7.9 version: 1.7.9 @@ -140,6 +143,9 @@ importers: react-dom: specifier: ^19.0.0 version: 19.0.0(react@19.0.0) + react-hook-form: + specifier: ^7.54.2 + version: 7.54.2(react@19.0.0) react-router-dom: specifier: ^7.1.5 version: 7.1.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -147,9 +153,6 @@ importers: '@eslint/js': specifier: ^9.19.0 version: 9.20.0 - '@microsoft/microsoft-graph-types': - specifier: ^2.40.0 - version: 2.40.0 '@types/react': specifier: ^19.0.8 version: 19.0.8 @@ -170,7 +173,7 @@ importers: version: 0.4.19(eslint@9.20.1(jiti@1.21.7)) globals: specifier: ^15.14.0 - version: 15.14.0 + version: 15.15.0 typescript: specifier: ~5.7.2 version: 5.7.3 @@ -1428,8 +1431,8 @@ packages: '@nestjs/common': ^9.0.0 || ^10.0.0 '@nestjs/core': ^9.0.0 || ^10.0.0 - '@noahspan/noahspan-components@1.0.0': - resolution: {integrity: sha512-MZaWmqN+Ci41mzVyejhlKE/1BbnYZRS/tekNgO5ZNPgzfS09TIdUb3at+KQhwqw2EQ4b4osk3RVzhIGxuSHYnA==} + '@noahspan/noahspan-components@1.3.1': + resolution: {integrity: sha512-7eyaxfR5NZIuFaAInT0v/n2O8ULE1zalMwaHvXN2SiU540FTNOGoH7fjd6DsY6RY+FBVJBD0Wo0gfzTy7RbPFg==} engines: {node: '>=18.0.0'} peerDependencies: react: ^18.2.0 @@ -1905,98 +1908,98 @@ packages: '@radix-ui/rect@1.0.1': resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} - '@rollup/rollup-android-arm-eabi@4.34.6': - resolution: {integrity: sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==} + '@rollup/rollup-android-arm-eabi@4.34.7': + resolution: {integrity: sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.34.6': - resolution: {integrity: sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==} + '@rollup/rollup-android-arm64@4.34.7': + resolution: {integrity: sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.34.6': - resolution: {integrity: sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==} + '@rollup/rollup-darwin-arm64@4.34.7': + resolution: {integrity: sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.34.6': - resolution: {integrity: sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==} + '@rollup/rollup-darwin-x64@4.34.7': + resolution: {integrity: sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.34.6': - resolution: {integrity: sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==} + '@rollup/rollup-freebsd-arm64@4.34.7': + resolution: {integrity: sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.34.6': - resolution: {integrity: sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==} + '@rollup/rollup-freebsd-x64@4.34.7': + resolution: {integrity: sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.34.6': - resolution: {integrity: sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==} + '@rollup/rollup-linux-arm-gnueabihf@4.34.7': + resolution: {integrity: sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.34.6': - resolution: {integrity: sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==} + '@rollup/rollup-linux-arm-musleabihf@4.34.7': + resolution: {integrity: sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.34.6': - resolution: {integrity: sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==} + '@rollup/rollup-linux-arm64-gnu@4.34.7': + resolution: {integrity: sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.34.6': - resolution: {integrity: sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==} + '@rollup/rollup-linux-arm64-musl@4.34.7': + resolution: {integrity: sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.34.6': - resolution: {integrity: sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==} + '@rollup/rollup-linux-loongarch64-gnu@4.34.7': + resolution: {integrity: sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.34.6': - resolution: {integrity: sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==} + '@rollup/rollup-linux-powerpc64le-gnu@4.34.7': + resolution: {integrity: sha512-vrDk9JDa/BFkxcS2PbWpr0C/LiiSLxFbNOBgfbW6P8TBe9PPHx9Wqbvx2xgNi1TOAyQHQJ7RZFqBiEohm79r0w==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.34.6': - resolution: {integrity: sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==} + '@rollup/rollup-linux-riscv64-gnu@4.34.7': + resolution: {integrity: sha512-rB+ejFyjtmSo+g/a4eovDD1lHWHVqizN8P0Hm0RElkINpS0XOdpaXloqM4FBkF9ZWEzg6bezymbpLmeMldfLTw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.34.6': - resolution: {integrity: sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==} + '@rollup/rollup-linux-s390x-gnu@4.34.7': + resolution: {integrity: sha512-nNXNjo4As6dNqRn7OrsnHzwTgtypfRA3u3AKr0B3sOOo+HkedIbn8ZtFnB+4XyKJojIfqDKmbIzO1QydQ8c+Pw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.34.6': - resolution: {integrity: sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==} + '@rollup/rollup-linux-x64-gnu@4.34.7': + resolution: {integrity: sha512-9kPVf9ahnpOMSGlCxXGv980wXD0zRR3wyk8+33/MXQIpQEOpaNe7dEHm5LMfyRZRNt9lMEQuH0jUKj15MkM7QA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.34.6': - resolution: {integrity: sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==} + '@rollup/rollup-linux-x64-musl@4.34.7': + resolution: {integrity: sha512-7wJPXRWTTPtTFDFezA8sle/1sdgxDjuMoRXEKtx97ViRxGGkVQYovem+Q8Pr/2HxiHp74SSRG+o6R0Yq0shPwQ==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.34.6': - resolution: {integrity: sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==} + '@rollup/rollup-win32-arm64-msvc@4.34.7': + resolution: {integrity: sha512-MN7aaBC7mAjsiMEZcsJvwNsQVNZShgES/9SzWp1HC9Yjqb5OpexYnRjF7RmE4itbeesHMYYQiAtUAQaSKs2Rfw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.34.6': - resolution: {integrity: sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==} + '@rollup/rollup-win32-ia32-msvc@4.34.7': + resolution: {integrity: sha512-aeawEKYswsFu1LhDM9RIgToobquzdtSc4jSVqHV8uApz4FVvhFl/mKh92wc8WpFc6aYCothV/03UjY6y7yLgbg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.34.6': - resolution: {integrity: sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==} + '@rollup/rollup-win32-x64-msvc@4.34.7': + resolution: {integrity: sha512-4ZedScpxxIrVO7otcZ8kCX1mZArtH2Wfj3uFCxRJ9NO80gg1XV0U/b2f/MKaGwj2X3QopHfoWiDQ917FRpwY3w==} cpu: [x64] os: [win32] @@ -3525,8 +3528,8 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.14.0: - resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} globby@11.1.0: @@ -4642,6 +4645,12 @@ packages: peerDependencies: react: ^19.0.0 + react-hook-form@7.54.2: + resolution: {integrity: sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4805,8 +4814,8 @@ packages: resolution: {integrity: sha512-3niqt5bXFY1InKU8HKWqqYTYjtrBaxBMnXELXCXUYgtNYGUtZM5rB46HIC430AyacL95iEniGf7RgqsesykLmQ==} engines: {node: '>=18.0'} - rollup@4.34.6: - resolution: {integrity: sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==} + rollup@4.34.7: + resolution: {integrity: sha512-8qhyN0oZ4x0H6wmBgfKxJtxM7qS98YJ0k0kNh5ECVtuchIJ7z9IVVvzpmtQyT10PXKMtBxYr1wQ5Apg8RS8kXQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -6992,7 +7001,7 @@ snapshots: - stream-browserify - supports-color - '@noahspan/noahspan-components@1.0.0(@mui/system@6.4.3(@emotion/react@11.14.0(@types/react@19.0.8)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(less@4.2.2)(moment@2.30.1)(postcss@8.5.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.4(prettier@3.5.0))(typescript@5.7.3)(webpack@5.97.1(esbuild@0.18.20))': + '@noahspan/noahspan-components@1.3.1(@mui/system@6.4.3(@emotion/react@11.14.0(@types/react@19.0.8)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0))(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(less@4.2.2)(moment@2.30.1)(postcss@8.5.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.4(prettier@3.5.0))(typescript@5.7.3)(webpack@5.97.1(esbuild@0.18.20))': dependencies: '@emotion/react': 11.14.0(@types/react@19.0.8)(react@19.0.0) '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.8)(react@19.0.0))(@types/react@19.0.8)(react@19.0.0) @@ -7502,61 +7511,61 @@ snapshots: dependencies: '@babel/runtime': 7.26.7 - '@rollup/rollup-android-arm-eabi@4.34.6': + '@rollup/rollup-android-arm-eabi@4.34.7': optional: true - '@rollup/rollup-android-arm64@4.34.6': + '@rollup/rollup-android-arm64@4.34.7': optional: true - '@rollup/rollup-darwin-arm64@4.34.6': + '@rollup/rollup-darwin-arm64@4.34.7': optional: true - '@rollup/rollup-darwin-x64@4.34.6': + '@rollup/rollup-darwin-x64@4.34.7': optional: true - '@rollup/rollup-freebsd-arm64@4.34.6': + '@rollup/rollup-freebsd-arm64@4.34.7': optional: true - '@rollup/rollup-freebsd-x64@4.34.6': + '@rollup/rollup-freebsd-x64@4.34.7': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.34.6': + '@rollup/rollup-linux-arm-gnueabihf@4.34.7': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.34.6': + '@rollup/rollup-linux-arm-musleabihf@4.34.7': optional: true - '@rollup/rollup-linux-arm64-gnu@4.34.6': + '@rollup/rollup-linux-arm64-gnu@4.34.7': optional: true - '@rollup/rollup-linux-arm64-musl@4.34.6': + '@rollup/rollup-linux-arm64-musl@4.34.7': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.34.6': + '@rollup/rollup-linux-loongarch64-gnu@4.34.7': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.34.6': + '@rollup/rollup-linux-powerpc64le-gnu@4.34.7': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.34.6': + '@rollup/rollup-linux-riscv64-gnu@4.34.7': optional: true - '@rollup/rollup-linux-s390x-gnu@4.34.6': + '@rollup/rollup-linux-s390x-gnu@4.34.7': optional: true - '@rollup/rollup-linux-x64-gnu@4.34.6': + '@rollup/rollup-linux-x64-gnu@4.34.7': optional: true - '@rollup/rollup-linux-x64-musl@4.34.6': + '@rollup/rollup-linux-x64-musl@4.34.7': optional: true - '@rollup/rollup-win32-arm64-msvc@4.34.6': + '@rollup/rollup-win32-arm64-msvc@4.34.7': optional: true - '@rollup/rollup-win32-ia32-msvc@4.34.6': + '@rollup/rollup-win32-ia32-msvc@4.34.7': optional: true - '@rollup/rollup-win32-x64-msvc@4.34.6': + '@rollup/rollup-win32-x64-msvc@4.34.7': optional: true '@schematics/angular@19.1.6(chokidar@3.6.0)': @@ -9517,7 +9526,7 @@ snapshots: globals@14.0.0: {} - globals@15.14.0: {} + globals@15.15.0: {} globby@11.1.0: dependencies: @@ -10823,6 +10832,10 @@ snapshots: react: 19.0.0 scheduler: 0.25.0 + react-hook-form@7.54.2(react@19.0.0): + dependencies: + react: 19.0.0 + react-is@16.13.1: {} react-is@18.3.1: {} @@ -10977,29 +10990,29 @@ snapshots: safe-stable-stringify: 2.5.0 semver-compare: 1.0.0 - rollup@4.34.6: + rollup@4.34.7: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.34.6 - '@rollup/rollup-android-arm64': 4.34.6 - '@rollup/rollup-darwin-arm64': 4.34.6 - '@rollup/rollup-darwin-x64': 4.34.6 - '@rollup/rollup-freebsd-arm64': 4.34.6 - '@rollup/rollup-freebsd-x64': 4.34.6 - '@rollup/rollup-linux-arm-gnueabihf': 4.34.6 - '@rollup/rollup-linux-arm-musleabihf': 4.34.6 - '@rollup/rollup-linux-arm64-gnu': 4.34.6 - '@rollup/rollup-linux-arm64-musl': 4.34.6 - '@rollup/rollup-linux-loongarch64-gnu': 4.34.6 - '@rollup/rollup-linux-powerpc64le-gnu': 4.34.6 - '@rollup/rollup-linux-riscv64-gnu': 4.34.6 - '@rollup/rollup-linux-s390x-gnu': 4.34.6 - '@rollup/rollup-linux-x64-gnu': 4.34.6 - '@rollup/rollup-linux-x64-musl': 4.34.6 - '@rollup/rollup-win32-arm64-msvc': 4.34.6 - '@rollup/rollup-win32-ia32-msvc': 4.34.6 - '@rollup/rollup-win32-x64-msvc': 4.34.6 + '@rollup/rollup-android-arm-eabi': 4.34.7 + '@rollup/rollup-android-arm64': 4.34.7 + '@rollup/rollup-darwin-arm64': 4.34.7 + '@rollup/rollup-darwin-x64': 4.34.7 + '@rollup/rollup-freebsd-arm64': 4.34.7 + '@rollup/rollup-freebsd-x64': 4.34.7 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.7 + '@rollup/rollup-linux-arm-musleabihf': 4.34.7 + '@rollup/rollup-linux-arm64-gnu': 4.34.7 + '@rollup/rollup-linux-arm64-musl': 4.34.7 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.7 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.7 + '@rollup/rollup-linux-riscv64-gnu': 4.34.7 + '@rollup/rollup-linux-s390x-gnu': 4.34.7 + '@rollup/rollup-linux-x64-gnu': 4.34.7 + '@rollup/rollup-linux-x64-musl': 4.34.7 + '@rollup/rollup-win32-arm64-msvc': 4.34.7 + '@rollup/rollup-win32-ia32-msvc': 4.34.7 + '@rollup/rollup-win32-x64-msvc': 4.34.7 fsevents: 2.3.3 run-async@2.4.1: {} @@ -11548,7 +11561,7 @@ snapshots: dependencies: esbuild: 0.24.2 postcss: 8.5.2 - rollup: 4.34.6 + rollup: 4.34.7 optionalDependencies: '@types/node': 20.17.17 fsevents: 2.3.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7352cff..b075621 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: - 'api' - - 'app' \ No newline at end of file + - 'app' + - 'app2' \ No newline at end of file