Imported from ikascom/ikas-app-examples (
examples/dashboard-actions-app/AGENTS.md). Install upstream withnpx skills add ikascom/ikas-app-examples --skill dashboard-actions-app. Copyright stays with the author.
Project Rules for Ikas App Starter App (Next.js)
Core Principles
- Prefer simplicity, readability, explicitness. Keep logic in small, testable functions.
- TypeScript strict; avoid any. Use precise types from generated GraphQL.
- Treat API tokens and secrets as sensitive; never log them.
Stack Overview
- Next.js 15 App Router, React 19, TypeScript, Tailwind + shadcn/ui.
- ikas Admin GraphQL via
@ikas/admin-api-clientwith codegen. - Session via
iron-session.
MCP Usage
- When generating new UI components, use the "shadcn" MCP to fetch component boilerplates and demos. Align with existing
src/components/ui/*structure. - When generating or exploring ikas GraphQL operations, use the "ikas" MCP list and introspect tools to discover available queries/mutations and their shapes before implementation.
- For ikas GraphQL specifically, follow this order before any implementation:
- Use the "ikas" MCP list tool to find the correct query/mutation name.
- Use the "ikas" MCP introspect tool to get the operation's full shape (variables, return fields, enums).
- Only after confirming via list + introspect, add the document to
graphql-requests.tsand run codegen.
- For ikas GraphQL specifically, follow this order before any implementation:
GraphQL and API Workflow
- Define queries/mutations in
src/lib/ikas-client/graphql-requests.tsusinggql. - Run
pnpm codegento regeneratesrc/lib/ikas-client/generated/graphql.tstypes and client wrappers. - Acquire a client with
getIkas(token)fromsrc/helpers/api-helpers.ts. - Execute queries via
ikasClient.queries.<name>()and mutations viaikasClient.mutations.<name>(variables).
Enforcement
- Do NOT write inline GraphQL strings inside API routes or components.
- Always import documents from
graphql-requests.tsand runpnpm codegenbefore usage. - Always call ikas operations through
ikasClient.queries|mutations.<operation>()to keep type-safety. - Before adding a new operation, first run the ikas MCP list tool, then introspect to confirm details.
Adding New API Requests (Procedure)
- Discover operation via MCP: run ikas list to locate the operation, then ikas introspect to confirm its schema.
- Add your GraphQL query/mutation to
src/lib/ikas-client/graphql-requests.tsusing thegqltag. - Run
pnpm codegento generate types and update the generated client. - Use
getIkasto create the ikas client inside API routes or server actions. - For a query, call
ikasClient.queries.<YourQuery>(); for a mutation, callikasClient.mutations.<YourMutation>(variables).
Project Conventions
- API routes under
src/app/api/*must validate session and fetch the token viagetUserFromRequestandAuthTokenManager. - Do not call ikas APIs from the browser; always go through server routes.
- Keep UI logic in components under
src/components/*; avoid business logic in pages.
Iframe Pages and Authentication Pattern
When building pages that will be loaded in iframes within the ikas dashboard (e.g., app actions, dashboard widgets):
Client-Side Pattern (Frontend)
- Always call
AppBridgeHelper.closeLoader()in a separateuseEffectwith empty dependency array on page mount to close the ikas platform loading indicator. - Always use
TokenHelpers.getTokenForIframeApp()to retrieve the JWT authentication token from the ikas app bridge. - Never make direct API calls to ikas from the frontend. Always go through backend API routes.
- Use
ApiRequestshelper to call backend endpoints with the token. - Follow the dashboard page pattern: Initialize token on mount, fetch data with token, handle loading/error states.
Example Pattern:
import { TokenHelpers } from '@/helpers/token-helpers';
import { ApiRequests } from '@/lib/api-requests';
import { AppBridgeHelper } from '@ikas/app-helpers';
function MyIframePage() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const fetchData = useCallback(async (token: string) => {
const res = await ApiRequests.ikas.getData(token);
if (res.status === 200 && res.data?.data) {
setData(res.data.data);
}
setLoading(false);
}, []);
const initializePage = useCallback(async () => {
const token = await TokenHelpers.getTokenForIframeApp();
if (token) {
await fetchData(token);
}
}, [fetchData]);
// Close the loader shown by ikas platform when opening the iframe
useEffect(() => {
AppBridgeHelper.closeLoader();
}, []);
useEffect(() => {
initializePage();
}, [initializePage]);
// ... render UI
}
Server-Side Pattern (Backend API)
- Create API endpoint under
/api/ikas/*for ikas-related operations. - Validate JWT token using
getUserFromRequest(request)to extractauthorizedAppIdandmerchantId. - Fetch OAuth token from
AuthTokenManager.get(authorizedAppId). - Call ikas API using
getIkas(authToken)client. - Return data in standard format:
{ data: { ...yourData } }.
Example Pattern:
export async function GET(request: NextRequest) {
const user = getUserFromRequest(request);
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const authToken = await AuthTokenManager.get(user.authorizedAppId);
if (!authToken) return NextResponse.json({ error: 'Auth token not found' }, { status: 404 });
const ikasClient = getIkas(authToken);
const response = await ikasClient.queries.someQuery();
if (response.isSuccess && response.data) {
return NextResponse.json({ data: { ...response.data } });
}
return NextResponse.json({ error: 'Failed' }, { status: 500 });
}
Adding New Iframe Endpoints
- Create backend API in
/api/ikas/<endpoint-name>/route.ts. - Add to ApiRequests in
src/lib/api-requests.ts:yourEndpoint: (token: string, params?) => makeGetRequest<YourResponse>({ url: '/api/ikas/your-endpoint', token, data: params }) - Use in frontend via
ApiRequests.ikas.yourEndpoint(token, params).
Important Rules
- ALWAYS call
AppBridgeHelper.closeLoader()in auseEffect(() => { AppBridgeHelper.closeLoader(); }, [])when the iframe page mounts to close the platform loading indicator. - NEVER bypass the token flow by hardcoding tokens or using environment variables for user authentication.
- ALWAYS wrap
useSearchParams()usage in a<Suspense>boundary (Next.js 15 requirement). - ALWAYS follow the established pattern: AppBridgeHelper.closeLoader() → TokenHelpers → ApiRequests → Backend API → ikas Client.
- ALWAYS handle loading and error states gracefully in iframe pages.
Security and Privacy
- Use
onCheckTokeningetIkasto auto-refresh tokens. Do not expose tokens in responses or logs. - TokenHelpers automatically caches tokens in sessionStorage with expiration validation.
- JWT tokens contain
authorizedAppId(aud) andmerchantId(sub) for user identification. - OAuth Callback Signature Validation: The OAuth callback endpoint validates authorization codes using HMAC-SHA256 signatures.
- Use
TokenHelpers.validateCodeSignature(code, signature, clientSecret)to verify code authenticity. - The callback route requires a
signaturequery parameter and validates it before exchanging the code for tokens. - State parameter validation is optional but recommended for additional CSRF protection.
- Use
Quality Gates
- Run
pnpm codegenwhengraphql-requests.tschanges. - Ensure type-safety and linter cleanliness before committing.
- Reject PRs that introduce raw GraphQL usage outside
graphql-requests.ts. - Keep naming consistent with
ikasbrand and command patterns.
Notes
- Prefer
ApiRequestsinsrc/lib/api-requests.tsto bridge frontend to backend endpoints.
Commit Message Rule
Use Conventional Commits format:
- Format:
<type>(<scope>): <short summary> - Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
- Scope: optional, lowercase, represents the module / package / area
- Summary: imperative mood, max 72 chars
Examples
- feat(cart): add discount code validation
- fix(auth): prevent token refresh loop
- docs(readme): update installation guide