Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/onboarding-app-initial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"saleor-app-onboarding": patch
---

Introduce the Onboarding app — a standalone Saleor App that mounts on the Dashboard home page
(`HOME_WIDGETS` extension) and walks new users through first-run tasks: creating a product,
exploring orders, opening the GraphQL playground, browsing extensions, and inviting staff.
Completion state persists in user metadata so progress is preserved across sessions.
20 changes: 20 additions & 0 deletions apps/onboarding/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# See `src/lib/env.ts` for the exhaustive list of supported env variables.

# App identifier used when building the manifest. Override per-environment if needed.
# MANIFEST_APP_ID=saleor.app.onboarding

# App name displayed in Dashboard. Override to recognize app instance during development.
# APP_NAME=Onboarding

# APP_LOG_LEVEL=info # one of "fatal" | "error" | "warn" | "info" | "debug" | "trace"

# Local development variables. When developed locally with Saleor inside docker, these can be set to:
# APP_IFRAME_BASE_URL=http://localhost:3000 # so Dashboard on host can access iframe
# APP_API_BASE_URL=http://host.docker.internal:3000 # so Saleor can reach the App from container
# https://docs.saleor.io/developer/extending/apps/local-app-development

# Sentry — optional, enable error tracking
# NEXT_PUBLIC_SENTRY_DSN=
# SENTRY_AUTH_TOKEN=
# SENTRY_ORG=
# SENTRY_PROJECT=
29 changes: 29 additions & 0 deletions apps/onboarding/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Saleor Onboarding App

Saleor App that provides a "Welcome / Onboarding" widget on the Dashboard home page. It guides
users through first-run tasks (creating a product, exploring orders, opening the GraphQL
playground, browsing extensions, inviting staff).

The app is a port of the Dashboard's built-in `WelcomePageOnboarding` widget into a standalone
Saleor App so onboarding can ship independently of Dashboard releases.

## Tech overview

- **Next.js App Router** for both API endpoints (`/api/manifest`, `/api/register`) and UI (`/`).
- **Manifest extension**: `HOME_WIDGETS` mount, `WIDGET` target, `GET` method (the staff JWT
comes from AppBridge automatically).
- **Client-only**: all GraphQL calls run in the browser using the user's JWT issued by AppBridge.
No webhooks, no APL persistence (a no-op APL satisfies the SDK's register handler).
- **State persistence**: completion state is stored under user metadata key `onboarding`
(drop-in compatible with the Dashboard widget's existing key) via `updateMetadata`.

## Local development

```bash
pnpm install
cp .env.example .env
pnpm --filter saleor-app-onboarding dev
```

See [the Saleor docs](https://docs.saleor.io/developer/extending/apps/local-app-development) for
running an app against a local Saleor instance.
59 changes: 59 additions & 0 deletions apps/onboarding/codegen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { type CodegenConfig } from "@graphql-codegen/cli";

const config: CodegenConfig = {
generates: {
"generated/graphql.ts": {
schema: "graphql/schema.graphql",
documents: ["graphql/**/*.graphql"],
plugins: [
{
add: {
content:
"type JSONValue = string | number | boolean | null | { [key: string]: JSONValue } | JSONValue[];",
},
},
{
typescript: {
enumsAsTypes: true,
},
},
"typescript-operations",
{
"typescript-urql": {
documentVariablePrefix: "Untyped",
fragmentVariablePrefix: "Untyped",
withHooks: false,
},
},
"typed-document-node",
],
config: {
dedupeFragments: true,
defaultScalarType: "unknown",
immutableTypes: true,
strictScalars: true,
skipTypename: true,
scalars: {
_Any: "unknown",
Date: "string",
DateTime: "string",
Decimal: "number",
Minute: "number",
GenericScalar: "JSONValue",
JSON: "JSONValue",
JSONString: "string",
Metadata: "Record<string, string>",
PositiveDecimal: "number",
Upload: "unknown",
UUID: "string",
WeightScalar: "number",
Day: "string",
Hour: "number",
PositiveInt: "number",
},
},
},
},
};

export default config;
31 changes: 31 additions & 0 deletions apps/onboarding/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { config } from "@saleor/eslint-config-apps/index.js";
import nodePlugin from "eslint-plugin-n";

/** @type {import("eslint").Linter.Config} */
export default [
...config,
{
name: "saleor-app-onboarding/custom-config",
files: ["**/*.ts", "**/*.tsx"],
plugins: {
n: nodePlugin,
},
rules: {
"n/no-process-env": "error",
},
},
{
name: "saleor-app-onboarding/override-no-process-env",
files: ["next.config.ts", "src/lib/env.ts", "src/instrumentation.ts"],
rules: {
"n/no-process-env": "off",
},
},
{
name: "saleor-app-onboarding/app-router-default-exports",
files: ["src/app/**/*"],
rules: {
"import/no-default-export": "off",
},
},
];
Loading
Loading