easyorders 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -69,9 +69,14 @@ my-theme/
69
69
  ├── theme-data.json
70
70
  ├── style.css
71
71
  ├── script.js
72
- └── sections/
73
- ├── header.liquid
74
- ├── footer.liquid
72
+ ├── sections/
73
+ ├── header.liquid
74
+ ├── footer.liquid
75
+ │ └── ...
76
+ └── home-sections/
77
+ ├── <section-key>/
78
+ │ ├── config.json
79
+ │ └── template.liquid
75
80
  └── ...
76
81
  ```
77
82
 
package/dist/bin/cli.js CHANGED
@@ -87,7 +87,7 @@ async function createTheme() {
87
87
  console.log("");
88
88
  console.log(` ${DIM}Next steps:${RESET}`);
89
89
  console.log(` ${CYAN}cd ${projectName}${RESET}`);
90
- console.log(` ${CYAN}npx easyorders start${RESET}`);
90
+ console.log(` ${CYAN} easyorders start${RESET}`);
91
91
  console.log("");
92
92
  }
93
93
  async function startDev() {
@@ -103,15 +103,36 @@ async function startDev() {
103
103
  const serverDir = path.join(CLI_ROOT, "server");
104
104
  const indexScript = path.join(serverDir, "index.js");
105
105
  const tunnelScript = path.join(serverDir, "tunnel.js");
106
- const cmd = `node "${tunnelScript}" & node "${indexScript}"`;
107
- const child = spawn(cmd, {
106
+ const childEnv = { ...process.env, THEME_DIR: themeDir };
107
+ // Spawn both processes independently for cross-platform support
108
+ // (Using shell `&` only works on Unix; on Windows cmd.exe it runs sequentially and hangs)
109
+ const tunnel = spawn("node", [tunnelScript], {
108
110
  stdio: "inherit",
109
111
  cwd,
110
- env: { ...process.env, THEME_DIR: themeDir },
111
- shell: true,
112
+ env: childEnv,
112
113
  });
113
- child.on("exit", (code) => {
114
- process.exit(code ?? 0);
114
+ const server = spawn("node", [indexScript], {
115
+ stdio: "inherit",
116
+ cwd,
117
+ env: childEnv,
118
+ });
119
+ let exitCode = 0;
120
+ function cleanup(code) {
121
+ exitCode = code;
122
+ tunnel.kill();
123
+ server.kill();
124
+ }
125
+ tunnel.on("exit", (code) => cleanup(code ?? 1));
126
+ server.on("exit", (code) => cleanup(code ?? 1));
127
+ process.on("SIGINT", () => {
128
+ tunnel.kill();
129
+ server.kill();
130
+ process.exit(exitCode);
131
+ });
132
+ process.on("SIGTERM", () => {
133
+ tunnel.kill();
134
+ server.kill();
135
+ process.exit(exitCode);
115
136
  });
116
137
  }
117
138
  function showMenu() {
@@ -23,6 +23,7 @@ const port = Number(process.env.PORT) || 4000;
23
23
  const URL_FILE = path.resolve(process.cwd(), ".tunnel-url");
24
24
  const THEME_DIR = process.env.THEME_DIR || path.resolve(process.cwd(), "theme");
25
25
  const SECTIONS_DIR = path.join(THEME_DIR, "sections");
26
+ const HOME_SECTIONS_DIR = path.join(THEME_DIR, "home-sections");
26
27
  const AUTH_READY_FILE = path.resolve(process.cwd(), ".auth-ready");
27
28
  app.use(express.json());
28
29
  function getBaseUrl(req) {
@@ -49,6 +50,26 @@ function buildSections() {
49
50
  template: fs.readFileSync(path.join(SECTIONS_DIR, file), "utf-8"),
50
51
  }));
51
52
  }
53
+ function buildHomeSections() {
54
+ if (!fs.existsSync(HOME_SECTIONS_DIR))
55
+ return [];
56
+ const result = [];
57
+ const entries = fs.readdirSync(HOME_SECTIONS_DIR, { withFileTypes: true });
58
+ for (const entry of entries) {
59
+ if (!entry.isDirectory())
60
+ continue;
61
+ const key = entry.name.replace(/-/g, "_");
62
+ const folderPath = path.join(HOME_SECTIONS_DIR, entry.name);
63
+ const configPath = path.join(folderPath, "config.json");
64
+ const templatePath = path.join(folderPath, "template.liquid");
65
+ if (!fs.existsSync(configPath) || !fs.existsSync(templatePath))
66
+ continue;
67
+ const config = readJsonFile(configPath);
68
+ const template = fs.readFileSync(templatePath, "utf-8");
69
+ result.push({ key, ...config, template });
70
+ }
71
+ return result;
72
+ }
52
73
  function extractSubdomain(input) {
53
74
  const trimmed = input.trim();
54
75
  try {
@@ -211,7 +232,9 @@ app.get("/theme", (req, res) => {
211
232
  const baseUrl = getBaseUrl(req);
212
233
  res.json({
213
234
  sections: buildSections(),
235
+ home_sections: buildHomeSections(),
214
236
  theme_data: readJsonFile(path.join(THEME_DIR, "theme-data.json")),
237
+ theme_data_schema: readJsonFile(path.join(THEME_DIR, "schema.json")),
215
238
  config: readJsonFile(path.join(THEME_DIR, "config.json")),
216
239
  style: `${baseUrl}/style.css`,
217
240
  script: `${baseUrl}/script.js`,
@@ -3,20 +3,7 @@
3
3
  "logo": "",
4
4
  "pages": [
5
5
  {
6
- "slug": "terms-and-conditions",
7
- "title": "شروط الاستخدام"
8
- },
9
- {
10
- "slug": "shipping-policy",
11
- "title": "سياسة الشحن"
12
- },
13
- {
14
- "slug": "refund-policy",
15
- "title": "سياسة الاستبدال و الاسترجاع"
16
- },
17
- {
18
- "slug": "privacy-policy",
19
- "title": "سياسات الخصوصية"
6
+ "id": "ADD_PAGE_ID_HERE"
20
7
  }
21
8
  ],
22
9
  "social": [
@@ -47,16 +34,7 @@
47
34
  ],
48
35
  "categories": [
49
36
  {
50
- "name": "Men",
51
- "slug": "womens-accessories"
52
- },
53
- {
54
- "name": "Women",
55
- "slug": "mens-accessories"
56
- },
57
- {
58
- "name": "New Arrival",
59
- "slug": "98"
37
+ "id": "ADD_CATEGORY_ID_HERE"
60
38
  }
61
39
  ],
62
40
  "payment_img": "https://easyorders.fra1.digitaloceanspaces.com/1773958283329740254payment_icons.svg",
@@ -66,45 +44,39 @@
66
44
  "logo": "",
67
45
  "links": [
68
46
  {
69
- "url": "/collections/womens-accessories",
70
- "name": "Men",
71
- "slug": "womens-accessories",
72
- "type": "category"
73
- },
74
- {
75
- "url": "/collections/mens-accessories",
76
- "name": "Women",
77
- "slug": "mens-accessories",
47
+ "id": "ADD_CATEGORY_ID_HERE",
78
48
  "type": "category"
79
49
  },
80
50
  {
81
- "url": "/collections/98",
82
- "name": "New Arrival",
83
- "slug": "98",
84
- "type": "category"
85
- },
86
- {
87
- "url": "/collections/30-off-tree-runner-go-tree-gliders",
88
- "name": "Sale",
89
- "slug": "30-off-tree-runner-go-tree-gliders",
90
- "type": "category"
51
+ "id": "ADD_PAGE_ID_HERE",
52
+ "type": "page"
91
53
  }
92
54
  ],
93
55
  "is_use_config": true
94
56
  },
95
57
  "palette": {
96
- "ft_bg": "",
97
- "hd_bg": "#a4e6b6",
98
- "ann_bg": "#000000",
99
- "ft_text": "",
58
+ "hd_bg": "",
100
59
  "hd_text": "",
60
+ "ann_bg": "",
101
61
  "ann_text": "",
62
+ "ft_bg": "",
63
+ "ft_text": "",
102
64
  "buy_btn_bg": "",
103
- "crt_btn_bg": "",
104
65
  "buy_btn_text": "",
105
- "crt_btn_text": "",
106
66
  "buy_btn_border": "",
107
- "crt_btn_border": ""
67
+ "crt_btn_bg": "",
68
+ "crt_btn_text": "",
69
+ "crt_btn_border": "",
70
+ "product_name_text": "",
71
+ "product_price_text": "",
72
+ "product_sale_price_text": "",
73
+ "body_bg": "",
74
+ "body_text": "",
75
+ "input_border": "",
76
+ "input_bg": "",
77
+ "input_text": "",
78
+ "checkout_form_bg": "",
79
+ "checkout_heading_text": ""
108
80
  },
109
81
  "theme_data": {
110
82
  "hero_slides": [],
@@ -0,0 +1,40 @@
1
+ {
2
+ "icon": "https://api.iconify.design/lucide:grid-3x3.svg",
3
+ "label": "Category mosaic",
4
+ "section_schema": [
5
+ {
6
+ "name": "title",
7
+ "type": "string",
8
+ "default": "Shop by mood",
9
+ "description": "Section heading"
10
+ },
11
+ {
12
+ "name": "subtitle",
13
+ "type": "string",
14
+ "default": "Curated drops for work, weekend, and everything between.",
15
+ "description": "Subheading under the title"
16
+ },
17
+ {
18
+ "name": "columns",
19
+ "type": "select",
20
+ "default": "3",
21
+ "description": "Columns on large screens",
22
+ "options": [
23
+ { "label": "Two columns", "value": "2" },
24
+ { "label": "Three columns", "value": "3" },
25
+ { "label": "Four columns", "value": "4" }
26
+ ]
27
+ },
28
+ {
29
+ "name": "gap_tight",
30
+ "type": "boolean",
31
+ "default": false,
32
+ "description": "Use tighter spacing between tiles"
33
+ },
34
+ {
35
+ "name": "mosaic_category_ids",
36
+ "type": "category_multi_select",
37
+ "description": "Categories to show as tiles (search and reorder in the picker). Images and titles load from each category."
38
+ }
39
+ ]
40
+ }
@@ -0,0 +1,155 @@
1
+ <section
2
+ class="category-mosaic eo-hs{% if section_data.gap_tight %} category-mosaic--tight{% endif %}{% if section_data.mosaic_category_ids == blank or section_data.mosaic_category_ids.size == 0 %} eo-hs--empty{% endif %}"
3
+ style="--mosaic-cols: {{ section_data.columns | default: '3' }};"
4
+ data-eo-hs-entity="categories"
5
+ data-eo-hs-ids="{{ section_data.mosaic_category_ids | join: ',' | strip }}"
6
+ >
7
+ <div class="category-mosaic__head">
8
+ {% if section_data.title != blank %}
9
+ <h2 class="category-mosaic__title">{{ section_data.title }}</h2>
10
+ {% endif %}
11
+ {% if section_data.subtitle != blank %}
12
+ <p class="category-mosaic__subtitle">{{ section_data.subtitle }}</p>
13
+ {% endif %}
14
+ </div>
15
+
16
+ <div class="category-mosaic__grid" data-eo-hs-mount>
17
+ {% if section_data.mosaic_category_ids and section_data.mosaic_category_ids.size > 0 %}
18
+ {% for id in section_data.mosaic_category_ids %}
19
+ <div class="eo-hs-skeleton eo-hs-skeleton--category" aria-hidden="true"></div>
20
+ {% endfor %}
21
+ {% else %}
22
+ <p class="category-mosaic__placeholder">Choose categories in the section settings to build this mosaic.</p>
23
+ {% endif %}
24
+ </div>
25
+ </section>
26
+
27
+ <style>
28
+ .category-mosaic {
29
+ padding: clamp(2.5rem, 5vw, 4rem) 1.25rem 3rem;
30
+ background: #fff;
31
+ color: #141218;
32
+ }
33
+ .category-mosaic--tight .category-mosaic__grid {
34
+ gap: 0.75rem;
35
+ }
36
+ .category-mosaic__head {
37
+ max-width: 720px;
38
+ margin: 0 auto 2rem;
39
+ text-align: center;
40
+ }
41
+ .category-mosaic__title {
42
+ margin: 0 0 0.5rem;
43
+ font-family: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
44
+ font-weight: 500;
45
+ font-size: clamp(1.75rem, 4vw, 2.35rem);
46
+ }
47
+ .category-mosaic__subtitle {
48
+ margin: 0;
49
+ font-size: 1rem;
50
+ line-height: 1.6;
51
+ color: #5c5852;
52
+ }
53
+ .category-mosaic__grid {
54
+ max-width: 1200px;
55
+ margin: 0 auto;
56
+ display: grid;
57
+ grid-template-columns: repeat(var(--mosaic-cols, 3), minmax(0, 1fr));
58
+ gap: 1.25rem;
59
+ }
60
+ .category-mosaic__grid.eo-hs-loading {
61
+ opacity: 0.85;
62
+ }
63
+ @media (max-width: 899px) {
64
+ .category-mosaic__grid {
65
+ grid-template-columns: repeat(2, minmax(0, 1fr));
66
+ }
67
+ }
68
+ @media (max-width: 520px) {
69
+ .category-mosaic__grid {
70
+ grid-template-columns: 1fr;
71
+ }
72
+ }
73
+ .category-mosaic .eo-hs-skeleton--category {
74
+ aspect-ratio: 3 / 4;
75
+ border-radius: 6px;
76
+ background: linear-gradient(110deg, #ece8e2 8%, #f5f2ee 18%, #ece8e2 33%);
77
+ background-size: 200% 100%;
78
+ animation: category-mosaic-shimmer 1.2s ease-in-out infinite;
79
+ }
80
+ .category-mosaic__placeholder {
81
+ grid-column: 1 / -1;
82
+ margin: 0;
83
+ padding: 1.25rem;
84
+ text-align: center;
85
+ color: #7a7368;
86
+ font-size: 0.9rem;
87
+ }
88
+ .category-mosaic.eo-hs--empty .category-mosaic__grid {
89
+ display: flex;
90
+ justify-content: center;
91
+ }
92
+ .category-mosaic .eo-hs-card--category {
93
+ position: relative;
94
+ display: block;
95
+ text-decoration: none;
96
+ color: inherit;
97
+ border-radius: 6px;
98
+ overflow: hidden;
99
+ background: #ece8e1;
100
+ transition: transform 0.25s ease, box-shadow 0.25s ease;
101
+ }
102
+ .category-mosaic .eo-hs-card--category:hover {
103
+ transform: translateY(-4px);
104
+ box-shadow: 0 22px 50px rgba(20, 18, 24, 0.18);
105
+ }
106
+ .category-mosaic .eo-hs-card--category .eo-hs-card__media {
107
+ display: block;
108
+ aspect-ratio: 3 / 4;
109
+ background: #ece8e2;
110
+ overflow: hidden;
111
+ }
112
+ .category-mosaic .eo-hs-card--category .eo-hs-card__media--empty {
113
+ min-height: 200px;
114
+ }
115
+ .category-mosaic .eo-hs-card--category .eo-hs-card__media img {
116
+ width: 100%;
117
+ height: 100%;
118
+ object-fit: cover;
119
+ display: block;
120
+ transition: transform 0.45s ease;
121
+ }
122
+ .category-mosaic .eo-hs-card--category:hover .eo-hs-card__media img {
123
+ transform: scale(1.04);
124
+ }
125
+ .category-mosaic .eo-hs-card--category .eo-hs-card__body {
126
+ position: absolute;
127
+ left: 0;
128
+ right: 0;
129
+ bottom: 0;
130
+ padding: 1.25rem 1.1rem 1.1rem;
131
+ background: linear-gradient(to top, rgba(12, 10, 14, 0.72), transparent 55%);
132
+ }
133
+ .category-mosaic .eo-hs-card--category .eo-hs-card__title {
134
+ font-size: 1.05rem;
135
+ font-weight: 600;
136
+ letter-spacing: 0.02em;
137
+ color: #fff;
138
+ }
139
+ .category-mosaic .eo-hs-error {
140
+ grid-column: 1 / -1;
141
+ margin: 0;
142
+ padding: 1rem;
143
+ text-align: center;
144
+ font-size: 0.88rem;
145
+ color: #8b3225;
146
+ }
147
+ @keyframes category-mosaic-shimmer {
148
+ 0% {
149
+ background-position: 100% 0;
150
+ }
151
+ 100% {
152
+ background-position: -100% 0;
153
+ }
154
+ }
155
+ </style>
@@ -0,0 +1,67 @@
1
+ {
2
+ "icon": "https://api.iconify.design/lucide:layout-template.svg",
3
+ "label": "Editorial feature",
4
+ "section_schema": [
5
+ {
6
+ "name": "image",
7
+ "type": "image",
8
+ "default": "",
9
+ "description": "Feature image"
10
+ },
11
+ {
12
+ "name": "eyebrow",
13
+ "type": "string",
14
+ "default": "Inside the atelier",
15
+ "description": "Small label above the title"
16
+ },
17
+ {
18
+ "name": "title",
19
+ "type": "string",
20
+ "default": "Tailoring that moves with you",
21
+ "description": "Headline"
22
+ },
23
+ {
24
+ "name": "body",
25
+ "type": "string",
26
+ "default": "Precision cuts and fluid fabrics—designed for city days and slow evenings. Discover the pieces our stylists reach for first.",
27
+ "description": "Body copy"
28
+ },
29
+ {
30
+ "name": "cta_label",
31
+ "type": "string",
32
+ "default": "Explore the story",
33
+ "description": "Link label"
34
+ },
35
+ {
36
+ "name": "cta_page_id",
37
+ "type": "page_single_select",
38
+ "description": "Simple page the CTA opens (link URL is resolved on the storefront)"
39
+ },
40
+ {
41
+ "name": "image_position",
42
+ "type": "select",
43
+ "default": "left",
44
+ "description": "Image side on desktop",
45
+ "options": [
46
+ { "label": "Image left", "value": "left" },
47
+ { "label": "Image right", "value": "right" }
48
+ ]
49
+ },
50
+ {
51
+ "name": "surface",
52
+ "type": "select",
53
+ "default": "light",
54
+ "description": "Color mood",
55
+ "options": [
56
+ { "label": "Light editorial", "value": "light" },
57
+ { "label": "Dark editorial", "value": "dark" }
58
+ ]
59
+ },
60
+ {
61
+ "name": "accent_color",
62
+ "type": "color",
63
+ "default": "#8B7355",
64
+ "description": "Eyebrow and link accent"
65
+ }
66
+ ]
67
+ }
@@ -0,0 +1,153 @@
1
+ <section
2
+ class="editorial-feature editorial-feature--{{ section_data.surface | default: 'light' }} editorial-feature--img-{{ section_data.image_position | default: 'left' }}"
3
+ style="--ed-accent: {{ section_data.accent_color | default: '#8B7355' }};"
4
+ >
5
+ <div class="editorial-feature__grid">
6
+ <div class="editorial-feature__media">
7
+ {% if section_data.image != blank %}
8
+ <img
9
+ class="editorial-feature__img"
10
+ src="{{ section_data.image }}"
11
+ alt="{{ section_data.title | default: 'Feature' }}"
12
+ loading="lazy"
13
+ width="900"
14
+ height="1100"
15
+ />
16
+ {% else %}
17
+ <div class="editorial-feature__placeholder" role="img" aria-label=""></div>
18
+ {% endif %}
19
+ </div>
20
+ <div class="editorial-feature__copy">
21
+ {% if section_data.eyebrow != blank %}
22
+ <p class="editorial-feature__eyebrow">{{ section_data.eyebrow }}</p>
23
+ {% endif %}
24
+ {% if section_data.title != blank %}
25
+ <h2 class="editorial-feature__title">{{ section_data.title }}</h2>
26
+ {% endif %}
27
+ {% if section_data.body != blank %}
28
+ <p class="editorial-feature__body">{{ section_data.body }}</p>
29
+ {% endif %}
30
+ {% if section_data.cta_label != blank %}
31
+ <a
32
+ class="editorial-feature__link"
33
+ href="#"
34
+ {% if section_data.cta_page_id != blank %}
35
+ data-eo-hs-cta="1"
36
+ data-eo-hs-cta-entity="pages"
37
+ data-eo-hs-cta-id="{{ section_data.cta_page_id }}"
38
+ {% endif %}
39
+ >
40
+ {{ section_data.cta_label }}
41
+ <span class="editorial-feature__link-arrow" aria-hidden="true">→</span>
42
+ </a>
43
+ {% endif %}
44
+ </div>
45
+ </div>
46
+ </section>
47
+
48
+ <style>
49
+ .editorial-feature {
50
+ padding: clamp(2.5rem, 5vw, 4.5rem) 1.25rem;
51
+ }
52
+ .editorial-feature--light {
53
+ background: #f6f3ee;
54
+ color: #1c1a17;
55
+ }
56
+ .editorial-feature--dark {
57
+ background: #121015;
58
+ color: #f4eee6;
59
+ }
60
+ .editorial-feature__grid {
61
+ max-width: 1120px;
62
+ margin: 0 auto;
63
+ display: grid;
64
+ gap: clamp(1.75rem, 4vw, 3rem);
65
+ align-items: center;
66
+ }
67
+ @media (min-width: 900px) {
68
+ .editorial-feature__grid {
69
+ grid-template-columns: 1.05fr 1fr;
70
+ }
71
+ .editorial-feature--img-right .editorial-feature__media {
72
+ order: 2;
73
+ }
74
+ .editorial-feature--img-right .editorial-feature__copy {
75
+ order: 1;
76
+ }
77
+ }
78
+ .editorial-feature__media {
79
+ position: relative;
80
+ }
81
+ .editorial-feature__img {
82
+ width: 100%;
83
+ display: block;
84
+ border-radius: 4px;
85
+ object-fit: cover;
86
+ aspect-ratio: 4 / 5;
87
+ box-shadow: 0 28px 80px rgba(0, 0, 0, 0.18);
88
+ }
89
+ .editorial-feature--dark .editorial-feature__img {
90
+ box-shadow: 0 28px 80px rgba(0, 0, 0, 0.55);
91
+ }
92
+ .editorial-feature__placeholder {
93
+ width: 100%;
94
+ aspect-ratio: 4 / 5;
95
+ border-radius: 4px;
96
+ background: linear-gradient(145deg, rgba(139, 115, 85, 0.35), rgba(28, 26, 23, 0.08));
97
+ }
98
+ .editorial-feature--dark .editorial-feature__placeholder {
99
+ background: linear-gradient(145deg, rgba(201, 169, 98, 0.25), rgba(0, 0, 0, 0.4));
100
+ }
101
+ .editorial-feature__copy {
102
+ display: flex;
103
+ flex-direction: column;
104
+ gap: 1rem;
105
+ padding: 0.25rem 0;
106
+ }
107
+ .editorial-feature__eyebrow {
108
+ margin: 0;
109
+ font-size: 0.72rem;
110
+ letter-spacing: 0.28em;
111
+ text-transform: uppercase;
112
+ font-weight: 600;
113
+ color: var(--ed-accent, #8b7355);
114
+ }
115
+ .editorial-feature__title {
116
+ margin: 0;
117
+ font-family: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
118
+ font-weight: 500;
119
+ font-size: clamp(1.85rem, 4vw, 2.65rem);
120
+ line-height: 1.15;
121
+ }
122
+ .editorial-feature__body {
123
+ margin: 0;
124
+ font-size: 1.02rem;
125
+ line-height: 1.75;
126
+ opacity: 0.88;
127
+ max-width: 34rem;
128
+ }
129
+ .editorial-feature__link {
130
+ margin-top: 0.25rem;
131
+ display: inline-flex;
132
+ align-items: center;
133
+ gap: 0.5rem;
134
+ font-size: 0.82rem;
135
+ font-weight: 600;
136
+ letter-spacing: 0.18em;
137
+ text-transform: uppercase;
138
+ text-decoration: none;
139
+ color: var(--ed-accent, #8b7355);
140
+ border-bottom: 1px solid currentColor;
141
+ padding-bottom: 2px;
142
+ width: fit-content;
143
+ transition: gap 0.2s ease, opacity 0.2s ease;
144
+ }
145
+ .editorial-feature__link:hover {
146
+ gap: 0.65rem;
147
+ opacity: 0.85;
148
+ }
149
+ .editorial-feature__link-arrow {
150
+ font-size: 1rem;
151
+ line-height: 1;
152
+ }
153
+ </style>