feat(listing): screenshots, framing script, listing copy; bookings list polish
- brand/make_screenshots.py frames raw shots at 1600x900 with captions + alt text - brand/LISTING.md: introduction, details, features, search terms, testing steps - bookings filters no longer overflow; footer reads '1 booking'; no empty pager Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ -43,7 +43,7 @@ export default function Bookings() {
|
||||
<s-button slot="secondary-actions" href={exportHref} target="_blank">Export CSV</s-button>
|
||||
<s-section>
|
||||
<Form method="get">
|
||||
<s-grid gridTemplateColumns="repeat(auto-fit, minmax(150px, 1fr))" gap="base" alignItems="end">
|
||||
<s-grid gridTemplateColumns="repeat(auto-fit, minmax(190px, 1fr))" gap="base" alignItems="end">
|
||||
<s-select label="Show" name="status" value={sp.get("status") ?? "upcoming"}>
|
||||
<s-option value="upcoming">Upcoming</s-option>
|
||||
<s-option value="review">Needs review</s-option>
|
||||
@ -72,7 +72,7 @@ export default function Bookings() {
|
||||
</Form>
|
||||
</s-section>
|
||||
<s-section padding="none">
|
||||
<s-table paginate hasNextPage={page * 50 < total} hasPreviousPage={page > 1}>
|
||||
<s-table>
|
||||
<s-table-header-row>
|
||||
<s-table-header listSlot="primary">Order</s-table-header>
|
||||
<s-table-header>Date</s-table-header>
|
||||
@ -113,7 +113,7 @@ export default function Bookings() {
|
||||
<s-box padding="base">
|
||||
<s-stack direction="inline" gap="base" alignItems="center">
|
||||
{page > 1 && <s-link href={`?${withPage(sp, page - 1)}`}>Previous</s-link>}
|
||||
<s-text color="subdued">{total} bookings · page {page}</s-text>
|
||||
<s-text color="subdued">{total} booking{total === 1 ? "" : "s"}{total > 50 ? ` · page ${page}` : ""}</s-text>
|
||||
{page * 50 < total && <s-link href={`?${withPage(sp, page + 1)}`}>Next</s-link>}
|
||||
</s-stack>
|
||||
</s-box>
|
||||
|
||||
74
brand/LISTING.md
Normal file
@ -0,0 +1,74 @@
|
||||
# Datewise: App Store listing content
|
||||
|
||||
Paste these into Partner Dashboard → Apps → Datewise: Delivery & Pickup → Distribution → App Store listing (English).
|
||||
Lengths are within Shopify's limits; check the counter in the form after pasting.
|
||||
|
||||
## Basics
|
||||
|
||||
- **App name:** Datewise: Delivery & Pickup *(must match `name` in shopify.app.toml)*
|
||||
- **App icon:** `brand/shopify/app-icon-1200x1200.png`
|
||||
- **App card subtitle:** Delivery and pickup dates and times, with slot limits
|
||||
- **Primary category:** Orders and shipping → Delivery and pickup
|
||||
- **Tags (suggested):** Delivery date, Store pickup, Local delivery, Time slots, Order scheduling
|
||||
|
||||
## App introduction (≤100 characters)
|
||||
|
||||
Let customers choose store pickup, local delivery or shipping, then a date and time, in the cart.
|
||||
|
||||
## App details (≤500 characters)
|
||||
|
||||
Datewise adds a scheduler to your cart. Customers pick a method, a location, a date and a time slot, and the place is held while they check out. You set opening hours, preparation time, cutoffs, holidays and orders per slot for each location. Rush days get their own limits, earlier cutoffs and a waitlist. Checkout requires a valid time on every Shopify plan, and the chosen time is shown on the order, in reminders and on the driver run sheet.
|
||||
|
||||
## Feature list (each ≤80 characters)
|
||||
|
||||
1. Store pickup, local delivery and shipping, each with its own hours and rules
|
||||
2. Time slots with order limits, held while the customer checks out
|
||||
3. Preparation time, same-day cutoffs, holidays and special opening hours
|
||||
4. Rush-day limits, earlier cutoffs and a waitlist for festivals and holidays
|
||||
5. Checkout requires a valid time on every plan, including Buy it now
|
||||
6. Customers reschedule from their order page; email, SMS and WhatsApp reminders
|
||||
7. Bookings list, daily capacity view, driver run sheets and CSV export
|
||||
|
||||
## Search terms (5)
|
||||
|
||||
1. delivery date
|
||||
2. store pickup
|
||||
3. delivery time slot
|
||||
4. local delivery scheduler
|
||||
5. order pickup date
|
||||
|
||||
## Pricing (Billing API plans, shown in the app)
|
||||
|
||||
- **Starter:** Free. 1 location, 50 scheduled orders a month.
|
||||
- **Growth:** $19/month, 14-day free trial. Up to 3 locations, unlimited orders, rush control and waitlist, fresh-stock windows, run sheets.
|
||||
- **Pro:** $49/month, 14-day free trial. Up to 50 locations, SMS and WhatsApp reminders, driving-distance zones.
|
||||
|
||||
## Media
|
||||
|
||||
- **Feature image (1600×900):** `brand/shopify/feature-image-1600x900.png`
|
||||
- **Screenshots (1600×900):** `brand/screenshots/listing/*.png`. Alt text for each is in `brand/screenshots/listing/alt-text.txt`.
|
||||
- **Screencast:** Unlisted YouTube URL *(record per the launch skill, Phase 9)*
|
||||
|
||||
## URLs and contact
|
||||
|
||||
- **Privacy policy:** https://datewise.metatronlabz.com/privacy
|
||||
- **Website:** https://datewise.metatronlabz.com
|
||||
- **Support email:** info@metatroncubesolutions.com
|
||||
- **Emergency contact:** set on the Partner account (Settings → Account)
|
||||
|
||||
## App testing information (for the reviewer)
|
||||
|
||||
No separate login: Datewise uses Shopify authentication.
|
||||
|
||||
1. **Install** the app and open it from the store's Apps menu. The **Today** page shows a 3-step setup guide.
|
||||
2. **Locations & hours:** open a location, switch on **Taking bookings**, check the pickup hours and slots, and click **Save location**. For pickup at checkout, the setup guide links to Shopify's Local pickup settings for that location.
|
||||
3. **Theme:** in the setup guide, click **Add block to cart page** (opens the theme editor with the Delivery date & time block added), then **Save**. Click **Turn on app embed**, then **Save**.
|
||||
4. **Settings:** turn on the **Checkout guard**, **Show the chosen date and time at checkout**, and **Hide pay-in-person methods for shipped orders**.
|
||||
5. **Storefront:** add a product to the cart, open the cart, choose **Store pickup**, a highlighted date and a time. The confirmation shows the place is held for 10 minutes.
|
||||
6. **Checkout:** choose **Pickup**; the pickup option shows the chosen date and time. Try checking out without choosing a time: the checkout guard blocks it with a message. Complete a test order.
|
||||
7. **Admin:** **Bookings** lists the order with its date and time. The order in Shopify has the date and time attributes, the pickup line with the time, and Datewise tags.
|
||||
8. **Paid features:** Settings → Plan → choose Growth (test charge) to unlock Rush control and the waitlist.
|
||||
9. **Uninstall:** pending reminders stop; store data is deleted 48 hours later via shop/redact.
|
||||
|
||||
- **Demo store URL:** https://veloxautomotive.myshopify.com
|
||||
- **Storefront password:** *(enter in the form yourself)*
|
||||
106
brand/make_screenshots.py
Normal file
@ -0,0 +1,106 @@
|
||||
"""Turns raw screenshots into 1600x900 Shopify App Store listing images in the Datewise brand.
|
||||
|
||||
Usage: python -I brand/make_screenshots.py
|
||||
Input: brand/screenshots/raw/<n>-<slug>.(png|jpg) (browser screenshots; CROP below trims each one)
|
||||
Output: brand/screenshots/listing/<n>-<slug>.png + alt-text.txt
|
||||
Rules (Shopify): 1600x900, no pricing, reviews or outcome claims on the image.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFilter, ImageFont
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
RAW = ROOT / "screenshots" / "raw"
|
||||
OUT = ROOT / "screenshots" / "listing"
|
||||
W, H = 1600, 900
|
||||
|
||||
TEAL_DEEP = (4, 90, 81) # #045A51
|
||||
TEAL = (13, 107, 93) # #0D6B5D
|
||||
SAGE = (232, 242, 237) # #E8F2ED
|
||||
WASH = (20, 125, 110)
|
||||
|
||||
# n: (title, subtitle, alt text)
|
||||
SHOTS = {
|
||||
1: ("Customers choose a time in the cart",
|
||||
"Store pickup, local delivery or shipping, then a date and a time slot",
|
||||
"Shopify cart page with the Datewise scheduler: store pickup selected, a calendar, time slots and a confirmation showing the time held"),
|
||||
2: ("Today's bookings at a glance",
|
||||
"Bookings, capacity for the next 14 days and upcoming festival dates",
|
||||
"Datewise Today page in the Shopify admin with booking counts and a 14-day capacity chart for a pickup location"),
|
||||
3: ("Hours, slots and limits for each location",
|
||||
"Preparation time, cutoffs, bookable days and orders per slot",
|
||||
"Location settings with pickup on, slot length, preparation time, same-day cutoff and orders per slot"),
|
||||
4: ("Every booking in one list",
|
||||
"Filter by date, method and location, move a booking or export CSV",
|
||||
"Bookings list with filters and a confirmed store pickup booking"),
|
||||
5: ("A valid time is required at checkout",
|
||||
"Works on every Shopify plan, including Buy it now and express checkouts",
|
||||
"Datewise settings with the checkout guard on, the date and time shown at checkout, and payment rules"),
|
||||
6: ("The chosen time stays with the order",
|
||||
"Shown on the delivery line, in the order details and as order tags",
|
||||
"Shopify order showing the pickup line with the chosen date and time, booking details and Datewise tags"),
|
||||
}
|
||||
|
||||
FONT = Path.home() / "AppData/Local/Microsoft/Windows/Fonts/Inter-VariableFont_slnt,wght.ttf"
|
||||
|
||||
|
||||
def font(size, weight=400):
|
||||
try:
|
||||
f = ImageFont.truetype(str(FONT), size)
|
||||
try:
|
||||
axes = f.get_variation_axes()
|
||||
f.set_variation_by_axes([weight] + [0] * (len(axes) - 1))
|
||||
except Exception:
|
||||
pass
|
||||
return f
|
||||
except OSError:
|
||||
return ImageFont.truetype("C:/Windows/Fonts/segoeuib.ttf" if weight >= 600 else "C:/Windows/Fonts/segoeui.ttf", size)
|
||||
|
||||
|
||||
def background():
|
||||
bg = Image.new("RGB", (W, H), TEAL_DEEP)
|
||||
glow = Image.new("L", (W, H), 0)
|
||||
ImageDraw.Draw(glow).ellipse((-520, -620, 900, 520), fill=80)
|
||||
glow = glow.filter(ImageFilter.GaussianBlur(170))
|
||||
return Image.composite(Image.new("RGB", (W, H), WASH), bg, glow)
|
||||
|
||||
|
||||
def compose(raw_path, title, subtitle):
|
||||
bg = background()
|
||||
d = ImageDraw.Draw(bg)
|
||||
d.text((80, 60), title, font=font(52, 700), fill=(255, 255, 255))
|
||||
d.text((82, 132), subtitle, font=font(27, 450), fill=SAGE)
|
||||
|
||||
shot = Image.open(raw_path).convert("RGB")
|
||||
max_w, max_h = W - 160, H - 230
|
||||
scale = min(max_w / shot.width, max_h / shot.height)
|
||||
shot = shot.resize((round(shot.width * scale), round(shot.height * scale)), Image.LANCZOS)
|
||||
x, y = (W - shot.width) // 2, 200
|
||||
# shadow + rounded frame
|
||||
shadow = Image.new("L", (W, H), 0)
|
||||
ImageDraw.Draw(shadow).rounded_rectangle((x + 6, y + 18, x + shot.width + 6, y + shot.height + 18), 18, fill=120)
|
||||
shadow = shadow.filter(ImageFilter.GaussianBlur(24))
|
||||
bg = Image.composite(Image.new("RGB", (W, H), (2, 40, 36)), bg, shadow)
|
||||
mask = Image.new("L", shot.size, 0)
|
||||
ImageDraw.Draw(mask).rounded_rectangle((0, 0, shot.width - 1, shot.height - 1), 16, fill=255)
|
||||
bg.paste(shot, (x, y), mask)
|
||||
return bg
|
||||
|
||||
|
||||
def main():
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
alt_lines = []
|
||||
for raw in sorted(RAW.glob("*.*")):
|
||||
n = int(raw.name.split("-", 1)[0])
|
||||
if n not in SHOTS:
|
||||
continue
|
||||
title, subtitle, alt = SHOTS[n]
|
||||
out = OUT / (raw.stem + ".png")
|
||||
compose(raw, title, subtitle).save(out, optimize=True)
|
||||
alt_lines.append(f"{out.name}: {alt}")
|
||||
print("wrote", out.name)
|
||||
(OUT / "alt-text.txt").write_text("\n".join(alt_lines) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
brand/screenshots/listing/1-cart.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
brand/screenshots/listing/2-today.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
brand/screenshots/listing/3-location.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
brand/screenshots/listing/5-settings.png
Normal file
|
After Width: | Height: | Size: 245 KiB |
4
brand/screenshots/listing/alt-text.txt
Normal file
@ -0,0 +1,4 @@
|
||||
1-cart.png: Shopify cart page with the Datewise scheduler: store pickup selected, a calendar, time slots and a confirmation showing the time held
|
||||
2-today.png: Datewise Today page in the Shopify admin with booking counts and a 14-day capacity chart for a pickup location
|
||||
3-location.png: Location settings with pickup on, slot length, preparation time, same-day cutoff and orders per slot
|
||||
5-settings.png: Datewise settings with the checkout guard on, the date and time shown at checkout, and payment rules
|
||||
BIN
brand/screenshots/raw/1-cart.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
brand/screenshots/raw/2-today.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
brand/screenshots/raw/3-location.png
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
brand/screenshots/raw/5-settings.png
Normal file
|
After Width: | Height: | Size: 188 KiB |