diff --git a/dev-server.log b/dev-server.log
new file mode 100644
index 00000000..d3dba065
--- /dev/null
+++ b/dev-server.log
@@ -0,0 +1,3 @@
+
+> bsmhub@0.1.0 dev /app
+> next dev
diff --git a/generate-lighthouserc.ts b/generate-lighthouserc.ts
index 17fc9740..b5c8d71b 100644
--- a/generate-lighthouserc.ts
+++ b/generate-lighthouserc.ts
@@ -4,7 +4,7 @@ import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { config } from 'dotenv';
-import { getAllRoutes } from './src/app/sitemap.xml/sitemap';
+import sitemap from './src/app/sitemap.xml/sitemap';
config({ path: '.env.local' });
@@ -15,13 +15,14 @@ async function generateConfig() {
console.log('๐ Generating Lighthouse CI config...');
// 1. sitemap์์ ๋์ ์ผ๋ก URL ๊ฒฝ๋ก๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- const staticPaths = await getAllRoutes();
+ const sitemapData = await sitemap();
+ const urls = sitemapData.map((item) => item.url.replace('https://bsmhub.vercel.app', 'http://localhost:3000'));
// 2. Lighthouse CI ์ค์ ๊ฐ์ฒด๋ฅผ ๋ง๋ญ๋๋ค.
const config = {
ci: {
collect: {
- url: staticPaths.map((path) => `http://localhost:3000${path}`),
+ url: urls,
startServerCommand: 'npm run start',
},
assert: {
@@ -50,4 +51,4 @@ async function generateConfig() {
}
// ์คํฌ๋ฆฝํธ ์คํ
-generateConfig();
+generateConfig();
\ No newline at end of file
diff --git a/jules-scratch/verification/check_portfolio_console.py b/jules-scratch/verification/check_portfolio_console.py
new file mode 100644
index 00000000..f5485209
--- /dev/null
+++ b/jules-scratch/verification/check_portfolio_console.py
@@ -0,0 +1,24 @@
+from playwright.sync_api import sync_playwright
+
+def run(playwright):
+ browser = playwright.chromium.launch(headless=True)
+ context = browser.new_context()
+ page = context.new_page()
+
+ # Collect console messages
+ messages = []
+ page.on("console", lambda msg: messages.append(f"{msg.type}: {msg.text}"))
+
+ # Go to a portfolio page
+ page.goto("http://localhost:3000/portfolio/Minjae-Kwon")
+ page.wait_for_load_state("domcontentloaded")
+
+ # Print console messages
+ for msg in messages:
+ print(msg)
+
+ context.close()
+ browser.close()
+
+with sync_playwright() as playwright:
+ run(playwright)
\ No newline at end of file
diff --git a/jules-scratch/verification/find_bad_requests.py b/jules-scratch/verification/find_bad_requests.py
new file mode 100644
index 00000000..42fe9a3f
--- /dev/null
+++ b/jules-scratch/verification/find_bad_requests.py
@@ -0,0 +1,23 @@
+from playwright.sync_api import sync_playwright
+
+def run(playwright):
+ browser = playwright.chromium.launch(headless=True)
+ context = browser.new_context()
+ page = context.new_page()
+
+ def handle_response(response):
+ if response.status == 400:
+ print(f"Bad Request URL: {response.url}")
+ print(f"Request Method: {response.request.method}")
+ # print(f"Request Post Data: {response.request.post_data}")
+
+ page.on("response", handle_response)
+
+ # Go to a portfolio page
+ page.goto("http://localhost:3000/portfolio/Minjae-Kwon", wait_until="networkidle")
+
+ context.close()
+ browser.close()
+
+with sync_playwright() as playwright:
+ run(playwright)
\ No newline at end of file
diff --git a/next.config.ts b/next.config.ts
index 60ef0e67..4dc948b8 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -9,7 +9,8 @@ const nextConfig: NextConfig = {
'bsmhubsp.insert.team',
],
},
- output: 'standalone',
+ // output: 'standalone',
+ productionBrowserSourceMaps: true,
};
export default nextConfig;
diff --git a/src/app/components/layout/Tabs.tsx b/src/app/components/layout/Tabs.tsx
index bb4c2331..3fefd478 100644
--- a/src/app/components/layout/Tabs.tsx
+++ b/src/app/components/layout/Tabs.tsx
@@ -28,7 +28,7 @@ const Tabs = ({ tabs }: TabsProps) => {
return (
{
const { isOpen, modalContent, closeModal } = useModal();
diff --git a/src/app/components/modal/inputs/SingleInput.tsx b/src/app/components/modal/inputs/SingleInput.tsx
index d967735b..d8c7d692 100644
--- a/src/app/components/modal/inputs/SingleInput.tsx
+++ b/src/app/components/modal/inputs/SingleInput.tsx
@@ -51,7 +51,9 @@ function Inputs({
`}
/>
{icon && iconMap[icon] && (
-
+
)}
);
diff --git a/src/app/components/modal/inputs/SkillTag.tsx b/src/app/components/modal/inputs/SkillTag.tsx
index fdc754bf..584aa3a9 100644
--- a/src/app/components/modal/inputs/SkillTag.tsx
+++ b/src/app/components/modal/inputs/SkillTag.tsx
@@ -3,7 +3,7 @@
// import { IconX } from "@tabler/icons-react";
// import { useRef, useEffect } from "react";
// // import AutosizeInput from 'react-input-autosize';
-// import './common/common.css';
+import './common/common.css';
// type WriteProps = {
// mode: 'write';
diff --git a/src/app/editor/page.tsx b/src/app/editor/page.tsx
index da3985ec..4d6b1ff9 100644
--- a/src/app/editor/page.tsx
+++ b/src/app/editor/page.tsx
@@ -12,5 +12,9 @@ export default function Editor() {
.catch((err) => console.error('Failed to load rich text editor:', err));
}, []);
- return
{RichTextEditorComponent && }
;
+ return (
+
+ {RichTextEditorComponent && }
+
+ );
}
diff --git a/src/app/globals.css b/src/app/globals.css
index bc0189a6..ad727c77 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -26,6 +26,7 @@ body {
font-family: 'Threat';
font-style: normal;
font-weight: 400;
+ font-display: swap;
src:
local('Threat'),
url('https://fonts.cdnfonts.com/s/95719/Threat-2OAeX.woff') format('woff');
@@ -35,6 +36,7 @@ body {
font-family: 'Material Symbols Outlined';
font-style: normal;
font-weight: 400;
+ font-display: swap;
src: url(https://fonts.gstatic.com/s/materialsymbolsoutlined/v215/kJF1BvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oDMzByHX9rA6RzaxHMPdY43zj-jCxv3fzvRNU22ZXGJpEpjC_1v-p_4MrImHCIJIZrDCvHOejbd5zrDAt.woff2)
format('woff2');
}
@@ -59,6 +61,7 @@ body {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
+ font-display: swap;
src: url(https://fonts.gstatic.com/s/materialicons/v142/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2)
format('woff2');
}
@@ -101,5 +104,5 @@ input[type="search"]::-webkit-search-results-decoration {
}
html {
- font-size: 12px;
+ font-size: 16px;
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 8fc53ddf..dd503b9a 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,8 +1,6 @@
import Footer from './components/layout/Footer';
import './globals.css';
import './responsive.css';
-import '@components/modal/inputs/common/common.css';
-import '@components/modal/modal.css';
import Header from '@components/layout/Header';
import { ModalProvider, Modal } from '@components/modal';
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 1936eb67..cfeae1ea 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -2,12 +2,11 @@
import { useEffect } from 'react';
// import Detail from '@components/detail/Detail';
-import Buttons from '@components/modal/inputs/Buttons';
-import Inputs from '@components/modal/inputs/SingleInput';
-import PictureUpload from '@components/modal/inputs/PictureUpload';
-import LabelInputs from '@components/modal/inputs/LabelOfInputs';
import InputListProvider from '@components/modal/inputs/InputListProvider';
-import InputOfModal from '@components/modal/inputs/InputOfModal';
+import dynamic from 'next/dynamic';
+const InputOfModal = dynamic(
+ () => import('@components/modal/inputs/InputOfModal'),
+);
import { FormConfig } from '@components/modal/inputs/types/inputTypes';
import { useModal } from '@components/modal';
import Checkbox from './components/modal/inputs/Checkbox';
@@ -104,33 +103,6 @@ export default function Home() {
return (
- {/* ๊ธฐ์กด ์ปดํฌ๋ํธ ํ
์คํธ */}
-
-
๊ฐ๋ณ ์ปดํฌ๋ํธ ํ
์คํธ
-
-
-
-
-
-
-
-
-
-
-
-
{/* React Hook Form ํตํฉ ํผ */}
diff --git a/src/app/portfolio/Portfolio.tsx b/src/app/portfolio/Portfolio.tsx
index 64840266..007cedc7 100644
--- a/src/app/portfolio/Portfolio.tsx
+++ b/src/app/portfolio/Portfolio.tsx
@@ -1,4 +1,3 @@
-import Image from 'next/image';
import { Body, TitleEN } from '../components/system/text';
import Tabs from '../components/layout/Tabs';
@@ -23,12 +22,17 @@ const Portfolio = async ({ profileName, path = 'home' }: PortfolioProps) => {
const uuid = profile.profile_id;
const studentInfo = profile.profile_permission[0].student;
- const profileDetail = await getProfileDetail(profileName);
- const cooperationProjects = await getCooperationProjects(uuid);
- const personalProjects = (await getPersonalProjects(uuid)).map((project) => ({
+ const [profileDetail, cooperationProjects, personalProjectsResult] =
+ await Promise.all([
+ getProfileDetail(profileName),
+ getCooperationProjects(uuid),
+ getPersonalProjects(uuid),
+ ]);
+
+ const personalProjects = personalProjectsResult.map((project) => ({
...project,
authors: [
- { profileImage: convertFromDatabaseImageURL(profile.profile_image) }, // getPersonalProjects ํจ์์์ Join์ผ๋ก ๊ฐ์ ธ์ค์ง ์์ ๊ฐ์ธ ํ๋กํ ์ด๋ฏธ์ง ์ถ๊ฐ
+ { profileImage: convertFromDatabaseImageURL(profile.profile_image) },
],
}));
@@ -58,12 +62,13 @@ const Portfolio = async ({ profileName, path = 'home' }: PortfolioProps) => {
return (
-
{profile.profile_name}
diff --git a/src/app/portfolio/SearchTab.tsx b/src/app/portfolio/SearchTab.tsx
index 508967cb..f5671839 100644
--- a/src/app/portfolio/SearchTab.tsx
+++ b/src/app/portfolio/SearchTab.tsx
@@ -2,7 +2,10 @@
import Link from 'next/link';
import { useState } from 'react';
-import PortfolioCard from '../components/card/portfolio/PortfolioCard';
+import dynamic from 'next/dynamic';
+const PortfolioCard = dynamic(
+ () => import('../components/card/portfolio/PortfolioCard'),
+);
import { PortfolioData } from './types';
import Inputs from '../components/modal/inputs/SingleInput';
import Checkbox from '../components/modal/inputs/Checkbox';
@@ -143,6 +146,7 @@ export default function SearchTab({
diff --git a/src/app/portfolio/[profileName]/getPortfolioParams.ts b/src/app/portfolio/[profileName]/getPortfolioParams.ts
new file mode 100644
index 00000000..dfb242eb
--- /dev/null
+++ b/src/app/portfolio/[profileName]/getPortfolioParams.ts
@@ -0,0 +1,8 @@
+import { getPersonalPortfolioData } from '@/services/server/portfolio/getPersonalPortfolioData';
+
+export async function getPortfolioParams() {
+ const portfolioData = await getPersonalPortfolioData();
+ return portfolioData.map((data) => ({
+ profileName: encodeURIComponent(data.profile.name),
+ }));
+}
\ No newline at end of file
diff --git a/src/app/portfolio/[profileName]/page.tsx b/src/app/portfolio/[profileName]/page.tsx
index 7c9e0288..80fdbf6d 100644
--- a/src/app/portfolio/[profileName]/page.tsx
+++ b/src/app/portfolio/[profileName]/page.tsx
@@ -1,4 +1,9 @@
import Portfolio from '../Portfolio';
+import { getPortfolioParams } from './getPortfolioParams';
+
+export async function generateStaticParams() {
+ return getPortfolioParams();
+}
interface PortfolioProps {
params: Promise<{ profileName: string }>;
diff --git a/src/app/sitemap.xml/sitemap.ts b/src/app/sitemap.xml/sitemap.ts
index 7a024f33..2d3c640a 100644
--- a/src/app/sitemap.xml/sitemap.ts
+++ b/src/app/sitemap.xml/sitemap.ts
@@ -1,115 +1,23 @@
-import fs from 'fs';
-import path from 'path';
-
+import { getPortfolioParams } from '../portfolio/[profileName]/getPortfolioParams';
import type { MetadataRoute } from 'next';
const siteConfig = {
- url: process.env.SITE_URL || 'https://example.com',
+ url: process.env.SITE_URL || 'https://bsmhub.vercel.app',
};
-const exclusiveRoutes = ['/auth*'];
-
-// Check if a route should be excluded based on exclusiveRoutes patterns
-function shouldExcludeRoute(route: string): boolean {
- return exclusiveRoutes.some((pattern) => {
- if (pattern.includes('*')) {
- // Convert wildcard pattern to regex
- const regexPattern = pattern.replace(/\\/g, '\\\\').replace(/\*/g, '.*').replace(/\//g, '\\/');
- const regex = new RegExp(`^${regexPattern}$`);
- return regex.test(route);
- }
- return route === pattern;
- });
-}
-
-// Recursively collect all pages with `page.tsx` or `page.jsx`
-export async function getStaticRoutes(
- dir = 'src/app', // Updated default directory to match the actual structure
- parentPath = '',
-): Promise
{
- const currentDir = path.join(process.cwd(), dir);
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
-
- let routes: string[] = [];
-
- for (const entry of entries) {
- const fullPath = path.join(currentDir, entry.name);
-
- if (entry.isDirectory()) {
- const routePath = path.join(parentPath, entry.name);
-
- // Collect possible page file paths
- const possiblePageFiles = ['page.tsx', 'page.jsx'].map((file) =>
- path.join(fullPath, file),
- );
- // Check if any of the possible page files exist
- const hasPage = possiblePageFiles.some((filePath) =>
- fs.existsSync(filePath),
- );
-
- if (hasPage) {
- routes.push(`/${routePath}`);
- }
-
- // Continue scanning nested folders recursively
- const nestedRoutes = await getStaticRoutes(
- path.join(dir, entry.name),
- routePath,
- );
-
- // Excludes routes with dynamic segments (e.g., [slug])
- const nestedStaticRoutes = nestedRoutes.filter(
- (route) => !route.match(/\[.+\]/),
- );
-
- routes = routes.concat(nestedStaticRoutes);
- }
- }
-
- return parentPath === '' ? ['/', ...routes] : routes;
-}
-
-export interface StaticParam {
- [key: string]: string;
-}
-
-// Get dynamic routes by calling `generateStaticParams` from dynamic pages
-async function getDynamicRoutes(
- subpath: string,
- dynamicSegment: string,
-): Promise {
- const filePath = path.join(process.cwd(), 'src/app', subpath);
- try {
- const staticParamsGenerator = (
- await import(
- path.join(filePath, `[${dynamicSegment}]`, 'staticParamsGenerator')
- )
- ).default;
- const params = (await staticParamsGenerator()) as string[];
- return params.map((route) => `/${subpath}/${route}`);
- } catch (error) {
- console.error('Error loading dynamic routes:', error);
- return []; // Return empty array on error
- }
-}
-
-export async function getAllRoutes(): Promise {
- return Promise.all([
- getStaticRoutes(),
- getDynamicRoutes('portfolio', 'profileName'),
- ])
- .then((results) => results.flat())
- .then((allRoutes) =>
- allRoutes.filter((route) => !shouldExcludeRoute(route)),
- );
-}
-
export default async function sitemap(): Promise {
- const allRoutes = await getAllRoutes();
+ const staticRoutes = ['/', '/editor', '/portfolio'];
- return allRoutes.map((route) => ({
- url: encodeURI(`${siteConfig.url}${route}`),
+ const portfolioParams = await getPortfolioParams();
+ const portfolioRoutes = portfolioParams.map((param) => ({
+ url: `${siteConfig.url}/portfolio/${param.profileName}`,
lastModified: new Date().toISOString(),
- priority: route === '/' ? 1 : 0.8,
}));
-}
+
+ const routes = staticRoutes.map((route) => ({
+ url: `${siteConfig.url}${route}`,
+ lastModified: new Date().toISOString(),
+ }));
+
+ return [...routes, ...portfolioRoutes];
+}
\ No newline at end of file
diff --git a/src/services/server/profile/getProfileDetail.ts b/src/services/server/profile/getProfileDetail.ts
index b1c22a6d..bb72dbe3 100644
--- a/src/services/server/profile/getProfileDetail.ts
+++ b/src/services/server/profile/getProfileDetail.ts
@@ -30,7 +30,7 @@ export const getProfileDetail = async (profileName: string): Promise ({
+ datas: (data?.profile_permission?.[0]?.student.student_certificates ?? []).map((item) => ({
value: item.certificates.certificate_name
}))
},
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 488a8654..ae972cfd 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -57,8 +57,8 @@ const config: Config = {
'light-gray-footer-bg': '#F5F5F5',
'light-gray': '#EAEAEC',
'gray-footer': '#51515C',
- 'placeholder-gray': '#858587',
- 'gray-base': '#5E5E5E',
+ 'placeholder-gray': '#757575',
+ 'gray-base': '#4A4A4A',
black: '#131313',
'blue-primary': '#1462FF',
'red-primary': '#FD462D',