diff --git a/api/functions/graphql/nexus-typegen.ts b/api/functions/graphql/nexus-typegen.ts index b0d01f0..1f0a9e1 100644 --- a/api/functions/graphql/nexus-typegen.ts +++ b/api/functions/graphql/nexus-typegen.ts @@ -34,7 +34,6 @@ export interface NexusGenInputs { id?: number | null; // Int tags: string[]; // [String!]! title: string; // String! - topicId: number; // Int! } UpdateProfileInput: { // input type avatar?: string | null; // String @@ -174,12 +173,9 @@ export interface NexusGenObjects { votes_count: number; // Int! } Tag: { // root type + icon?: string | null; // String id: number; // Int! - title: string; // String! - } - Topic: { // root type - icon: string; // String! - id: number; // Int! + isOfficial?: boolean | null; // Boolean title: string; // String! } User: { // root type @@ -288,8 +284,8 @@ export interface NexusGenFieldTypes { id: number; // Int! location: string; // String! start_date: NexusGenScalars['Date']; // Date! + tags: NexusGenRootTypes['Tag'][]; // [Tag!]! title: string; // String! - topics: NexusGenRootTypes['Topic'][]; // [Topic!]! website: string; // String! } LnurlDetails: { // field return type @@ -333,7 +329,6 @@ export interface NexusGenFieldTypes { Query: { // field return type allCategories: NexusGenRootTypes['Category'][]; // [Category!]! allProjects: NexusGenRootTypes['Project'][]; // [Project!]! - allTopics: NexusGenRootTypes['Topic'][]; // [Topic!]! getAllHackathons: NexusGenRootTypes['Hackathon'][]; // [Hackathon!]! getCategory: NexusGenRootTypes['Category']; // Category! getDonationsStats: NexusGenRootTypes['DonationsStats']; // DonationsStats! @@ -345,7 +340,8 @@ export interface NexusGenFieldTypes { hottestProjects: NexusGenRootTypes['Project'][]; // [Project!]! me: NexusGenRootTypes['User'] | null; // User newProjects: NexusGenRootTypes['Project'][]; // [Project!]! - popularTopics: NexusGenRootTypes['Topic'][]; // [Topic!]! + officialTags: NexusGenRootTypes['Tag'][]; // [Tag!]! + popularTags: NexusGenRootTypes['Tag'][]; // [Tag!]! profile: NexusGenRootTypes['User'] | null; // User projectsByCategory: NexusGenRootTypes['Project'][]; // [Project!]! searchProjects: NexusGenRootTypes['Project'][]; // [Project!]! @@ -374,17 +370,13 @@ export interface NexusGenFieldTypes { id: number; // Int! tags: NexusGenRootTypes['Tag'][]; // [Tag!]! title: string; // String! - topic: NexusGenRootTypes['Topic']; // Topic! type: string; // String! votes_count: number; // Int! } Tag: { // field return type + icon: string | null; // String id: number; // Int! - title: string; // String! - } - Topic: { // field return type - icon: string; // String! - id: number; // Int! + isOfficial: boolean | null; // Boolean title: string; // String! } User: { // field return type @@ -489,8 +481,8 @@ export interface NexusGenFieldTypeNames { id: 'Int' location: 'String' start_date: 'Date' + tags: 'Tag' title: 'String' - topics: 'Topic' website: 'String' } LnurlDetails: { // field return type name @@ -534,7 +526,6 @@ export interface NexusGenFieldTypeNames { Query: { // field return type name allCategories: 'Category' allProjects: 'Project' - allTopics: 'Topic' getAllHackathons: 'Hackathon' getCategory: 'Category' getDonationsStats: 'DonationsStats' @@ -546,7 +537,8 @@ export interface NexusGenFieldTypeNames { hottestProjects: 'Project' me: 'User' newProjects: 'Project' - popularTopics: 'Topic' + officialTags: 'Tag' + popularTags: 'Tag' profile: 'User' projectsByCategory: 'Project' searchProjects: 'Project' @@ -575,17 +567,13 @@ export interface NexusGenFieldTypeNames { id: 'Int' tags: 'Tag' title: 'String' - topic: 'Topic' type: 'String' votes_count: 'Int' } Tag: { // field return type name - id: 'Int' - title: 'String' - } - Topic: { // field return type name icon: 'String' id: 'Int' + isOfficial: 'Boolean' title: 'String' } User: { // field return type name @@ -658,7 +646,7 @@ export interface NexusGenArgTypes { } getAllHackathons: { // args sortBy?: string | null; // String - topic?: number | null; // Int + tag?: number | null; // Int } getCategory: { // args id: number; // Int! @@ -666,8 +654,8 @@ export interface NexusGenArgTypes { getFeed: { // args skip?: number | null; // Int sortBy?: string | null; // String + tag?: number | null; // Int take: number | null; // Int - topic?: number | null; // Int } getLnurlDetailsForProject: { // args project_id: number; // Int! diff --git a/api/functions/graphql/schema.graphql b/api/functions/graphql/schema.graphql index 3c67804..8d9775d 100644 --- a/api/functions/graphql/schema.graphql +++ b/api/functions/graphql/schema.graphql @@ -78,8 +78,8 @@ type Hackathon { id: Int! location: String! start_date: Date! + tags: [Tag!]! title: String! - topics: [Topic!]! website: String! } @@ -145,11 +145,10 @@ type Project { type Query { allCategories: [Category!]! allProjects(skip: Int = 0, take: Int = 50): [Project!]! - allTopics: [Topic!]! - getAllHackathons(sortBy: String, topic: Int): [Hackathon!]! + getAllHackathons(sortBy: String, tag: Int): [Hackathon!]! getCategory(id: Int!): Category! getDonationsStats: DonationsStats! - getFeed(skip: Int = 0, sortBy: String, take: Int = 10, topic: Int = 0): [Post!]! + getFeed(skip: Int = 0, sortBy: String, tag: Int = 0, take: Int = 10): [Post!]! getLnurlDetailsForProject(project_id: Int!): LnurlDetails! getPostById(id: Int!, type: POST_TYPE!): Post! getProject(id: Int!): Project! @@ -157,7 +156,8 @@ type Query { hottestProjects(skip: Int = 0, take: Int = 50): [Project!]! me: User newProjects(skip: Int = 0, take: Int = 50): [Project!]! - popularTopics: [Topic!]! + officialTags: [Tag!]! + popularTags: [Tag!]! profile(id: Int!): User projectsByCategory(category_id: Int!, skip: Int = 0, take: Int = 10): [Project!]! searchProjects(search: String!, skip: Int = 0, take: Int = 50): [Project!]! @@ -188,7 +188,6 @@ type Story implements PostBase { id: Int! tags: [Tag!]! title: String! - topic: Topic! type: String! votes_count: Int! } @@ -199,17 +198,12 @@ input StoryInputType { id: Int tags: [String!]! title: String! - topicId: Int! } type Tag { + icon: String id: Int! - title: String! -} - -type Topic { - icon: String! - id: Int! + isOfficial: Boolean title: String! } diff --git a/api/functions/graphql/types/hackathon.js b/api/functions/graphql/types/hackathon.js index 5b434f3..346ef25 100644 --- a/api/functions/graphql/types/hackathon.js +++ b/api/functions/graphql/types/hackathon.js @@ -20,10 +20,10 @@ const Hackathon = objectType({ t.nonNull.date('end_date'); t.nonNull.string('location'); t.nonNull.string('website'); - t.nonNull.list.nonNull.field('topics', { - type: "Topic", + t.nonNull.list.nonNull.field('tags', { + type: "Tag", resolve: (parent) => { - return prisma.hackathon.findUnique({ where: { id: parent.id } }).topics(); + return prisma.hackathon.findUnique({ where: { id: parent.id } }).tags(); } }); } @@ -36,10 +36,10 @@ const getAllHackathons = extendType({ type: "Hackathon", args: { sortBy: stringArg(), - topic: intArg(), + tag: intArg(), }, resolve(_, args) { - const { sortBy, topic } = args; + const { sortBy, tag } = args; return prisma.hackathon.findMany({ where: { ...(sortBy === 'Upcoming' && { @@ -57,10 +57,12 @@ const getAllHackathons = extendType({ } }), - ...(topic && { - topics: { + + + ...(tag && { + tags: { some: { - id: topic + id: tag } } }) diff --git a/api/functions/graphql/types/index.js b/api/functions/graphql/types/index.js index ddbf999..75e2db0 100644 --- a/api/functions/graphql/types/index.js +++ b/api/functions/graphql/types/index.js @@ -6,8 +6,10 @@ const post = require('./post') const users = require('./users') const hackathon = require('./hackathon') const donation = require('./donation') +const tag = require('./tag') module.exports = { + ...tag, ...scalars, ...category, ...project, diff --git a/api/functions/graphql/types/post.js b/api/functions/graphql/types/post.js index 6d5b934..c1e0df1 100644 --- a/api/functions/graphql/types/post.js +++ b/api/functions/graphql/types/post.js @@ -32,15 +32,6 @@ const asQuestionType = asType('Question') const asBountyType = asType('Bounty') -const Topic = objectType({ - name: 'Topic', - definition(t) { - t.nonNull.int('id'); - t.nonNull.string('title'); - t.nonNull.string('icon'); - } -}) - const Author = objectType({ name: 'Author', definition(t) { @@ -52,39 +43,7 @@ const Author = objectType({ }) -const allTopics = extendType({ - type: "Query", - definition(t) { - t.nonNull.list.nonNull.field('allTopics', { - type: "Topic", - resolve: () => { - return prisma.topic.findMany({ - }); - } - }) - } -}) - - -const popularTopics = extendType({ - type: "Query", - definition(t) { - t.nonNull.list.nonNull.field('popularTopics', { - type: "Topic", - resolve: () => { - return prisma.topic.findMany({ - take: 6, - orderBy: { - stories: { - _count: 'desc' - } - }, - }); - } - }) - } -}) const PostBase = interfaceType({ name: 'PostBase', @@ -132,14 +91,6 @@ const Story = objectType({ return post._count.comments; } }); - t.nonNull.field('topic', { - type: "Topic", - resolve: parent => { - return prisma.story.findUnique({ - where: { id: parent.id } - }).topic() - } - }) t.nonNull.field('author', { type: "Author", resolve: (parent) => @@ -158,7 +109,6 @@ const StoryInputType = inputObjectType({ t.nonNull.string('body'); t.nonNull.string('cover_image'); t.nonNull.list.nonNull.string('tags'); - t.nonNull.int('topicId'); } }) const createStory = extendType({ @@ -168,7 +118,7 @@ const createStory = extendType({ type: 'Story', args: { data: StoryInputType }, async resolve(_root, args, ctx) { - const { id, title, body, cover_image, tags, topicId } = args.data; + const { id, title, body, cover_image, tags } = args.data; const user = await getUserByPubKey(ctx.userPubKey); // Do some validation @@ -215,11 +165,6 @@ const createStory = extendType({ } }) }, - topic: { - connect: { - id: topicId - } - }, } }) @@ -244,11 +189,6 @@ const createStory = extendType({ } }) }, - topic: { - connect: { - id: topicId - } - }, user: { connect: { id: user.id, @@ -398,11 +338,11 @@ const getFeed = extendType({ args: { ...paginationArgs({ take: 10 }), sortBy: stringArg(), // all, popular, trending, newest - topic: intArg({ + tag: intArg({ default: 0 }) }, - resolve(_, { take, skip, topic, sortBy, }) { + resolve(_, { take, skip, tag, sortBy, }) { let orderBy = { createdAt: "desc" }; @@ -415,7 +355,13 @@ const getFeed = extendType({ return prisma.story.findMany({ orderBy: orderBy, where: { - topic_id: topic ? topic : undefined, + ...(tag && { + tags: { + some: { + id: tag + } + }, + }) }, skip, take, @@ -485,7 +431,6 @@ module.exports = { // Types POST_TYPE, Author, - Topic, PostBase, BountyApplication, Bounty, @@ -495,8 +440,6 @@ module.exports = { PostComment, Post, // Queries - allTopics, - popularTopics, getFeed, getPostById, getTrendingPosts, diff --git a/api/functions/graphql/types/project.js b/api/functions/graphql/types/project.js index 52ca97e..a9fed49 100644 --- a/api/functions/graphql/types/project.js +++ b/api/functions/graphql/types/project.js @@ -64,19 +64,7 @@ const Award = objectType({ } }) -const Tag = objectType({ - name: 'Tag', - definition(t) { - t.nonNull.int('id'); - t.nonNull.string('title'); - // t.nonNull.list.nonNull.field('project', { - // type: "Project", - // resolve: (parent) => { - // return prisma.tag.findUnique({ where: { id: parent.id } }).project(); - // } - // }) - } -}) + const getProject = extendType({ @@ -255,7 +243,6 @@ module.exports = { // Types Project, Award, - Tag, // Queries getProject, allProjects, diff --git a/api/functions/graphql/types/tag.js b/api/functions/graphql/types/tag.js new file mode 100644 index 0000000..a482073 --- /dev/null +++ b/api/functions/graphql/types/tag.js @@ -0,0 +1,59 @@ +const { objectType, extendType } = require("nexus"); +const { prisma } = require('../../../prisma'); + +const Tag = objectType({ + name: 'Tag', + definition(t) { + t.nonNull.int('id'); + t.nonNull.string('title'); + t.string('icon'); + t.boolean('isOfficial'); + } +}); + +const officialTags = extendType({ + type: "Query", + definition(t) { + t.nonNull.list.nonNull.field('officialTags', { + type: "Tag", + resolve: () => { + return prisma.tag.findMany({ + where: { + isOfficial: true + } + }); + } + }) + } +}) + +const popularTags = extendType({ + type: "Query", + definition(t) { + t.nonNull.list.nonNull.field('popularTags', { + type: "Tag", + resolve: () => { + return prisma.tag.findMany({ + where: { + isOfficial: true + }, + take: 8, + orderBy: { + stories: { + _count: 'desc' + } + }, + }); + } + }) + } +}) + +module.exports = { + // Types + Tag, + + // Queries + popularTags, + officialTags, +} \ No newline at end of file diff --git a/prisma/migrations/20220614113018_drop_topics_and_update_tags/migration.sql b/prisma/migrations/20220614113018_drop_topics_and_update_tags/migration.sql new file mode 100644 index 0000000..9d25885 --- /dev/null +++ b/prisma/migrations/20220614113018_drop_topics_and_update_tags/migration.sql @@ -0,0 +1,46 @@ +/* + Warnings: + + - You are about to drop the `Topic` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `_HackathonToTopic` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "Question" DROP CONSTRAINT "Question_topic_id_fkey"; + +-- DropForeignKey +ALTER TABLE "Story" DROP CONSTRAINT "Story_topic_id_fkey"; + +-- DropForeignKey +ALTER TABLE "_HackathonToTopic" DROP CONSTRAINT "_HackathonToTopic_A_fkey"; + +-- DropForeignKey +ALTER TABLE "_HackathonToTopic" DROP CONSTRAINT "_HackathonToTopic_B_fkey"; + +-- AlterTable +ALTER TABLE "Tag" ADD COLUMN "icon" TEXT, +ADD COLUMN "isOfficial" BOOLEAN NOT NULL DEFAULT false; + +-- DropTable +DROP TABLE "Topic"; + +-- DropTable +DROP TABLE "_HackathonToTopic"; + +-- CreateTable +CREATE TABLE "_HackathonToTag" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "_HackathonToTag_AB_unique" ON "_HackathonToTag"("A", "B"); + +-- CreateIndex +CREATE INDEX "_HackathonToTag_B_index" ON "_HackathonToTag"("B"); + +-- AddForeignKey +ALTER TABLE "_HackathonToTag" ADD FOREIGN KEY ("A") REFERENCES "Hackathon"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_HackathonToTag" ADD FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20220614124708_remove_topic_id_col/migration.sql b/prisma/migrations/20220614124708_remove_topic_id_col/migration.sql new file mode 100644 index 0000000..b2fac15 --- /dev/null +++ b/prisma/migrations/20220614124708_remove_topic_id_col/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - You are about to drop the column `topic_id` on the `Question` table. All the data in the column will be lost. + - You are about to drop the column `topic_id` on the `Story` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Question" DROP COLUMN "topic_id"; + +-- AlterTable +ALTER TABLE "Story" DROP COLUMN "topic_id"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c682ca7..764540c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,12 +12,15 @@ generator client { // ----------------- model Tag { - id Int @id @default(autoincrement()) - title String @unique + id Int @id @default(autoincrement()) + title String @unique + icon String? + isOfficial Boolean @default(false) - project Project[] - stories Story[] - questions Question[] + project Project[] + stories Story[] + questions Question[] + hackathons Hackathon[] } model Vote { @@ -119,8 +122,6 @@ model Story { cover_image String votes_count Int @default(0) - topic Topic @relation(fields: [topic_id], references: [id]) - topic_id Int tags Tag[] @@ -139,8 +140,6 @@ model Question { excerpt String votes_count Int @default(0) - topic Topic @relation(fields: [topic_id], references: [id]) - topic_id Int tags Tag[] @@ -150,16 +149,6 @@ model Question { comments PostComment[] @relation("QuestionComment") } -model Topic { - id Int @id @default(autoincrement()) - title String @unique - icon String - - stories Story[] - questions Question[] - hackathons Hackathon[] -} - model PostComment { id Int @id @default(autoincrement()) body String @@ -196,7 +185,7 @@ model Hackathon { website String votes_count Int @default(0) - topics Topic[] + tags Tag[] } // ----------------- diff --git a/src/Components/Inputs/FilesInput/FilesThumbnails.tsx b/src/Components/Inputs/FilesInput/FilesThumbnails.tsx index 72cdcd5..362528e 100644 --- a/src/Components/Inputs/FilesInput/FilesThumbnails.tsx +++ b/src/Components/Inputs/FilesInput/FilesThumbnails.tsx @@ -1,6 +1,4 @@ -import React, { useMemo } from 'react' -import { MdClose } from 'react-icons/md'; -import IconButton from 'src/Components/IconButton/IconButton'; +import { useMemo } from 'react' import FileThumbnail from './FileThumbnail'; interface Props { diff --git a/src/Components/Inputs/TagsInput/TagsInput.tsx b/src/Components/Inputs/TagsInput/TagsInput.tsx index 2e44606..6704e1d 100644 --- a/src/Components/Inputs/TagsInput/TagsInput.tsx +++ b/src/Components/Inputs/TagsInput/TagsInput.tsx @@ -1,10 +1,20 @@ -import { motion } from "framer-motion"; -import { useState } from "react"; + import { useController } from "react-hook-form"; import Badge from "src/Components/Badge/Badge"; -import { Tag as ApiTag } from "src/utils/interfaces"; +import CreatableSelect from 'react-select/creatable'; +import { OnChangeValue, StylesConfig, components, OptionProps } from "react-select"; +import { useOfficialTagsQuery } from "src/graphql"; -type Tag = Pick +interface Option { + readonly label: string; + readonly value: string; + readonly icon: string | null +} + +type Tag = { + title: string, + icon: string | null +} interface Props { classes?: { @@ -17,6 +27,46 @@ interface Props { } +const transformer = { + tagToOption: (tag: Tag): Option => ({ label: tag.title, value: tag.title, icon: tag.icon }), + optionToTag: (o: Option): Tag => ({ title: o.value, icon: null }) +} + +const OptionComponent = (props: OptionProps