diff --git a/.gitignore b/.gitignore
index 631f556..279ef03 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,3 @@
.cursor/rules/nx-rules.mdc
.github/instructions/nx.instructions.md
node_modules
-apps/website/src/content/.obsidian/workspace.json
diff --git a/apps/content/package.json b/apps/content/package.json
index 8b1752d..e260f72 100644
--- a/apps/content/package.json
+++ b/apps/content/package.json
@@ -10,7 +10,7 @@
"start": "sanity start",
"build": "sanity build",
"prebuild": "npm run typegen",
- "typegen": "sanity schema extract && sanity typegen generate",
+ "typegen": "sanity schema extract --enforce-required-fields && sanity typegen generate",
"deploy": "sanity deploy",
"deploy-graphql": "sanity graphql deploy",
"login": "sanity login"
@@ -20,22 +20,22 @@
],
"dependencies": {
"@sanity/icons": "^3.7.4",
- "@sanity/vision": "^4.2.0",
+ "@sanity/vision": "^4.15.0",
"easymde": "^2.20.0",
- "react": "^19.1.1",
- "react-dom": "^19.1.1",
- "sanity": "^4.2.0",
- "sanity-plugin-cloudinary": "^1.3.1",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "sanity": "^4.15.0",
+ "sanity-plugin-cloudinary": "^1.4.0",
"sanity-plugin-markdown": "^6.0.0",
- "sanity-plugin-mux-input": "^2.8.1",
+ "sanity-plugin-mux-input": "^2.11.2",
"styled-components": "^6.1.19"
},
"devDependencies": {
"@sanity/eslint-config-studio": "^5.0.2",
- "@types/react": "^19.1.9",
- "eslint": "^9.32.0",
+ "@types/react": "^19.2.6",
+ "eslint": "^9.39.1",
"prettier": "^3.6.2",
- "typescript": "^5.9.2"
+ "typescript": "^5.9.3"
},
"prettier": {
"semi": false,
diff --git a/apps/content/sanity.config.ts b/apps/content/sanity.config.ts
index f7834d0..3bcfb94 100644
--- a/apps/content/sanity.config.ts
+++ b/apps/content/sanity.config.ts
@@ -24,14 +24,43 @@ export default defineConfig({
structure: (S) => {
return S.list()
.title('Content')
- .items(
- S.documentTypeListItems().filter(
+ .items([
+ ...S.documentTypeListItems().filter(
(li) =>
- !['Episode', 'Collection', 'Episode Tag', 'Video asset'].includes(
- li.getTitle() ?? '',
- ),
+ ![
+ 'Episode',
+ 'Collection',
+ 'Episode Tag',
+ 'Video asset',
+ 'Rewards',
+ 'FAQ',
+ 'Rules',
+ 'Hackathon',
+ ].includes(li.getTitle() ?? ''),
),
- )
+ S.divider(),
+ S.listItem()
+ .title('Hackathons')
+ .schemaType('hackathon')
+ .child(S.documentTypeList('hackathon').title('Hackathons')),
+ S.listItem()
+ .title('FAQs')
+ .schemaType('faq')
+ .child(S.documentTypeList('faq').title('FAQs')),
+ S.listItem()
+ .title('Rewards')
+ .schemaType('rewards')
+ .child(S.documentTypeList('rewards').title('Rewards')),
+ S.listItem()
+ .title('Rules')
+ .schemaType('rules')
+ .child(S.documentTypeList('rules').title('Rules')),
+ S.divider(),
+ S.listItem()
+ .title('Episodes')
+ .schemaType('episode')
+ .child(S.documentTypeList('episode').title('Episodes')),
+ ])
},
}),
],
diff --git a/apps/content/schema.json b/apps/content/schema.json
index 69b647e..4459bba 100644
--- a/apps/content/schema.json
+++ b/apps/content/schema.json
@@ -1,4 +1,41 @@
[
+ {
+ "name": "resourceItem",
+ "type": "type",
+ "value": {
+ "type": "object",
+ "attributes": {
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "resourceItem"
+ }
+ },
+ "title": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "description": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "url": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ }
+ }
+ }
+ },
{
"name": "episodeImage",
"type": "type",
@@ -70,7 +107,7 @@
"value": {
"type": "string"
},
- "optional": true
+ "optional": false
},
"caption": {
"type": "objectAttribute",
@@ -82,6 +119,94 @@
}
}
},
+ {
+ "name": "sanity.imageCrop",
+ "type": "type",
+ "value": {
+ "type": "object",
+ "attributes": {
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "sanity.imageCrop"
+ }
+ },
+ "top": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": false
+ },
+ "bottom": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": false
+ },
+ "left": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": false
+ },
+ "right": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": false
+ }
+ }
+ }
+ },
+ {
+ "name": "sanity.imageHotspot",
+ "type": "type",
+ "value": {
+ "type": "object",
+ "attributes": {
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "sanity.imageHotspot"
+ }
+ },
+ "x": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": false
+ },
+ "y": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": false
+ },
+ "height": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": false
+ },
+ "width": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": false
+ }
+ }
+ }
+ },
{
"name": "episodeTag",
"type": "document",
@@ -122,7 +247,7 @@
"value": {
"type": "string"
},
- "optional": true
+ "optional": false
},
"slug": {
"type": "objectAttribute",
@@ -130,7 +255,7 @@
"type": "inline",
"name": "slug"
},
- "optional": true
+ "optional": false
},
"description": {
"type": "objectAttribute",
@@ -141,6 +266,36 @@
}
}
},
+ {
+ "name": "slug",
+ "type": "type",
+ "value": {
+ "type": "object",
+ "attributes": {
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "slug"
+ }
+ },
+ "current": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "source": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ }
+ }
+ }
+ },
{
"name": "sponsor",
"type": "document",
@@ -181,7 +336,7 @@
"value": {
"type": "string"
},
- "optional": true
+ "optional": false
},
"slug": {
"type": "objectAttribute",
@@ -189,7 +344,7 @@
"type": "inline",
"name": "slug"
},
- "optional": true
+ "optional": false
},
"logo": {
"type": "objectAttribute",
@@ -197,111 +352,884 @@
"type": "inline",
"name": "cloudinary.asset"
},
- "optional": true
+ "optional": false
},
"link": {
"type": "objectAttribute",
"value": {
"type": "string"
},
- "optional": true
+ "optional": false
}
}
},
{
- "name": "person",
- "type": "document",
- "attributes": {
- "_id": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- }
- },
- "_type": {
- "type": "objectAttribute",
- "value": {
- "type": "string",
- "value": "person"
- }
- },
- "_createdAt": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- }
- },
- "_updatedAt": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- }
- },
- "_rev": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- }
- },
- "name": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
+ "name": "cloudinary.asset",
+ "type": "type",
+ "value": {
+ "type": "object",
+ "attributes": {
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "cloudinary.asset"
+ }
},
- "optional": true
- },
- "slug": {
- "type": "objectAttribute",
- "value": {
- "type": "inline",
- "name": "slug"
+ "public_id": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
},
- "optional": true
- },
- "photo": {
- "type": "objectAttribute",
- "value": {
- "type": "inline",
- "name": "cloudinary.asset"
+ "resource_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
},
- "optional": true
- },
- "bio": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
+ "type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "format": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "version": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": true
+ },
+ "url": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "secure_url": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "width": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": true
+ },
+ "height": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": true
+ },
+ "bytes": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": true
+ },
+ "duration": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": true
+ },
+ "tags": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "array",
+ "of": {
+ "type": "string"
+ }
+ },
+ "optional": true
+ },
+ "created_at": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "derived": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "array",
+ "of": {
+ "type": "object",
+ "attributes": {
+ "_key": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ }
+ },
+ "rest": {
+ "type": "inline",
+ "name": "derived"
+ }
+ }
+ },
+ "optional": true
+ },
+ "access_mode": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "context": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "inline",
+ "name": "cloudinary.assetContext"
+ },
+ "optional": true
+ }
+ }
+ }
+ },
+ {
+ "name": "person",
+ "type": "document",
+ "attributes": {
+ "_id": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "person"
+ }
+ },
+ "_createdAt": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_updatedAt": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_rev": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "slug": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "inline",
+ "name": "slug"
+ },
+ "optional": false
+ },
+ "photo": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "inline",
+ "name": "cloudinary.asset"
+ },
+ "optional": true
+ },
+ "bio": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "inline",
+ "name": "markdown"
+ },
+ "optional": true
+ },
+ "links": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "array",
+ "of": {
+ "type": "object",
+ "attributes": {
+ "label": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "url": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "link"
+ }
+ }
+ },
+ "rest": {
+ "type": "object",
+ "attributes": {
+ "_key": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
+ "optional": true
+ },
+ "subscription": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "object",
+ "attributes": {
+ "customer": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "level": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "union",
+ "of": [
+ {
+ "type": "string",
+ "value": "Silver Tier Supporter"
+ },
+ {
+ "type": "string",
+ "value": "Gold Tier Supporter"
+ },
+ {
+ "type": "string",
+ "value": "Platinum Tier Supporter"
+ }
+ ]
+ },
+ "optional": true
+ },
+ "status": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "union",
+ "of": [
+ {
+ "type": "string",
+ "value": "incomplete"
+ },
+ {
+ "type": "string",
+ "value": "incomplete_expired"
+ },
+ {
+ "type": "string",
+ "value": "trialing"
+ },
+ {
+ "type": "string",
+ "value": "active"
+ },
+ {
+ "type": "string",
+ "value": "past_due"
+ },
+ {
+ "type": "string",
+ "value": "canceled"
+ },
+ {
+ "type": "string",
+ "value": "unpaid"
+ },
+ {
+ "type": "string",
+ "value": "paused"
+ }
+ ]
+ },
+ "optional": true
+ },
+ "date": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ }
+ }
+ },
+ "optional": true
+ },
+ "user_id": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ }
+ }
+ },
+ {
+ "name": "markdown",
+ "type": "type",
+ "value": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "rules",
+ "type": "document",
+ "attributes": {
+ "_id": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "rules"
+ }
+ },
+ "_createdAt": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_updatedAt": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_rev": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "title": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "description": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "weight": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": false
+ },
+ "setAsDefault": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "boolean"
+ },
+ "optional": true
+ }
+ }
+ },
+ {
+ "name": "rewards",
+ "type": "document",
+ "attributes": {
+ "_id": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "rewards"
+ }
+ },
+ "_createdAt": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_updatedAt": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_rev": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "title": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "description": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "image": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "inline",
+ "name": "cloudinary.asset"
+ },
+ "optional": false
+ },
+ "weight": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": false
+ },
+ "setAsDefault": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "boolean"
+ },
+ "optional": true
+ }
+ }
+ },
+ {
+ "name": "faq",
+ "type": "document",
+ "attributes": {
+ "_id": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "faq"
+ }
+ },
+ "_createdAt": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_updatedAt": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_rev": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "question": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "answer": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "weight": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "number"
+ },
+ "optional": false
+ },
+ "setAsDefault": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "boolean"
+ },
+ "optional": true
+ }
+ }
+ },
+ {
+ "name": "hackathon",
+ "type": "document",
+ "attributes": {
+ "_id": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "hackathon"
+ }
+ },
+ "_createdAt": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_updatedAt": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_rev": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "title": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "slug": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "inline",
+ "name": "slug"
+ },
+ "optional": false
+ },
+ "pubDate": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "deadline": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "description": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "hero_image": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "inline",
+ "name": "cloudinary.asset"
+ },
+ "optional": true
+ },
+ "hero_title": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "body": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "inline",
+ "name": "markdown"
+ },
+ "optional": false
+ },
+ "episodes": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "array",
+ "of": {
+ "type": "object",
+ "attributes": {
+ "_ref": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "reference"
+ }
+ },
+ "_weak": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "boolean"
+ },
+ "optional": true
+ }
+ },
+ "dereferencesTo": "episode",
+ "rest": {
+ "type": "object",
+ "attributes": {
+ "_key": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
+ "optional": true
+ },
+ "submissionForm": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": false
+ },
+ "rewards": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "array",
+ "of": {
+ "type": "object",
+ "attributes": {
+ "_ref": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "reference"
+ }
+ },
+ "_weak": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "boolean"
+ },
+ "optional": true
+ }
+ },
+ "dereferencesTo": "rewards",
+ "rest": {
+ "type": "object",
+ "attributes": {
+ "_key": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
+ "optional": true
+ },
+ "faq": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "array",
+ "of": {
+ "type": "object",
+ "attributes": {
+ "_ref": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "reference"
+ }
+ },
+ "_weak": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "boolean"
+ },
+ "optional": true
+ }
+ },
+ "dereferencesTo": "faq",
+ "rest": {
+ "type": "object",
+ "attributes": {
+ "_key": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
},
"optional": true
},
- "links": {
+ "sponsors": {
"type": "objectAttribute",
"value": {
"type": "array",
"of": {
"type": "object",
"attributes": {
- "label": {
+ "_ref": {
"type": "objectAttribute",
"value": {
"type": "string"
- },
- "optional": true
+ }
},
- "url": {
+ "_type": {
"type": "objectAttribute",
"value": {
- "type": "string"
+ "type": "string",
+ "value": "reference"
+ }
+ },
+ "_weak": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "boolean"
},
"optional": true
+ }
+ },
+ "dereferencesTo": "sponsor",
+ "rest": {
+ "type": "object",
+ "attributes": {
+ "_key": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
+ "optional": true
+ },
+ "rules": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "array",
+ "of": {
+ "type": "object",
+ "attributes": {
+ "_ref": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
},
"_type": {
"type": "objectAttribute",
"value": {
"type": "string",
- "value": "link"
+ "value": "reference"
}
+ },
+ "_weak": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "boolean"
+ },
+ "optional": true
}
},
+ "dereferencesTo": "rules",
"rest": {
"type": "object",
"attributes": {
@@ -317,95 +1245,50 @@
},
"optional": true
},
- "subscription": {
+ "resources": {
"type": "objectAttribute",
"value": {
- "type": "object",
- "attributes": {
- "customer": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "level": {
- "type": "objectAttribute",
- "value": {
- "type": "union",
- "of": [
- {
- "type": "string",
- "value": "Silver Tier Supporter"
- },
- {
- "type": "string",
- "value": "Gold Tier Supporter"
- },
- {
- "type": "string",
- "value": "Platinum Tier Supporter"
- }
- ]
- },
- "optional": true
- },
- "status": {
- "type": "objectAttribute",
- "value": {
- "type": "union",
- "of": [
- {
- "type": "string",
- "value": "incomplete"
- },
- {
- "type": "string",
- "value": "incomplete_expired"
- },
- {
- "type": "string",
- "value": "trialing"
- },
- {
- "type": "string",
- "value": "active"
- },
- {
- "type": "string",
- "value": "past_due"
- },
- {
- "type": "string",
- "value": "canceled"
- },
- {
- "type": "string",
- "value": "unpaid"
- },
- {
- "type": "string",
- "value": "paused"
- }
- ]
- },
- "optional": true
+ "type": "array",
+ "of": {
+ "type": "object",
+ "attributes": {
+ "_key": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ }
},
- "date": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
+ "rest": {
+ "type": "inline",
+ "name": "resourceItem"
}
}
},
"optional": true
},
- "user_id": {
+ "share_image": {
"type": "objectAttribute",
"value": {
- "type": "string"
+ "type": "inline",
+ "name": "cloudinary.asset"
+ },
+ "optional": false
+ },
+ "hidden": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "union",
+ "of": [
+ {
+ "type": "string",
+ "value": "visible"
+ },
+ {
+ "type": "string",
+ "value": "hidden"
+ }
+ ]
},
"optional": true
}
@@ -451,7 +1334,7 @@
"value": {
"type": "string"
},
- "optional": true
+ "optional": false
},
"slug": {
"type": "objectAttribute",
@@ -459,28 +1342,29 @@
"type": "inline",
"name": "slug"
},
- "optional": true
+ "optional": false
},
"publish_date": {
"type": "objectAttribute",
"value": {
"type": "string"
},
- "optional": true
+ "optional": false
},
"short_description": {
"type": "objectAttribute",
"value": {
"type": "string"
},
- "optional": true
+ "optional": false
},
"description": {
"type": "objectAttribute",
"value": {
- "type": "string"
+ "type": "inline",
+ "name": "markdown"
},
- "optional": true
+ "optional": false
},
"people": {
"type": "objectAttribute",
@@ -570,6 +1454,50 @@
},
"optional": true
},
+ "hackathons": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "array",
+ "of": {
+ "type": "object",
+ "attributes": {
+ "_ref": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "reference"
+ }
+ },
+ "_weak": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "boolean"
+ },
+ "optional": true
+ }
+ },
+ "dereferencesTo": "hackathon",
+ "rest": {
+ "type": "object",
+ "attributes": {
+ "_key": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
+ "optional": true
+ },
"resources": {
"type": "objectAttribute",
"value": {
@@ -722,7 +1650,8 @@
"transcript": {
"type": "objectAttribute",
"value": {
- "type": "string"
+ "type": "inline",
+ "name": "markdown"
},
"optional": true
}
@@ -762,7 +1691,53 @@
}
]
},
- "optional": true
+ "optional": true
+ }
+ }
+ },
+ {
+ "name": "mux.video",
+ "type": "type",
+ "value": {
+ "type": "object",
+ "attributes": {
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "mux.video"
+ }
+ },
+ "asset": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "object",
+ "attributes": {
+ "_ref": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ }
+ },
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "reference"
+ }
+ },
+ "_weak": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "boolean"
+ },
+ "optional": true
+ }
+ },
+ "dereferencesTo": "mux.videoAsset"
+ },
+ "optional": true
+ }
}
}
},
@@ -806,7 +1781,7 @@
"value": {
"type": "string"
},
- "optional": true
+ "optional": false
},
"slug": {
"type": "objectAttribute",
@@ -814,14 +1789,14 @@
"type": "inline",
"name": "slug"
},
- "optional": true
+ "optional": false
},
"release_year": {
"type": "objectAttribute",
"value": {
"type": "string"
},
- "optional": true
+ "optional": false
},
"series": {
"type": "objectAttribute",
@@ -851,7 +1826,7 @@
},
"dereferencesTo": "series"
},
- "optional": true
+ "optional": false
},
"episodes": {
"type": "objectAttribute",
@@ -939,7 +1914,7 @@
"value": {
"type": "string"
},
- "optional": true
+ "optional": false
},
"slug": {
"type": "objectAttribute",
@@ -947,14 +1922,14 @@
"type": "inline",
"name": "slug"
},
- "optional": true
+ "optional": false
},
"description": {
"type": "objectAttribute",
"value": {
"type": "string"
},
- "optional": true
+ "optional": false
},
"image": {
"type": "objectAttribute",
@@ -1071,52 +2046,6 @@
}
}
},
- {
- "name": "mux.video",
- "type": "type",
- "value": {
- "type": "object",
- "attributes": {
- "_type": {
- "type": "objectAttribute",
- "value": {
- "type": "string",
- "value": "mux.video"
- }
- },
- "asset": {
- "type": "objectAttribute",
- "value": {
- "type": "object",
- "attributes": {
- "_ref": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- }
- },
- "_type": {
- "type": "objectAttribute",
- "value": {
- "type": "string",
- "value": "reference"
- }
- },
- "_weak": {
- "type": "objectAttribute",
- "value": {
- "type": "boolean"
- },
- "optional": true
- }
- },
- "dereferencesTo": "mux.videoAsset"
- },
- "optional": true
- }
- }
- }
- },
{
"name": "mux.videoAsset",
"type": "document",
@@ -1266,6 +2195,13 @@
},
"optional": true
},
+ "video_quality": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ },
"master_access": {
"type": "objectAttribute",
"value": {
@@ -1585,7 +2521,7 @@
}
},
{
- "name": "cloudinary.assetDerived",
+ "name": "cloudinary.assetContext",
"type": "type",
"value": {
"type": "object",
@@ -1594,27 +2530,14 @@
"type": "objectAttribute",
"value": {
"type": "string",
- "value": "cloudinary.assetDerived"
+ "value": "cloudinary.assetContext"
}
},
- "raw_transformation": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "url": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "secure_url": {
+ "custom": {
"type": "objectAttribute",
"value": {
- "type": "string"
+ "type": "inline",
+ "name": "cloudinary.assetContextCustom"
},
"optional": true
}
@@ -1622,7 +2545,7 @@
}
},
{
- "name": "cloudinary.asset",
+ "name": "cloudinary.assetDerived",
"type": "type",
"value": {
"type": "object",
@@ -1631,174 +2554,33 @@
"type": "objectAttribute",
"value": {
"type": "string",
- "value": "cloudinary.asset"
+ "value": "cloudinary.assetDerived"
}
},
- "public_id": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "resource_type": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "type": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "format": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "version": {
- "type": "objectAttribute",
- "value": {
- "type": "number"
- },
- "optional": true
- },
- "url": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "secure_url": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "width": {
- "type": "objectAttribute",
- "value": {
- "type": "number"
- },
- "optional": true
- },
- "height": {
- "type": "objectAttribute",
- "value": {
- "type": "number"
- },
- "optional": true
- },
- "bytes": {
- "type": "objectAttribute",
- "value": {
- "type": "number"
- },
- "optional": true
- },
- "duration": {
- "type": "objectAttribute",
- "value": {
- "type": "number"
- },
- "optional": true
- },
- "tags": {
- "type": "objectAttribute",
- "value": {
- "type": "array",
- "of": {
- "type": "string"
- }
- },
- "optional": true
- },
- "created_at": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "derived": {
- "type": "objectAttribute",
- "value": {
- "type": "array",
- "of": {
- "type": "object",
- "attributes": {
- "_key": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- }
- }
- },
- "rest": {
- "type": "inline",
- "name": "cloudinary.assetDerived"
- }
- }
- },
- "optional": true
- },
- "access_mode": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "context": {
+ "raw_transformation": {
"type": "objectAttribute",
"value": {
- "type": "inline",
- "name": "cloudinary.assetContext"
- },
- "optional": true
- }
- }
- }
- },
- {
- "name": "cloudinary.assetContext",
- "type": "type",
- "value": {
- "type": "object",
- "attributes": {
- "_type": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "url": {
"type": "objectAttribute",
"value": {
- "type": "string",
- "value": "cloudinary.assetContext"
- }
+ "type": "string"
+ },
+ "optional": true
},
- "custom": {
+ "secure_url": {
"type": "objectAttribute",
"value": {
- "type": "inline",
- "name": "cloudinary.assetContextCustom"
+ "type": "string"
},
"optional": true
}
}
}
},
- {
- "name": "markdown",
- "type": "type",
- "value": {
- "type": "string"
- }
- },
{
"name": "sanity.imagePaletteSwatch",
"type": "type",
@@ -1933,27 +2715,27 @@
"value": {
"type": "number"
},
- "optional": true
+ "optional": false
},
"width": {
"type": "objectAttribute",
"value": {
"type": "number"
},
- "optional": true
+ "optional": false
},
"aspectRatio": {
"type": "objectAttribute",
"value": {
"type": "number"
},
- "optional": true
+ "optional": false
}
}
}
},
{
- "name": "sanity.imageHotspot",
+ "name": "sanity.imageMetadata",
"type": "type",
"value": {
"type": "object",
@@ -1962,78 +2744,58 @@
"type": "objectAttribute",
"value": {
"type": "string",
- "value": "sanity.imageHotspot"
+ "value": "sanity.imageMetadata"
}
},
- "x": {
- "type": "objectAttribute",
- "value": {
- "type": "number"
- },
- "optional": true
- },
- "y": {
+ "location": {
"type": "objectAttribute",
"value": {
- "type": "number"
+ "type": "inline",
+ "name": "geopoint"
},
"optional": true
},
- "height": {
+ "dimensions": {
"type": "objectAttribute",
"value": {
- "type": "number"
+ "type": "inline",
+ "name": "sanity.imageDimensions"
},
"optional": true
},
- "width": {
+ "palette": {
"type": "objectAttribute",
"value": {
- "type": "number"
+ "type": "inline",
+ "name": "sanity.imagePalette"
},
"optional": true
- }
- }
- }
- },
- {
- "name": "sanity.imageCrop",
- "type": "type",
- "value": {
- "type": "object",
- "attributes": {
- "_type": {
- "type": "objectAttribute",
- "value": {
- "type": "string",
- "value": "sanity.imageCrop"
- }
},
- "top": {
+ "lqip": {
"type": "objectAttribute",
"value": {
- "type": "number"
+ "type": "string"
},
"optional": true
},
- "bottom": {
+ "blurHash": {
"type": "objectAttribute",
"value": {
- "type": "number"
+ "type": "string"
},
"optional": true
},
- "left": {
+ "hasAlpha": {
"type": "objectAttribute",
"value": {
- "type": "number"
+ "type": "boolean"
},
"optional": true
},
- "right": {
+ "isOpaque": {
"type": "objectAttribute",
"value": {
- "type": "number"
+ "type": "boolean"
},
"optional": true
}
@@ -2176,6 +2938,43 @@
}
}
},
+ {
+ "name": "sanity.assetSourceData",
+ "type": "type",
+ "value": {
+ "type": "object",
+ "attributes": {
+ "_type": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string",
+ "value": "sanity.assetSourceData"
+ }
+ },
+ "name": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "id": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ },
+ "url": {
+ "type": "objectAttribute",
+ "value": {
+ "type": "string"
+ },
+ "optional": true
+ }
+ }
+ }
+ },
{
"name": "sanity.imageAsset",
"type": "document",
@@ -2320,74 +3119,6 @@
}
}
},
- {
- "name": "sanity.imageMetadata",
- "type": "type",
- "value": {
- "type": "object",
- "attributes": {
- "_type": {
- "type": "objectAttribute",
- "value": {
- "type": "string",
- "value": "sanity.imageMetadata"
- }
- },
- "location": {
- "type": "objectAttribute",
- "value": {
- "type": "inline",
- "name": "geopoint"
- },
- "optional": true
- },
- "dimensions": {
- "type": "objectAttribute",
- "value": {
- "type": "inline",
- "name": "sanity.imageDimensions"
- },
- "optional": true
- },
- "palette": {
- "type": "objectAttribute",
- "value": {
- "type": "inline",
- "name": "sanity.imagePalette"
- },
- "optional": true
- },
- "lqip": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "blurHash": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "hasAlpha": {
- "type": "objectAttribute",
- "value": {
- "type": "boolean"
- },
- "optional": true
- },
- "isOpaque": {
- "type": "objectAttribute",
- "value": {
- "type": "boolean"
- },
- "optional": true
- }
- }
- }
- },
{
"name": "geopoint",
"type": "type",
@@ -2424,72 +3155,5 @@
}
}
}
- },
- {
- "name": "slug",
- "type": "type",
- "value": {
- "type": "object",
- "attributes": {
- "_type": {
- "type": "objectAttribute",
- "value": {
- "type": "string",
- "value": "slug"
- }
- },
- "current": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "source": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- }
- }
- }
- },
- {
- "name": "sanity.assetSourceData",
- "type": "type",
- "value": {
- "type": "object",
- "attributes": {
- "_type": {
- "type": "objectAttribute",
- "value": {
- "type": "string",
- "value": "sanity.assetSourceData"
- }
- },
- "name": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "id": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- },
- "url": {
- "type": "objectAttribute",
- "value": {
- "type": "string"
- },
- "optional": true
- }
- }
- }
}
]
diff --git a/apps/content/schemaTypes/documents/episode.ts b/apps/content/schemaTypes/documents/episode.ts
index fe8c4a1..7cc5146 100644
--- a/apps/content/schemaTypes/documents/episode.ts
+++ b/apps/content/schemaTypes/documents/episode.ts
@@ -66,6 +66,14 @@ export const episode = defineType({
of: [defineArrayMember({type: 'reference', to: [{type: 'sponsor'}]})],
group: 'details',
}),
+ defineField({
+ name: 'hackathons',
+ type: 'array',
+ title: 'Related Hackathons',
+ description: 'Hackathons that this episode is associated with',
+ of: [defineArrayMember({type: 'reference', to: [{type: 'hackathon'}]})],
+ group: 'details',
+ }),
defineField({
name: 'resources',
type: 'array',
diff --git a/apps/content/schemaTypes/documents/faqs.ts b/apps/content/schemaTypes/documents/faqs.ts
new file mode 100644
index 0000000..cc9a701
--- /dev/null
+++ b/apps/content/schemaTypes/documents/faqs.ts
@@ -0,0 +1,53 @@
+import {HelpCircleIcon} from '@sanity/icons'
+import {defineField, defineType} from 'sanity'
+
+export const faq = defineType({
+ name: 'faq',
+ title: 'FAQ',
+ type: 'document',
+ icon: HelpCircleIcon,
+ fields: [
+ defineField({
+ name: 'question',
+ title: 'Question',
+ type: 'string',
+ validation: (Rule) => Rule.required().min(10).max(200),
+ }),
+ defineField({
+ name: 'answer',
+ title: 'Answer',
+ type: 'text',
+ validation: (Rule) => Rule.required().min(20).max(1000),
+ }),
+ defineField({
+ name: 'weight',
+ title: 'Weight',
+ type: 'number',
+ description: 'Used to determine the order of FAQs (lower numbers appear first)',
+ validation: (Rule) => Rule.required().integer().min(0),
+ initialValue: 0,
+ }),
+ defineField({
+ name: 'setAsDefault',
+ title: 'Set as Default',
+ type: 'boolean',
+ description: 'Check this to use this FAQ as a default for all new hackathons created',
+ initialValue: false,
+ }),
+ ],
+ preview: {
+ select: {
+ title: 'question',
+ subtitle: 'answer',
+ defaultSetting: 'setAsDefault',
+ },
+ prepare(selection) {
+ const {title, subtitle, defaultSetting} = selection
+ const defaultStatus = defaultSetting ? '(Default)' : ''
+ return {
+ title: `${defaultStatus} ${title}`,
+ subtitle: subtitle ? `${subtitle.substring(0, 60)}...` : '',
+ }
+ },
+ },
+})
diff --git a/apps/content/schemaTypes/documents/hackathon-submission.ts b/apps/content/schemaTypes/documents/hackathon-submission.ts
new file mode 100644
index 0000000..697314d
--- /dev/null
+++ b/apps/content/schemaTypes/documents/hackathon-submission.ts
@@ -0,0 +1,71 @@
+import {defineField, defineType} from 'sanity'
+import {DocumentIcon} from '@sanity/icons'
+
+export const hackathonSubmission = defineType({
+ name: 'hackathonSubmission',
+ type: 'document',
+ title: 'Hackathon Submission',
+ icon: DocumentIcon,
+ fields: [
+ defineField({
+ name: 'hackathon',
+ type: 'reference',
+ to: [{type: 'hackathon'}],
+ title: 'Hackathon',
+ description: 'The hackathon this submission is for',
+ validation: (Rule) => Rule.required().error('Hackathon is required'),
+ }),
+ defineField({
+ name: 'person',
+ type: 'reference',
+ to: [{type: 'person'}],
+ title: 'Submitter',
+ description: 'The person who submitted this entry',
+ }),
+ defineField({
+ name: 'githubRepo',
+ type: 'url',
+ title: 'GitHub Repository',
+ description: 'Link to the source code repository',
+ validation: (Rule) =>
+ Rule.required()
+ .uri({scheme: ['https']})
+ .error('GitHub repo URL is required'),
+ }),
+ defineField({
+ name: 'deployedUrl',
+ type: 'url',
+ title: 'Deployed URL',
+ description: 'Link to the deployed web app',
+ }),
+ defineField({
+ name: 'demoVideo',
+ type: 'url',
+ title: 'Demo Video',
+ description: 'Link to a demo video (YouTube, Loom, etc.)',
+ }),
+ defineField({
+ name: 'submittedAt',
+ type: 'datetime',
+ title: 'Submitted At',
+ description: 'When this submission was received',
+ initialValue: () => new Date().toISOString(),
+ }),
+ ],
+ preview: {
+ select: {
+ fullName: 'person.name',
+ hackathonTitle: 'hackathon.title',
+ submittedAt: 'submittedAt',
+ },
+ prepare({fullName, hackathonTitle, submittedAt}) {
+ const date = submittedAt ? new Date(submittedAt).toLocaleDateString() : 'No date'
+
+ return {
+ title: fullName || 'Unknown Submitter',
+ subtitle: `${hackathonTitle || 'Unknown Hackathon'} - ${date}`,
+ media: DocumentIcon,
+ }
+ },
+ },
+})
diff --git a/apps/content/schemaTypes/documents/hackathon.ts b/apps/content/schemaTypes/documents/hackathon.ts
new file mode 100644
index 0000000..d348c38
--- /dev/null
+++ b/apps/content/schemaTypes/documents/hackathon.ts
@@ -0,0 +1,252 @@
+import {defineArrayMember, defineField, defineType} from 'sanity'
+import {RocketIcon, PlayIcon} from '@sanity/icons'
+
+export const hackathon = defineType({
+ name: 'hackathon',
+ type: 'document',
+ title: 'Hackathon',
+ icon: RocketIcon,
+ groups: [
+ {name: 'content', title: 'Content', default: true},
+ {name: 'seo', title: 'SEO & Publishing'},
+ ],
+ fields: [
+ defineField({
+ name: 'title',
+ type: 'string',
+ validation: (Rule) => Rule.required().error('Hackathon title is required'),
+ group: 'content',
+ }),
+ defineField({
+ name: 'slug',
+ type: 'slug',
+ description: 'URL-friendly identifier for this hackathon',
+ options: {
+ source: 'title',
+ maxLength: 96,
+ },
+ validation: (Rule) => Rule.required().error('Slug is required for URL generation'),
+ group: 'content',
+ }),
+ defineField({
+ name: 'pubDate',
+ type: 'datetime',
+ title: 'Publish Date',
+ description: 'When this hackathon announcement should be published',
+ options: {
+ timeStep: 30,
+ },
+ validation: (Rule) => Rule.required().error('Publish date is required'),
+ group: 'content',
+ }),
+ defineField({
+ name: 'deadline',
+ type: 'datetime',
+ title: 'Deadline',
+ description: 'When this hackathon submission deadline is',
+ options: {
+ timeStep: 30,
+ },
+ validation: (Rule) => Rule.required().error('Deadline is required'),
+ group: 'content',
+ }),
+ defineField({
+ name: 'description',
+ type: 'text',
+ description: 'Brief overview for previews and SEO',
+ validation: (Rule) => Rule.required().error('Description is required for SEO'),
+ group: 'content',
+ }),
+ defineField({
+ name: 'hero_image',
+ type: 'cloudinary.asset',
+ title: 'Hero Image',
+ description: 'Large hero image displayed at the top of the hackathon page',
+ options: {hotspot: true},
+ group: 'content',
+ }),
+ defineField({
+ name: 'hero_title',
+ type: 'string',
+ title: 'Hero Title',
+ description:
+ 'Optional custom title for the hero section (defaults to hackathon title if not provided)',
+ group: 'content',
+ }),
+ defineField({
+ name: 'body',
+ type: 'markdown',
+ description: 'Full hackathon details, rules, and content',
+ validation: (Rule) => Rule.required().error('Body content is required'),
+ group: 'content',
+ }),
+ defineField({
+ name: 'episodes',
+ type: 'array',
+ title: 'Related Episodes',
+ description: 'Episodes associated with this hackathon',
+ of: [
+ defineArrayMember({
+ type: 'reference',
+ to: [{type: 'episode'}],
+ options: {
+ disableNew: true,
+ },
+ }),
+ ],
+ group: 'content',
+ }),
+ defineField({
+ name: 'submissionForm',
+ type: 'url',
+ title: 'Submission Form',
+ description: 'URL to the submission form for this hackathon',
+ validation: (Rule) =>
+ Rule.required()
+ .uri({scheme: ['http', 'https']})
+ .error('Submission form must be a valid URL'),
+ group: 'content',
+ }),
+ defineField({
+ name: 'rewards',
+ type: 'array',
+ title: 'Rewards',
+ description: 'Rewards collection for this hackathon',
+ of: [
+ defineArrayMember({
+ type: 'reference',
+ to: [{type: 'rewards'}],
+ options: {
+ disableNew: true,
+ },
+ }),
+ ],
+ initialValue: async (_props: any, {getClient}: any) => {
+ const client = getClient({apiVersion: '2024-01-01'})
+ const defaultRewards = await client.fetch(
+ '*[_type == "rewards" && setAsDefault == true] | order(weight asc) { _id }',
+ )
+ return defaultRewards.map((reward: {_id: string}) => ({
+ _type: 'reference',
+ _ref: reward._id,
+ _key: reward._id,
+ }))
+ },
+ group: 'content',
+ }),
+ defineField({
+ name: 'faq',
+ type: 'array',
+ title: 'FAQ',
+ description: 'FAQ collection for this hackathon',
+ of: [
+ defineArrayMember({
+ type: 'reference',
+ to: [{type: 'faq'}],
+ options: {
+ disableNew: true,
+ },
+ }),
+ ],
+ initialValue: async (_props: any, {getClient}: any) => {
+ const client = getClient({apiVersion: '2024-01-01'})
+ const defaultFaqs = await client.fetch(
+ '*[_type == "faq" && setAsDefault == true] | order(weight asc) { _id }',
+ )
+ return defaultFaqs.map((faq: {_id: string}) => ({
+ _type: 'reference',
+ _ref: faq._id,
+ _key: faq._id,
+ }))
+ },
+ group: 'content',
+ }),
+ defineField({
+ name: 'sponsors',
+ type: 'array',
+ of: [defineArrayMember({type: 'reference', to: [{type: 'sponsor'}]})],
+ group: 'content',
+ }),
+ defineField({
+ name: 'rules',
+ type: 'array',
+ title: 'Rules',
+ description: 'Rules collection for this hackathon',
+ of: [
+ defineArrayMember({
+ type: 'reference',
+ to: [{type: 'rules'}],
+ options: {
+ disableNew: true,
+ },
+ }),
+ ],
+ initialValue: async (_props: any, {getClient}: any) => {
+ const client = getClient({apiVersion: '2024-01-01'})
+ const defaultRules = await client.fetch(
+ '*[_type == "rules" && setAsDefault == true] | order(weight asc) { _id }',
+ )
+ return defaultRules.map((rule: {_id: string}) => ({
+ _type: 'reference',
+ _ref: rule._id,
+ _key: rule._id,
+ }))
+ },
+ group: 'content',
+ }),
+ defineField({
+ name: 'resources',
+ type: 'array',
+ title: 'Resources',
+ description: 'Helpful resources for hackathon participants',
+ of: [{type: 'resourceItem'}],
+ options: {
+ sortable: true,
+ },
+ group: 'content',
+ }),
+ defineField({
+ name: 'share_image',
+ type: 'cloudinary.asset',
+ title: 'Share Image',
+ description: 'Image for social media sharing',
+ options: {hotspot: true},
+ validation: (Rule) => Rule.required().error('Share image is required for social media'),
+ group: 'seo',
+ }),
+ defineField({
+ name: 'hidden',
+ type: 'string',
+ description: 'Control whether this hackathon appears on the website',
+ options: {
+ list: [
+ {title: 'Visible', value: 'visible'},
+ {title: 'Hidden', value: 'hidden'},
+ ],
+ layout: 'radio',
+ },
+ initialValue: 'visible',
+ group: 'seo',
+ }),
+ ],
+ preview: {
+ select: {
+ title: 'title',
+ pubDate: 'pubDate',
+ hidden: 'hidden',
+ },
+ prepare({title, pubDate, hidden}) {
+ const date = pubDate ? new Date(pubDate).toLocaleDateString() : 'No date'
+ const status = hidden === 'hidden' ? ' (Hidden)' : ''
+
+ return {
+ title: title || 'Untitled Hackathon',
+ subtitle: `${date}${status}`,
+ media: RocketIcon,
+ }
+ },
+ },
+ initialValue: () => ({
+ hidden: 'visible',
+ }),
+})
diff --git a/apps/content/schemaTypes/documents/person.ts b/apps/content/schemaTypes/documents/person.ts
index 536bdfe..1a3742e 100644
--- a/apps/content/schemaTypes/documents/person.ts
+++ b/apps/content/schemaTypes/documents/person.ts
@@ -1,5 +1,5 @@
-import {defineField, defineType} from 'sanity'
-import {LinkIcon, UserIcon} from '@sanity/icons'
+import {defineArrayMember, defineField, defineType} from 'sanity'
+import {LinkIcon, RocketIcon} from '@sanity/icons'
function slugify(str: string) {
return String(str)
@@ -30,6 +30,7 @@ export const person = defineType({
groups: [
{name: 'profile', title: 'Profile', default: true},
{name: 'social', title: 'Social & Links'},
+ {name: 'hackathons', title: 'Hackathons'},
{name: 'subscription', title: 'Subscription Data'},
],
fields: [
@@ -115,6 +116,38 @@ export const person = defineType({
description: 'Read-only data from Clerk',
group: 'subscription',
}),
+ defineField({
+ name: 'hackathons',
+ type: 'array',
+ title: 'Hackathon Participation',
+ description: 'Hackathons this person has participated in',
+ of: [
+ defineArrayMember({
+ type: 'reference',
+ to: [{type: 'hackathon'}],
+ options: {
+ disableNew: true,
+ },
+ }),
+ ],
+ group: 'hackathons',
+ }),
+ defineField({
+ name: 'hackathonSubmissions',
+ type: 'array',
+ title: 'Hackathon Submissions',
+ description: 'Submissions this person has made to hackathons',
+ of: [
+ defineArrayMember({
+ type: 'reference',
+ to: [{type: 'hackathonSubmission'}],
+ options: {
+ disableNew: true,
+ },
+ }),
+ ],
+ group: 'hackathons',
+ }),
],
__experimental_formPreviewTitle: false,
preview: {
diff --git a/apps/content/schemaTypes/documents/rewards.ts b/apps/content/schemaTypes/documents/rewards.ts
new file mode 100644
index 0000000..cedca25
--- /dev/null
+++ b/apps/content/schemaTypes/documents/rewards.ts
@@ -0,0 +1,61 @@
+import {defineField, defineType} from 'sanity'
+import {StarIcon} from '@sanity/icons'
+
+export const rewards = defineType({
+ name: 'rewards',
+ title: 'Rewards',
+ type: 'document',
+ icon: StarIcon,
+ fields: [
+ defineField({
+ name: 'title',
+ title: 'Title',
+ type: 'string',
+ validation: (Rule) => Rule.required().min(3).max(100),
+ }),
+ defineField({
+ name: 'description',
+ title: 'Description',
+ type: 'text',
+ validation: (Rule) => Rule.required().min(10).max(500),
+ }),
+ defineField({
+ name: 'image',
+ title: 'Image',
+ type: 'cloudinary.asset',
+ options: {hotspot: true},
+ validation: (Rule) => Rule.required().error('Image is required'),
+ }),
+ defineField({
+ name: 'weight',
+ title: 'Weight',
+ type: 'number',
+ description: 'Used to determine the order of rewards (lower numbers appear first)',
+ validation: (Rule) => Rule.required().integer().min(0),
+ initialValue: 0,
+ }),
+ defineField({
+ name: 'setAsDefault',
+ title: 'Set as Default',
+ type: 'boolean',
+ description: 'Check this to use this reward as a default for all new hackathons created',
+ initialValue: false,
+ }),
+ ],
+ preview: {
+ select: {
+ title: 'title',
+ subtitle: 'description',
+ media: 'image',
+ defaultSetting: 'setAsDefault',
+ },
+ prepare(selection) {
+ const {title, subtitle, defaultSetting} = selection
+ const defaultStatus = defaultSetting ? '(Default)' : ''
+ return {
+ title: `${defaultStatus} ${title}`,
+ subtitle: subtitle ? `${subtitle.substring(0, 60)}...` : '',
+ }
+ },
+ },
+})
diff --git a/apps/content/schemaTypes/documents/rules.ts b/apps/content/schemaTypes/documents/rules.ts
new file mode 100644
index 0000000..756fa41
--- /dev/null
+++ b/apps/content/schemaTypes/documents/rules.ts
@@ -0,0 +1,53 @@
+import {ListIcon} from '@sanity/icons'
+import {defineField, defineType} from 'sanity'
+
+export const rules = defineType({
+ name: 'rules',
+ title: 'Rules',
+ type: 'document',
+ icon: ListIcon,
+ fields: [
+ defineField({
+ name: 'title',
+ title: 'Title',
+ type: 'string',
+ validation: (Rule) => Rule.required().min(3).max(100),
+ }),
+ defineField({
+ name: 'description',
+ title: 'Description',
+ type: 'text',
+ validation: (Rule) => Rule.required().min(10).max(500),
+ }),
+ defineField({
+ name: 'weight',
+ title: 'Weight',
+ type: 'number',
+ description: 'Used to determine the order of rules (lower numbers appear first)',
+ validation: (Rule) => Rule.required().integer().min(0),
+ initialValue: 0,
+ }),
+ defineField({
+ name: 'setAsDefault',
+ title: 'Set as Default',
+ type: 'boolean',
+ description: 'Check this to use this rule as a default for all new hackathons created',
+ initialValue: false,
+ }),
+ ],
+ preview: {
+ select: {
+ title: 'title',
+ subtitle: 'description',
+ defaultSetting: 'setAsDefault',
+ },
+ prepare(selection) {
+ const {title, subtitle, defaultSetting} = selection
+ const defaultStatus = defaultSetting ? '(Default)' : ''
+ return {
+ title: `${defaultStatus} ${title}`,
+ subtitle: subtitle ? `${subtitle.substring(0, 60)}...` : '',
+ }
+ },
+ },
+})
diff --git a/apps/content/schemaTypes/index.ts b/apps/content/schemaTypes/index.ts
index 43cd866..99ac958 100644
--- a/apps/content/schemaTypes/index.ts
+++ b/apps/content/schemaTypes/index.ts
@@ -2,8 +2,14 @@ import {defineField, defineType} from 'sanity'
import {PlayIcon, UserIcon, TagIcon, ImageIcon, FolderIcon, StarIcon} from '@sanity/icons'
import {person} from './documents/person'
import {episode} from './documents/episode'
+import {hackathon} from './documents/hackathon'
+import {hackathonSubmission} from './documents/hackathon-submission'
+import {faq} from './documents/faqs'
+import {rewards} from './documents/rewards'
+import {rules} from './documents/rules'
import {episodeTag} from './documents/tags'
import {episodeImage} from './objects/episode-image'
+import resourceItem from './objects/resource-item'
function slugify(str: string) {
return String(str)
@@ -253,4 +259,18 @@ const sponsor = defineType({
},
})
-export const schemaTypes = [series, collection, episode, person, sponsor, episodeTag, episodeImage]
+export const schemaTypes = [
+ series,
+ collection,
+ episode,
+ hackathon,
+ hackathonSubmission,
+ faq,
+ rewards,
+ rules,
+ person,
+ sponsor,
+ episodeTag,
+ episodeImage,
+ resourceItem,
+]
diff --git a/apps/content/schemaTypes/objects/resource-item.ts b/apps/content/schemaTypes/objects/resource-item.ts
new file mode 100644
index 0000000..6ace69e
--- /dev/null
+++ b/apps/content/schemaTypes/objects/resource-item.ts
@@ -0,0 +1,42 @@
+import {defineField, defineType} from 'sanity'
+
+export default defineType({
+ name: 'resourceItem',
+ title: 'Resource Item',
+ type: 'object',
+ fields: [
+ defineField({
+ name: 'title',
+ title: 'Title',
+ type: 'string',
+ validation: (Rule) => Rule.required().min(3).max(100),
+ }),
+ defineField({
+ name: 'description',
+ title: 'Description',
+ type: 'text',
+ validation: (Rule) => Rule.required().min(10).max(500),
+ }),
+ defineField({
+ name: 'url',
+ title: 'URL',
+ type: 'url',
+ validation: (Rule) => Rule.required().error('URL is required'),
+ }),
+ ],
+ preview: {
+ select: {
+ title: 'title',
+ subtitle: 'description',
+ url: 'url',
+ },
+ prepare(selection) {
+ const {title, subtitle, url} = selection
+ return {
+ title,
+ subtitle: subtitle ? `${subtitle.substring(0, 60)}...` : '',
+ url: url ? new URL(url).hostname : 'No URL',
+ }
+ },
+ },
+})
diff --git a/apps/shortlinks/public/_redirects b/apps/shortlinks/public/_redirects
index cdb9ffc..9808f66 100644
--- a/apps/shortlinks/public/_redirects
+++ b/apps/shortlinks/public/_redirects
@@ -16,7 +16,7 @@ https://codetv-links.netlify.app/* https://codetv.dev/:splat 301!
# quick links
-/discord https://discord.gg/codetv
+/discord https://discord.gg/lwj
/youtube https://youtube.com/@codetv-dev
/store https://store.codetv.dev/
/4d1a https://codetv.dev/series/web-dev-challenge/s0
@@ -46,17 +46,12 @@ https://codetv-links.netlify.app/* https://codetv.dev/:splat 301!
/wdc/s2e5 https://codetv.dev/series/web-dev-challenge/s2/e5-worst-developer-urges
/wdc/s2e6 https://codetv.dev/series/web-dev-challenge/s2/e6-future-of-ai-native-ux
/wdc/s2e7 https://codetv.dev/series/web-dev-challenge/s2/e7-touch-grass
-/wdc/s2e8 https://codetv.dev/series/web-dev-challenge/s2/e8-personal-software
-/wdc/s2e9 https://codetv.dev/series/web-dev-challenge/s2/e9-most-important-app
-/wdc/s2e10 https://codetv.dev/series/web-dev-challenge/s2/e10-no-keyboards-allowed
-/wdc/s2e11 https://codetv.dev/series/web-dev-challenge/s2/e11-build-a-hotline
/wdc/hackathon https://codetv.dev/blog/web-dev-challenge-hackathon-s2e11-code-powered-phone-hotline
-/wdc/hackathon/submit https://twitter.com/twilio/status/1987928456030560568
+/wdc/hackathon/submit https://x.com/twilio/status/1987971002643710442
/wdc-giveaway /wdc/hackathon
/wdc-hackathon /wdc/hackathon
-/challenge /wdc/hackathon
/wdc-hackathon-3-submit https://forms.gle/hPkGDCb1R66TGocw9
/wdc-hackathon-4-submit https://forms.gle/BfQtaRbpZhZDAMKx6
/wdc-hackathon-5-submit https://forms.gle/48BA8qeHykdN5pXp9
@@ -175,9 +170,6 @@ https://codetv-links.netlify.app/* https://codetv.dev/:splat 301!
/nordcraft https://nordcraft.com/?utm_source=codetv&utm_medium=video&utm_campaign=web-dev-challenge
/intuit https://intuit.avature.net/eventlisting?pipelineId=13447
/apollo https://www.apollographql.com/codetv/connectors?utm_source=codetv&utm_medium=video&utm_campaign=web-dev-challenge
-/hashbrown https://hashbrown.dev/?utm_source=codetv&utm_medium=video&utm_campaign=web-dev-challenge
-/goose https://block.github.io/goose/?utm_source=codetv&utm_medium=video&utm_campaign=web-dev-challenge
-/twilio https://twil.io/wdc-signup
/benq https://www.benq.com/en-us/campaign/best-coding-monitor-for-programmers.html?utm_source=influencer_codetv&utm_medium=affiliate&utm_campaign=lcd_programming_rd280u_na_2025_us_awareness
/uplift https://www.upliftdesk.com/?utm_source=codetv&utm_medium=video&utm_campaign=web-dev-challenge
@@ -202,9 +194,6 @@ https://codetv-links.netlify.app/* https://codetv.dev/:splat 301!
/book https://calendly.com/codetv/livestream
/30min https://calendly.com/codetv/30min
-# DevSquares
-/ds https://devsquares.codetv.dev/webu/play
-
# this link goes to the active or next-scheduled live stream
/live https://youtube.com/@codetv-dev/live
diff --git a/apps/website/public/_redirects b/apps/website/public/_redirects
index 1002475..02b8e1e 100644
--- a/apps/website/public/_redirects
+++ b/apps/website/public/_redirects
@@ -9,16 +9,23 @@
/lh /series/leet-heat
/leet-heat /series/leet-heat
/series/leet-heat /series/leet-heat/s1 301!
+/series/web-lunch /series/web-lunch/s1 301!
# LWJ episode redirects
/let-s-learn-minecraft /series/learn-with-jason/s3
/series/leet-heat/s1/christopher-vs-jeremy /series/leet-heat/s1/e4-christopher-vs-jeremy
# Old pages
-/partners https://partners.codetv.dev 301!
-/support /dashboard/sign-up 301!
+/partners https://partners.codetv.dev 301!
+/support /dashboard/sign-up 301!
+
+/series/unspoken-tech /series/web-lunch 301!
+/series/unspoken-tech/s1/adam-argyle /series/web-lunch/s1/adam-argyle 301!
+/series/unspoken-tech/s1/maggie-appleton /series/web-lunch/s1/maggie-appleton 301!
+/series/unspoken-tech/s1/rachel-lee-nabors /series/web-lunch/s1/rachel-lee-nabors 301!
+/series/unspoken-tech/s1/salma-alam-naylor /series/web-lunch/s1/salma-alam-naylor 301!
+/series/unspoken-tech/s1/sunil-pai /series/web-lunch/s1/sunil-pai 301!
-/series/web-dev-challenge/s2/most-important-app /series/web-dev-challenge/s2/e9-most-important-app 301!
/what-s-new-in-redux-toolkit-2-0 /series/learn-with-jason/s7/what-s-new-in-redux-toolkit-2-0
/blog/oklch-better-color-css-browse /blog/oklch-better-color-css-browser
/blog/false /blog
diff --git a/apps/website/src/actions/index.ts b/apps/website/src/actions/index.ts
index 5c94c4a..57cc192 100644
--- a/apps/website/src/actions/index.ts
+++ b/apps/website/src/actions/index.ts
@@ -2,7 +2,6 @@ import { defineAction } from 'astro:actions';
import { z } from 'astro:content';
import { inngest } from '@codetv/inngest';
import { addSubscriber } from '@codetv/kit';
-import { getPersonById } from '@codetv/sanity';
export const server = {
user: {
@@ -65,13 +64,14 @@ export const server = {
return false;
}
- await inngest.send({
- name: 'discord/user.alumni-role.add',
- data: {
- userId: user.id,
- role: 'Web Dev Challenge Alumni',
- },
- });
+ await inngest.send({
+ name: 'discord/user.role.add',
+ data: {
+ type: 'alumni',
+ userId: user.id,
+ role: 'wdc_alumni',
+ },
+ });
return true;
},
@@ -80,7 +80,7 @@ export const server = {
forms: {
wdc: defineAction({
accept: 'form',
- handler: async (formData, context) => {
+ handler: async (formData) => {
const linkLabels = formData.getAll('link_label[]');
const linkUrls = formData.getAll('link_url[]');
const links = linkUrls
@@ -112,22 +112,6 @@ export const server = {
username: formData.get('username'),
};
- // if someone created a new account, the Sanity ID might not exist
- // in time to populate the form, so grab it here
- if (!rawInput.id) {
- const user = await context.locals.currentUser();
- const userDetails = await getPersonById(
- { user_id: user?.id ?? '' },
- { useCdn: false },
- );
-
- if (!userDetails?._id) {
- throw new Error(`missing Sanity ID for ${rawInput.signature}`);
- }
-
- rawInput.id = userDetails._id;
- }
-
const InputSchema = z.object({
signature: z.string(),
role: z.union([z.literal('developer'), z.literal('advisor')]),
diff --git a/apps/website/src/components/cards/card.astro b/apps/website/src/components/cards/card.astro
index 41c50ed..cc23851 100644
--- a/apps/website/src/components/cards/card.astro
+++ b/apps/website/src/components/cards/card.astro
@@ -141,6 +141,7 @@ if (image.src) {
font-size: 1rem;
gap: 8px;
line-height: 1.45;
+ padding: 0 8px 8px;
position: relative;
z-index: 10;
}
@@ -148,8 +149,8 @@ if (image.src) {
.tagline {
color: var(--text-muted);
display: flex;
- font-family: var(--font-family-heading);
- font-size: clamp(0.75em, 4cqi, 1.125em);
+ font-size: 0.75em;
+ font-variation-settings: 'wdth' 80;
font-weight: normal;
gap: 10px;
line-height: 0.7;
@@ -175,7 +176,7 @@ if (image.src) {
.details {
display: flex;
- font-size: clamp(0.75em, 5cqi, 1.125em);
+ font-size: 1em;
font-weight: 500;
gap: 20px;
line-height: 1.25;
@@ -191,8 +192,9 @@ if (image.src) {
color: var(--white);
display: inline-block;
font-family: var(--font-family);
- font-size: clamp(0.875em, 5cqi, 1.125em);
- font-weight: normal;
+ font-size: 0.75em;
+ font-variation-settings: 'wdth' 100;
+ font-weight: 200;
line-height: 0.9;
padding: 0;
text-decoration: none;
diff --git a/apps/website/src/components/design-system/block.astro b/apps/website/src/components/design-system/block.astro
new file mode 100644
index 0000000..c046af5
--- /dev/null
+++ b/apps/website/src/components/design-system/block.astro
@@ -0,0 +1,78 @@
+---
+export interface Props {
+ variant?: 'default' | 'two-column-asymmetrical';
+ emphasized?: boolean;
+ name?: string;
+}
+
+const { variant = 'default', emphasized = false, name } = Astro.props;
+---
+
+