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; +--- + +
+ +
+ + diff --git a/apps/website/src/components/design-system/card.astro b/apps/website/src/components/design-system/card.astro new file mode 100644 index 0000000..e8645c8 --- /dev/null +++ b/apps/website/src/components/design-system/card.astro @@ -0,0 +1,141 @@ +--- +export interface Props { + title?: string; + variant?: 'primary' | 'secondary'; + link?: string; +} + +const { title, variant = 'primary', link } = Astro.props; +--- + +
+ { + link && ( + + + + ) + } + {!link && } +
+
+ { + title && link && ( +

+ {title} +

+ ) + } + {title && !link &&

{title}

} + {!title && } +
+
+ +
+
+
+ + diff --git a/apps/website/src/components/design-system/grid.astro b/apps/website/src/components/design-system/grid.astro new file mode 100644 index 0000000..38c757c --- /dev/null +++ b/apps/website/src/components/design-system/grid.astro @@ -0,0 +1,38 @@ +--- +export interface Props { + columns?: number; +} + +const { columns = 3 } = Astro.props; +--- + +
+ +
+ + diff --git a/apps/website/src/components/design-system/list.astro b/apps/website/src/components/design-system/list.astro new file mode 100644 index 0000000..1090098 --- /dev/null +++ b/apps/website/src/components/design-system/list.astro @@ -0,0 +1,15 @@ +--- + +--- + +
+ +
+ + diff --git a/apps/website/src/components/episode-preview.astro b/apps/website/src/components/episode-preview.astro index 49e4711..ce42308 100644 --- a/apps/website/src/components/episode-preview.astro +++ b/apps/website/src/components/episode-preview.astro @@ -180,7 +180,7 @@ const description = } &::after { - color: var(--text-strong); + color: var(--text-emphasized); content: attr(data-overlay-text); font-size: 0.75rem; font-weight: 600; diff --git a/apps/website/src/components/eyebrow.astro b/apps/website/src/components/eyebrow.astro index 8de9923..3c28584 100644 --- a/apps/website/src/components/eyebrow.astro +++ b/apps/website/src/components/eyebrow.astro @@ -1,35 +1,33 @@ --- // TODO: should this be a field in the CMS to make it easier to announce things? + +// disable for now +return null; --- -