robodev 0.24.0 → 0.25.0

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.
Files changed (24) hide show
  1. package/package.json +1 -1
  2. package/templates/auth-chat/package.json +1 -1
  3. package/templates/backend/package.json +1 -1
  4. package/templates/empty/package.json +1 -1
  5. package/templates/marketplace/api/_lib.seed.test.ts +10 -0
  6. package/templates/marketplace/api/_lib.ts +114 -57
  7. package/templates/marketplace/api/categories.ts +1 -1
  8. package/templates/marketplace/api/listings/[id].ts +1 -1
  9. package/templates/marketplace/api/listings/paginate.ts +1 -1
  10. package/templates/marketplace/api/listings.ts +1 -1
  11. package/templates/marketplace/apps/fe/src/assets/locales/en/translation.json +1 -0
  12. package/templates/marketplace/apps/fe/src/assets/locales/sl/translation.json +1 -0
  13. package/templates/marketplace/apps/fe/src/components/features/marketplace/ListingDetailsPage.tsx +91 -49
  14. package/templates/marketplace/apps/fe/src/components/features/marketplace/ListingForm.tsx +278 -0
  15. package/templates/marketplace/apps/fe/src/components/features/marketplace/ListingFormPage.tsx +6 -222
  16. package/templates/marketplace/apps/fe/src/components/features/marketplace/ListingGallery.tsx +54 -0
  17. package/templates/marketplace/apps/fe/src/components/features/marketplace/MyListingsPage.tsx +61 -8
  18. package/templates/marketplace/apps/fe/src/components/features/marketplace/OrderStatusTag.tsx +24 -0
  19. package/templates/marketplace/apps/fe/src/components/features/marketplace/OrderSummaryCard.tsx +39 -0
  20. package/templates/marketplace/apps/fe/src/components/features/marketplace/OrdersPage.tsx +57 -45
  21. package/templates/marketplace/apps/fe/src/pages/(private)/my-listings.tsx +11 -1
  22. package/templates/marketplace/apps/fe/src/pages/(private)/sell/index.tsx +6 -10
  23. package/templates/marketplace/package.json +1 -1
  24. package/templates/space/package.json +1 -1
@@ -0,0 +1,24 @@
1
+ import { Tag } from "@povio/ui";
2
+ import { useTranslation } from "react-i18next";
3
+
4
+ type OrderStatus = "paid" | "shipped" | "completed";
5
+
6
+ const STATUS_COLOR: Record<OrderStatus, "warning" | "primary" | "success"> = {
7
+ paid: "warning",
8
+ shipped: "primary",
9
+ completed: "success",
10
+ };
11
+
12
+ interface OrderStatusTagProps {
13
+ status: string;
14
+ }
15
+
16
+ export function OrderStatusTag({ status }: OrderStatusTagProps) {
17
+ const { t } = useTranslation();
18
+ const known = status === "paid" || status === "shipped" || status === "completed" ? status : null;
19
+ if (!known) {
20
+ return null;
21
+ }
22
+
23
+ return <Tag color={STATUS_COLOR[known]}>{t(($) => $.orders.status[known])}</Tag>;
24
+ }
@@ -0,0 +1,39 @@
1
+ import { Button, Typography } from "@povio/ui/tanstack";
2
+ import { useTranslation } from "react-i18next";
3
+
4
+ import { Card } from "@/components/shared/ui/Card";
5
+ import type { OrderModels } from "@/openapi/order/order.models";
6
+ import { formatCents } from "@/utils/listing-form";
7
+
8
+ import { OrderStatusTag } from "./OrderStatusTag";
9
+
10
+ interface OrderSummaryCardProps {
11
+ order: OrderModels.OrderDto;
12
+ }
13
+
14
+ export function OrderSummaryCard({ order }: OrderSummaryCardProps) {
15
+ const { t } = useTranslation();
16
+
17
+ return (
18
+ <Card>
19
+ <Typography size="title-5">{order.items.map((item) => item.title).join(", ")}</Typography>
20
+ <div className="flex flex-wrap items-center gap-2">
21
+ <OrderStatusTag status={order.status} />
22
+ <Typography
23
+ size="label-3"
24
+ className="text-text-default-2"
25
+ >
26
+ {formatCents(order.totalCents)}
27
+ </Typography>
28
+ </div>
29
+ <Button
30
+ size="xs"
31
+ variant="outlined"
32
+ color="secondary"
33
+ link={{ to: "/orders/$id", params: { id: order.id } }}
34
+ >
35
+ {t(($) => $.orders.view)}
36
+ </Button>
37
+ </Card>
38
+ );
39
+ }
@@ -1,12 +1,4 @@
1
- import {
2
- Button,
3
- TextArea,
4
- TextInput,
5
- Typography,
6
- useForm,
7
- useFormValue,
8
- useToast,
9
- } from "@povio/ui/tanstack";
1
+ import { Button, TextArea, TextInput, Typography, useForm, useFormValue, useToast } from "@povio/ui/tanstack";
10
2
  import { useQueryClient } from "@tanstack/react-query";
11
3
  import { useTranslation } from "react-i18next";
12
4
  import { z } from "zod";
@@ -21,6 +13,9 @@ import { ReviewQueries } from "@/openapi/review/review.queries";
21
13
  import { SaleQueries } from "@/openapi/sale/sale.queries";
22
14
  import { formatCents } from "@/utils/listing-form";
23
15
 
16
+ import { OrderStatusTag } from "./OrderStatusTag";
17
+ import { OrderSummaryCard } from "./OrderSummaryCard";
18
+
24
19
  export function OrdersPage() {
25
20
  const { t } = useTranslation();
26
21
  const { data: purchases, isLoading: loadingPurchases } = PurchaseQueries.useGetAll();
@@ -30,58 +25,59 @@ export function OrdersPage() {
30
25
  <div className="flex flex-col gap-8">
31
26
  <PageHeader title={t(($) => $.orders.title)} />
32
27
  <section className="flex flex-col gap-3">
33
- <Typography size="title-5" variant="prominent-1">
28
+ <Typography
29
+ size="title-5"
30
+ variant="prominent-1"
31
+ >
34
32
  {t(($) => $.orders.purchases)}
35
33
  </Typography>
36
34
  {loadingPurchases ? (
37
35
  <LoadingState />
38
36
  ) : !purchases || purchases.length === 0 ? (
39
- <Typography size="body-3" className="text-text-default-2">
37
+ <Typography
38
+ size="body-3"
39
+ className="text-text-default-2"
40
+ >
40
41
  {t(($) => $.orders.emptyPurchases)}
41
42
  </Typography>
42
43
  ) : (
43
- purchases.map((order) => <OrderSummary key={order.id} order={order} />)
44
+ purchases.map((order) => (
45
+ <OrderSummaryCard
46
+ key={order.id}
47
+ order={order}
48
+ />
49
+ ))
44
50
  )}
45
51
  </section>
46
52
  <section className="flex flex-col gap-3">
47
- <Typography size="title-5" variant="prominent-1">
53
+ <Typography
54
+ size="title-5"
55
+ variant="prominent-1"
56
+ >
48
57
  {t(($) => $.orders.sales)}
49
58
  </Typography>
50
59
  {loadingSales ? (
51
60
  <LoadingState />
52
61
  ) : !sales || sales.length === 0 ? (
53
- <Typography size="body-3" className="text-text-default-2">
62
+ <Typography
63
+ size="body-3"
64
+ className="text-text-default-2"
65
+ >
54
66
  {t(($) => $.orders.emptySales)}
55
67
  </Typography>
56
68
  ) : (
57
- sales.map((order) => <OrderSummary key={order.id} order={order} />)
69
+ sales.map((order) => (
70
+ <OrderSummaryCard
71
+ key={order.id}
72
+ order={order}
73
+ />
74
+ ))
58
75
  )}
59
76
  </section>
60
77
  </div>
61
78
  );
62
79
  }
63
80
 
64
- function OrderSummary({ order }: { order: OrderModels.OrderDto }) {
65
- const { t } = useTranslation();
66
- return (
67
- <Card>
68
- <Typography size="title-5">{order.items.map((item) => item.title).join(", ")}</Typography>
69
- <Typography size="label-3" className="text-text-default-2">
70
- {t(($) => $.orders.status[order.status as "paid" | "shipped" | "completed"])} ·{" "}
71
- {formatCents(order.totalCents)}
72
- </Typography>
73
- <Button
74
- size="xs"
75
- variant="outlined"
76
- color="secondary"
77
- link={{ to: "/orders/$id", params: { id: order.id } }}
78
- >
79
- {order.id.slice(0, 8)}
80
- </Button>
81
- </Card>
82
- );
83
- }
84
-
85
81
  interface OrderDetailPageProps {
86
82
  order: OrderModels.OrderDto;
87
83
  role: "buyer" | "seller";
@@ -159,34 +155,47 @@ export function OrderDetailPage({ order, role }: OrderDetailPageProps) {
159
155
  title={t(($) => $.orders.title)}
160
156
  />
161
157
  <Card>
162
- <Typography size="label-3" className="text-text-default-2">
163
- {t(($) => $.orders.status[order.status as "paid" | "shipped" | "completed"])}
164
- </Typography>
158
+ <OrderStatusTag status={order.status} />
165
159
  {order.items.map((item) => (
166
- <Typography key={item.id} size="body-3">
160
+ <Typography
161
+ key={item.id}
162
+ size="body-3"
163
+ >
167
164
  {item.qty} × {item.title} · {formatCents(item.unitPriceCents)}
168
165
  </Typography>
169
166
  ))}
170
167
  <Typography size="body-3">
171
168
  {t(($) => $.orders.total)}: {formatCents(order.totalCents)}
172
169
  </Typography>
173
- <Typography size="label-3" className="text-text-default-2">
170
+ <Typography
171
+ size="label-3"
172
+ className="text-text-default-2"
173
+ >
174
174
  {order.shippingName}, {order.shippingLine1}, {order.shippingCity}
175
175
  </Typography>
176
176
  {role === "seller" && order.status === "paid" ? (
177
- <Button size="xs" onPress={() => void onShip()}>
177
+ <Button
178
+ size="xs"
179
+ onPress={() => void onShip()}
180
+ >
178
181
  {t(($) => $.orders.ship)}
179
182
  </Button>
180
183
  ) : null}
181
184
  {role === "buyer" && order.status === "shipped" ? (
182
- <Button size="xs" onPress={() => void onComplete()}>
185
+ <Button
186
+ size="xs"
187
+ onPress={() => void onComplete()}
188
+ >
183
189
  {t(($) => $.orders.complete)}
184
190
  </Button>
185
191
  ) : null}
186
192
  </Card>
187
193
 
188
194
  {role === "buyer" && order.status === "completed" ? (
189
- <form onSubmit={reviewForm.handleSubmit(onReview)} className="flex max-w-xl flex-col gap-3">
195
+ <form
196
+ onSubmit={reviewForm.handleSubmit(onReview)}
197
+ className="flex max-w-xl flex-col gap-3"
198
+ >
190
199
  <Typography size="title-5">{t(($) => $.orders.review)}</Typography>
191
200
  <div className="flex flex-wrap gap-1">
192
201
  {order.items.map((item) => (
@@ -214,7 +223,10 @@ export function OrderDetailPage({ order, role }: OrderDetailPageProps) {
214
223
  field={{ form: reviewForm, name: "body" }}
215
224
  label={t(($) => $.orders.reviewBody)}
216
225
  />
217
- <Button type="submit" size="xs">
226
+ <Button
227
+ type="submit"
228
+ size="xs"
229
+ >
218
230
  {t(($) => $.orders.reviewSubmit)}
219
231
  </Button>
220
232
  </form>
@@ -1,22 +1,32 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
2
  import { useTranslation } from "react-i18next";
3
+ import { z } from "zod";
3
4
 
4
5
  import { MyListingsPage } from "@/components/features/marketplace/MyListingsPage";
5
6
  import { AppHead } from "@/components/shared/head/AppHead";
6
7
 
8
+ const myListingsSearchSchema = z.object({
9
+ create: z
10
+ .union([z.boolean(), z.string()])
11
+ .optional()
12
+ .transform((value) => value === true || value === "true"),
13
+ });
14
+
7
15
  function PageComponent() {
8
16
  const { t } = useTranslation();
17
+ const { create } = Route.useSearch();
9
18
  return (
10
19
  <>
11
20
  <AppHead
12
21
  title={t(($) => $.sell.head.title)}
13
22
  description={t(($) => $.sell.head.description)}
14
23
  />
15
- <MyListingsPage />
24
+ <MyListingsPage openCreate={create} />
16
25
  </>
17
26
  );
18
27
  }
19
28
 
20
29
  export const Route = createFileRoute("/(private)/my-listings")({
21
30
  component: PageComponent,
31
+ validateSearch: myListingsSearchSchema,
22
32
  });
@@ -1,16 +1,12 @@
1
- import { createFileRoute } from "@tanstack/react-router";
2
- import { useTranslation } from "react-i18next";
3
-
4
- import { ListingFormPage } from "@/components/features/marketplace/ListingFormPage";
5
- import { AppHead } from "@/components/shared/head/AppHead";
1
+ import { Navigate, createFileRoute } from "@tanstack/react-router";
6
2
 
7
3
  function PageComponent() {
8
- const { t } = useTranslation();
9
4
  return (
10
- <>
11
- <AppHead title={t(($) => $.listingForm.createTitle)} />
12
- <ListingFormPage />
13
- </>
5
+ <Navigate
6
+ to="/my-listings"
7
+ search={{ create: true }}
8
+ replace
9
+ />
14
10
  );
15
11
  }
16
12
 
@@ -11,7 +11,7 @@
11
11
  "@robodev-ai/sdk": "^0.6.0"
12
12
  },
13
13
  "devDependencies": {
14
- "robodev": "^0.24.0",
14
+ "robodev": "^0.25.0",
15
15
  "rulesync": "^8.18.0",
16
16
  "typescript": "^5.9.2"
17
17
  }
@@ -11,7 +11,7 @@
11
11
  "@robodev-ai/sdk": "^0.6.0"
12
12
  },
13
13
  "devDependencies": {
14
- "robodev": "^0.24.0",
14
+ "robodev": "^0.25.0",
15
15
  "rulesync": "^8.18.0",
16
16
  "typescript": "^5.9.2"
17
17
  }