diff --git a/.github/workflows/deploy-app-walrus.yml b/.github/workflows/deploy-app-walrus.yml index ff5c5d3f..193b1299 100644 --- a/.github/workflows/deploy-app-walrus.yml +++ b/.github/workflows/deploy-app-walrus.yml @@ -64,6 +64,7 @@ jobs: VITE_MEMWAL_REGISTRY_ID: ${{ vars.VITE_MEMWAL_REGISTRY_ID }} VITE_SEAL_KEY_SERVERS: ${{ vars.VITE_SEAL_KEY_SERVERS }} VITE_DOCS_URL: ${{ vars.VITE_DOCS_URL }} + VITE_DEMO_URLS: ${{ vars.VITE_DEMO_URLS }} - name: Deploy to Walrus Site (Mainnet) id: deploy-mainnet diff --git a/.github/workflows/release-sdk.yml b/.github/workflows/release-sdk.yml index 65659e03..9823aac2 100644 --- a/.github/workflows/release-sdk.yml +++ b/.github/workflows/release-sdk.yml @@ -8,7 +8,6 @@ on: - dev paths: - 'packages/sdk/**' - - '.changeset/**' - '.github/workflows/release-sdk.yml' workflow_dispatch: @@ -47,34 +46,58 @@ jobs: run: pnpm build:sdk # ── main branch → stable release (latest) ── - - name: Create Release PR or Publish to npm + - name: Publish stable release if: github.ref == 'refs/heads/main' - uses: changesets/action@v1 - with: - title: 'chore: version packages' - commit: 'chore: version packages' - publish: pnpm changeset publish + run: | + BASE_VERSION=$(node -p "require('./packages/sdk/package.json').version") + npm view @mysten/memwal@$BASE_VERSION version 2>/dev/null \ + && echo "Version $BASE_VERSION already published, skipping" && exit 0 + cd packages/sdk && npm publish --access public env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - # ── staging branch → release candidate (rc tag) ── + # ── staging branch → release candidate (rc tag, auto-increment) ── - name: Publish staging release candidate if: github.ref == 'refs/heads/staging' run: | - PKG_VERSION=$(node -p "require('./packages/sdk/package.json').version") - npm view @mysten/memwal@$PKG_VERSION version 2>/dev/null && echo "Version $PKG_VERSION already published, skipping" && exit 0 - cd packages/sdk && npm publish --tag rc --access public + BASE_VERSION=$(node -p "require('./packages/sdk/package.json').version") + LATEST=$(npm view @mysten/memwal versions --json 2>/dev/null \ + | node -p " + const versions = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); + const nums = (Array.isArray(versions) ? versions : [versions]) + .filter(v => v.startsWith('${BASE_VERSION}-rc.')) + .map(v => +v.split('-rc.')[1]) + .sort((a,b) => b - a); + nums.length ? nums[0] : -1; + " || echo "-1") + NEXT=$((LATEST + 1)) + NEW_VERSION="${BASE_VERSION}-rc.${NEXT}" + echo "Publishing @mysten/memwal@${NEW_VERSION}" + cd packages/sdk + node -e "const p=require('./package.json');p.version='${NEW_VERSION}';require('fs').writeFileSync('./package.json',JSON.stringify(p,null,4)+'\n')" + npm publish --tag rc --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - # ── dev branch → prerelease (dev tag) ── + # ── dev branch → prerelease (dev tag, auto-increment) ── - name: Publish dev prerelease if: github.ref == 'refs/heads/dev' run: | - PKG_VERSION=$(node -p "require('./packages/sdk/package.json').version") - npm view @mysten/memwal@$PKG_VERSION version 2>/dev/null && echo "Version $PKG_VERSION already published, skipping" && exit 0 - cd packages/sdk && npm publish --tag dev --access public + BASE_VERSION=$(node -p "require('./packages/sdk/package.json').version") + LATEST=$(npm view @mysten/memwal versions --json 2>/dev/null \ + | node -p " + const versions = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); + const nums = (Array.isArray(versions) ? versions : [versions]) + .filter(v => v.startsWith('${BASE_VERSION}-dev.')) + .map(v => +v.split('-dev.')[1]) + .sort((a,b) => b - a); + nums.length ? nums[0] : -1; + " || echo "-1") + NEXT=$((LATEST + 1)) + NEW_VERSION="${BASE_VERSION}-dev.${NEXT}" + echo "Publishing @mysten/memwal@${NEW_VERSION}" + cd packages/sdk + node -e "const p=require('./package.json');p.version='${NEW_VERSION}';require('fs').writeFileSync('./package.json',JSON.stringify(p,null,4)+'\n')" + npm publish --tag dev --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/apps/app/.env.example b/apps/app/.env.example index bcb651fd..8eaaa605 100644 --- a/apps/app/.env.example +++ b/apps/app/.env.example @@ -16,6 +16,10 @@ VITE_MEMWAL_SERVER_URL=http://localhost:8000 # Docs URL (separate deployment) VITE_DOCS_URL=http://localhost:5174 +# Demo app links (comma-separated, format: Label|URL) +# Example: VITE_DEMO_URLS=Chat Demo|https://chat.example.com,Agent Demo|https://agent.example.com +VITE_DEMO_URLS= + # ══════════════════════════════════════════════════════════════ # ▷ INACTIVE: MAINNET (uncomment below, comment out TESTNET above) # ══════════════════════════════════════════════════════════════ diff --git a/apps/app/Dockerfile b/apps/app/Dockerfile index 4bc5d12c..f7f8fd18 100644 --- a/apps/app/Dockerfile +++ b/apps/app/Dockerfile @@ -35,6 +35,7 @@ ARG VITE_SUI_NETWORK=mainnet ARG VITE_ENOKI_API_KEY ARG VITE_GOOGLE_CLIENT_ID ARG VITE_DOCS_URL +ARG VITE_DEMO_URLS ENV VITE_MEMWAL_SERVER_URL=$VITE_MEMWAL_SERVER_URL ENV VITE_MEMWAL_PACKAGE_ID=$VITE_MEMWAL_PACKAGE_ID @@ -43,6 +44,7 @@ ENV VITE_SUI_NETWORK=$VITE_SUI_NETWORK ENV VITE_ENOKI_API_KEY=$VITE_ENOKI_API_KEY ENV VITE_GOOGLE_CLIENT_ID=$VITE_GOOGLE_CLIENT_ID ENV VITE_DOCS_URL=$VITE_DOCS_URL +ARG VITE_DEMO_URLS=$VITE_DEMO_URLS RUN pnpm --filter @memwal/app build diff --git a/apps/app/src/config.ts b/apps/app/src/config.ts index 4f510690..b14f7734 100644 --- a/apps/app/src/config.ts +++ b/apps/app/src/config.ts @@ -14,4 +14,10 @@ export const config = { .split(',').map(s => s.trim()).filter(Boolean) as string[], sidecarUrl: import.meta.env.VITE_SIDECAR_URL as string || 'http://localhost:9000', docsUrl: import.meta.env.VITE_DOCS_URL as string || '', + demoUrls: (import.meta.env.VITE_DEMO_URLS as string || '') + .split(',').map(s => s.trim()).filter(Boolean) + .map(entry => { + const [label, url] = entry.split('|').map(s => s.trim()) + return url ? { label, url } : { label: label, url: label } + }), } as const diff --git a/apps/app/src/index.css b/apps/app/src/index.css index 80ae5753..abd7d38d 100644 --- a/apps/app/src/index.css +++ b/apps/app/src/index.css @@ -515,6 +515,90 @@ h1, h2, h3 { box-shadow: 1px 1px 0 #000000; } +/* ── Demo Dropdown ── */ + +.lp-demo-dropdown { + position: relative; +} + +.lp-demo-trigger { + display: flex; + align-items: center; + gap: 4px; + background: none; + border: 2px solid #000000; + border-radius: 12px; + padding: 10px 18px; + font-size: 0.92rem; + font-weight: 700; + font-family: var(--font-sans); + cursor: pointer; + transition: transform 0.15s, box-shadow 0.15s; + box-shadow: 3px 3px 0 #000000; + color: #000000; + background: #ffffff; +} + +.lp-demo-trigger:hover { + transform: translate(-2px, -2px); + box-shadow: 5px 5px 0 #000000; +} + +.lp-demo-trigger:active { + transform: translate(2px, 2px); + box-shadow: 1px 1px 0 #000000; +} + +.lp-demo-chevron { + transition: transform 0.2s; +} + +.lp-demo-chevron.open { + transform: rotate(180deg); +} + +.lp-demo-menu { + position: absolute; + top: calc(100% + 8px); + right: 0; + min-width: 200px; + background: #ffffff; + border: 2px solid #000000; + border-radius: 12px; + box-shadow: 4px 4px 0 #000000; + padding: 6px; + z-index: 100; + animation: lp-dropdown-in 0.15s ease-out; +} + +@keyframes lp-dropdown-in { + from { + opacity: 0; + transform: translateY(-4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.lp-demo-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 14px; + color: #000000; + text-decoration: none; + font-size: 0.88rem; + font-weight: 600; + border-radius: 8px; + transition: background 0.1s; +} + +.lp-demo-item:hover { + background: #E8FF75; +} + /* ── Hero ── */ .lp-hero { diff --git a/apps/app/src/pages/LandingPage.tsx b/apps/app/src/pages/LandingPage.tsx index 2b9b6296..80ba3715 100644 --- a/apps/app/src/pages/LandingPage.tsx +++ b/apps/app/src/pages/LandingPage.tsx @@ -9,7 +9,8 @@ import { useWallets, } from '@mysten/dapp-kit' import { isEnokiWallet, type EnokiWallet, type AuthProvider } from '@mysten/enoki' -import { Github } from 'lucide-react' +import { ChevronDown, Github } from 'lucide-react' +import { useRef, useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { config } from '../config' import memwalLogo from '../assets/memwal-logo.svg' @@ -29,6 +30,19 @@ export default function LandingPage() { const navigate = useNavigate() const hasEnokiConfig = config.enokiApiKey && config.googleClientId + const demoUrls = config.demoUrls + const [demoOpen, setDemoOpen] = useState(false) + const demoRef = useRef(null) + + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (demoRef.current && !demoRef.current.contains(e.target as Node)) { + setDemoOpen(false) + } + } + document.addEventListener('mousedown', handleClickOutside) + return () => document.removeEventListener('mousedown', handleClickOutside) + }, []) const handleConnect = () => { if (currentAccount) { @@ -49,6 +63,32 @@ export default function LandingPage() {
+ {demoUrls.length > 0 && ( +
+ + {demoOpen && ( +
+ {demoUrls.map(({ label, url }) => ( + setDemoOpen(false)} + > + {label} + + ))} +
+ )} +
+ )} {currentAccount ? (