diff --git a/.github/workflows/preview-cleanup.yml b/.github/workflows/preview-cleanup.yml new file mode 100644 index 00000000..11a4cc78 --- /dev/null +++ b/.github/workflows/preview-cleanup.yml @@ -0,0 +1,34 @@ +name: PR Preview Cleanup (S3 Cleanup + CloudFront 무효화) + +on: + pull_request: + types: [closed] + +jobs: + cleanup-preview: + runs-on: ubuntu-latest + + env: + BUCKET_NAME: starsync + + steps: + - name: AWS 인증 정보 설정 + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Preview S3 파일 삭제 (PR 닫힐 때) + run: | + PREVIEW_PATH="preview/pr-${{ github.event.pull_request.number }}" + echo "🧹 S3 경로 삭제 중: s3://$BUCKET_NAME/$PREVIEW_PATH/" + aws s3 rm s3://$BUCKET_NAME/$PREVIEW_PATH/ --recursive + + - name: CloudFront 캐시 무효화 (Preview PR 경로) + run: | + INVALIDATION_PATH="/preview/pr-${{ github.event.pull_request.number }}/*" + echo "🌀 캐시 무효화 대상: $INVALIDATION_PATH" + aws cloudfront create-invalidation \ + --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \ + --paths "$INVALIDATION_PATH" diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 00000000..b5b748dc --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,54 @@ +name: PR Preview Deploy to S3 and CloudFront + +on: + pull_request: + branches: + - develop + - main + +permissions: + pull-requests: write + contents: read + +jobs: + Deploy: + runs-on: ubuntu-latest + + env: + BUCKET_NAME: starsync + VITE_BASE_URL: ${{ secrets.VITE_BASE_URL }} + + steps: + - name: Github Repository 파일 불러오기 + uses: actions/checkout@v4 + + - name: Node.js 환경 설정 + uses: actions/setup-node@v4 + with: + node-version: 22.14.0 + + - name: 의존성 설치 (npm ci) + run: npm ci + + - name: 프로젝트 빌드 (Vite) + run: npm run build -- --mode production + + - name: AWS 인증 정보 설정 + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Preview S3 업로드 (PR 번호 기준) + run: | + PREVIEW_PATH="preview/pr-${{ github.event.pull_request.number }}" + echo "Preview 경로: s3://$BUCKET_NAME/$PREVIEW_PATH/" + aws s3 sync dist/ s3://$BUCKET_NAME/$PREVIEW_PATH/ --delete + + - name: PR에 Preview 링크 남기기 + uses: marocchino/sticky-pull-request-comment@v2 + with: + message: | + 🚀 **PR Preview 배포 완료!** + 🔗 [Preview 확인하기](https://preview.starsync.wiki/preview/pr-${{ github.event.pull_request.number }}) \ No newline at end of file diff --git a/biome.json b/biome.json index 66653488..d0c51b37 100644 --- a/biome.json +++ b/biome.json @@ -18,7 +18,7 @@ "attributePosition": "auto" }, "organizeImports": { - "enabled": true + "enabled": false }, "linter": { "enabled": true, diff --git a/src/App.jsx b/src/App.jsx index 32ad1446..35a48ff6 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { RouterProvider } from 'react-router-dom'; -import { SplashScreen } from './components/loadingStatus/splashScreen'; +import { AlertManager } from './components/alertManager'; +import { SplashScreen } from './components/loadingStatus'; export default function App({ router }) { const [showSplashScreen, setShowSplashScreen] = useState(true); @@ -19,5 +20,10 @@ export default function App({ router }) { () => clearInterval(splashScreenInterval); }, []); - return <>{showSplashScreen ? : }; + return ( + <> + + {showSplashScreen ? : } + + ); } diff --git a/src/Root.jsx b/src/Root.jsx index b2bd0358..e0068f38 100644 --- a/src/Root.jsx +++ b/src/Root.jsx @@ -1,24 +1,40 @@ +import { Outlet, useLocation, useNavigation } from 'react-router-dom'; import { Header } from '@/components/header'; import { LAYOUT } from '@/constants/layout'; -import { Outlet, useLocation, useNavigation } from 'react-router-dom'; -import { Footer } from './components/footer'; -import { PendingUI } from './components/loadingStatus/pendingUI'; +import { Footer } from '@/components/footer'; +import { PendingUI } from './components/loadingStatus'; function Root() { const navigation = useNavigation(); - const isLoading = navigation.state !== 'idle'; // 'loading' 또는 'submitting' + const location = useLocation(); + + const currentPath = location.pathname; + const nextPath = navigation.location?.pathname; + const isSamePath = currentPath === nextPath; - const { pathname } = useLocation(); - const isLanding = pathname === '/'; + const isLoading = navigation.state !== 'idle' && !isSamePath; + + const isLanding = currentPath === '/'; return ( <> - {isLoading && } - {!isLanding &&
} -
- -
- {!isLanding &&