diff --git a/.claude/skills/contribute/SKILL.md b/.claude/skills/contribute/SKILL.md new file mode 100644 index 0000000..a1e0512 --- /dev/null +++ b/.claude/skills/contribute/SKILL.md @@ -0,0 +1,41 @@ +--- +name: contribute +description: Implement design from a screenshot or a definition. Use model Claude Sonnet 4.5 for this task. +--- + +# Contribute + +## Overview + +Implements a design from a screenshot or a definition in a Extend Apps Directory structure. + +## When to Use + +Use this skill when: + +- You want to contribute to Extend Apps Directory. +- You are not familiar on how to contribute to Extend Apps Directory. + +## Implementation Principles + +1. Always follow semantic HTML. +2. DO NOT SKEW FROM THE DESIGN. Use exactly the same style _per element_. There may be flaws in the design, but it is what it is. +3. DO NOT CREATE ADDITIONAL FILES that are otherwise not required. Focus on the given task at hand. +4. If the prompt specifies a file (or files) that already exists, do not recreate them. +5. When running a code snippet (command line), use the following as terminal preference: + 1. Windows: Powershell 7. + 2. \*nix: Zsh. +6. Implement responsive design with mobile-first approach using CSS media queries. +7. For texts in the design, COPY THEM WORD FOR WORD. Do not deviate the text content from the design. Ensure that this not only covers a "free text", but also texts inside cards and text inside buttons. Ensure that there are no missing elements. + +## Instructions + +Make sure to keep the implementation principles above in mind when doing these steps. + +### Step 0: Verify requirements + +Ensure that you can establish connection to Figma MCP Server according [to the guide](https://developers.figma.com/docs/figma-mcp-server/remote-server-installation/#claude-code). If there is no MCP server named "figma", terminate the action. + +### Step 1: Implement the features + +Use the resources obtained from Figma MCP server to implement each of the sections. Ensure high quality of the implementation and it's pixel-perfect to what's provided in the design. \ No newline at end of file diff --git a/.claude/skills/create-plan/SKILL.md b/.claude/skills/create-plan/SKILL.md new file mode 100644 index 0000000..bc310c3 --- /dev/null +++ b/.claude/skills/create-plan/SKILL.md @@ -0,0 +1,26 @@ +--- +name: create-plan +description: "Create a detailed design plan from a raw plan." +--- + +# Plan + +## Overview + +Reads a file from `.claude/raw-plans` and creates a detailed plan in `.claude/plans` with the same file base name. + +## When to Use + +Use this skill when: + +- You want to contribute to Extend Apps Directory. +- You are not familiar on how to contribute to Extend Apps Directory. +- You want to make sure that your AI-assisted development can be as accurate as possible (reducing back and forth). + +## Flow + +Given a file name in the format of `.claude/raw-plans/{name}.md`, read that high-level plan and convert it into a detailed plan `.claude/plans/{name}.md`. Use all of the information in the raw plans and use it into the final plan. The final plan should contain these sections: + +1. Overview: an outline of the file. +2. Module breakdown (non-technical): visual elements, assets required, questions. +3. Module breakdown (technical): per-section high-level implementation detail. diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 7518933..5a0f7d3 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -2,8 +2,6 @@ name: Validate Extend Apps Directory on: pull_request: - paths: - - 'app/data/extend-apps-directory.json' jobs: validate: @@ -25,3 +23,6 @@ jobs: - name: Run prebuild script run: yarn prebuild + + - name: Build + run: yarn build diff --git a/.gitignore b/.gitignore index cc7c3b4..9b44b18 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,8 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -.yarn/install-state.gz \ No newline at end of file +.yarn/install-state.gz + +.claude/settings.local.json +.claude/plans +.claude/raw-plans \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..50c924b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,41 @@ +# Extend Apps Directory + +## Prerequisites + +- Node.js LTS version, ideally v24 +- Yarn modern (_with_ Corepack) + +Always start with `yarn` in the root project to install the dependencies before you run any scripts in `package.json`, if you haven't already. + +## Conventions + +- Use React for the UI library. +- Use Bootstrap utility classes, such as `d-flex` or `m-4` to style components. +- Use Ant Design library for the UI components. +- Always generate formatted code with Prettier. +- When generating JavaScript syntaxes, use the syntaxes available from the `module` field from the currently used `tsconfig.json`. +- For styling, follow these order: + - If possible to be styled built-in with Ant Design, use it. + - If the Ant Design style can't be overridden with Bootstrap class, then create a `*.module.css` file CLOSE TO THE component that uses it (in the same folder as the component). Sometimes it needs `!important` to override Ant Design styles, only use it when it can't be overridden in a "normal way" (e.g. using higher CSS selector specificity). +- When running a code snippet (command line), use the following as terminal preference: + - Windows: Powershell 7. + - \*nix: Bash/Zsh. +- When it comes to naming variables: + - Make sure booleans always start with `is`, `has`, or `can`. For example: `isModalOpen`, `hasQueryParameter`, `canUpdate`. + - Make sure functions always start with a verb or if it returns boolean, use boolean-y name. For example: `showModal()`, `hasQueryParameter()`. + +### React-specific conventions + +- When hiding modals, prefer using `open` prop as opposed to returning `null`. For example: + +```tsx +// Don't do this: +if (!isOpen) return null + +// Instead: + +``` + +## Testing + +Run `yarn prebuild && yarn typecheck` from the root project. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/README.md b/README.md index 6876780..0b92fb9 100644 --- a/README.md +++ b/README.md @@ -50,16 +50,30 @@ yarn start ## Project Structure -- `app/`: Contains the main application code. - - `assets/`: Static assets like images and logos. - - `components/`: Reusable React components. - - `data/`: JSON data for the Extend apps. - - `routes/`: Route components for different pages. - - `types/`: TypeScript type definitions. - - `app.css`: Global CSS styles. - - `root.tsx`: The root component of the application. - - `routes.ts`: Route configuration. -- `public/`: Public assets that are not processed by Vite. -- `vite.config.ts`: Vite configuration file. -- `package.json`: Project dependencies and scripts. -- `tsconfig.json`: TypeScript configuration. +- `app/`: Contains the main application code. + - `assets/`: Static assets like images and logos. + - `components/`: Reusable React components. + - `data/`: JSON data for the Extend apps. + - `routes/`: Route components for different pages. + - `types/`: TypeScript type definitions. + - `app.css`: Global CSS styles. + - `root.tsx`: The root component of the application. + - `routes.ts`: Route configuration. +- `public/`: Public assets that are not processed by Vite. +- `vite.config.ts`: Vite configuration file. +- `package.json`: Project dependencies and scripts. +- `tsconfig.json`: TypeScript configuration. + +## AI integration + +### Requirements + +- Download Claude Code: https://code.claude.com/docs/en/quickstart +- Set up Figma MCP server: https://developers.figma.com/docs/figma-mcp-server/remote-server-installation/#claude-code. + +### How-to + +- Copy `resources/plan.md` into `.claude/raw-plans/{page_name}.md`. For example: `.claude/raw-plans/{feature_name}.md`. +- Open Claude Code, make sure the model is Sonnet 4.5 (or higher). After that, run this: `/create-plan from .claude/raw-plans/{feature_name}.md.`. +- Answer the questions provided by the AI agent in the new plan document `.claude/plans/{feature_name}.md`. +- Inside Claude Code, run `/contribute with the context of .claude/plans/{feature_name}-sonnet.md.`. diff --git a/app/components/ContributingGuideModal.module.css b/app/components/ContributingGuideModal.module.css new file mode 100644 index 0000000..e5ddbea --- /dev/null +++ b/app/components/ContributingGuideModal.module.css @@ -0,0 +1,73 @@ +.modal { + max-width: 100%; +} + +@media (min-width: 768px) { + .modal { + width: 640px !important; + } +} + +.modalHeader { + gap: 12px; + padding: 20px 24px; + border-bottom: 1px solid #f0f0f0; +} + +.modalTitle { + margin: 0px !important; +} + +.modalContent { + padding: 24px; +} + +.instructionsList { + margin: 0; + padding-left: 20px; + color: #000000a6; +} + +.instructionsList li { + margin-bottom: 0; + line-height: 32px; +} + +.instructionsList ol { + margin-top: 0; + margin-bottom: 0; +} + +.instructionsList ol li { + line-height: 32px; +} + +.instructionsList a { + text-decoration: underline; +} + +@media (max-width: 480px) { + :global(.ant-modal), + :global(.ant-modal-content) { + height: 100%; + width: 100vw; + margin: 0px !important; + top: 0px; + } + + :global(.ant-modal-content) { + border-radius: 0px !important; + } + + :global(.ant-modal-body) { + height: 100%; + } + + .modalTitle { + font-size: 20px !important; + } + + .modalContent { + min-height: 100vh; + } +} diff --git a/app/components/ContributingGuideModal.tsx b/app/components/ContributingGuideModal.tsx new file mode 100644 index 0000000..7e4f2de --- /dev/null +++ b/app/components/ContributingGuideModal.tsx @@ -0,0 +1,129 @@ +import { CloseOutlined, ExportOutlined, VideoCameraFilled } from '@ant-design/icons' +import { Alert, Button, Modal, Typography } from 'antd' +import { useEffect } from 'react' +import { useSearchParams } from 'react-router-dom' +import styles from './ContributingGuideModal.module.css' +import { CONTRIBUTING_GUIDELINE_MODAL_QUERY_PARAMETER } from '~/types/ui' + +const { Title, Paragraph, Text } = Typography + +// External link constants +const LINKS = { + PUBLIC_FOLDER: 'https://github.com/AccelByte/extend-apps-directory/tree/main/public', + JSON_DATA: 'https://github.com/AccelByte/extend-apps-directory/blob/main/app/data/extend-apps-directory.json', + REPOSITORY: 'https://github.com/AccelByte/extend-apps-directory', + VIDEO: 'https://www.youtube.com/watch?v=Y3YU5g6ri5I' +} as const + +export function ContributingGuideModal() { + const [searchParams, setSearchParams] = useSearchParams() + const isModalOpen = searchParams.get(CONTRIBUTING_GUIDELINE_MODAL_QUERY_PARAMETER) === 'true' + + useEffect(() => { + // Track modal view with Google Analytics + if (isModalOpen && window.gtag) { + window.gtag('event', 'view_contributing_guide', { + event_category: 'engagement', + event_label: 'Contributing Guide Modal Opened' + }) + } + }, [isModalOpen]) + + const handleClose = () => { + searchParams.delete(CONTRIBUTING_GUIDELINE_MODAL_QUERY_PARAMETER) + setSearchParams(searchParams, { preventScrollReset: true }) + } + + return ( + +
+ + Submit your app + + +
+
+
+
+ + Follow these steps to submit a Pull Request (PR) to our team for review and approval: + +
    +
  1. + Go public: Ensure your repository (e.g., GitHub) is set to public. +
  2. +
  3. + Add app icon: Upload your icon{' '} + + + here + + + . Recommended: 512x512px. +
  4. +
  5. + Register app or suite: Add your app's details in extend-apps-directory.json{' '} + + + here + + + . +
      +
    1. + App: A single app hosted in one repository. +
    2. +
    3. + Suite: A collection of multiple related apps (e.g., Gacha Suite). +
    4. +
    +
  6. +
  7. + Submit PR: Open a Pull Request to this{' '} + + + repository + + + , which our team will review. +
  8. +
  9. + Go live: Once approved and merged, your app will automatically appear in the directory. +
  10. +
+
+ } + message={ +
+ Build your first Extend app in minutes using AI. + +
+ } + showIcon + /> +
+
+
+ ) +} diff --git a/app/components/ExtendDirectoryDetailModal.tsx b/app/components/ExtendDirectoryDetailModal.tsx index f344456..e4b15ea 100644 --- a/app/components/ExtendDirectoryDetailModal.tsx +++ b/app/components/ExtendDirectoryDetailModal.tsx @@ -1,11 +1,10 @@ -import { CloseOutlined } from '@ant-design/icons' +import { CloseOutlined, ExportOutlined } from '@ant-design/icons' import { Badge, Button, Card, Modal, Tag, Typography } from 'antd' import { useSearchParams } from 'react-router-dom' import type { ExtendDirectoryAppInfo, RepositoryInfo } from '~/types/extend' import { Language, mapLanguageToColor, mapRepositoryTypeToText } from '~/types/extend' import extendApps from '../data/extend-apps-directory.json' import styles from './ExtendDirectoryDetailModal.module.css' -import { ExternalLink } from './ExternalLink' import type { ReactNode } from 'react' const { Title, Text, Paragraph } = Typography @@ -43,7 +42,7 @@ const Repository = ({ index: isIndexRepository, title, description, url, languag
{elements}
) diff --git a/app/components/ExternalLink.tsx b/app/components/ExternalLink.tsx deleted file mode 100644 index e18bf1b..0000000 --- a/app/components/ExternalLink.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import Icon from '@ant-design/icons' -import { type ComponentProps } from 'react' - -const ExternalLinkSvg = () => ( - - - -) - -export const ExternalLink = (props: Partial>) => diff --git a/app/global.d.ts b/app/global.d.ts new file mode 100644 index 0000000..e861973 --- /dev/null +++ b/app/global.d.ts @@ -0,0 +1,6 @@ +// Global type declarations + +interface Window { + gtag?: (command: string, ...args: any[]) => void + dataLayer?: any[] +} diff --git a/app/routes/home.module.css b/app/routes/home.module.css index ae1fdec..a6f097a 100644 --- a/app/routes/home.module.css +++ b/app/routes/home.module.css @@ -14,3 +14,13 @@ .title { color: #000000e0; } + +.contributingLink { + color: #0b6cff; + text-decoration: underline; +} + +.contributingLink:hover { + color: #0b6cff; + text-decoration: underline; +} diff --git a/app/routes/home.tsx b/app/routes/home.tsx index 08064b6..c34d2a0 100644 --- a/app/routes/home.tsx +++ b/app/routes/home.tsx @@ -1,11 +1,13 @@ import { Select, Typography } from 'antd' -import { useSearchParams } from 'react-router-dom' +import { Link, useSearchParams } from 'react-router-dom' +import { ContributingGuideModal } from '~/components/ContributingGuideModal' import { ExtendDirectoryDetailModal } from '~/components/ExtendDirectoryDetailModal' import accelByteLogo from '../assets/accelbyte.svg' import { ExtendDirectoryItems } from '../components/ExtendDirectoryItems' import extendAppsJson from '../data/extend-apps-directory.json' import { type ExtendDirectoryAppInfo, ExternalInfo, FilterDevelopedBy } from '../types/extend' import styles from './home.module.css' +import { CONTRIBUTING_GUIDELINE_MODAL_QUERY_PARAMETER } from '~/types/ui' const { Title, Text } = Typography @@ -34,7 +36,7 @@ export default function Home() { const extendApps = developedBy && developedBy !== FilterDevelopedBy.All - ? extendAppsJson.filter((e) => { + ? extendAppsJson.filter(e => { if (developedBy === FilterDevelopedBy.External) { return e.creator !== FilterDevelopedBy.AccelByte } @@ -62,7 +64,13 @@ export default function Home() { Extend Apps Directory - Enhance your games with powerful Extend apps crafted by our community. + + Power up your game with community-built Extend apps. Have an app to share?{' '} + + Learn how to build and submit an app + + . +