Skip to content

Commit cae9021

Browse files
Merge pull request #500 from gridaco/canary
Daily
2 parents 8a6470a + 12aa781 commit cae9021

File tree

33 files changed

+1385
-866
lines changed

33 files changed

+1385
-866
lines changed

apps/backgrounds/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
"typecheck": "tsc --noEmit"
1212
},
1313
"dependencies": {
14-
"@next/third-parties": "16.1.2",
14+
"@next/third-parties": "16.1.3",
1515
"@react-three/drei": "^10.0.7",
1616
"@react-three/fiber": "9.1.2",
1717
"clsx": "^2.1.1",
1818
"motion": "^12.11.0",
19-
"next": "16.1.2",
19+
"next": "16.1.3",
2020
"react": "19.2.3",
2121
"react-dom": "19.2.3",
2222
"shadergradient": "^1.2.14",
@@ -31,7 +31,7 @@
3131
"@types/react-dom": "^19",
3232
"@types/three": "^0.170.0",
3333
"eslint": "^9",
34-
"eslint-config-next": "16.1.2",
34+
"eslint-config-next": "16.1.3",
3535
"tailwindcss": "^4",
3636
"typescript": "^5"
3737
}

apps/viewer/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
"lint": "eslint",
1010
"typecheck": "tsc --noEmit"
1111
},
12-
"packageManager": "[email protected]",
1312
"dependencies": {
1413
"@uidotdev/usehooks": "^2.4.1",
1514
"lucide-react": "^0.511.0",
16-
"next": "16.1.2",
15+
"next": "16.1.3",
1716
"pdfjs-dist": "4.8.69",
1817
"react": "19.2.3",
1918
"react-dom": "19.2.3",
@@ -27,9 +26,10 @@
2726
"@types/react": "^19",
2827
"@types/react-dom": "^19",
2928
"eslint": "^9",
30-
"eslint-config-next": "16.1.2",
29+
"eslint-config-next": "16.1.3",
3130
"postcss": "^8",
3231
"tailwindcss": "^4",
3332
"typescript": "^5"
34-
}
33+
},
34+
"packageManager": "[email protected]"
3535
}

database/database.types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ export type Database = MergeDeep<
2727
Row: DatabaseGenerated["public"]["Tables"]["customer"]["Row"] & {
2828
tags: string[];
2929
};
30+
/**
31+
* `customer_with_tags` is a view with an INSTEAD OF INSERT trigger.
32+
* We model Insert so client code can insert without `any` casts.
33+
*/
34+
Insert: DatabaseGenerated["public"]["Tables"]["customer"]["Insert"] & {
35+
tags?: string[] | null;
36+
};
3037
};
3138
};
3239
};

editor/app/(api)/(public)/v1/session/[session]/field/[field]/challenge/email/start/route.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,44 @@ export async function POST(
122122
.select("lang")
123123
.eq("form_id", ctx.form.id)
124124
.single();
125+
126+
if (!formDoc) {
127+
return NextResponse.json({ error: "form not found" }, { status: 404 });
128+
}
129+
130+
// Resolve brand info for email sender/display.
131+
// Prefer published tenant branding (`www.title` / `www.publisher`) when available,
132+
// otherwise fall back to a generic placeholder. (Do not expose internal project names.)
133+
const { data: www_list, error: www_err } = await service_role.www
134+
.from("www")
135+
.select("title, publisher, lang")
136+
.eq("project_id", ctx.form.project_id)
137+
.limit(1);
138+
139+
const www = !www_err && www_list && www_list.length > 0 ? www_list[0] : null;
140+
141+
const brand_name =
142+
www && typeof www.title === "string" && www.title
143+
? String(www.title)
144+
: "(Untitled)";
145+
146+
const publisher =
147+
www && typeof www.publisher === "string" && www.publisher
148+
? String(www.publisher)
149+
: "";
150+
const brand_support_url =
151+
publisher.startsWith("http://") || publisher.startsWith("https://")
152+
? publisher
153+
: undefined;
154+
const brand_support_contact = publisher.includes("@") ? publisher : undefined;
155+
156+
const langCandidate =
157+
www && typeof www.lang === "string" && www.lang ? www.lang : formDoc.lang;
125158
const emailLang: CIAMVerificationEmailLang = select_lang(
126-
(formDoc as { lang?: unknown } | null)?.lang,
159+
langCandidate,
127160
supported_languages,
128161
"en"
129162
);
130-
131-
const brand_name = "Grida";
132163
const { error: resend_err } = await resend.emails.send({
133164
from: `${brand_name} <[email protected]>`,
134165
to: email,
@@ -138,6 +169,8 @@ export async function POST(
138169
brand_name,
139170
expires_in_minutes,
140171
lang: emailLang,
172+
brand_support_url,
173+
brand_support_contact,
141174
}),
142175
});
143176

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
"use client";
2+
3+
import React, { useState } from "react";
4+
import { TagInput } from "@/components/tag";
5+
import { ComponentDemo } from "../component-demo";
6+
7+
export default function TagsPage() {
8+
const [tags, setTags] = useState<{ id: string; text: string }[]>([
9+
{ id: "react", text: "react" },
10+
{ id: "typescript", text: "typescript" },
11+
]);
12+
const [tagsWithAutocomplete, setTagsWithAutocomplete] = useState<
13+
{ id: string; text: string }[]
14+
>([]);
15+
const [emptyTags, setEmptyTags] = useState<{ id: string; text: string }[]>(
16+
[]
17+
);
18+
const [coloredTags, setColoredTags] = useState<
19+
{ id: string; text: string; color?: string }[]
20+
>([{ id: "urgent", text: "urgent", color: "#ef4444" }]);
21+
22+
const autocompleteOptions = [
23+
{ id: "apple", text: "apple" },
24+
{ id: "banana", text: "banana" },
25+
{ id: "cherry", text: "cherry" },
26+
{ id: "date", text: "date" },
27+
{ id: "elderberry", text: "elderberry" },
28+
{ id: "fig", text: "fig" },
29+
{ id: "grape", text: "grape" },
30+
{ id: "honeydew", text: "honeydew" },
31+
{ id: "kiwi", text: "kiwi" },
32+
{ id: "lemon", text: "lemon" },
33+
{ id: "mango", text: "mango" },
34+
{ id: "orange", text: "orange" },
35+
{ id: "papaya", text: "papaya" },
36+
{ id: "raspberry", text: "raspberry" },
37+
{ id: "strawberry", text: "strawberry" },
38+
];
39+
40+
const coloredAutocompleteOptions = [
41+
{ id: "urgent", text: "urgent", color: "#ef4444" },
42+
{ id: "review", text: "review", color: "#f59e0b" },
43+
{ id: "planned", text: "planned", color: "#3b82f6" },
44+
{ id: "done", text: "done", color: "#22c55e" },
45+
{ id: "blocked", text: "blocked", color: "#a855f7" },
46+
];
47+
48+
return (
49+
<main className="container max-w-screen-lg mx-auto py-10">
50+
<div className="space-y-8">
51+
<div>
52+
<h1 className="text-3xl font-bold mb-2">Tag Input</h1>
53+
<p className="text-gray-600">
54+
A flexible tag input component with autocomplete support for
55+
managing multiple values.
56+
</p>
57+
</div>
58+
59+
<hr />
60+
61+
<section className="space-y-4">
62+
<div>
63+
<h2 className="text-xl font-semibold mb-1">Basic Usage</h2>
64+
<p className="text-sm text-gray-600">
65+
Create and manage tags with keyboard support
66+
</p>
67+
</div>
68+
<ComponentDemo
69+
notes={
70+
<>
71+
<strong>Tips:</strong> Type and press Enter to add tags. Click
72+
on a tag to remove it.
73+
</>
74+
}
75+
>
76+
<TagInput
77+
tags={tags}
78+
setTags={setTags}
79+
activeTagIndex={null}
80+
setActiveTagIndex={() => {}}
81+
/>
82+
</ComponentDemo>
83+
</section>
84+
85+
<hr />
86+
87+
<section className="space-y-4">
88+
<div>
89+
<h2 className="text-xl font-semibold mb-1">With Autocomplete</h2>
90+
<p className="text-sm text-gray-600">
91+
Enable autocomplete suggestions for faster input
92+
</p>
93+
</div>
94+
<ComponentDemo
95+
notes={
96+
<>
97+
<strong>Tips:</strong> Start typing to see autocomplete
98+
suggestions. Select from the dropdown or create custom tags.
99+
</>
100+
}
101+
>
102+
<TagInput
103+
tags={tagsWithAutocomplete}
104+
setTags={setTagsWithAutocomplete}
105+
enableAutocomplete
106+
autocompleteOptions={autocompleteOptions}
107+
activeTagIndex={null}
108+
setActiveTagIndex={() => {}}
109+
/>
110+
</ComponentDemo>
111+
</section>
112+
113+
<hr />
114+
115+
<section className="space-y-4">
116+
<div>
117+
<h2 className="text-xl font-semibold mb-1">With Colors</h2>
118+
<p className="text-sm text-gray-600">
119+
Autocomplete and selected tags can be color-coded.
120+
</p>
121+
</div>
122+
<ComponentDemo
123+
notes={
124+
<>
125+
<strong>Tips:</strong> Pick from the colored suggestions to see
126+
the chips tinted, Notion-style.
127+
</>
128+
}
129+
>
130+
<TagInput
131+
tags={coloredTags}
132+
setTags={setColoredTags}
133+
enableAutocomplete
134+
autocompleteOptions={coloredAutocompleteOptions}
135+
activeTagIndex={null}
136+
setActiveTagIndex={() => {}}
137+
/>
138+
</ComponentDemo>
139+
</section>
140+
141+
<hr />
142+
143+
<section className="space-y-4">
144+
<div>
145+
<h2 className="text-xl font-semibold mb-1">Empty State</h2>
146+
<p className="text-sm text-gray-600">
147+
Tag input without any initial values
148+
</p>
149+
</div>
150+
<ComponentDemo notes="Start typing to add your first tag.">
151+
<TagInput
152+
tags={emptyTags}
153+
setTags={setEmptyTags}
154+
activeTagIndex={null}
155+
setActiveTagIndex={() => {}}
156+
/>
157+
</ComponentDemo>
158+
</section>
159+
</div>
160+
</main>
161+
);
162+
}

0 commit comments

Comments
 (0)