diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..510e9410 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,201 @@ +name: CI/CD Pipeline + +on: + push: + branches: + - main + - dev + pull_request: + types: [opened, synchronize, reopened, closed] + +concurrency: + group: "CI-${{ github.ref_name }}-${{ github.event.number || github.sha }}" + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + # Build job for all scenarios + build: + runs-on: [self-hosted, core] + if: github.event.action != 'closed' + outputs: + image-tag: ${{ steps.image-tag.outputs.tag }} + port: ${{ steps.port.outputs.port }} + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Determine image tag and port + id: image-tag + run: | + if [ "${{ github.ref_name }}" = "main" ]; then + echo "tag=latest" >> $GITHUB_OUTPUT + elif [ "${{ github.ref_name }}" = "dev" ]; then + echo "tag=dev" >> $GITHUB_OUTPUT + else + echo "tag=pr-${{ github.event.number }}" >> $GITHUB_OUTPUT + fi + + - name: Determine port for PR + id: port + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + # Calculate port: 3100 + PR number (ensures unique ports) + PORT=$((3100 + ${{ github.event.number }})) + echo "port=$PORT" >> $GITHUB_OUTPUT + fi + + - name: Create .env file + run: | + echo "NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}" >> .env + echo "NEXT_PUBLIC_BASE_URL_ATTACHMENT=${{ secrets.NEXT_PUBLIC_BASE_URL_ATTACHMENT }}" >> .env + + - name: Login to Docker Hub + run: docker login -u mrbadri -p ${{ secrets.DOCKER_TOKEN }} + + - name: Build Docker Image + run: | + TAG=${{ steps.image-tag.outputs.tag }} + if [ "${{ github.ref_name }}" = "main" ]; then + docker compose -f docker-compose.prod.yml build + elif [ "${{ github.ref_name }}" = "dev" ]; then + docker compose -f docker-compose.dev.yml build + docker tag mrbadri/pixel-client:dev mrbadri/pixel-client:$TAG + else + # For PR - build and tag + docker build -t mrbadri/pixel-client:$TAG -f ./apps/core/Dockerfile . + fi + + - name: Push Docker Image to Docker Hub + run: | + TAG=${{ steps.image-tag.outputs.tag }} + docker push mrbadri/pixel-client:$TAG + + # Deploy to production (main branch) + deploy-production: + runs-on: [self-hosted, core] + needs: build + if: github.ref_name == 'main' && github.event_name == 'push' + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Create .env file + run: | + echo "NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}" >> .env + echo "NEXT_PUBLIC_BASE_URL_ATTACHMENT=${{ secrets.NEXT_PUBLIC_BASE_URL_ATTACHMENT }}" >> .env + + - name: Deploy Production + run: | + docker compose -f docker-compose.prod.yml pull client + docker compose -f docker-compose.prod.yml down || true + docker compose -f docker-compose.prod.yml up -d + + # Deploy to development (dev branch) + deploy-development: + runs-on: [self-hosted, core] + needs: build + if: github.ref_name == 'dev' && github.event_name == 'push' + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Create .env file + run: | + echo "NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}" >> .env + echo "NEXT_PUBLIC_BASE_URL_ATTACHMENT=${{ secrets.NEXT_PUBLIC_BASE_URL_ATTACHMENT }}" >> .env + + - name: Deploy Development + run: | + docker compose -f docker-compose.dev.yml pull client-dev + docker compose -f docker-compose.dev.yml down || true + docker compose -f docker-compose.dev.yml up -d + + # Deploy PR preview + deploy-preview: + runs-on: [self-hosted, core] + needs: build + if: github.event_name == 'pull_request' && github.event.action != 'closed' + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Create .env file + run: | + echo "NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}" >> .env + echo "NEXT_PUBLIC_BASE_URL_ATTACHMENT=${{ secrets.NEXT_PUBLIC_BASE_URL_ATTACHMENT }}" >> .env + + - name: Create PR-specific compose file + run: | + PR_NUMBER=${{ github.event.number }} + PORT=${{ needs.build.outputs.port }} + + # Create PR-specific docker-compose file + sed "s/PR_NUMBER/$PR_NUMBER/g; s/PORT_NUMBER/$PORT/g" docker-compose.preview.yml > docker-compose.pr-$PR_NUMBER.yml + + - name: Deploy Preview + run: | + PR_NUMBER=${{ github.event.number }} + + # Pull the image + docker pull mrbadri/pixel-client:pr-$PR_NUMBER + + # Stop and remove existing preview if exists + docker compose -f docker-compose.pr-$PR_NUMBER.yml down || true + + # Start the preview + docker compose -f docker-compose.pr-$PR_NUMBER.yml up -d + + - name: Comment PR with preview link + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: preview-deployment + message: | + ### πŸš€ Preview Deployed Successfully! + + Your pull request preview is now available: + - πŸ“ **Preview URL**: http://82.115.24.87:${{ needs.build.outputs.port }} + - πŸ”— **Container**: `client-preview-${{ github.event.number }}` + - 🐳 **Image**: `mrbadri/pixel-client:pr-${{ github.event.number }}` + + This preview will be automatically cleaned up when the PR is closed. + + # Cleanup PR preview when closed + cleanup-preview: + runs-on: [self-hosted, core] + if: github.event_name == 'pull_request' && github.event.action == 'closed' + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Cleanup PR preview + run: | + PR_NUMBER=${{ github.event.number }} + + # Stop and remove preview containers + docker compose -f docker-compose.pr-$PR_NUMBER.yml down || true + + # Remove the compose file + rm -f docker-compose.pr-$PR_NUMBER.yml || true + + # Remove the Docker image + docker rmi mrbadri/pixel-client:pr-$PR_NUMBER || true + + - name: Post Cleanup Confirmation Comment + if: always() + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: preview-cleanup + message: | + ### 🧹 Preview Cleanup Complete + + All preview deployment resources for this pull request have been successfully removed: + - 🐳 Docker container `client-preview-${{ github.event.number }}` stopped and removed + - πŸ“¦ Docker image `mrbadri/pixel-client:pr-${{ github.event.number }}` cleaned up + - πŸ“ Docker compose file removed + + The cleanup process has finished for PR #${{ github.event.number }}. diff --git a/apps/core/Dockerfile b/apps/core/Dockerfile index 649135cc..b0e216cd 100644 --- a/apps/core/Dockerfile +++ b/apps/core/Dockerfile @@ -9,7 +9,7 @@ RUN apk update RUN apk add --no-cache libc6-compat # Set working directory WORKDIR /app -RUN yarn global add turbo +RUN npm install -g pnpm turbo COPY . . # RUN npm run prepare:ci RUN turbo prune core --docker @@ -19,13 +19,12 @@ FROM base AS installer RUN apk update RUN apk add --no-cache libc6-compat WORKDIR /app +RUN npm install -g pnpm turbo # First install the dependencies (as they change less often) COPY --from=builder /app/out/json/ . -# Configure Yarn with better network settings and use Taobao registry -RUN yarn config set registry https://registry.npmmirror.com/ && \ - yarn config set network-timeout 600000 && \ - yarn install --network-timeout 600000 --no-network-concurrency +# Configure pnpm with better network settings and use Taobao registry +RUN pnpm install --frozen-lockfile # Build the project COPY --from=builder /app/out/full/ . @@ -37,7 +36,7 @@ COPY --from=builder /app/out/full/ . # ARG TURBO_TOKEN # ENV TURBO_TOKEN=$TURBO_TOKEN -RUN yarn turbo build --filter=core +RUN pnpm turbo build FROM base AS runner WORKDIR /app @@ -47,6 +46,10 @@ RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs USER nextjs +# Environment variables +ENV NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL} +ENV NEXT_PUBLIC_BASE_URL_ATTACHMENT=${NEXT_PUBLIC_BASE_URL_ATTACHMENT} + # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=installer --chown=nextjs:nodejs /app/apps/core/.next/standalone ./ diff --git a/apps/core/app/(landing)/(home)/[tab]/page.tsx b/apps/core/app/(landing)/(home)/[tab]/page.tsx new file mode 100644 index 00000000..e670a90f --- /dev/null +++ b/apps/core/app/(landing)/(home)/[tab]/page.tsx @@ -0,0 +1,19 @@ +import { LandingPage } from "../../_components/landing-page"; + +export const revalidate = 60; + +export default async function Page({ + params, +}: { + params: Promise<{ + tab: string; + }>; + searchParams: Promise<{ + [key: string]: string | string[] | undefined; + }>; +}) { + const { tab } = await params; + + console.log(tab); + return ; +} diff --git a/apps/core/app/(landing)/(home)/layout.tsx b/apps/core/app/(landing)/(home)/layout.tsx new file mode 100644 index 00000000..fa599c72 --- /dev/null +++ b/apps/core/app/(landing)/(home)/layout.tsx @@ -0,0 +1,21 @@ +import { BackgroundLanding } from "../_components/background-landing"; +import { TextGenerateEffect } from "../_components/TextGenerateEffect"; + +const words = `Find thousands of meticulously crafted resources by pixel geniuses to supercharge your creativity.`; + +const Layout = ({ children }: { children: React.ReactNode }) => { + return ( +
+ {/* Background */} + + {/* Header */} +
+ +
+ + {children} +
+ ); +}; + +export default Layout; diff --git a/apps/core/app/(landing)/(home)/page.tsx b/apps/core/app/(landing)/(home)/page.tsx new file mode 100644 index 00000000..e2cae342 --- /dev/null +++ b/apps/core/app/(landing)/(home)/page.tsx @@ -0,0 +1,8 @@ +import { LandingPage } from "../_components/landing-page"; + +// ISR 60s +export const revalidate = 60; + +export default function Page() { + return ; +} diff --git a/apps/core/app/(landing)/_assets/earth.svg b/apps/core/app/(landing)/_assets/earth.svg new file mode 100644 index 00000000..c019a078 --- /dev/null +++ b/apps/core/app/(landing)/_assets/earth.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/core/app/(landing)/_components/background-landing.tsx b/apps/core/app/(landing)/_components/background-landing.tsx new file mode 100644 index 00000000..d8fc9aee --- /dev/null +++ b/apps/core/app/(landing)/_components/background-landing.tsx @@ -0,0 +1,131 @@ +import Image from "next/image"; +import { DashedCircles } from "@repo/ui/components"; +import blender from "../_assets/blender.svg"; +import planet from "../_assets/planet.svg"; +import figma from "../_assets/figma.svg"; +import shadowBGLanding from "../_assets/shadow-bg.svg"; +import sketch from "../_assets/sketch.svg"; + +export function BackgroundLanding() { + return ( + <> + {/* Background Shadow */} + bg-landing + +
+ + bg-landing + +
+ +
+ + bg-landing + +
+ +
+ + bg-landing + +
+ +
+ + bg-landing + +
+ + ); +} diff --git a/apps/core/app/(landing)/_components/desktop-navbar/navbar-links.tsx b/apps/core/app/(landing)/_components/desktop-navbar/navbar-links.tsx index fc0c3a0f..f0e3a4b6 100644 --- a/apps/core/app/(landing)/_components/desktop-navbar/navbar-links.tsx +++ b/apps/core/app/(landing)/_components/desktop-navbar/navbar-links.tsx @@ -18,10 +18,7 @@ const NavbarLinks = () => { Browse - Become an author - - ); diff --git a/apps/core/app/(landing)/_components/desktop-navbar/navbar.tsx b/apps/core/app/(landing)/_components/desktop-navbar/navbar.tsx index 05b7012d..f3a091a9 100644 --- a/apps/core/app/(landing)/_components/desktop-navbar/navbar.tsx +++ b/apps/core/app/(landing)/_components/desktop-navbar/navbar.tsx @@ -1,81 +1,271 @@ "use client"; import PixelIcon from "@repo/icons/pxiel"; import SearchIcon from "@repo/icons/serach"; -import { useRef, useState } from "react"; -import Addtocard from "../addtoCard"; -import NavbarLinks from "./navbar-links"; -import FeaturesNavbar from "./features-navbar"; -import Searchbar, { RefSearchHandle } from "./search-bar"; -import { BrowseMegaMenu } from "../browseMegaMenu/browse-mega-menu"; +import XIcon from "@repo/icons/x"; +import { BaseInput, Chip } from "@repo/ui/components"; +import { cn } from "@repo/ui/lib/utils"; +import { AnimatePresence, motion } from "framer-motion"; +import Image from "next/image"; +import Link from "next/link"; +import { useState } from "react"; +import earth from "../../_assets/earth.svg"; import { useMegaMenuStore } from "../../store/mega-menu"; -import { useCartStore } from "../../store/cart-store"; +import { BrowseMegaMenu } from "../browseMegaMenu/browse-mega-menu"; import ShoppingBagDropdown from "../shopping-bag-dropdown"; -import Link from "next/link"; - +import FeaturesNavbar from "./features-navbar"; +import NavbarLinks from "./navbar-links"; const Navbar = ({ islogin }: { islogin: boolean }) => { const { isOpenMegaMenu } = useMegaMenuStore(); - const { isAddToCartOpen } = useCartStore(); const [isSearchActive, setIsSearchActive] = useState(false); - const [isSearchVisible, setIsSearchVisible] = useState(false); - - const refSerchHandle = useRef(null); const handleOpenSearch = () => { setIsSearchActive(true); - refSerchHandle.current?.focus(); }; const handleCloseSearch = () => { setIsSearchActive(false); }; - const isMobile = false; - return ( <> -