mirror of
https://github.com/aljazceru/landscape-template.git
synced 2025-12-19 07:14:22 +01:00
Merge commit 'refs/pull/151/head' of https://github.com/peakshift/makers.bolt.fun into image-upload-management
This commit is contained in:
@@ -28,6 +28,11 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface NexusGenInputs {
|
export interface NexusGenInputs {
|
||||||
|
ImageInput: { // input type
|
||||||
|
id?: string | null; // String
|
||||||
|
name?: string | null; // String
|
||||||
|
url: string; // String!
|
||||||
|
}
|
||||||
MakerRoleInput: { // input type
|
MakerRoleInput: { // input type
|
||||||
id: number; // Int!
|
id: number; // Int!
|
||||||
level: NexusGenEnums['RoleLevelEnum']; // RoleLevelEnum!
|
level: NexusGenEnums['RoleLevelEnum']; // RoleLevelEnum!
|
||||||
@@ -36,7 +41,7 @@ export interface NexusGenInputs {
|
|||||||
id: number; // Int!
|
id: number; // Int!
|
||||||
}
|
}
|
||||||
ProfileDetailsInput: { // input type
|
ProfileDetailsInput: { // input type
|
||||||
avatar?: string | null; // String
|
avatar?: NexusGenInputs['ImageInput'] | null; // ImageInput
|
||||||
bio?: string | null; // String
|
bio?: string | null; // String
|
||||||
discord?: string | null; // String
|
discord?: string | null; // String
|
||||||
email?: string | null; // String
|
email?: string | null; // String
|
||||||
@@ -59,7 +64,7 @@ export interface NexusGenInputs {
|
|||||||
}
|
}
|
||||||
StoryInputType: { // input type
|
StoryInputType: { // input type
|
||||||
body: string; // String!
|
body: string; // String!
|
||||||
cover_image?: string | null; // String
|
cover_image?: NexusGenInputs['ImageInput'] | null; // ImageInput
|
||||||
id?: number | null; // Int
|
id?: number | null; // Int
|
||||||
is_published?: boolean | null; // Boolean
|
is_published?: boolean | null; // Boolean
|
||||||
tags: string[]; // [String!]!
|
tags: string[]; // [String!]!
|
||||||
@@ -94,7 +99,6 @@ export interface NexusGenScalars {
|
|||||||
|
|
||||||
export interface NexusGenObjects {
|
export interface NexusGenObjects {
|
||||||
Author: { // root type
|
Author: { // root type
|
||||||
avatar: string; // String!
|
|
||||||
id: number; // Int!
|
id: number; // Int!
|
||||||
join_date: NexusGenScalars['Date']; // Date!
|
join_date: NexusGenScalars['Date']; // Date!
|
||||||
lightning_address?: string | null; // String
|
lightning_address?: string | null; // String
|
||||||
@@ -110,7 +114,6 @@ export interface NexusGenObjects {
|
|||||||
applicants_count: number; // Int!
|
applicants_count: number; // Int!
|
||||||
applications: NexusGenRootTypes['BountyApplication'][]; // [BountyApplication!]!
|
applications: NexusGenRootTypes['BountyApplication'][]; // [BountyApplication!]!
|
||||||
body: string; // String!
|
body: string; // String!
|
||||||
cover_image?: string | null; // String
|
|
||||||
createdAt: NexusGenScalars['Date']; // Date!
|
createdAt: NexusGenScalars['Date']; // Date!
|
||||||
deadline: string; // String!
|
deadline: string; // String!
|
||||||
excerpt: string; // String!
|
excerpt: string; // String!
|
||||||
@@ -128,7 +131,6 @@ export interface NexusGenObjects {
|
|||||||
workplan: string; // String!
|
workplan: string; // String!
|
||||||
}
|
}
|
||||||
Category: { // root type
|
Category: { // root type
|
||||||
cover_image?: string | null; // String
|
|
||||||
icon?: string | null; // String
|
icon?: string | null; // String
|
||||||
id: number; // Int!
|
id: number; // Int!
|
||||||
title: string; // String!
|
title: string; // String!
|
||||||
@@ -153,7 +155,6 @@ export interface NexusGenObjects {
|
|||||||
title: string; // String!
|
title: string; // String!
|
||||||
}
|
}
|
||||||
Hackathon: { // root type
|
Hackathon: { // root type
|
||||||
cover_image: string; // String!
|
|
||||||
description: string; // String!
|
description: string; // String!
|
||||||
end_date: NexusGenScalars['Date']; // Date!
|
end_date: NexusGenScalars['Date']; // Date!
|
||||||
id: number; // Int!
|
id: number; // Int!
|
||||||
@@ -180,7 +181,6 @@ export interface NexusGenObjects {
|
|||||||
}
|
}
|
||||||
Mutation: {};
|
Mutation: {};
|
||||||
MyProfile: { // root type
|
MyProfile: { // root type
|
||||||
avatar: string; // String!
|
|
||||||
bio?: string | null; // String
|
bio?: string | null; // String
|
||||||
discord?: string | null; // String
|
discord?: string | null; // String
|
||||||
email?: string | null; // String
|
email?: string | null; // String
|
||||||
@@ -212,13 +212,10 @@ export interface NexusGenObjects {
|
|||||||
votes_count: number; // Int!
|
votes_count: number; // Int!
|
||||||
}
|
}
|
||||||
Project: { // root type
|
Project: { // root type
|
||||||
cover_image: string; // String!
|
|
||||||
description: string; // String!
|
description: string; // String!
|
||||||
id: number; // Int!
|
id: number; // Int!
|
||||||
lightning_address?: string | null; // String
|
lightning_address?: string | null; // String
|
||||||
lnurl_callback_url?: string | null; // String
|
lnurl_callback_url?: string | null; // String
|
||||||
screenshots: string[]; // [String!]!
|
|
||||||
thumbnail_image: string; // String!
|
|
||||||
title: string; // String!
|
title: string; // String!
|
||||||
votes_count: number; // Int!
|
votes_count: number; // Int!
|
||||||
website: string; // String!
|
website: string; // String!
|
||||||
@@ -236,7 +233,6 @@ export interface NexusGenObjects {
|
|||||||
}
|
}
|
||||||
Story: { // root type
|
Story: { // root type
|
||||||
body: string; // String!
|
body: string; // String!
|
||||||
cover_image?: string | null; // String
|
|
||||||
createdAt: NexusGenScalars['Date']; // Date!
|
createdAt: NexusGenScalars['Date']; // Date!
|
||||||
excerpt: string; // String!
|
excerpt: string; // String!
|
||||||
id: number; // Int!
|
id: number; // Int!
|
||||||
@@ -253,7 +249,6 @@ export interface NexusGenObjects {
|
|||||||
title: string; // String!
|
title: string; // String!
|
||||||
}
|
}
|
||||||
Tournament: { // root type
|
Tournament: { // root type
|
||||||
cover_image: string; // String!
|
|
||||||
description: string; // String!
|
description: string; // String!
|
||||||
end_date: NexusGenScalars['Date']; // Date!
|
end_date: NexusGenScalars['Date']; // Date!
|
||||||
id: number; // Int!
|
id: number; // Int!
|
||||||
@@ -304,7 +299,6 @@ export interface NexusGenObjects {
|
|||||||
projects: NexusGenRootTypes['Project'][]; // [Project!]!
|
projects: NexusGenRootTypes['Project'][]; // [Project!]!
|
||||||
}
|
}
|
||||||
User: { // root type
|
User: { // root type
|
||||||
avatar: string; // String!
|
|
||||||
bio?: string | null; // String
|
bio?: string | null; // String
|
||||||
discord?: string | null; // String
|
discord?: string | null; // String
|
||||||
github?: string | null; // String
|
github?: string | null; // String
|
||||||
|
|||||||
@@ -115,6 +115,12 @@ type Hackathon {
|
|||||||
website: String!
|
website: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input ImageInput {
|
||||||
|
id: String
|
||||||
|
name: String
|
||||||
|
url: String!
|
||||||
|
}
|
||||||
|
|
||||||
type LnurlDetails {
|
type LnurlDetails {
|
||||||
commentAllowed: Int
|
commentAllowed: Int
|
||||||
maxSendable: Int
|
maxSendable: Int
|
||||||
@@ -219,7 +225,7 @@ type PostComment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input ProfileDetailsInput {
|
input ProfileDetailsInput {
|
||||||
avatar: String
|
avatar: ImageInput
|
||||||
bio: String
|
bio: String
|
||||||
discord: String
|
discord: String
|
||||||
email: String
|
email: String
|
||||||
@@ -330,7 +336,7 @@ type Story implements PostBase {
|
|||||||
|
|
||||||
input StoryInputType {
|
input StoryInputType {
|
||||||
body: String!
|
body: String!
|
||||||
cover_image: String
|
cover_image: ImageInput
|
||||||
id: Int
|
id: Int
|
||||||
is_published: Boolean
|
is_published: Boolean
|
||||||
tags: [String!]!
|
tags: [String!]!
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ const {
|
|||||||
extendType,
|
extendType,
|
||||||
nonNull,
|
nonNull,
|
||||||
} = require('nexus');
|
} = require('nexus');
|
||||||
const { prisma } = require('../../../prisma')
|
const { prisma } = require('../../../prisma');
|
||||||
|
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||||
|
|
||||||
|
|
||||||
const Category = objectType({
|
const Category = objectType({
|
||||||
@@ -12,7 +13,18 @@ const Category = objectType({
|
|||||||
definition(t) {
|
definition(t) {
|
||||||
t.nonNull.int('id');
|
t.nonNull.int('id');
|
||||||
t.nonNull.string('title');
|
t.nonNull.string('title');
|
||||||
t.string('cover_image');
|
t.string('cover_image', {
|
||||||
|
async resolve(parent) {
|
||||||
|
if (!parent.cover_image_id) return null
|
||||||
|
const imgObject = await prisma.hostedImage.findUnique({
|
||||||
|
where: {
|
||||||
|
id: parent.cover_image_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return resolveImgObjectToUrl(imgObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
t.string('icon');
|
t.string('icon');
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const {
|
|||||||
nonNull,
|
nonNull,
|
||||||
} = require('nexus');
|
} = require('nexus');
|
||||||
const { prisma } = require('../../../prisma');
|
const { prisma } = require('../../../prisma');
|
||||||
|
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -15,7 +16,18 @@ const Hackathon = objectType({
|
|||||||
t.nonNull.int('id');
|
t.nonNull.int('id');
|
||||||
t.nonNull.string('title');
|
t.nonNull.string('title');
|
||||||
t.nonNull.string('description');
|
t.nonNull.string('description');
|
||||||
t.nonNull.string('cover_image');
|
t.nonNull.string('cover_image', {
|
||||||
|
async resolve(parent) {
|
||||||
|
if (!parent.cover_image_id) return null
|
||||||
|
const imgObject = await prisma.hostedImage.findUnique({
|
||||||
|
where: {
|
||||||
|
id: parent.cover_image_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return resolveImgObjectToUrl(imgObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
t.nonNull.date('start_date');
|
t.nonNull.date('start_date');
|
||||||
t.nonNull.date('end_date');
|
t.nonNull.date('end_date');
|
||||||
t.nonNull.string('location');
|
t.nonNull.string('location');
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const scalars = require('./_scalars')
|
const scalars = require('./_scalars')
|
||||||
|
const misc = require('./misc')
|
||||||
const category = require('./category')
|
const category = require('./category')
|
||||||
const project = require('./project')
|
const project = require('./project')
|
||||||
const vote = require('./vote')
|
const vote = require('./vote')
|
||||||
@@ -10,6 +11,7 @@ const donation = require('./donation')
|
|||||||
const tag = require('./tag')
|
const tag = require('./tag')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
...misc,
|
||||||
...tag,
|
...tag,
|
||||||
...scalars,
|
...scalars,
|
||||||
...category,
|
...category,
|
||||||
|
|||||||
19
api/functions/graphql/types/misc.js
Normal file
19
api/functions/graphql/types/misc.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const { objectType, extendType, inputObjectType } = require("nexus");
|
||||||
|
const { prisma } = require('../../../prisma');
|
||||||
|
|
||||||
|
const ImageInput = inputObjectType({
|
||||||
|
name: 'ImageInput',
|
||||||
|
definition(t) {
|
||||||
|
t.string('id');
|
||||||
|
t.string('name');
|
||||||
|
t.nonNull.string('url');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// Types
|
||||||
|
ImageInput,
|
||||||
|
|
||||||
|
// Queries
|
||||||
|
}
|
||||||
@@ -15,6 +15,9 @@ const { prisma } = require('../../../prisma');
|
|||||||
const { getUserByPubKey } = require('../../../auth/utils/helperFuncs');
|
const { getUserByPubKey } = require('../../../auth/utils/helperFuncs');
|
||||||
const { ApolloError } = require('apollo-server-lambda');
|
const { ApolloError } = require('apollo-server-lambda');
|
||||||
const { marked } = require('marked');
|
const { marked } = require('marked');
|
||||||
|
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||||
|
const { ImageInput } = require('./misc');
|
||||||
|
const { deleteImage } = require('../../../services/imageUpload.service');
|
||||||
|
|
||||||
|
|
||||||
const POST_TYPE = enumType({
|
const POST_TYPE = enumType({
|
||||||
@@ -37,7 +40,17 @@ const Author = objectType({
|
|||||||
definition(t) {
|
definition(t) {
|
||||||
t.nonNull.int('id');
|
t.nonNull.int('id');
|
||||||
t.nonNull.string('name');
|
t.nonNull.string('name');
|
||||||
t.nonNull.string('avatar');
|
t.nonNull.string('avatar', {
|
||||||
|
async resolve(parent) {
|
||||||
|
const imgObject = await prisma.hostedImage.findUnique({
|
||||||
|
where: {
|
||||||
|
id: parent.avatar_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return resolveImgObjectToUrl(imgObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
t.nonNull.date('join_date');
|
t.nonNull.date('join_date');
|
||||||
|
|
||||||
t.string('lightning_address');
|
t.string('lightning_address');
|
||||||
@@ -71,7 +84,18 @@ const Story = objectType({
|
|||||||
t.nonNull.string('type', {
|
t.nonNull.string('type', {
|
||||||
resolve: () => t.typeName
|
resolve: () => t.typeName
|
||||||
});
|
});
|
||||||
t.string('cover_image');
|
t.string('cover_image', {
|
||||||
|
async resolve(parent) {
|
||||||
|
if (!parent.cover_image_id) return null
|
||||||
|
const imgObject = await prisma.hostedImage.findUnique({
|
||||||
|
where: {
|
||||||
|
id: parent.cover_image_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return resolveImgObjectToUrl(imgObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
t.nonNull.list.nonNull.field('comments', {
|
t.nonNull.list.nonNull.field('comments', {
|
||||||
type: "PostComment",
|
type: "PostComment",
|
||||||
resolve: (parent) => []
|
resolve: (parent) => []
|
||||||
@@ -111,7 +135,9 @@ const StoryInputType = inputObjectType({
|
|||||||
t.int('id');
|
t.int('id');
|
||||||
t.nonNull.string('title');
|
t.nonNull.string('title');
|
||||||
t.nonNull.string('body');
|
t.nonNull.string('body');
|
||||||
t.string('cover_image');
|
t.field('cover_image', {
|
||||||
|
type: ImageInput
|
||||||
|
})
|
||||||
t.nonNull.list.nonNull.string('tags');
|
t.nonNull.list.nonNull.string('tags');
|
||||||
t.boolean('is_published')
|
t.boolean('is_published')
|
||||||
}
|
}
|
||||||
@@ -137,7 +163,18 @@ const Bounty = objectType({
|
|||||||
t.nonNull.string('type', {
|
t.nonNull.string('type', {
|
||||||
resolve: () => 'Bounty'
|
resolve: () => 'Bounty'
|
||||||
});
|
});
|
||||||
t.string('cover_image');
|
t.string('cover_image', {
|
||||||
|
async resolve(parent) {
|
||||||
|
if (!parent.cover_image_id) return null
|
||||||
|
const imgObject = await prisma.hostedImage.findUnique({
|
||||||
|
where: {
|
||||||
|
id: parent.cover_image_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return resolveImgObjectToUrl(imgObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
t.nonNull.string('deadline');
|
t.nonNull.string('deadline');
|
||||||
t.nonNull.int('reward_amount');
|
t.nonNull.int('reward_amount');
|
||||||
t.nonNull.int('applicants_count');
|
t.nonNull.int('applicants_count');
|
||||||
@@ -342,6 +379,64 @@ const getPostById = extendType({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const addCoverImage = async (providerImageId) => {
|
||||||
|
const newCoverImage = await prisma.hostedImage.findFirst({
|
||||||
|
where: {
|
||||||
|
provider_image_id: providerImageId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!newCoverImage) throw new ApolloError("New cover image not found")
|
||||||
|
|
||||||
|
await prisma.hostedImage.update({
|
||||||
|
where: {
|
||||||
|
id: newCoverImage.id
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
is_used: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return newCoverImage
|
||||||
|
}
|
||||||
|
|
||||||
|
const getHostedImageIdsFromBody = async (body, oldBodyImagesIds = null) => {
|
||||||
|
let bodyImageIds = []
|
||||||
|
|
||||||
|
const regex = /(?:!\[(.*?)\]\((.*?)\))/g
|
||||||
|
let match;
|
||||||
|
while((match = regex.exec(body))) {
|
||||||
|
const [,,value] = match
|
||||||
|
|
||||||
|
// Useful for old external images in case of duplicates. We need to be sure we are targeting an image from the good story.
|
||||||
|
const where = oldBodyImagesIds ? {
|
||||||
|
AND: [
|
||||||
|
{ url: value },
|
||||||
|
{ id: { in: oldBodyImagesIds } }
|
||||||
|
]
|
||||||
|
} :
|
||||||
|
{
|
||||||
|
url: value,
|
||||||
|
}
|
||||||
|
|
||||||
|
const hostedImage = await prisma.hostedImage.findFirst({
|
||||||
|
where
|
||||||
|
})
|
||||||
|
if (hostedImage) {
|
||||||
|
bodyImageIds.push(hostedImage.id)
|
||||||
|
await prisma.hostedImage.update({
|
||||||
|
where: {
|
||||||
|
id: hostedImage.id
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
is_used: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bodyImageIds
|
||||||
|
}
|
||||||
|
|
||||||
const createStory = extendType({
|
const createStory = extendType({
|
||||||
type: 'Mutation',
|
type: 'Mutation',
|
||||||
definition(t) {
|
definition(t) {
|
||||||
@@ -358,20 +453,64 @@ const createStory = extendType({
|
|||||||
|
|
||||||
let was_published = false;
|
let was_published = false;
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: validate post data
|
||||||
|
|
||||||
|
let coverImage = null
|
||||||
|
let bodyImageIds = []
|
||||||
|
|
||||||
|
// Edit story
|
||||||
if (id) {
|
if (id) {
|
||||||
const oldPost = await prisma.story.findFirst({
|
const oldPost = await prisma.story.findFirst({
|
||||||
where: { id },
|
where: { id },
|
||||||
select: {
|
select: {
|
||||||
user_id: true,
|
user_id: true,
|
||||||
is_published: true
|
is_published: true,
|
||||||
|
cover_image_id: true,
|
||||||
|
body_image_ids: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
was_published = oldPost.is_published;
|
was_published = oldPost.is_published;
|
||||||
if (user.id !== oldPost.user_id)
|
if (user.id !== oldPost.user_id) throw new ApolloError("Not post author")
|
||||||
throw new ApolloError("Not post author")
|
|
||||||
}
|
|
||||||
// TODO: validate post data
|
|
||||||
|
|
||||||
|
// Body images
|
||||||
|
bodyImageIds = await getHostedImageIdsFromBody(body, oldPost.body_image_ids)
|
||||||
|
|
||||||
|
// Old cover image is found
|
||||||
|
if (oldPost.cover_image_id) {
|
||||||
|
const oldCoverImage = await prisma.hostedImage.findFirst({
|
||||||
|
where : {
|
||||||
|
id: oldPost.cover_image_id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// New cover image
|
||||||
|
if (cover_image?.id && cover_image.id !== oldCoverImage?.provider_image_id) {
|
||||||
|
await deleteImage(oldCoverImage.id)
|
||||||
|
coverImage = await addCoverImage(cover_image.id)
|
||||||
|
} else {
|
||||||
|
coverImage = oldCoverImage
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No old image found and new cover image
|
||||||
|
if (cover_image?.id) {
|
||||||
|
coverImage = await addCoverImage(cover_image.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove unused body images
|
||||||
|
const unusedImagesIds = oldPost.body_image_ids.filter(x => !bodyImageIds.includes(x));
|
||||||
|
unusedImagesIds.map(async i => await deleteImage(i))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Body images
|
||||||
|
bodyImageIds = await getHostedImageIdsFromBody(body)
|
||||||
|
|
||||||
|
// New story and new cover image
|
||||||
|
if (cover_image?.id) {
|
||||||
|
coverImage = await addCoverImage(cover_image.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Preprocess & insert
|
// Preprocess & insert
|
||||||
const htmlBody = marked.parse(body);
|
const htmlBody = marked.parse(body);
|
||||||
@@ -383,6 +522,16 @@ const createStory = extendType({
|
|||||||
.replace(/"/g, '"')
|
.replace(/"/g, '"')
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
|
const coverImageRel = coverImage ? {
|
||||||
|
cover_image_rel: {
|
||||||
|
connect:
|
||||||
|
{
|
||||||
|
id: coverImage ? coverImage.id : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} : {}
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
await prisma.story.update({
|
await prisma.story.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -398,7 +547,7 @@ const createStory = extendType({
|
|||||||
data: {
|
data: {
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
cover_image,
|
cover_image: '',
|
||||||
excerpt,
|
excerpt,
|
||||||
is_published: was_published || is_published,
|
is_published: was_published || is_published,
|
||||||
tags: {
|
tags: {
|
||||||
@@ -415,16 +564,17 @@ const createStory = extendType({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
body_image_ids: bodyImageIds,
|
||||||
|
...coverImageRel
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return await prisma.story.create({
|
||||||
return prisma.story.create({
|
|
||||||
data: {
|
data: {
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
cover_image,
|
cover_image: '',
|
||||||
excerpt,
|
excerpt,
|
||||||
is_published,
|
is_published,
|
||||||
tags: {
|
tags: {
|
||||||
@@ -445,7 +595,9 @@ const createStory = extendType({
|
|||||||
connect: {
|
connect: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
body_image_ids: bodyImageIds,
|
||||||
|
...coverImageRel
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -470,17 +622,39 @@ const deleteStory = extendType({
|
|||||||
const oldPost = await prisma.story.findFirst({
|
const oldPost = await prisma.story.findFirst({
|
||||||
where: { id },
|
where: { id },
|
||||||
select: {
|
select: {
|
||||||
user_id: true
|
user_id: true,
|
||||||
|
body_image_ids: true,
|
||||||
|
cover_image_id: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (user.id !== oldPost.user_id)
|
if (user.id !== oldPost.user_id)
|
||||||
throw new ApolloError("Not post author")
|
throw new ApolloError("Not post author")
|
||||||
|
|
||||||
return prisma.story.delete({
|
const deletedPost = await prisma.story.delete({
|
||||||
where: {
|
where: {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const coverImage = await prisma.hostedImage.findMany({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ id: oldPost.cover_image_id },
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
in: oldPost.body_image_ids
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
provider_image_id: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
coverImage.map(async i => await deleteImage(i.id))
|
||||||
|
|
||||||
|
return deletedPost
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const {
|
|||||||
nonNull,
|
nonNull,
|
||||||
} = require('nexus')
|
} = require('nexus')
|
||||||
const { prisma } = require('../../../prisma');
|
const { prisma } = require('../../../prisma');
|
||||||
|
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||||
|
|
||||||
const { paginationArgs, getLnurlDetails, lightningAddressToLnurl } = require('./helpers');
|
const { paginationArgs, getLnurlDetails, lightningAddressToLnurl } = require('./helpers');
|
||||||
const { MakerRole } = require('./users');
|
const { MakerRole } = require('./users');
|
||||||
@@ -17,9 +18,44 @@ const Project = objectType({
|
|||||||
t.nonNull.int('id');
|
t.nonNull.int('id');
|
||||||
t.nonNull.string('title');
|
t.nonNull.string('title');
|
||||||
t.nonNull.string('description');
|
t.nonNull.string('description');
|
||||||
t.nonNull.string('cover_image');
|
t.nonNull.string('cover_image', {
|
||||||
t.nonNull.string('thumbnail_image');
|
async resolve(parent) {
|
||||||
t.nonNull.list.nonNull.string('screenshots');
|
if (!parent.cover_image_id) return null
|
||||||
|
const imgObject = await prisma.hostedImage.findUnique({
|
||||||
|
where: {
|
||||||
|
id: parent.cover_image_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return resolveImgObjectToUrl(imgObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.nonNull.string('thumbnail_image', {
|
||||||
|
async resolve(parent) {
|
||||||
|
if (!parent.thumbnail_image_id) return null
|
||||||
|
const imgObject = await prisma.hostedImage.findUnique({
|
||||||
|
where: {
|
||||||
|
id: parent.thumbnail_image_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return resolveImgObjectToUrl(imgObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.nonNull.list.nonNull.string('screenshots', {
|
||||||
|
async resolve(parent) {
|
||||||
|
if (!parent.screenshots_ids) return null
|
||||||
|
const imgObject = await prisma.hostedImage.findMany({
|
||||||
|
where: {
|
||||||
|
id: { in: parent.screenshots_ids }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return imgObject.map(img => {
|
||||||
|
return resolveImgObjectToUrl(img);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
t.nonNull.string('website');
|
t.nonNull.string('website');
|
||||||
t.string('lightning_address');
|
t.string('lightning_address');
|
||||||
t.string('lnurl_callback_url');
|
t.string('lnurl_callback_url');
|
||||||
|
|||||||
0
api/functions/graphql/types/tournaments.js
Normal file
0
api/functions/graphql/types/tournaments.js
Normal file
@@ -3,7 +3,10 @@ const { prisma } = require('../../../prisma');
|
|||||||
const { objectType, extendType, intArg, nonNull, inputObjectType, interfaceType, list, enumType } = require("nexus");
|
const { objectType, extendType, intArg, nonNull, inputObjectType, interfaceType, list, enumType } = require("nexus");
|
||||||
const { getUserByPubKey } = require("../../../auth/utils/helperFuncs");
|
const { getUserByPubKey } = require("../../../auth/utils/helperFuncs");
|
||||||
const { removeNulls } = require("./helpers");
|
const { removeNulls } = require("./helpers");
|
||||||
|
const { ImageInput } = require('./misc');
|
||||||
const { Tournament } = require('./tournament');
|
const { Tournament } = require('./tournament');
|
||||||
|
const { resolveImgObjectToUrl } = require('../../../utils/resolveImageUrl');
|
||||||
|
const { deleteImage } = require('../../../services/imageUpload.service');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -13,7 +16,18 @@ const BaseUser = interfaceType({
|
|||||||
definition(t) {
|
definition(t) {
|
||||||
t.nonNull.int('id');
|
t.nonNull.int('id');
|
||||||
t.nonNull.string('name');
|
t.nonNull.string('name');
|
||||||
t.nonNull.string('avatar');
|
t.nonNull.string('avatar', {
|
||||||
|
async resolve(parent) {
|
||||||
|
if (!parent.avatar_id) return null
|
||||||
|
const imgObject = await prisma.hostedImage.findUnique({
|
||||||
|
where: {
|
||||||
|
id: parent.avatar_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return resolveImgObjectToUrl(imgObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
t.nonNull.date('join_date');
|
t.nonNull.date('join_date');
|
||||||
t.string('role');
|
t.string('role');
|
||||||
t.string('jobTitle')
|
t.string('jobTitle')
|
||||||
@@ -266,7 +280,9 @@ const ProfileDetailsInput = inputObjectType({
|
|||||||
name: 'ProfileDetailsInput',
|
name: 'ProfileDetailsInput',
|
||||||
definition(t) {
|
definition(t) {
|
||||||
t.string('name');
|
t.string('name');
|
||||||
t.string('avatar');
|
t.field('avatar', {
|
||||||
|
type: ImageInput
|
||||||
|
})
|
||||||
t.string('email')
|
t.string('email')
|
||||||
t.string('jobTitle')
|
t.string('jobTitle')
|
||||||
t.string('lightning_address')
|
t.string('lightning_address')
|
||||||
@@ -294,14 +310,48 @@ const updateProfileDetails = extendType({
|
|||||||
throw new Error("You have to login");
|
throw new Error("You have to login");
|
||||||
// TODO: validate new data
|
// TODO: validate new data
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
// Check if the user uploaded a new image, and if so,
|
||||||
|
// remove the old one from the hosting service, then replace it with this one
|
||||||
|
// ----------------
|
||||||
|
let avatarId = user.avatar_id;
|
||||||
|
if (args.data.avatar.id) {
|
||||||
|
const newAvatarProviderId = args.data.avatar.id;
|
||||||
|
const newAvatar = await prisma.hostedImage.findFirst({
|
||||||
|
where: {
|
||||||
|
provider_image_id: newAvatarProviderId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (newAvatar && newAvatar.id !== user.avatar_id) {
|
||||||
|
avatarId = newAvatar.id;
|
||||||
|
|
||||||
|
await prisma.hostedImage.update({
|
||||||
|
where: {
|
||||||
|
id: newAvatar.id
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
is_used: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await deleteImage(user.avatar_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Preprocess & insert
|
// Preprocess & insert
|
||||||
|
|
||||||
return prisma.user.update({
|
return prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
},
|
},
|
||||||
data: removeNulls(args.data)
|
data: removeNulls({
|
||||||
|
...args.data,
|
||||||
|
avatar_id: avatarId,
|
||||||
|
|
||||||
|
//hack to remove avatar from args.data
|
||||||
|
// can be removed later with a schema data validator
|
||||||
|
avatar: '',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -85,11 +85,21 @@ const loginHandler = async (req, res) => {
|
|||||||
const nostr_prv_key = generatePrivateKey();
|
const nostr_prv_key = generatePrivateKey();
|
||||||
const nostr_pub_key = getPublicKey(nostr_prv_key);
|
const nostr_pub_key = getPublicKey(nostr_prv_key);
|
||||||
|
|
||||||
|
const avatar = await prisma.hostedImage.create({
|
||||||
|
data: {
|
||||||
|
filename: 'avatar.svg',
|
||||||
|
provider: 'external',
|
||||||
|
is_used: true,
|
||||||
|
url: `https://avatars.dicebear.com/api/bottts/${key}.svg`,
|
||||||
|
provider_image_id: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const createdUser = await prisma.user.create({
|
const createdUser = await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
pubKey: key,
|
pubKey: key,
|
||||||
name: key,
|
name: key,
|
||||||
avatar: `https://avatars.dicebear.com/api/bottts/${key}.svg`,
|
avatar_id: avatar.id,
|
||||||
nostr_prv_key,
|
nostr_prv_key,
|
||||||
nostr_pub_key,
|
nostr_pub_key,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ const extractKeyFromCookie = require('../../utils/extractKeyFromCookie')
|
|||||||
const { getUserByPubKey } = require('../../auth/utils/helperFuncs')
|
const { getUserByPubKey } = require('../../auth/utils/helperFuncs')
|
||||||
const { getDirectUploadUrl } = require('../../services/imageUpload.service')
|
const { getDirectUploadUrl } = require('../../services/imageUpload.service')
|
||||||
const { prisma } = require('../../prisma')
|
const { prisma } = require('../../prisma')
|
||||||
|
const { getUrlFromProvider } = require('../../utils/resolveImageUrl')
|
||||||
|
|
||||||
const postUploadImageUrl = async (req, res) => {
|
const postUploadImageUrl = async (req, res) => {
|
||||||
|
|
||||||
return res.status(404).send("This api is in progress");
|
|
||||||
|
|
||||||
const userPubKey = await extractKeyFromCookie(req.headers.cookie ?? req.headers.Cookie)
|
const userPubKey = await extractKeyFromCookie(req.headers.cookie ?? req.headers.Cookie)
|
||||||
const user = await getUserByPubKey(userPubKey)
|
const user = await getUserByPubKey(userPubKey)
|
||||||
|
|
||||||
@@ -22,11 +21,16 @@ const postUploadImageUrl = async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const uploadUrl = await getDirectUploadUrl()
|
const uploadUrl = await getDirectUploadUrl()
|
||||||
|
|
||||||
await prisma.hostedImage.create({
|
const hostedImage = await prisma.hostedImage.create({
|
||||||
data: { id: uploadUrl.id, filename },
|
data: {
|
||||||
|
filename,
|
||||||
|
url: getUrlFromProvider(uploadUrl.provider, uploadUrl.id),
|
||||||
|
provider_image_id: uploadUrl.id,
|
||||||
|
provider: uploadUrl.provider
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return res.status(200).json(uploadUrl)
|
return res.status(200).json({ id: hostedImage.id, uploadURL: uploadUrl.uploadURL })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).send('Unexpected error happened, please try again')
|
res.status(500).send('Unexpected error happened, please try again')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
const { CONSTS } = require('../utils')
|
const { CONSTS } = require('../utils')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const FormData = require('form-data')
|
const FormData = require('form-data')
|
||||||
|
const { prisma } = require('../prisma')
|
||||||
|
|
||||||
const BASE_URL = 'https://api.cloudflare.com/client/v4'
|
const BASE_URL = 'https://api.cloudflare.com/client/v4'
|
||||||
|
|
||||||
const operationUrls = {
|
const operationUrls = {
|
||||||
'image.uploadUrl': `${BASE_URL}/accounts/${CONSTS.CLOUDFLARE_IMAGE_ACCOUNT_ID}/images/v2/direct_upload`,
|
'image.uploadUrl': `${BASE_URL}/accounts/${CONSTS.CLOUDFLARE_IMAGE_ACCOUNT_ID}/images/v2/direct_upload`,
|
||||||
|
'image.delete': `${BASE_URL}/accounts/${CONSTS.CLOUDFLARE_IMAGE_ACCOUNT_ID}/images/v1/`,
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAxiosConfig() {
|
||||||
|
return {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${CONSTS.CLOUDFLARE_IMAGE_API_KEY}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDirectUploadUrl() {
|
async function getDirectUploadUrl() {
|
||||||
@@ -27,9 +37,62 @@ async function getDirectUploadUrl() {
|
|||||||
throw new Error(result.data, { cause: result.data.errors })
|
throw new Error(result.data, { cause: result.data.errors })
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.data.result
|
const data = result.data.result
|
||||||
|
|
||||||
|
return { id: data.id, uploadURL: data.uploadURL, provider: 'cloudflare' }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteImageFromProvider(providerImageId) {
|
||||||
|
try {
|
||||||
|
const url = operationUrls['image.delete'] + providerImageId
|
||||||
|
const result = await axios.delete(url, getAxiosConfig())
|
||||||
|
|
||||||
|
if (!result.data.success) {
|
||||||
|
throw new Error(result.data, { cause: result.data.errors })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteImage(hostedImageId) {
|
||||||
|
if (!hostedImageId) throw new Error("argument 'hostedImageId' must be provider")
|
||||||
|
|
||||||
|
const hostedImage = await prisma.hostedImage.findFirst({
|
||||||
|
where: {
|
||||||
|
id: hostedImageId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!hostedImage) throw new Error(`No HostedImage row found for HostedImage.id=${hostedImageId}`)
|
||||||
|
if (hostedImage.provider_image_id && hostedImage.provider_image_id === '')
|
||||||
|
throw new Error(`Field 'provider_image_id' for HostedImage.id=${hostedImageId} must not be empty. Current value '${hostedImage.provider_image_id}'`)
|
||||||
|
|
||||||
|
// Set is_used to false in case of deletion fail from the hosting image provider. The scheduled job will try to delete the HostedImage row
|
||||||
|
await prisma.hostedImage.update({
|
||||||
|
where: {
|
||||||
|
id: hostedImage.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
is_used: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (hostedImage.provider_image_id && hostedImage.provider_image_id !== '') {
|
||||||
|
deleteImageFromProvider(hostedImage.provider_image_id)
|
||||||
|
.then(async () => {
|
||||||
|
await prisma.hostedImage.delete({
|
||||||
|
where: {
|
||||||
|
id: hostedImageId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => console.error(error))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getDirectUploadUrl,
|
getDirectUploadUrl,
|
||||||
|
deleteImage,
|
||||||
|
deleteImageFromProvider,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const JWT_SECRET = process.env.JWT_SECRET
|
|||||||
const LNURL_AUTH_HOST = process.env.LNURL_AUTH_HOST
|
const LNURL_AUTH_HOST = process.env.LNURL_AUTH_HOST
|
||||||
const CLOUDFLARE_IMAGE_ACCOUNT_ID = process.env.CLOUDFLARE_IMAGE_ACCOUNT_ID
|
const CLOUDFLARE_IMAGE_ACCOUNT_ID = process.env.CLOUDFLARE_IMAGE_ACCOUNT_ID
|
||||||
const CLOUDFLARE_IMAGE_API_KEY = process.env.CLOUDFLARE_IMAGE_API_KEY
|
const CLOUDFLARE_IMAGE_API_KEY = process.env.CLOUDFLARE_IMAGE_API_KEY
|
||||||
|
const CLOUDFLARE_IMAGE_ACCOUNT_HASH = process.env.CLOUDFLARE_IMAGE_ACCOUNT_HASH
|
||||||
|
|
||||||
const CONSTS = {
|
const CONSTS = {
|
||||||
JWT_SECRET,
|
JWT_SECRET,
|
||||||
@@ -10,6 +11,7 @@ const CONSTS = {
|
|||||||
LNURL_AUTH_HOST,
|
LNURL_AUTH_HOST,
|
||||||
CLOUDFLARE_IMAGE_ACCOUNT_ID,
|
CLOUDFLARE_IMAGE_ACCOUNT_ID,
|
||||||
CLOUDFLARE_IMAGE_API_KEY,
|
CLOUDFLARE_IMAGE_API_KEY,
|
||||||
|
CLOUDFLARE_IMAGE_ACCOUNT_HASH
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = CONSTS
|
module.exports = CONSTS
|
||||||
|
|||||||
45
api/utils/resolveImageUrl.js
Normal file
45
api/utils/resolveImageUrl.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
const { CLOUDFLARE_IMAGE_ACCOUNT_HASH } = require('./consts')
|
||||||
|
|
||||||
|
const PROVIDERS = [
|
||||||
|
{
|
||||||
|
name: 'cloudflare',
|
||||||
|
prefixUrl: `https://imagedelivery.net/${CLOUDFLARE_IMAGE_ACCOUNT_HASH}/`,
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
default: true,
|
||||||
|
name: 'public',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* resolveImgObjectToUrl
|
||||||
|
* @param {object} imgObject
|
||||||
|
* @param {string} variant - List to be defined. DEFAULT TO 'public'
|
||||||
|
* @returns {string} image url
|
||||||
|
*/
|
||||||
|
function resolveImgObjectToUrl(imgObject, variant = null) {
|
||||||
|
if (!imgObject) throw new Error('Image not found')
|
||||||
|
|
||||||
|
if (imgObject.provider === 'external') {
|
||||||
|
return imgObject.url
|
||||||
|
}
|
||||||
|
|
||||||
|
return getUrlFromProvider(imgObject.provider, imgObject.provider_image_id, variant)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUrlFromProvider(provider, providerImageId, variant = null) {
|
||||||
|
const p = PROVIDERS.find((p) => p.name === provider)
|
||||||
|
|
||||||
|
if (p) {
|
||||||
|
if (p && p.name === 'cloudflare') {
|
||||||
|
const variantName = variant ?? p.variants.find((v) => v.default).name
|
||||||
|
return p.prefixUrl + providerImageId + '/' + variantName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Hosting images provider not supported')
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { resolveImgObjectToUrl, getUrlFromProvider }
|
||||||
659
package-lock.json
generated
659
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,11 @@
|
|||||||
"@reduxjs/toolkit": "^1.8.1",
|
"@reduxjs/toolkit": "^1.8.1",
|
||||||
"@remirror/pm": "^1.0.16",
|
"@remirror/pm": "^1.0.16",
|
||||||
"@remirror/react": "^1.0.34",
|
"@remirror/react": "^1.0.34",
|
||||||
|
"@rpldy/mock-sender": "^1.0.1",
|
||||||
|
"@rpldy/upload-button": "^1.0.1",
|
||||||
|
"@rpldy/upload-drop-zone": "^1.0.1",
|
||||||
|
"@rpldy/upload-preview": "^1.0.1",
|
||||||
|
"@rpldy/uploady": "^1.0.1",
|
||||||
"@shopify/react-web-worker": "^5.0.1",
|
"@shopify/react-web-worker": "^5.0.1",
|
||||||
"@szhsin/react-menu": "^3.0.2",
|
"@szhsin/react-menu": "^3.0.2",
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- The primary key for the `HostedImage` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||||
|
- The `id` column on the `HostedImage` table would be dropped and recreated. This will lead to data loss if there is data in the column.
|
||||||
|
- Added the required column `provider` to the `HostedImage` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `provider_image_id` to the `HostedImage` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `url` to the `HostedImage` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
-- START Custom SQL
|
||||||
|
-- Because of the breaking changes, we have to apply some custom SQL.
|
||||||
|
-- By chance, the previous HostedImage migration is not used it production, so we can remove all data from this table
|
||||||
|
DELETE FROM "HostedImage";
|
||||||
|
-- END Custom SQL
|
||||||
|
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "HostedImage" DROP CONSTRAINT "HostedImage_pkey",
|
||||||
|
ADD COLUMN "provider" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "provider_image_id" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "url" TEXT NOT NULL,
|
||||||
|
DROP COLUMN "id",
|
||||||
|
ADD COLUMN "id" SERIAL NOT NULL,
|
||||||
|
ADD CONSTRAINT "HostedImage_pkey" PRIMARY KEY ("id");
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "UserKey" ALTER COLUMN "name" SET DEFAULT E'My new wallet key';
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[image_id]` on the table `Award` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[cover_image_id]` on the table `Category` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[cover_image_id]` on the table `Hackathon` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[thumbnail_image_id]` on the table `Project` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[cover_image_id]` on the table `Project` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[cover_image_id]` on the table `Story` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[avatar_id]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Award" ADD COLUMN "image_id" INTEGER;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Category" ADD COLUMN "cover_image_id" INTEGER;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Hackathon" ADD COLUMN "cover_image_id" INTEGER;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Project" ADD COLUMN "cover_image_id" INTEGER,
|
||||||
|
ADD COLUMN "screenshots_ids" INTEGER[],
|
||||||
|
ADD COLUMN "thumbnail_image_id" INTEGER;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Story" ADD COLUMN "body_image_ids" INTEGER[],
|
||||||
|
ADD COLUMN "cover_image_id" INTEGER;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ADD COLUMN "avatar_id" INTEGER;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Award_image_id_key" ON "Award"("image_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Category_cover_image_id_key" ON "Category"("cover_image_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Hackathon_cover_image_id_key" ON "Hackathon"("cover_image_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Project_thumbnail_image_id_key" ON "Project"("thumbnail_image_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Project_cover_image_id_key" ON "Project"("cover_image_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Story_cover_image_id_key" ON "Story"("cover_image_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_avatar_id_key" ON "User"("avatar_id");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "User" ADD CONSTRAINT "User_avatar_id_fkey" FOREIGN KEY ("avatar_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Category" ADD CONSTRAINT "Category_cover_image_id_fkey" FOREIGN KEY ("cover_image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Project" ADD CONSTRAINT "Project_thumbnail_image_id_fkey" FOREIGN KEY ("thumbnail_image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Project" ADD CONSTRAINT "Project_cover_image_id_fkey" FOREIGN KEY ("cover_image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Award" ADD CONSTRAINT "Award_image_id_fkey" FOREIGN KEY ("image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Story" ADD CONSTRAINT "Story_cover_image_id_fkey" FOREIGN KEY ("cover_image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Hackathon" ADD CONSTRAINT "Hackathon_cover_image_id_fkey" FOREIGN KEY ("cover_image_id") REFERENCES "HostedImage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
@@ -44,6 +44,8 @@ model User {
|
|||||||
pubKey String? @unique
|
pubKey String? @unique
|
||||||
name String?
|
name String?
|
||||||
avatar String?
|
avatar String?
|
||||||
|
avatar_id Int? @unique
|
||||||
|
avatar_rel HostedImage? @relation("User_Avatar", fields: [avatar_id], references: [id])
|
||||||
role String @default("user")
|
role String @default("user")
|
||||||
|
|
||||||
email String?
|
email String?
|
||||||
@@ -115,6 +117,8 @@ model Category {
|
|||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
title String
|
title String
|
||||||
cover_image String?
|
cover_image String?
|
||||||
|
cover_image_id Int? @unique
|
||||||
|
cover_image_rel HostedImage? @relation("Category_CoverImage", fields: [cover_image_id], references: [id])
|
||||||
icon String?
|
icon String?
|
||||||
|
|
||||||
project Project[]
|
project Project[]
|
||||||
@@ -125,9 +129,14 @@ model Project {
|
|||||||
title String
|
title String
|
||||||
description String
|
description String
|
||||||
screenshots String[]
|
screenshots String[]
|
||||||
|
screenshots_ids Int[]
|
||||||
website String
|
website String
|
||||||
thumbnail_image String?
|
thumbnail_image String?
|
||||||
|
thumbnail_image_id Int? @unique
|
||||||
|
thumbnail_image_rel HostedImage? @relation("Project_Thumbnail", fields: [thumbnail_image_id], references: [id])
|
||||||
cover_image String?
|
cover_image String?
|
||||||
|
cover_image_id Int? @unique
|
||||||
|
cover_image_rel HostedImage? @relation("Project_CoverImage", fields: [cover_image_id], references: [id])
|
||||||
lightning_address String?
|
lightning_address String?
|
||||||
lnurl_callback_url String?
|
lnurl_callback_url String?
|
||||||
|
|
||||||
@@ -158,6 +167,8 @@ model Award {
|
|||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
title String
|
title String
|
||||||
image String
|
image String
|
||||||
|
image_id Int? @unique
|
||||||
|
image_rel HostedImage? @relation("Award_Image", fields: [image_id], references: [id])
|
||||||
url String
|
url String
|
||||||
|
|
||||||
project Project @relation(fields: [project_id], references: [id])
|
project Project @relation(fields: [project_id], references: [id])
|
||||||
@@ -174,8 +185,11 @@ model Story {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
body String
|
body String
|
||||||
|
body_image_ids Int[]
|
||||||
excerpt String
|
excerpt String
|
||||||
cover_image String?
|
cover_image String?
|
||||||
|
cover_image_id Int? @unique
|
||||||
|
cover_image_rel HostedImage? @relation("Story_CoverImage", fields: [cover_image_id], references: [id])
|
||||||
votes_count Int @default(0)
|
votes_count Int @default(0)
|
||||||
is_published Boolean @default(true)
|
is_published Boolean @default(true)
|
||||||
|
|
||||||
@@ -235,6 +249,8 @@ model Hackathon {
|
|||||||
start_date DateTime @db.Date
|
start_date DateTime @db.Date
|
||||||
end_date DateTime @db.Date
|
end_date DateTime @db.Date
|
||||||
cover_image String
|
cover_image String
|
||||||
|
cover_image_id Int? @unique
|
||||||
|
cover_image_rel HostedImage? @relation("Hackathon_CoverImage", fields: [cover_image_id], references: [id])
|
||||||
description String
|
description String
|
||||||
location String
|
location String
|
||||||
website String
|
website String
|
||||||
@@ -272,10 +288,21 @@ model GeneratedK1 {
|
|||||||
// Hosted Image
|
// Hosted Image
|
||||||
// -----------------
|
// -----------------
|
||||||
model HostedImage {
|
model HostedImage {
|
||||||
id String @id
|
id Int @id @default(autoincrement())
|
||||||
filename String
|
filename String
|
||||||
|
provider_image_id String
|
||||||
|
provider String
|
||||||
|
url String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
is_used Boolean @default(false)
|
is_used Boolean @default(false)
|
||||||
|
|
||||||
|
ProjectThumbnail Project? @relation("Project_Thumbnail")
|
||||||
|
ProjectCoverImage Project? @relation("Project_CoverImage")
|
||||||
|
CategoryCoverImage Category? @relation("Category_CoverImage")
|
||||||
|
AwardImage Award? @relation("Award_Image")
|
||||||
|
HackathonCoverImage Hackathon? @relation("Hackathon_CoverImage")
|
||||||
|
StoryCoverImage Story? @relation("Story_CoverImage")
|
||||||
|
User User? @relation("User_Avatar")
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------
|
// -----------------
|
||||||
|
|||||||
@@ -67,6 +67,190 @@ async function main() {
|
|||||||
|
|
||||||
await createTournament();
|
await createTournament();
|
||||||
|
|
||||||
|
// await migrateOldImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function migrateOldImages() {
|
||||||
|
console.log('Migrating old images data to HostedImage');
|
||||||
|
|
||||||
|
// Can't use prisma method createMany() for columns like Project.screenshots, because this method doesn't return created IDs.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project
|
||||||
|
**/
|
||||||
|
const projects = await prisma.project.findMany({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
screenshots: true,
|
||||||
|
cover_image: true,
|
||||||
|
thumbnail_image: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for (const project of projects) {
|
||||||
|
/**
|
||||||
|
* Project.screenshots to Project.screenshots_ids
|
||||||
|
**/
|
||||||
|
let projectScreenshotIds = [];
|
||||||
|
for (const screenshot of project.screenshots) {
|
||||||
|
let hostedImageId = await _insertInHostedImage(screenshot)
|
||||||
|
projectScreenshotIds.push(hostedImageId);
|
||||||
|
}
|
||||||
|
if (projectScreenshotIds.length > 0) {
|
||||||
|
await _updateObjectWithHostedImageId(prisma.project, project.id, {
|
||||||
|
screenshots_ids: projectScreenshotIds,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project.cover_image to Project.cover_image_id
|
||||||
|
**/
|
||||||
|
if (project.cover_image) {
|
||||||
|
let hostedImageId = await _insertInHostedImage(project.cover_image)
|
||||||
|
await _updateObjectWithHostedImageId(prisma.project, project.id, {
|
||||||
|
cover_image_id: hostedImageId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project.thumbnail_image to Project.thumbnail_image_id
|
||||||
|
**/
|
||||||
|
if (project.cover_image) {
|
||||||
|
let hostedImageId = await _insertInHostedImage(project.thumbnail_image)
|
||||||
|
await _updateObjectWithHostedImageId(prisma.project, project.id, {
|
||||||
|
thumbnail_image_id: hostedImageId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Category
|
||||||
|
**/
|
||||||
|
const categories = await prisma.category.findMany({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
cover_image: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for (const category of categories) {
|
||||||
|
if (category.cover_image) {
|
||||||
|
let hostedImageId = await _insertInHostedImage(category.cover_image)
|
||||||
|
await _updateObjectWithHostedImageId(prisma.category, category.id, {
|
||||||
|
cover_image_id: hostedImageId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Award
|
||||||
|
**/
|
||||||
|
const awards = await prisma.award.findMany({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
image: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for (const award of awards) {
|
||||||
|
if (award.image) {
|
||||||
|
let hostedImageId = await _insertInHostedImage(award.image)
|
||||||
|
await _updateObjectWithHostedImageId(prisma.award, award.id, {
|
||||||
|
image_id: hostedImageId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hackaton
|
||||||
|
**/
|
||||||
|
const hackatons = await prisma.hackathon.findMany({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
cover_image: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for (const hackaton of hackatons) {
|
||||||
|
if (hackaton.cover_image) {
|
||||||
|
let hostedImageId = await _insertInHostedImage(hackaton.cover_image)
|
||||||
|
await _updateObjectWithHostedImageId(prisma.hackathon, hackaton.id, {
|
||||||
|
cover_image_id: hostedImageId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Story
|
||||||
|
**/
|
||||||
|
const stories = await prisma.story.findMany({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
cover_image: true,
|
||||||
|
body: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for (const story of stories) {
|
||||||
|
/**
|
||||||
|
* Story.body to Story.body_image_ids
|
||||||
|
**/
|
||||||
|
let bodyImageIds = [];
|
||||||
|
const regex = /(?:!\[(.*?)\]\((.*?)\))/g
|
||||||
|
let match;
|
||||||
|
while ((match = regex.exec(story.body))) {
|
||||||
|
const [, , value] = match
|
||||||
|
let hostedImageId = await _insertInHostedImage(value)
|
||||||
|
bodyImageIds.push(hostedImageId)
|
||||||
|
}
|
||||||
|
if (bodyImageIds.length > 0) {
|
||||||
|
await _updateObjectWithHostedImageId(prisma.story, story.id, {
|
||||||
|
body_image_ids: bodyImageIds,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Story.cover_image to Story.cover_image_id
|
||||||
|
**/
|
||||||
|
if (story.cover_image) {
|
||||||
|
let hostedImageId = await _insertInHostedImage(story.cover_image)
|
||||||
|
await _updateObjectWithHostedImageId(prisma.story, story.id, {
|
||||||
|
cover_image_id: hostedImageId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User
|
||||||
|
**/
|
||||||
|
const users = await prisma.user.findMany({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
avatar: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for (const user of users) {
|
||||||
|
if (user.avatar) {
|
||||||
|
let hostedImageId = await _insertInHostedImage(user.avatar)
|
||||||
|
await _updateObjectWithHostedImageId(prisma.user, user.id, {
|
||||||
|
avatar_id: hostedImageId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _insertInHostedImage(url) {
|
||||||
|
const newHostedImage = await prisma.hostedImage.create({
|
||||||
|
data: {
|
||||||
|
filename: "default.png",
|
||||||
|
provider: "external",
|
||||||
|
provider_image_id: "",
|
||||||
|
url,
|
||||||
|
is_used: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return newHostedImage.id;
|
||||||
|
}
|
||||||
|
async function _updateObjectWithHostedImageId(prismaObject, objectId, data) {
|
||||||
|
await prismaObject.update({
|
||||||
|
where: { id: objectId },
|
||||||
|
data,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createCategories() {
|
async function createCategories() {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock Service Worker (0.39.1).
|
* Mock Service Worker (0.39.2).
|
||||||
* @see https://github.com/mswjs/msw
|
* @see https://github.com/mswjs/msw
|
||||||
* - Please do NOT modify this file.
|
* - Please do NOT modify this file.
|
||||||
* - Please do NOT serve this file on production.
|
* - Please do NOT serve this file on production.
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ function App() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return <div id="app" className='w-full'>
|
return <div id="app" className='w-full'>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title >makers.bolt.fun</title>
|
<title >makers.bolt.fun</title>
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import { useToggle } from "@react-hookz/web";
|
|
||||||
import React from "react";
|
|
||||||
import { FileDrop } from "react-file-drop";
|
|
||||||
|
|
||||||
export default function DropInput({
|
|
||||||
value: files,
|
|
||||||
onChange,
|
|
||||||
emptyContent,
|
|
||||||
draggingContent,
|
|
||||||
hasFilesContent,
|
|
||||||
height,
|
|
||||||
multiple = false,
|
|
||||||
allowedType = "*",
|
|
||||||
classes = {
|
|
||||||
base: "",
|
|
||||||
idle: "",
|
|
||||||
dragging: "",
|
|
||||||
},
|
|
||||||
}) {
|
|
||||||
const [isDragging, toggleDrag] = useToggle(false);
|
|
||||||
const fileInputRef = React.useRef(null);
|
|
||||||
|
|
||||||
const onAddFiles = (_files) => {
|
|
||||||
onChange(_files);
|
|
||||||
// do something with your files...
|
|
||||||
};
|
|
||||||
|
|
||||||
const uploadClick = () => {
|
|
||||||
fileInputRef.current.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
const status = isDragging ? "dragging" : files ? "has-files" : "empty";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
height: height + "px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FileDrop
|
|
||||||
onDrop={(files) => onAddFiles(files)}
|
|
||||||
onTargetClick={uploadClick}
|
|
||||||
onFrameDragEnter={() => toggleDrag(true)}
|
|
||||||
onFrameDragLeave={() => toggleDrag(false)}
|
|
||||||
onFrameDrop={() => toggleDrag(false)}
|
|
||||||
className={`h-full cursor-pointer`}
|
|
||||||
targetClassName={`h-full ${classes.base} ${
|
|
||||||
status === "empty" && classes.idle
|
|
||||||
}`}
|
|
||||||
draggingOverFrameClassName={`${classes.dragging}`}
|
|
||||||
>
|
|
||||||
{status === "dragging" && draggingContent}
|
|
||||||
{status === "empty" && emptyContent}
|
|
||||||
{status === "has-files" && hasFilesContent}
|
|
||||||
</FileDrop>
|
|
||||||
<input
|
|
||||||
onChange={(e) => onAddFiles(e.target.files)}
|
|
||||||
ref={fileInputRef}
|
|
||||||
type="file"
|
|
||||||
className="hidden"
|
|
||||||
multiple={multiple}
|
|
||||||
accept={allowedType}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
|
||||||
import { BsImages } from 'react-icons/bs';
|
|
||||||
import Button from 'src/Components/Button/Button';
|
|
||||||
|
|
||||||
import FilesInput from './FilesInput';
|
|
||||||
import FileDropInput from './FilesDropInput';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Shared/Inputs/Files Input',
|
|
||||||
component: FilesInput,
|
|
||||||
|
|
||||||
} as ComponentMeta<typeof FilesInput>;
|
|
||||||
|
|
||||||
const Template: ComponentStory<typeof FilesInput> = (args) => <FilesInput {...args} />
|
|
||||||
|
|
||||||
|
|
||||||
export const DefaultButton = Template.bind({});
|
|
||||||
DefaultButton.args = {
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CustomizedButton = Template.bind({});
|
|
||||||
CustomizedButton.args = {
|
|
||||||
multiple: true,
|
|
||||||
uploadBtn: <Button color='primary'><span className="align-middle">Drop Images</span> <BsImages className='ml-12 scale-125' /></Button>
|
|
||||||
}
|
|
||||||
|
|
||||||
const DropTemplate: ComponentStory<typeof FileDropInput> = (args) => <div className="max-w-[500px]"><FileDropInput {...args as any} /></div>
|
|
||||||
export const DropZoneInput = DropTemplate.bind({});
|
|
||||||
DropZoneInput.args = {
|
|
||||||
onChange: console.log,
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import { useMemo } from "react";
|
|
||||||
import { MdClose } from "react-icons/md";
|
|
||||||
import IconButton from "src/Components/IconButton/IconButton";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
file: File | string,
|
|
||||||
onRemove?: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFileType(file: File | string) {
|
|
||||||
if (typeof file === 'string') {
|
|
||||||
if (/^http[^?]*.(jpg|jpeg|gif|png|tiff|bmp)(\?(.*))?$/gmi.test(file))
|
|
||||||
return 'image'
|
|
||||||
if (/\.(pdf|doc|docx)$/.test(file))
|
|
||||||
return 'document';
|
|
||||||
|
|
||||||
return 'unknown'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (file['type'].split('/')[0] === 'image')
|
|
||||||
return 'image'
|
|
||||||
|
|
||||||
return 'unknown'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ThumbnailFile = {
|
|
||||||
name: string;
|
|
||||||
src: string;
|
|
||||||
type: ReturnType<typeof getFileType>
|
|
||||||
}
|
|
||||||
|
|
||||||
function processFile(file: Props['file']): ThumbnailFile {
|
|
||||||
|
|
||||||
const fileType = getFileType(file);
|
|
||||||
|
|
||||||
if (typeof file === 'string') return { name: file, src: file, type: fileType };
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: file.name,
|
|
||||||
src: URL.createObjectURL(file),
|
|
||||||
type: fileType
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default function FileThumbnail({ file: f, onRemove }: Props) {
|
|
||||||
|
|
||||||
const file = useMemo(() => processFile(f), [f])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="bg-gray-100 rounded-8 p-12 shrink-0 flex gap-4 overflow-hidden">
|
|
||||||
<div className="w-[100px]">
|
|
||||||
<p className="text-body6 overflow-hidden overflow-ellipsis whitespace-nowrap">
|
|
||||||
{file.name}
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
href={file.src}
|
|
||||||
target='_blank'
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
file.type === 'image' && <img src={file.src} alt={file.name} className="p-4 w-3/4 mx-auto max-h-full object-contain" />
|
|
||||||
}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="w-32 shrink-0 self-start" >
|
|
||||||
<IconButton size="sm" className="hover:bg-gray-500" onClick={onRemove}>
|
|
||||||
<MdClose />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import { FaImage } from "react-icons/fa";
|
|
||||||
import { UnionToObjectKeys } from "src/utils/types/utils";
|
|
||||||
import DropInput from "./DropInput";
|
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
height?: number
|
|
||||||
multiple?: boolean;
|
|
||||||
value?: File[] | string[] | string;
|
|
||||||
max?: number;
|
|
||||||
onBlur?: () => void;
|
|
||||||
onChange?: (files: (File | string)[] | null) => void
|
|
||||||
uploadBtn?: JSX.Element
|
|
||||||
uploadText?: string;
|
|
||||||
allowedType?: 'images';
|
|
||||||
classes?: Partial<{
|
|
||||||
base: string,
|
|
||||||
idle: string,
|
|
||||||
dragging: string,
|
|
||||||
hasFiles: string
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileAccept: UnionToObjectKeys<Props, 'allowedType'> = {
|
|
||||||
images: ".png, .jpg, .jpeg"
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const fileUrlToObject = async (url: string, fileName: string = 'filename') => {
|
|
||||||
const res = await fetch(url);
|
|
||||||
const contentType = res.headers.get('content-type') as string;
|
|
||||||
const blob = await res.blob()
|
|
||||||
const file = new File([blob], fileName, { contentType } as any)
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function FilesInput({
|
|
||||||
height = 200,
|
|
||||||
multiple,
|
|
||||||
value,
|
|
||||||
max = 3,
|
|
||||||
onBlur,
|
|
||||||
onChange,
|
|
||||||
allowedType = 'images',
|
|
||||||
classes,
|
|
||||||
...props
|
|
||||||
}: Props) {
|
|
||||||
|
|
||||||
|
|
||||||
const baseClasses = classes?.base ?? 'p-32 rounded-8 text-center flex flex-col justify-center items-center'
|
|
||||||
const idleClasses = classes?.idle ?? 'bg-primary-50 hover:bg-primary-25 border border-dashed border-primary-500 text-gray-800'
|
|
||||||
const draggingClasses = classes?.dragging ?? 'bg-primary-500 text-white'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropInput
|
|
||||||
height={height}
|
|
||||||
emptyContent={defaultEmptyContent}
|
|
||||||
draggingContent={defaultDraggingContent}
|
|
||||||
hasFilesContent={defaultHasFilesContent}
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
multiple={multiple}
|
|
||||||
allowedType={fileAccept[allowedType]}
|
|
||||||
classes={{
|
|
||||||
base: baseClasses,
|
|
||||||
idle: idleClasses,
|
|
||||||
dragging: draggingClasses
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultEmptyContent = (
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
<FaImage className="scale-150 mr-8 text-gray-400" />{" "}
|
|
||||||
<span className="align-middle">Drop your files here</span>
|
|
||||||
</div>
|
|
||||||
<p className="mt-4">
|
|
||||||
or <button className="hover:underline font-bold">Click to Upload</button>{" "}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const defaultDraggingContent = <p className="font-bold text-body2">Drop your files here ⬇⬇⬇</p>;
|
|
||||||
|
|
||||||
const defaultHasFilesContent = (
|
|
||||||
<p className="font-bolder">Files Uploaded Successfully!!</p>
|
|
||||||
);
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
import { createAction } from "@reduxjs/toolkit";
|
|
||||||
import React, { ChangeEvent, useCallback, useRef } from "react"
|
|
||||||
import { BsUpload } from "react-icons/bs";
|
|
||||||
import { FaImage } from "react-icons/fa";
|
|
||||||
import Button from "src/Components/Button/Button"
|
|
||||||
import { openModal } from "src/redux/features/modals.slice";
|
|
||||||
import { useAppDispatch } from "src/utils/hooks";
|
|
||||||
import { useReduxEffect } from "src/utils/hooks/useReduxEffect";
|
|
||||||
import { UnionToObjectKeys } from "src/utils/types/utils";
|
|
||||||
import FilesThumbnails from "./FilesThumbnails";
|
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
multiple?: boolean;
|
|
||||||
value?: File[] | string[] | string;
|
|
||||||
max?: number;
|
|
||||||
onBlur?: () => void;
|
|
||||||
onChange?: (files: (File | string)[] | null) => void
|
|
||||||
uploadBtn?: JSX.Element
|
|
||||||
uploadText?: string;
|
|
||||||
allowedType?: 'images';
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileAccept: UnionToObjectKeys<Props, 'allowedType'> = {
|
|
||||||
images: ".png, .jpg, .jpeg"
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const fileUrlToObject = async (url: string, fileName: string = 'filename') => {
|
|
||||||
const res = await fetch(url);
|
|
||||||
const contentType = res.headers.get('content-type') as string;
|
|
||||||
const blob = await res.blob()
|
|
||||||
const file = new File([blob], fileName, { contentType } as any)
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
|
|
||||||
const INSERT_IMAGE_ACTION = createAction<{ src: string, alt?: string }>('COVER_IMAGE_INSERTED')({ src: '', alt: "" })
|
|
||||||
|
|
||||||
const FilesInput = React.forwardRef<any, Props>(({
|
|
||||||
multiple,
|
|
||||||
value,
|
|
||||||
max = 3,
|
|
||||||
onBlur,
|
|
||||||
onChange,
|
|
||||||
allowedType = 'images',
|
|
||||||
uploadText = 'Upload files',
|
|
||||||
...props
|
|
||||||
}, ref) => {
|
|
||||||
|
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const handleClick = () => {
|
|
||||||
// ref.current.click();
|
|
||||||
dispatch(openModal({
|
|
||||||
Modal: "InsertImageModal",
|
|
||||||
props: {
|
|
||||||
callbackAction: {
|
|
||||||
type: INSERT_IMAGE_ACTION.type,
|
|
||||||
payload: {
|
|
||||||
src: "",
|
|
||||||
alt: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
const onInsertImgUrl = useCallback(({ payload: { src, alt } }: typeof INSERT_IMAGE_ACTION) => {
|
|
||||||
if (typeof value === 'string')
|
|
||||||
onChange?.([value, src]);
|
|
||||||
else
|
|
||||||
onChange?.([...(value ?? []), src]);
|
|
||||||
}, [onChange, value])
|
|
||||||
|
|
||||||
useReduxEffect(onInsertImgUrl, INSERT_IMAGE_ACTION.type)
|
|
||||||
|
|
||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const files = e.target.files && Array.from(e.target.files).slice(0, max);
|
|
||||||
if (typeof value === 'string')
|
|
||||||
onChange?.([value, ...(files ?? [])]);
|
|
||||||
else
|
|
||||||
onChange?.([...(value ?? []), ...(files ?? [])]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRemove = async (idx: number) => {
|
|
||||||
if (!value) return onChange?.([]);
|
|
||||||
if (typeof value === 'string')
|
|
||||||
onChange?.([]);
|
|
||||||
else {
|
|
||||||
let files = [...value]
|
|
||||||
files.splice(idx, 1);
|
|
||||||
|
|
||||||
//change all files urls to file objects
|
|
||||||
const filesConverted = await Promise.all(files.map(async file => {
|
|
||||||
if (typeof file === 'string') return await fileUrlToObject(file, "")
|
|
||||||
else return file;
|
|
||||||
}))
|
|
||||||
|
|
||||||
onChange?.(filesConverted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const canUploadMore = multiple ?
|
|
||||||
!value || (value && value.length < max)
|
|
||||||
:
|
|
||||||
!value || value.length === 0
|
|
||||||
|
|
||||||
|
|
||||||
const uploadBtn = props.uploadBtn ?
|
|
||||||
React.cloneElement(props.uploadBtn, { onClick: handleClick })
|
|
||||||
:
|
|
||||||
<Button type='button' onClick={handleClick} ><span className="align-middle">{uploadText}</span> <FaImage className="ml-12 scale-125" /></Button>
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<FilesThumbnails files={value} onRemove={handleRemove} />
|
|
||||||
{
|
|
||||||
canUploadMore &&
|
|
||||||
<>
|
|
||||||
{uploadBtn}
|
|
||||||
<input
|
|
||||||
ref={ref}
|
|
||||||
type="file"
|
|
||||||
onBlur={onBlur}
|
|
||||||
style={{ display: 'none' }}
|
|
||||||
multiple={multiple}
|
|
||||||
accept={fileAccept[allowedType]}
|
|
||||||
onChange={handleChange} />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
export default FilesInput;
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { useMemo } from 'react'
|
|
||||||
import FileThumbnail from './FileThumbnail';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
files?: (File | string)[] | string;
|
|
||||||
onRemove?: (idx: number) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function processFiles(files: Props['files']) {
|
|
||||||
|
|
||||||
if (!files) return [];
|
|
||||||
if (typeof files === 'string') return [files];
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function FilesThumbnails({ files, onRemove }: Props) {
|
|
||||||
const filesConverted = useMemo(() => processFiles(files), [files])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex gap-12 mb-12">
|
|
||||||
{
|
|
||||||
filesConverted.map((file, idx) => <FileThumbnail
|
|
||||||
key={idx}
|
|
||||||
file={file}
|
|
||||||
onRemove={() => onRemove?.(idx)} />)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import AvatarInput from './AvatarInput';
|
||||||
|
import { WrapFormController } from 'src/utils/storybook/decorators';
|
||||||
|
import { ImageType } from '../SingleImageUploadInput/SingleImageUploadInput';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Shared/Inputs/Files Inputs/Avatar ',
|
||||||
|
component: AvatarInput,
|
||||||
|
decorators: [
|
||||||
|
WrapFormController<{ avatar: ImageType | null }>({
|
||||||
|
logValues: true,
|
||||||
|
name: "avatar",
|
||||||
|
defaultValues: {
|
||||||
|
avatar: null
|
||||||
|
}
|
||||||
|
})]
|
||||||
|
} as ComponentMeta<typeof AvatarInput>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof AvatarInput> = (args, context) => {
|
||||||
|
|
||||||
|
return <AvatarInput {...context.controller} {...args} />
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
||||||
|
Default.args = {
|
||||||
|
}
|
||||||
105
src/Components/Inputs/FilesInputs/AvatarInput/AvatarInput.tsx
Normal file
105
src/Components/Inputs/FilesInputs/AvatarInput/AvatarInput.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import React, { ComponentProps, useRef } from 'react'
|
||||||
|
import { AiOutlineCloudUpload } from 'react-icons/ai';
|
||||||
|
import { CgArrowsExchangeV } from 'react-icons/cg';
|
||||||
|
import { FiCamera } from 'react-icons/fi';
|
||||||
|
import { IoMdClose } from 'react-icons/io';
|
||||||
|
import { RotatingLines } from 'react-loader-spinner';
|
||||||
|
import { Nullable } from 'remirror';
|
||||||
|
import { useIsDraggingOnElement } from 'src/utils/hooks';
|
||||||
|
import SingleImageUploadInput from '../SingleImageUploadInput/SingleImageUploadInput'
|
||||||
|
|
||||||
|
type Value = ComponentProps<typeof SingleImageUploadInput>['value']
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
width?: number;
|
||||||
|
isRemovable?: boolean
|
||||||
|
value: Value;
|
||||||
|
onChange: (new_value: Nullable<Value>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AvatarInput(props: Props) {
|
||||||
|
|
||||||
|
const dropAreaRef = useRef<HTMLDivElement>(null!)
|
||||||
|
const isDragging = useIsDraggingOnElement({ ref: dropAreaRef });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: props.width ?? 120,
|
||||||
|
}}
|
||||||
|
ref={dropAreaRef}
|
||||||
|
className='aspect-square rounded-full outline outline-2 outline-gray-200 overflow-hidden cursor-pointer '
|
||||||
|
>
|
||||||
|
<SingleImageUploadInput
|
||||||
|
value={props.value}
|
||||||
|
onChange={props.onChange}
|
||||||
|
wrapperClass='rounded-full bg-white h-full'
|
||||||
|
render={({ img, isUploading, isDraggingOnWindow }) =>
|
||||||
|
<div className="w-full h-full rounded-full relative group">
|
||||||
|
{!img &&
|
||||||
|
<div className='w-full h-full rounded-full bg-white hover:bg-gray-100 flex flex-col justify-center items-center'>
|
||||||
|
<p className="text-center text-gray-400 text-body2 mb-8"><FiCamera /></p>
|
||||||
|
<div className={`text-gray-400 text-center text-body5`}>
|
||||||
|
Add Image
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
{img &&
|
||||||
|
<>
|
||||||
|
<img src={img.url} className='w-full h-full object-cover rounded-full' alt="" />
|
||||||
|
{!isUploading &&
|
||||||
|
<div className="flex flex-wrap gap-16 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 ">
|
||||||
|
<button type='button' className='py-8 px-12 rounded-full bg-black bg-opacity-70 opacity-0 group-hover:opacity-100 hover:bg-opacity-90 transition-opacity text-white text-body1'>
|
||||||
|
<CgArrowsExchangeV />
|
||||||
|
</button>
|
||||||
|
{props.isRemovable && <button type='button' className='py-8 px-12 rounded-full bg-black bg-opacity-70 opacity-0 group-hover:opacity-100 hover:bg-opacity-90 transition-opacity text-white text-body1' onClick={(e) => { e.stopPropagation(); props.onChange(null) }}>
|
||||||
|
<IoMdClose />
|
||||||
|
</button>}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</>}
|
||||||
|
{isUploading &&
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-gray-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold transition-transform"
|
||||||
|
>
|
||||||
|
<RotatingLines
|
||||||
|
strokeColor="#fff"
|
||||||
|
strokeWidth="3"
|
||||||
|
animationDuration="0.75"
|
||||||
|
width="48"
|
||||||
|
visible={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{isDraggingOnWindow &&
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
`absolute inset-0 ${isDragging ? 'bg-primary-600' : 'bg-primary-400'} bg-opacity-80 flex flex-col justify-center items-center text-white font-bold transition-transform`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: 0 }}
|
||||||
|
animate={
|
||||||
|
isDragging ? {
|
||||||
|
y: 5,
|
||||||
|
transition: {
|
||||||
|
duration: .4,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: 'mirror',
|
||||||
|
}
|
||||||
|
} : {
|
||||||
|
y: 0
|
||||||
|
}}
|
||||||
|
className='text-center text-body4'
|
||||||
|
>
|
||||||
|
<AiOutlineCloudUpload className="scale-150 text-body2" />
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import { WrapFormController } from 'src/utils/storybook/decorators';
|
||||||
|
import { ImageType } from '../SingleImageUploadInput/SingleImageUploadInput';
|
||||||
|
import CoverImageInput from './CoverImageInput';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Shared/Inputs/Files Inputs/Cover Image ',
|
||||||
|
component: CoverImageInput,
|
||||||
|
decorators: [
|
||||||
|
WrapFormController<{ thumbnail: ImageType | null }>({
|
||||||
|
logValues: true,
|
||||||
|
name: "thumbnail",
|
||||||
|
defaultValues: {
|
||||||
|
thumbnail: null
|
||||||
|
}
|
||||||
|
})]
|
||||||
|
} as ComponentMeta<typeof CoverImageInput>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof CoverImageInput> = (args, context) => {
|
||||||
|
|
||||||
|
return <div className="aspect-[5/2] md:aspect-[4/1] rounded-t-16 overflow-hidden">
|
||||||
|
<CoverImageInput {...context.controller} {...args} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
||||||
|
Default.args = {
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import React, { ComponentProps, useEffect, useRef, useState } from 'react'
|
||||||
|
import { FaImage } from 'react-icons/fa';
|
||||||
|
import { CgArrowsExchangeV } from 'react-icons/cg';
|
||||||
|
import { IoMdClose } from 'react-icons/io';
|
||||||
|
import { RotatingLines } from 'react-loader-spinner';
|
||||||
|
import { Nullable } from 'remirror';
|
||||||
|
import SingleImageUploadInput from '../SingleImageUploadInput/SingleImageUploadInput'
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { AiOutlineCloudUpload } from 'react-icons/ai';
|
||||||
|
import { useIsDraggingOnElement } from 'src/utils/hooks';
|
||||||
|
|
||||||
|
type Value = ComponentProps<typeof SingleImageUploadInput>['value']
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value: Value;
|
||||||
|
rounded?: string;
|
||||||
|
onChange: (new_value: Nullable<Value>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CoverImageInput(props: Props) {
|
||||||
|
|
||||||
|
const dropAreaRef = useRef<HTMLDivElement>(null!)
|
||||||
|
const isDragging = useIsDraggingOnElement({ ref: dropAreaRef });
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='overflow-hidden cursor-pointer w-full h-full'
|
||||||
|
ref={dropAreaRef}
|
||||||
|
>
|
||||||
|
<SingleImageUploadInput
|
||||||
|
value={props.value}
|
||||||
|
onChange={props.onChange}
|
||||||
|
wrapperClass='h-full'
|
||||||
|
render={({ img, isUploading, isDraggingOnWindow }) =>
|
||||||
|
<div className="w-full h-full group relative ">
|
||||||
|
{!img && <div className='w-full h-full flex flex-col justify-center items-center bg-gray-500 outline outline-2 outline-gray-200'>
|
||||||
|
<p className="text-center text-gray-100 text-body1 md:text-h1 mb-8"><FaImage /></p>
|
||||||
|
<div className={`text-gray-100 text-center text-body4`}>
|
||||||
|
Drop a <span className="font-bold">COVER IMAGE</span> here or <br /> <span className="text-blue-300 underline">Click to browse</span>
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
{img && <>
|
||||||
|
<img src={img.url} className={`w-full h-full ${props.rounded ?? 'rounded-12'} object-cover`} alt="" />
|
||||||
|
{!isUploading &&
|
||||||
|
<div className="flex flex-wrap gap-16 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 ">
|
||||||
|
|
||||||
|
<button type='button' className='py-8 px-16 rounded-12 bg-black bg-opacity-70 opacity-0 group-hover:opacity-100 hover:bg-opacity-90 transition-opacity text-white text-h1'>
|
||||||
|
<CgArrowsExchangeV />
|
||||||
|
</button>
|
||||||
|
<button type='button' className='py-8 px-16 rounded-12 bg-black bg-opacity-70 opacity-0 group-hover:opacity-100 hover:bg-opacity-90 transition-opacity text-white text-h1' onClick={(e) => { e.stopPropagation(); props.onChange(null) }}>
|
||||||
|
<IoMdClose />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</>}
|
||||||
|
{isUploading &&
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-gray-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold transition-transform"
|
||||||
|
>
|
||||||
|
<RotatingLines
|
||||||
|
strokeColor="#fff"
|
||||||
|
strokeWidth="3"
|
||||||
|
animationDuration="0.75"
|
||||||
|
width="48"
|
||||||
|
visible={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{isDraggingOnWindow &&
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
`absolute inset-0 ${isDragging ? 'bg-primary-600' : 'bg-primary-400'} bg-opacity-80 flex flex-col justify-center items-center text-white font-bold transition-transform`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: 0 }}
|
||||||
|
animate={
|
||||||
|
isDragging ? {
|
||||||
|
y: 5,
|
||||||
|
transition: {
|
||||||
|
duration: .4,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: 'mirror',
|
||||||
|
}
|
||||||
|
} : {
|
||||||
|
y: 0
|
||||||
|
}}
|
||||||
|
className='text-center text-body1'
|
||||||
|
>
|
||||||
|
<AiOutlineCloudUpload className="scale-150 text-h1 mb-16" />
|
||||||
|
<br />
|
||||||
|
Drop here to upload
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import FileUploadInput from './FileUploadInput';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Shared/Inputs/Files Inputs/Basic',
|
||||||
|
component: FileUploadInput,
|
||||||
|
|
||||||
|
} as ComponentMeta<typeof FileUploadInput>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof FileUploadInput> = (args) => <FileUploadInput {...args} />
|
||||||
|
|
||||||
|
|
||||||
|
export const DefaultButton = Template.bind({});
|
||||||
|
DefaultButton.args = {
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import Uploady, { useUploady, useRequestPreSend, UPLOADER_EVENTS, } from "@rpldy/uploady";
|
||||||
|
import { asUploadButton } from "@rpldy/upload-button";
|
||||||
|
import Button from "src/Components/Button/Button";
|
||||||
|
import { fetchUploadUrl } from "../fetch-upload-img-url";
|
||||||
|
import ImagePreviews from "./ImagePreviews";
|
||||||
|
import { FaImage } from "react-icons/fa";
|
||||||
|
import UploadDropZone from "@rpldy/upload-drop-zone";
|
||||||
|
import { forwardRef, useCallback } from "react";
|
||||||
|
import styles from './styles.module.scss'
|
||||||
|
import { MdFileUpload } from "react-icons/md";
|
||||||
|
import { AiOutlineCloudUpload } from "react-icons/ai";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UploadBtn = asUploadButton((props: any) => {
|
||||||
|
|
||||||
|
useRequestPreSend(async (data) => {
|
||||||
|
|
||||||
|
const filename = data.items?.[0].file.name ?? ''
|
||||||
|
|
||||||
|
const url = await fetchUploadUrl({ filename });
|
||||||
|
return {
|
||||||
|
options: {
|
||||||
|
destination: {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// const handleClick = async () => {
|
||||||
|
// // Make a request to get the url
|
||||||
|
// try {
|
||||||
|
// var bodyFormData = new FormData();
|
||||||
|
// bodyFormData.append('requireSignedURLs', "false");
|
||||||
|
// const res = await axios({
|
||||||
|
// url: 'https://cors-anywhere.herokuapp.com/https://api.cloudflare.com/client/v4/accounts/783da4f06e5fdb9012c0632959a6f5b3/images/v2/direct_upload',
|
||||||
|
// method: 'POST',
|
||||||
|
// data: bodyFormData,
|
||||||
|
// headers: {
|
||||||
|
// "Authorization": "Bearer Xx2-CdsTliYkq6Ayz-1GX4CZubdQVxMwOSDbajP0",
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// uploady.upload(res.data.result.uploadUrl, {
|
||||||
|
// destination: res.data.result.uploadUrl
|
||||||
|
// })
|
||||||
|
// } catch (error) {
|
||||||
|
// console.log(error);
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// // make the request with the files
|
||||||
|
// // uploady.upload()
|
||||||
|
// }
|
||||||
|
|
||||||
|
return <Button {...props} color='primary'>
|
||||||
|
Upload Image <FaImage className="ml-8 scale-125 align-middle" />
|
||||||
|
</Button>
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const DropZone = forwardRef<any, any>((props, ref) => {
|
||||||
|
const { onClick, ...buttonProps } = props;
|
||||||
|
|
||||||
|
useRequestPreSend(async (data) => {
|
||||||
|
|
||||||
|
const filename = data.items?.[0].file.name ?? ''
|
||||||
|
|
||||||
|
const url = await fetchUploadUrl({ filename });
|
||||||
|
return {
|
||||||
|
options: {
|
||||||
|
destination: {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onZoneClick = useCallback(
|
||||||
|
(e: any) => {
|
||||||
|
if (onClick) {
|
||||||
|
onClick(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onClick]
|
||||||
|
);
|
||||||
|
|
||||||
|
return <UploadDropZone
|
||||||
|
{...buttonProps}
|
||||||
|
ref={ref}
|
||||||
|
onDragOverClassName={styles.active}
|
||||||
|
extraProps={{ onClick: onZoneClick }}
|
||||||
|
className={`${styles.zone} border-2 w-full min-h-[200px] max-w-[600px] rounded-16 flex flex-col justify-center items-center text text-body3 border-dashed`}
|
||||||
|
>
|
||||||
|
<div className={`${styles.idle_content} text-gray-600`}>
|
||||||
|
Drop your <span className="font-bold uppercase">IMAGES</span> here or <button className="font-bold text-blue-400 underline">Click to browse</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
y: 5,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: .5,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: 'mirror'
|
||||||
|
}}
|
||||||
|
className={`${styles.active_content} text-white font-bold`}>
|
||||||
|
Drop it to upload <AiOutlineCloudUpload className="scale-150 text-body1 ml-16" />
|
||||||
|
</motion.div>
|
||||||
|
</UploadDropZone>
|
||||||
|
})
|
||||||
|
|
||||||
|
const DropZoneButton = asUploadButton(DropZone);
|
||||||
|
|
||||||
|
|
||||||
|
export default function FileUploadInput(props: Props) {
|
||||||
|
return (
|
||||||
|
<Uploady
|
||||||
|
multiple={true}
|
||||||
|
inputFieldName='file'
|
||||||
|
grouped={false}
|
||||||
|
listeners={{
|
||||||
|
[UPLOADER_EVENTS.ITEM_FINISH]: (item) => {
|
||||||
|
const { id, filename, variants } = item?.uploadResponse?.data?.result ?? {}
|
||||||
|
if (id) {
|
||||||
|
console.log(id, filename, variants);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DropZoneButton />
|
||||||
|
{/* <UploadBtn /> */}
|
||||||
|
<ImagePreviews />
|
||||||
|
</Uploady>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import UploadPreview, { PreviewComponentProps, PreviewMethods } from '@rpldy/upload-preview'
|
||||||
|
import { useAbortItem, useItemAbortListener, useItemCancelListener, useItemErrorListener, useItemProgressListener } from '@rpldy/uploady';
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { RotatingLines } from 'react-loader-spinner';
|
||||||
|
|
||||||
|
export default function ImagePreviews() {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-16 mt-24">
|
||||||
|
<UploadPreview PreviewComponent={CustomImagePreview} rememberPreviousBatches />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CustomImagePreview({ id, url }: PreviewComponentProps) {
|
||||||
|
|
||||||
|
const [progress, setProgress] = useState<number>(0);
|
||||||
|
const [itemState, setItemState] = useState<string>(STATES.PROGRESS);
|
||||||
|
|
||||||
|
|
||||||
|
useItemProgressListener(item => {
|
||||||
|
if (item.completed > progress) {
|
||||||
|
setProgress(() => item.completed);
|
||||||
|
|
||||||
|
if (item.completed === 100) {
|
||||||
|
setItemState(STATES.DONE)
|
||||||
|
} else {
|
||||||
|
setItemState(STATES.PROGRESS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, id);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useItemAbortListener(item => {
|
||||||
|
setItemState(STATES.CANCELLED);
|
||||||
|
}, id);
|
||||||
|
|
||||||
|
|
||||||
|
useItemCancelListener(item => {
|
||||||
|
setItemState(STATES.CANCELLED);
|
||||||
|
}, id);
|
||||||
|
|
||||||
|
useItemErrorListener(item => {
|
||||||
|
setItemState(STATES.ERROR);
|
||||||
|
}, id);
|
||||||
|
|
||||||
|
return <div className="aspect-video relative rounded-12 md:rounded-16 overflow-hidden border-2 border-gray-200">
|
||||||
|
<img src={url}
|
||||||
|
className={`
|
||||||
|
w-full h-full object-cover
|
||||||
|
${itemState === STATES.PROGRESS && 'opacity-50'}
|
||||||
|
`}
|
||||||
|
alt="" />
|
||||||
|
<div className="text-body5 absolute inset-0"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{itemState === STATES.PROGRESS &&
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-gray-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold transition-transform"
|
||||||
|
>
|
||||||
|
<RotatingLines
|
||||||
|
strokeColor="#fff"
|
||||||
|
strokeWidth="3"
|
||||||
|
animationDuration="0.75"
|
||||||
|
width="48"
|
||||||
|
visible={true}
|
||||||
|
/>
|
||||||
|
</div>}
|
||||||
|
{itemState === STATES.ERROR &&
|
||||||
|
<div className="absolute inset-0 bg-red-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold">
|
||||||
|
Failed...
|
||||||
|
</div>}
|
||||||
|
{itemState === STATES.CANCELLED &&
|
||||||
|
<div className="absolute inset-0 bg-red-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold">
|
||||||
|
Cancelled
|
||||||
|
</div>}
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATES = {
|
||||||
|
PROGRESS: "PROGRESS",
|
||||||
|
DONE: "DONE",
|
||||||
|
CANCELLED: "CANCELLED",
|
||||||
|
ERROR: "ERROR"
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATE_COLORS = {
|
||||||
|
[STATES.PROGRESS]: "#f4e4a4",
|
||||||
|
[STATES.DONE]: "#a5f7b3",
|
||||||
|
[STATES.CANCELLED]: "#f7cdcd",
|
||||||
|
[STATES.ERROR]: "#ee4c4c"
|
||||||
|
};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
.zone {
|
||||||
|
background-color: #f2f4f7;
|
||||||
|
border-color: #e4e7ec;
|
||||||
|
|
||||||
|
.active_content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.idle_content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #b3a0ff;
|
||||||
|
border-color: #9e88ff;
|
||||||
|
|
||||||
|
.active_content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.idle_content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import UploadPreview, { PreviewComponentProps, PreviewMethods } from '@rpldy/upload-preview'
|
||||||
|
import { useAbortItem, useItemAbortListener, useItemCancelListener, useItemErrorListener, useItemProgressListener } from '@rpldy/uploady';
|
||||||
|
import { useState } from 'react'
|
||||||
|
import ScreenShotsThumbnail from './ScreenshotThumbnail'
|
||||||
|
|
||||||
|
export default function ImagePreviews() {
|
||||||
|
return (
|
||||||
|
<UploadPreview PreviewComponent={CustomImagePreview} rememberPreviousBatches />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CustomImagePreview({ id, url }: PreviewComponentProps) {
|
||||||
|
|
||||||
|
const [progress, setProgress] = useState<number>(0);
|
||||||
|
const [itemState, setItemState] = useState<string>(STATES.PROGRESS);
|
||||||
|
|
||||||
|
const abortItem = useAbortItem();
|
||||||
|
|
||||||
|
|
||||||
|
useItemProgressListener(item => {
|
||||||
|
if (item.completed > progress) {
|
||||||
|
setProgress(() => item.completed);
|
||||||
|
|
||||||
|
if (item.completed === 100) {
|
||||||
|
setItemState(STATES.DONE)
|
||||||
|
} else {
|
||||||
|
setItemState(STATES.PROGRESS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, id);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useItemAbortListener(item => {
|
||||||
|
setItemState(STATES.CANCELLED);
|
||||||
|
}, id);
|
||||||
|
|
||||||
|
|
||||||
|
useItemCancelListener(item => {
|
||||||
|
setItemState(STATES.CANCELLED);
|
||||||
|
}, id);
|
||||||
|
|
||||||
|
useItemErrorListener(item => {
|
||||||
|
setItemState(STATES.ERROR);
|
||||||
|
}, id);
|
||||||
|
|
||||||
|
if (itemState === STATES.DONE || itemState === STATES.CANCELLED)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return <ScreenShotsThumbnail
|
||||||
|
url={url}
|
||||||
|
isLoading={itemState === STATES.PROGRESS}
|
||||||
|
isError={itemState === STATES.ERROR}
|
||||||
|
onCancel={() => {
|
||||||
|
abortItem(id)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
// return <div className="aspect-video relative rounded-12 md:rounded-16 overflow-hidden border-2 border-gray-200">
|
||||||
|
// <img src={url}
|
||||||
|
// className={`
|
||||||
|
// w-full h-full object-cover
|
||||||
|
// ${itemState === STATES.PROGRESS && 'opacity-50'}
|
||||||
|
// `}
|
||||||
|
// alt="" />
|
||||||
|
// <div className="text-body5 absolute inset-0"
|
||||||
|
// >
|
||||||
|
// </div>
|
||||||
|
// {itemState === STATES.PROGRESS &&
|
||||||
|
// <div
|
||||||
|
// className="absolute inset-0 bg-gray-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold transition-transform"
|
||||||
|
// >
|
||||||
|
// <RotatingLines
|
||||||
|
// strokeColor="#fff"
|
||||||
|
// strokeWidth="3"
|
||||||
|
// animationDuration="0.75"
|
||||||
|
// width="48"
|
||||||
|
// visible={true}
|
||||||
|
// />
|
||||||
|
// </div>}
|
||||||
|
// {itemState === STATES.ERROR &&
|
||||||
|
// <div className="absolute inset-0 bg-red-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold">
|
||||||
|
// Failed...
|
||||||
|
// </div>}
|
||||||
|
// {itemState === STATES.CANCELLED &&
|
||||||
|
// <div className="absolute inset-0 bg-red-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold">
|
||||||
|
// Cancelled
|
||||||
|
// </div>}
|
||||||
|
// </div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATES = {
|
||||||
|
PROGRESS: "PROGRESS",
|
||||||
|
DONE: "DONE",
|
||||||
|
CANCELLED: "CANCELLED",
|
||||||
|
ERROR: "ERROR"
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { FaTimes } from 'react-icons/fa';
|
||||||
|
import { RotatingLines } from 'react-loader-spinner';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
url?: string,
|
||||||
|
isLoading?: boolean;
|
||||||
|
isError?: boolean;
|
||||||
|
onCancel?: () => void;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ScreenshotThumbnail({ url, isLoading, isError, onCancel }: Props) {
|
||||||
|
|
||||||
|
const isEmpty = !url;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`
|
||||||
|
aspect-video relative rounded-16 md:rounded-14 overflow-hidden border-2 border-gray-200
|
||||||
|
${isEmpty && "border-dashed"}
|
||||||
|
`}>
|
||||||
|
{!isEmpty && <img src={url}
|
||||||
|
className={`
|
||||||
|
w-full h-full object-cover
|
||||||
|
${isLoading && 'opacity-50'}
|
||||||
|
`}
|
||||||
|
alt="" />}
|
||||||
|
<div className="text-body5 absolute inset-0"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{isLoading &&
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-gray-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold transition-transform"
|
||||||
|
>
|
||||||
|
<RotatingLines
|
||||||
|
strokeColor="#fff"
|
||||||
|
strokeWidth="3"
|
||||||
|
animationDuration="0.75"
|
||||||
|
width="48"
|
||||||
|
visible={true}
|
||||||
|
/>
|
||||||
|
</div>}
|
||||||
|
{isError &&
|
||||||
|
<div className="absolute inset-0 bg-red-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold">
|
||||||
|
Failed...
|
||||||
|
</div>}
|
||||||
|
{!isEmpty &&
|
||||||
|
<button className="absolute bg-gray-900 hover:bg-opacity-100 bg-opacity-60 text-white rounded-full w-32 h-32 top-8 right-8" onClick={() => onCancel?.()}><FaTimes /></button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import ScreenshotsInput, { ScreenshotType } from './ScreenshotsInput';
|
||||||
|
import { WrapForm, WrapFormController } from 'src/utils/storybook/decorators';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Shared/Inputs/Files Inputs/Screenshots',
|
||||||
|
component: ScreenshotsInput,
|
||||||
|
decorators: [
|
||||||
|
WrapFormController<{ screenshots: Array<ScreenshotType> }>({
|
||||||
|
logValues: true,
|
||||||
|
name: "screenshots",
|
||||||
|
defaultValues: {
|
||||||
|
screenshots: []
|
||||||
|
}
|
||||||
|
})]
|
||||||
|
} as ComponentMeta<typeof ScreenshotsInput>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof ScreenshotsInput> = (args, context) => {
|
||||||
|
|
||||||
|
return <ScreenshotsInput {...context.controller} {...args} />
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const Empty = Template.bind({});
|
||||||
|
Empty.args = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithValues = Template.bind({});
|
||||||
|
WithValues.decorators = [
|
||||||
|
WrapFormController<{ screenshots: Array<ScreenshotType> }>({
|
||||||
|
logValues: true,
|
||||||
|
name: "screenshots",
|
||||||
|
defaultValues: {
|
||||||
|
screenshots: [{
|
||||||
|
id: '123',
|
||||||
|
name: 'tree',
|
||||||
|
url: "https://picsum.photos/id/1021/800/800.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '555',
|
||||||
|
name: 'whatever',
|
||||||
|
url: "https://picsum.photos/id/600/800/800.jpg"
|
||||||
|
},]
|
||||||
|
}
|
||||||
|
}) as any
|
||||||
|
];
|
||||||
|
WithValues.args = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Full = Template.bind({});
|
||||||
|
Full.decorators = [
|
||||||
|
WrapFormController<{ screenshots: Array<ScreenshotType> }>({
|
||||||
|
logValues: true,
|
||||||
|
name: "screenshots",
|
||||||
|
defaultValues: {
|
||||||
|
screenshots: [
|
||||||
|
{
|
||||||
|
id: '123',
|
||||||
|
name: 'tree',
|
||||||
|
url: "https://picsum.photos/id/1021/800/800.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '555',
|
||||||
|
name: 'whatever',
|
||||||
|
url: "https://picsum.photos/id/600/800/800.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '562',
|
||||||
|
name: 'Moon',
|
||||||
|
url: "https://picsum.photos/id/32/800/800.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '342',
|
||||||
|
name: 'Sun',
|
||||||
|
url: "https://picsum.photos/id/523/800/800.jpg"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}) as any
|
||||||
|
];
|
||||||
|
Full.args = {
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import Uploady, { useRequestPreSend, UPLOADER_EVENTS } from "@rpldy/uploady";
|
||||||
|
import { asUploadButton } from "@rpldy/upload-button";
|
||||||
|
// import { fetchUploadUrl } from "./fetch-upload-img-url";
|
||||||
|
import ImagePreviews from "./ImagePreviews";
|
||||||
|
import UploadDropZone from "@rpldy/upload-drop-zone";
|
||||||
|
import { forwardRef, useCallback, useState } from "react";
|
||||||
|
import styles from './styles.module.scss'
|
||||||
|
import { AiOutlineCloudUpload } from "react-icons/ai";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { getMockSenderEnhancer } from "@rpldy/mock-sender";
|
||||||
|
import ScreenshotThumbnail from "./ScreenshotThumbnail";
|
||||||
|
import { FiCamera } from "react-icons/fi";
|
||||||
|
import { Control, Path, useController } from "react-hook-form";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const mockSenderEnhancer = getMockSenderEnhancer({
|
||||||
|
delay: 1500,
|
||||||
|
});
|
||||||
|
|
||||||
|
const MAX_UPLOAD_COUNT = 4 as const;
|
||||||
|
|
||||||
|
export interface ScreenshotType {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value: ScreenshotType[],
|
||||||
|
onChange: (new_value: ScreenshotType[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default function ScreenshotsInput(props: Props) {
|
||||||
|
|
||||||
|
const { value: uploadedFiles, onChange } = props;
|
||||||
|
|
||||||
|
|
||||||
|
const [uploadingCount, setUploadingCount] = useState(0)
|
||||||
|
|
||||||
|
|
||||||
|
const canUploadMore = uploadingCount + uploadedFiles.length < MAX_UPLOAD_COUNT;
|
||||||
|
const placeholdersCount = (MAX_UPLOAD_COUNT - (uploadingCount + uploadedFiles.length + 1));
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Uploady
|
||||||
|
multiple={true}
|
||||||
|
inputFieldName='file'
|
||||||
|
grouped={false}
|
||||||
|
enhancer={mockSenderEnhancer}
|
||||||
|
listeners={{
|
||||||
|
[UPLOADER_EVENTS.BATCH_ADD]: (batch) => {
|
||||||
|
setUploadingCount(v => v + batch.items.length)
|
||||||
|
},
|
||||||
|
[UPLOADER_EVENTS.ITEM_FINALIZE]: () => setUploadingCount(v => v - 1),
|
||||||
|
[UPLOADER_EVENTS.ITEM_FINISH]: (item) => {
|
||||||
|
|
||||||
|
// Just for mocking purposes
|
||||||
|
const dataUrl = URL.createObjectURL(item.file);
|
||||||
|
|
||||||
|
const { id, filename, variants } = item?.uploadResponse?.data?.result ?? {
|
||||||
|
id: Math.random().toString(),
|
||||||
|
filename: item.file.name,
|
||||||
|
variants: [
|
||||||
|
"",
|
||||||
|
dataUrl
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (id) {
|
||||||
|
onChange([...uploadedFiles, { id, name: filename, url: variants[1] }].slice(-MAX_UPLOAD_COUNT))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-16 mt-24">
|
||||||
|
{canUploadMore && <DropZoneButton />}
|
||||||
|
{uploadedFiles.map(f => <ScreenshotThumbnail
|
||||||
|
key={f.id}
|
||||||
|
url={f.url}
|
||||||
|
onCancel={() => {
|
||||||
|
onChange(uploadedFiles.filter(file => file.id !== f.id))
|
||||||
|
}} />)}
|
||||||
|
<ImagePreviews />
|
||||||
|
{(placeholdersCount > 0) &&
|
||||||
|
Array(placeholdersCount).fill(0).map((_, idx) => <ScreenshotThumbnail key={idx} />)}
|
||||||
|
</div>
|
||||||
|
</Uploady>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DropZone = forwardRef<any, any>((props, ref) => {
|
||||||
|
const { onClick, ...buttonProps } = props;
|
||||||
|
|
||||||
|
|
||||||
|
useRequestPreSend(async (data) => {
|
||||||
|
|
||||||
|
const filename = data.items?.[0].file.name ?? ''
|
||||||
|
|
||||||
|
// const url = await fetchUploadUrl({ filename });
|
||||||
|
return {
|
||||||
|
options: {
|
||||||
|
destination: {
|
||||||
|
url: "URL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onZoneClick = useCallback(
|
||||||
|
(e: any) => {
|
||||||
|
if (onClick) {
|
||||||
|
onClick(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onClick]
|
||||||
|
);
|
||||||
|
|
||||||
|
return <UploadDropZone
|
||||||
|
{...buttonProps}
|
||||||
|
ref={ref}
|
||||||
|
onDragOverClassName={styles.active}
|
||||||
|
extraProps={{ onClick: onZoneClick }}
|
||||||
|
className={`${styles.zone} aspect-video relative rounded-16 md:rounded-14 overflow-hidden border-2 border-gray-200 flex flex-col justify-center items-center cursor-pointer border-dashed`}
|
||||||
|
>
|
||||||
|
<div className={styles.idle_content}>
|
||||||
|
<p className="text-center text-gray-400 text-body1 mb-8"><FiCamera /></p>
|
||||||
|
<div className={`text-gray-600 text-center text-body4`}>
|
||||||
|
<span className="text-blue-500 underline">Browse images</span> or <br /> <span className="text-blue-500">drop </span>
|
||||||
|
them here
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
y: 5,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: .5,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: 'mirror'
|
||||||
|
}}
|
||||||
|
className={`${styles.active_content} text-white font-bold text-center`}>
|
||||||
|
Drop to upload <br /> <AiOutlineCloudUpload className="scale-150 text-body1 mt-16" />
|
||||||
|
</motion.div>
|
||||||
|
</UploadDropZone>
|
||||||
|
})
|
||||||
|
|
||||||
|
const DropZoneButton = asUploadButton(DropZone);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
.zone {
|
||||||
|
background-color: #f2f4f7;
|
||||||
|
border-color: #e4e7ec;
|
||||||
|
|
||||||
|
.active_content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.idle_content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #b3a0ff;
|
||||||
|
border-color: #9e88ff;
|
||||||
|
|
||||||
|
.active_content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.idle_content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import UploadPreview, { PreviewComponentProps, PreviewMethods } from '@rpldy/upload-preview'
|
||||||
|
import { useAbortItem, useItemAbortListener, useItemCancelListener, useItemErrorListener, useItemProgressListener } from '@rpldy/uploady';
|
||||||
|
import { useState } from 'react'
|
||||||
|
import ScreenShotsThumbnail from './ScreenshotThumbnail'
|
||||||
|
|
||||||
|
export default function ImagePreviews() {
|
||||||
|
return (
|
||||||
|
<UploadPreview PreviewComponent={CustomImagePreview} rememberPreviousBatches />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CustomImagePreview({ id, url }: PreviewComponentProps) {
|
||||||
|
|
||||||
|
const [progress, setProgress] = useState<number>(0);
|
||||||
|
const [itemState, setItemState] = useState<string>(STATES.PROGRESS);
|
||||||
|
|
||||||
|
const abortItem = useAbortItem();
|
||||||
|
|
||||||
|
|
||||||
|
useItemProgressListener(item => {
|
||||||
|
if (item.completed > progress) {
|
||||||
|
setProgress(() => item.completed);
|
||||||
|
|
||||||
|
if (item.completed === 100) {
|
||||||
|
setItemState(STATES.DONE)
|
||||||
|
} else {
|
||||||
|
setItemState(STATES.PROGRESS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, id);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useItemAbortListener(item => {
|
||||||
|
setItemState(STATES.CANCELLED);
|
||||||
|
}, id);
|
||||||
|
|
||||||
|
|
||||||
|
useItemCancelListener(item => {
|
||||||
|
setItemState(STATES.CANCELLED);
|
||||||
|
}, id);
|
||||||
|
|
||||||
|
useItemErrorListener(item => {
|
||||||
|
setItemState(STATES.ERROR);
|
||||||
|
}, id);
|
||||||
|
|
||||||
|
if (itemState === STATES.DONE || itemState === STATES.CANCELLED)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return <ScreenShotsThumbnail
|
||||||
|
url={url}
|
||||||
|
isLoading={itemState === STATES.PROGRESS}
|
||||||
|
isError={itemState === STATES.ERROR}
|
||||||
|
onCancel={() => {
|
||||||
|
abortItem(id)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
// return <div className="aspect-video relative rounded-12 md:rounded-16 overflow-hidden border-2 border-gray-200">
|
||||||
|
// <img src={url}
|
||||||
|
// className={`
|
||||||
|
// w-full h-full object-cover
|
||||||
|
// ${itemState === STATES.PROGRESS && 'opacity-50'}
|
||||||
|
// `}
|
||||||
|
// alt="" />
|
||||||
|
// <div className="text-body5 absolute inset-0"
|
||||||
|
// >
|
||||||
|
// </div>
|
||||||
|
// {itemState === STATES.PROGRESS &&
|
||||||
|
// <div
|
||||||
|
// className="absolute inset-0 bg-gray-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold transition-transform"
|
||||||
|
// >
|
||||||
|
// <RotatingLines
|
||||||
|
// strokeColor="#fff"
|
||||||
|
// strokeWidth="3"
|
||||||
|
// animationDuration="0.75"
|
||||||
|
// width="48"
|
||||||
|
// visible={true}
|
||||||
|
// />
|
||||||
|
// </div>}
|
||||||
|
// {itemState === STATES.ERROR &&
|
||||||
|
// <div className="absolute inset-0 bg-red-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold">
|
||||||
|
// Failed...
|
||||||
|
// </div>}
|
||||||
|
// {itemState === STATES.CANCELLED &&
|
||||||
|
// <div className="absolute inset-0 bg-red-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold">
|
||||||
|
// Cancelled
|
||||||
|
// </div>}
|
||||||
|
// </div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATES = {
|
||||||
|
PROGRESS: "PROGRESS",
|
||||||
|
DONE: "DONE",
|
||||||
|
CANCELLED: "CANCELLED",
|
||||||
|
ERROR: "ERROR"
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { FaTimes } from 'react-icons/fa';
|
||||||
|
import { RotatingLines } from 'react-loader-spinner';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
url?: string,
|
||||||
|
isLoading?: boolean;
|
||||||
|
isError?: boolean;
|
||||||
|
onCancel?: () => void;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ScreenshotThumbnail({ url, isLoading, isError, onCancel }: Props) {
|
||||||
|
|
||||||
|
const isEmpty = !url;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`
|
||||||
|
aspect-video relative rounded-16 md:rounded-14 overflow-hidden border-2 border-gray-200
|
||||||
|
${isEmpty && "border-dashed"}
|
||||||
|
`}>
|
||||||
|
{!isEmpty && <img src={url}
|
||||||
|
className={`
|
||||||
|
w-full h-full object-cover
|
||||||
|
${isLoading && 'opacity-50'}
|
||||||
|
`}
|
||||||
|
alt="" />}
|
||||||
|
<div className="text-body5 absolute inset-0"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{isLoading &&
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-gray-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold transition-transform"
|
||||||
|
>
|
||||||
|
<RotatingLines
|
||||||
|
strokeColor="#fff"
|
||||||
|
strokeWidth="3"
|
||||||
|
animationDuration="0.75"
|
||||||
|
width="48"
|
||||||
|
visible={true}
|
||||||
|
/>
|
||||||
|
</div>}
|
||||||
|
{isError &&
|
||||||
|
<div className="absolute inset-0 bg-red-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold">
|
||||||
|
Failed...
|
||||||
|
</div>}
|
||||||
|
{!isEmpty &&
|
||||||
|
<button className="absolute bg-gray-900 hover:bg-opacity-100 bg-opacity-60 text-white rounded-full w-32 h-32 top-8 right-8" onClick={() => onCancel?.()}><FaTimes /></button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import SingleImageUploadInput, { ImageType } from './SingleImageUploadInput';
|
||||||
|
import { WrapFormController } from 'src/utils/storybook/decorators';
|
||||||
|
import { RotatingLines } from 'react-loader-spinner';
|
||||||
|
import { FiCamera, } from 'react-icons/fi';
|
||||||
|
import { FaExchangeAlt, FaImage } from 'react-icons/fa';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Shared/Inputs/Files Inputs/Single Image Upload ',
|
||||||
|
component: SingleImageUploadInput,
|
||||||
|
decorators: [
|
||||||
|
WrapFormController<{ avatar: ImageType | null }>({
|
||||||
|
logValues: true,
|
||||||
|
name: "avatar",
|
||||||
|
defaultValues: {
|
||||||
|
avatar: null
|
||||||
|
}
|
||||||
|
})]
|
||||||
|
} as ComponentMeta<typeof SingleImageUploadInput>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof SingleImageUploadInput> = (args, context) => {
|
||||||
|
|
||||||
|
return <SingleImageUploadInput {...context.controller} {...args} />
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import Uploady, { useRequestPreSend, UPLOADER_EVENTS, useAbortAll } from "@rpldy/uploady";
|
||||||
|
import { asUploadButton } from "@rpldy/upload-button";
|
||||||
|
// import { fetchUploadUrl } from "./fetch-upload-img-url";
|
||||||
|
import UploadDropZone from "@rpldy/upload-drop-zone";
|
||||||
|
import { forwardRef, ReactElement, useCallback, useState } from "react";
|
||||||
|
import styles from './styles.module.scss'
|
||||||
|
import { getMockSenderEnhancer } from "@rpldy/mock-sender";
|
||||||
|
import { NotificationsService } from "src/services";
|
||||||
|
import { useIsDraggingOnElement } from 'src/utils/hooks';
|
||||||
|
import { fetchUploadImageUrl } from "src/api/uploading";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const mockSenderEnhancer = getMockSenderEnhancer({
|
||||||
|
delay: 1500,
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export interface ImageType {
|
||||||
|
id?: string | null,
|
||||||
|
name?: string | null,
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RenderPropArgs = {
|
||||||
|
isUploading?: boolean;
|
||||||
|
img: ImageType | null,
|
||||||
|
onAbort: () => void,
|
||||||
|
isDraggingOnWindow?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value: ImageType | null | undefined,
|
||||||
|
onChange: (new_value: ImageType | null) => void;
|
||||||
|
wrapperClass?: string;
|
||||||
|
render: (args: RenderPropArgs) => ReactElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default function SingleImageUploadInput(props: Props) {
|
||||||
|
|
||||||
|
const { value, onChange, render } = props;
|
||||||
|
|
||||||
|
|
||||||
|
const [currentlyUploadingItem, setCurrentlyUploadingItem] = useState<ImageType | null>(null)
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Uploady
|
||||||
|
accept="image/*"
|
||||||
|
inputFieldName='file'
|
||||||
|
grouped={false}
|
||||||
|
listeners={{
|
||||||
|
[UPLOADER_EVENTS.ITEM_START]: (item) => {
|
||||||
|
onChange(null)
|
||||||
|
|
||||||
|
setCurrentlyUploadingItem({
|
||||||
|
id: item.id,
|
||||||
|
url: URL.createObjectURL(item.file),
|
||||||
|
name: item.file.name,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[UPLOADER_EVENTS.ITEM_ERROR]: (item) => {
|
||||||
|
NotificationsService.error("An error happened while uploading. Please try again.")
|
||||||
|
},
|
||||||
|
[UPLOADER_EVENTS.ITEM_FINALIZE]: () => setCurrentlyUploadingItem(null),
|
||||||
|
[UPLOADER_EVENTS.ITEM_FINISH]: (item) => {
|
||||||
|
|
||||||
|
const { id, filename, variants } = item?.uploadResponse?.data?.result;
|
||||||
|
const url = (variants as string[]).find(v => v.includes('public'));
|
||||||
|
|
||||||
|
if (id && url) {
|
||||||
|
onChange({ id, name: filename, url, })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DropZoneButton
|
||||||
|
extraProps={{
|
||||||
|
renderProps: {
|
||||||
|
isUploading: !!currentlyUploadingItem,
|
||||||
|
img: currentlyUploadingItem ?? value ?? null,
|
||||||
|
render,
|
||||||
|
wrapperClass: props.wrapperClass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Uploady>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const DropZone = forwardRef<any, any>((props, ref) => {
|
||||||
|
const { onClick, children, renderProps, ...buttonProps } = props;
|
||||||
|
|
||||||
|
const isDraggingOnWindow = useIsDraggingOnElement()
|
||||||
|
|
||||||
|
useRequestPreSend(async (data) => {
|
||||||
|
|
||||||
|
const filename = data.items?.[0].file.name ?? ''
|
||||||
|
|
||||||
|
const res = await fetchUploadImageUrl({ filename });
|
||||||
|
|
||||||
|
return {
|
||||||
|
options: {
|
||||||
|
destination: {
|
||||||
|
url: res.uploadURL
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onZoneClick = useCallback(
|
||||||
|
(e: any) => {
|
||||||
|
if (onClick) {
|
||||||
|
onClick(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onClick]
|
||||||
|
);
|
||||||
|
|
||||||
|
return <UploadDropZone
|
||||||
|
{...buttonProps}
|
||||||
|
ref={ref}
|
||||||
|
type='button'
|
||||||
|
onDragOverClassName={'drag-active'}
|
||||||
|
extraProps={{ onClick: onZoneClick }}
|
||||||
|
className={renderProps.wrapperClass}
|
||||||
|
>
|
||||||
|
|
||||||
|
{renderProps.render({
|
||||||
|
img: renderProps.img,
|
||||||
|
isUploading: renderProps.isUploading,
|
||||||
|
isDraggingOnWindow,
|
||||||
|
})}
|
||||||
|
</UploadDropZone>
|
||||||
|
})
|
||||||
|
|
||||||
|
const DropZoneButton = asUploadButton(DropZone);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
.zone {
|
||||||
|
background-color: #f2f4f7;
|
||||||
|
border-color: #e4e7ec;
|
||||||
|
|
||||||
|
.active_content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.idle_content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #b3a0ff;
|
||||||
|
border-color: #9e88ff;
|
||||||
|
|
||||||
|
.active_content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.idle_content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||||
|
import ThumbnailInput from './ThumbnailInput';
|
||||||
|
import { WrapFormController } from 'src/utils/storybook/decorators';
|
||||||
|
import { ImageType } from '../SingleImageUploadInput/SingleImageUploadInput';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Shared/Inputs/Files Inputs/Thumbnail ',
|
||||||
|
component: ThumbnailInput,
|
||||||
|
decorators: [
|
||||||
|
WrapFormController<{ thumbnail: ImageType | null }>({
|
||||||
|
logValues: true,
|
||||||
|
name: "thumbnail",
|
||||||
|
defaultValues: {
|
||||||
|
thumbnail: null
|
||||||
|
}
|
||||||
|
})]
|
||||||
|
} as ComponentMeta<typeof ThumbnailInput>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof ThumbnailInput> = (args, context) => {
|
||||||
|
|
||||||
|
return <ThumbnailInput {...context.controller} {...args} />
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
||||||
|
Default.args = {
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import React, { ComponentProps } from 'react'
|
||||||
|
import { FiCamera } from 'react-icons/fi';
|
||||||
|
import { RotatingLines } from 'react-loader-spinner';
|
||||||
|
import { Nullable } from 'remirror';
|
||||||
|
import SingleImageUploadInput from '../SingleImageUploadInput/SingleImageUploadInput'
|
||||||
|
|
||||||
|
type Value = ComponentProps<typeof SingleImageUploadInput>['value']
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
width?: number
|
||||||
|
value: Value;
|
||||||
|
onChange: (new_value: Nullable<Value>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ThumbnailInput(props: Props) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: props.width ?? 120,
|
||||||
|
}}
|
||||||
|
className='aspect-square rounded-16 outline outline-2 outline-gray-200 overflow-hidden cursor-pointer bg-white hover:bg-gray-100'
|
||||||
|
>
|
||||||
|
<SingleImageUploadInput
|
||||||
|
value={props.value}
|
||||||
|
onChange={props.onChange}
|
||||||
|
wrapperClass='h-full'
|
||||||
|
render={({ img, isUploading }) => <div className="w-full h-full relative flex flex-col justify-center items-center">
|
||||||
|
{img && <img src={img.url} className='w-full h-full object-cover rounded-16' alt="" />}
|
||||||
|
{!img &&
|
||||||
|
<>
|
||||||
|
<p className="text-center text-gray-400 text-body2 mb-8"><FiCamera /></p>
|
||||||
|
<div className={`text-gray-400 text-center text-body5`}>
|
||||||
|
Add Image
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
{isUploading &&
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-gray-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold transition-transform"
|
||||||
|
>
|
||||||
|
<RotatingLines
|
||||||
|
strokeColor="#fff"
|
||||||
|
strokeWidth="3"
|
||||||
|
animationDuration="0.75"
|
||||||
|
width="48"
|
||||||
|
visible={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
25
src/Components/Inputs/FilesInputs/fetch-upload-img-url.tsx
Normal file
25
src/Components/Inputs/FilesInputs/fetch-upload-img-url.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { NotificationsService } from "src/services";
|
||||||
|
|
||||||
|
export async function fetchUploadUrl(options?: Partial<{ filename: string }>) {
|
||||||
|
|
||||||
|
const { filename } = options ?? {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const bodyFormData = new FormData();
|
||||||
|
bodyFormData.append('requireSignedURLs', "false");
|
||||||
|
const res = await axios({
|
||||||
|
url: 'https://cors-anywhere.herokuapp.com/https://api.cloudflare.com/client/v4/accounts/783da4f06e5fdb9012c0632959a6f5b3/images/v2/direct_upload',
|
||||||
|
method: 'POST',
|
||||||
|
data: bodyFormData,
|
||||||
|
headers: {
|
||||||
|
"Authorization": "Bearer XXX",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return res.data.result.uploadURL as string;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
NotificationsService.error("A network error happened.")
|
||||||
|
return "couldnt fetch upload url";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,9 @@ interface Option {
|
|||||||
readonly description: string | null
|
readonly description: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type Tag = Omit<OfficialTagsQuery['officialTags'][number], 'id'>
|
type Tag = Omit<OfficialTagsQuery['officialTags'][number], 'id'>
|
||||||
|
type Value = { title: Tag['title'] }
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
classes?: {
|
classes?: {
|
||||||
@@ -22,11 +24,80 @@ interface Props {
|
|||||||
}
|
}
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
max?: number;
|
max?: number;
|
||||||
|
value: Value[];
|
||||||
|
onChange?: (new_value: Value[]) => void;
|
||||||
|
onBlur?: () => void;
|
||||||
[k: string]: any
|
[k: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function TagsInput({
|
||||||
|
classes,
|
||||||
|
placeholder = 'Write some tags',
|
||||||
|
max = 5,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
onBlur,
|
||||||
|
...props }: Props) {
|
||||||
|
|
||||||
|
const officalTags = useOfficialTagsQuery();
|
||||||
|
|
||||||
|
|
||||||
|
const handleChange = (newValue: OnChangeValue<Option, true>,) => {
|
||||||
|
onChange?.([...newValue.map(transformer.optionToTag)]);
|
||||||
|
onBlur?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const maxReached = value.length >= max;
|
||||||
|
|
||||||
|
const currentPlaceholder = maxReached ? '' : value.length > 0 ? "Add Another..." : placeholder;
|
||||||
|
|
||||||
|
const tagsOptions = !maxReached ? (officalTags.data?.officialTags ?? []).filter(t => !value.some((v) => v.title === t.title)).map(transformer.tagToOption) : [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${classes?.container}`}>
|
||||||
|
<Select
|
||||||
|
isLoading={officalTags.loading}
|
||||||
|
options={tagsOptions}
|
||||||
|
isMulti
|
||||||
|
isOptionDisabled={() => maxReached}
|
||||||
|
placeholder={currentPlaceholder}
|
||||||
|
noOptionsMessage={() => {
|
||||||
|
return maxReached
|
||||||
|
? "You've reached the max number of tags."
|
||||||
|
: "No tags available";
|
||||||
|
}}
|
||||||
|
closeMenuOnSelect={false}
|
||||||
|
value={value.map(transformer.valueToOption)}
|
||||||
|
onChange={handleChange as any}
|
||||||
|
onBlur={onBlur}
|
||||||
|
components={{
|
||||||
|
Option: OptionComponent,
|
||||||
|
// ValueContainer: CustomValueContainer
|
||||||
|
}}
|
||||||
|
styles={colourStyles as any}
|
||||||
|
theme={(theme) => ({
|
||||||
|
...theme,
|
||||||
|
borderRadius: 8,
|
||||||
|
colors: {
|
||||||
|
...theme.colors,
|
||||||
|
primary: 'var(--primary)',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{/* <div className="flex mt-16 gap-8 flex-wrap">
|
||||||
|
{(value as Tag[]).map((tag, idx) => <Badge color="gray" key={tag.title} onRemove={() => handleRemove(idx)} >{tag.title}</Badge>)}
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const transformer = {
|
const transformer = {
|
||||||
|
valueToOption: (tag: Value): Option => ({ label: tag.title, value: tag.title, icon: null, description: null }),
|
||||||
tagToOption: (tag: Tag): Option => ({ label: tag.title, value: tag.title, icon: tag.icon, description: tag.description }),
|
tagToOption: (tag: Tag): Option => ({ label: tag.title, value: tag.title, icon: tag.icon, description: tag.description }),
|
||||||
optionToTag: (o: Option): Tag => ({ title: o.value, icon: o.icon, description: o.description, })
|
optionToTag: (o: Option): Tag => ({ title: o.value, icon: o.icon, description: o.description, })
|
||||||
}
|
}
|
||||||
@@ -107,75 +178,3 @@ const colourStyles: StylesConfig = {
|
|||||||
paddingRight: 0,
|
paddingRight: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default function TagsInput({
|
|
||||||
classes,
|
|
||||||
placeholder = 'Write some tags',
|
|
||||||
max = 5,
|
|
||||||
...props }: Props) {
|
|
||||||
|
|
||||||
const officalTags = useOfficialTagsQuery();
|
|
||||||
|
|
||||||
const { field: { value, onChange, onBlur } } = useController({
|
|
||||||
name: props.name ?? "tags",
|
|
||||||
control: props.control,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
const handleChange = (newValue: OnChangeValue<Option, true>,) => {
|
|
||||||
onChange([...newValue.map(transformer.optionToTag)]);
|
|
||||||
onBlur();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const handleRemove = (idx: number) => {
|
|
||||||
onChange((value as Tag[]).filter((_, i) => idx !== i))
|
|
||||||
onBlur();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const maxReached = value.length >= max;
|
|
||||||
|
|
||||||
const currentPlaceholder = maxReached ? '' : value.length > 0 ? "Add Another..." : placeholder;
|
|
||||||
|
|
||||||
const tagsOptions = !maxReached ? (officalTags.data?.officialTags ?? []).filter(t => !value.some((v: Tag) => v.title === t.title)).map(transformer.tagToOption) : [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`${classes?.container}`}>
|
|
||||||
<Select
|
|
||||||
isLoading={officalTags.loading}
|
|
||||||
options={tagsOptions}
|
|
||||||
isMulti
|
|
||||||
isOptionDisabled={() => maxReached}
|
|
||||||
placeholder={currentPlaceholder}
|
|
||||||
noOptionsMessage={() => {
|
|
||||||
return maxReached
|
|
||||||
? "You've reached the max number of tags."
|
|
||||||
: "No tags available";
|
|
||||||
}}
|
|
||||||
closeMenuOnSelect={false}
|
|
||||||
value={value.map(transformer.tagToOption)}
|
|
||||||
onChange={handleChange as any}
|
|
||||||
onBlur={onBlur}
|
|
||||||
components={{
|
|
||||||
Option: OptionComponent,
|
|
||||||
// ValueContainer: CustomValueContainer
|
|
||||||
}}
|
|
||||||
styles={colourStyles as any}
|
|
||||||
theme={(theme) => ({
|
|
||||||
...theme,
|
|
||||||
borderRadius: 8,
|
|
||||||
colors: {
|
|
||||||
...theme.colors,
|
|
||||||
primary: 'var(--primary)',
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
{/* <div className="flex mt-16 gap-8 flex-wrap">
|
|
||||||
{(value as Tag[]).map((tag, idx) => <Badge color="gray" key={tag.title} onRemove={() => handleRemove(idx)} >{tag.title}</Badge>)}
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
import React, { FormEvent, useState } from 'react'
|
|
||||||
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
|
|
||||||
import { motion } from 'framer-motion'
|
|
||||||
import { IoClose } from 'react-icons/io5'
|
|
||||||
import Button from 'src/Components/Button/Button'
|
|
||||||
import { useAppDispatch } from 'src/utils/hooks'
|
|
||||||
import { PayloadAction } from '@reduxjs/toolkit'
|
|
||||||
|
|
||||||
interface Props extends ModalCard {
|
|
||||||
callbackAction: PayloadAction<{ src: string, alt?: string }>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function InsertImageModal({ onClose, direction, callbackAction, ...props }: Props) {
|
|
||||||
|
|
||||||
const [urlInput, setUrlInput] = useState("")
|
|
||||||
const [altInput, setAltInput] = useState("")
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const handleSubmit = (e: FormEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
if (urlInput.length > 10) {
|
|
||||||
// onInsert({ src: urlInput, alt: altInput })
|
|
||||||
const action = Object.assign({}, callbackAction);
|
|
||||||
action.payload = { src: urlInput, alt: altInput }
|
|
||||||
dispatch(action)
|
|
||||||
onClose?.();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
custom={direction}
|
|
||||||
variants={modalCardVariants}
|
|
||||||
initial='initial'
|
|
||||||
animate="animate"
|
|
||||||
exit='exit'
|
|
||||||
className="modal-card max-w-[660px] p-24 rounded-xl relative"
|
|
||||||
>
|
|
||||||
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
|
|
||||||
<h2 className='text-h5 font-bold'>Add Image</h2>
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div className="grid md:grid-cols-3 gap-16 mt-32">
|
|
||||||
<div className='md:col-span-2'>
|
|
||||||
<p className="text-body5">
|
|
||||||
Image URL
|
|
||||||
</p>
|
|
||||||
<div className="input-wrapper mt-8 relative">
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className="input-text"
|
|
||||||
value={urlInput}
|
|
||||||
onChange={e => setUrlInput(e.target.value)}
|
|
||||||
placeholder='https://images.com/my-image'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-body5">
|
|
||||||
Alt Text
|
|
||||||
</p>
|
|
||||||
<div className="input-wrapper mt-8 relative">
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
className="input-text"
|
|
||||||
value={altInput}
|
|
||||||
onChange={e => setAltInput(e.target.value)}
|
|
||||||
placeholder=''
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-32 w-1/2 mx-auto aspect-video bg-gray-200 rounded-10">
|
|
||||||
{urlInput && <img
|
|
||||||
src={urlInput}
|
|
||||||
className='w-full h-full object-cover rounded-10'
|
|
||||||
alt={altInput}
|
|
||||||
/>}
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-16 justify-end mt-32">
|
|
||||||
<Button onClick={onClose}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button type='submit' color='primary' >
|
|
||||||
Add
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</motion.div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@ import InsertImageModal from './InsertImageModal';
|
|||||||
import { ModalsDecorator } from 'src/utils/storybook/decorators';
|
import { ModalsDecorator } from 'src/utils/storybook/decorators';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Shared/Inputs/Text Editor/Insert Image Modal',
|
title: 'Shared/Inputs/Files Inputs/Image Modal',
|
||||||
component: InsertImageModal,
|
component: InsertImageModal,
|
||||||
|
|
||||||
decorators: [ModalsDecorator]
|
decorators: [ModalsDecorator]
|
||||||
@@ -14,4 +14,13 @@ export default {
|
|||||||
const Template: ComponentStory<typeof InsertImageModal> = (args) => <InsertImageModal {...args} />;
|
const Template: ComponentStory<typeof InsertImageModal> = (args) => <InsertImageModal {...args} />;
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
export const Default = Template.bind({});
|
||||||
|
Default.args = {
|
||||||
|
callbackAction: {
|
||||||
|
type: "INSERT_IMAGE_IN_STORY",
|
||||||
|
payload: {
|
||||||
|
src: "",
|
||||||
|
alt: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
176
src/Components/Modals/InsertImageModal/InsertImageModal.tsx
Normal file
176
src/Components/Modals/InsertImageModal/InsertImageModal.tsx
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import React, { FormEvent, useRef, useState } from 'react'
|
||||||
|
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { IoClose } from 'react-icons/io5'
|
||||||
|
import Button from 'src/Components/Button/Button'
|
||||||
|
import { useAppDispatch, useIsDraggingOnElement } from 'src/utils/hooks'
|
||||||
|
import { PayloadAction } from '@reduxjs/toolkit'
|
||||||
|
import { RotatingLines } from 'react-loader-spinner'
|
||||||
|
import { FaExchangeAlt, FaImage } from 'react-icons/fa'
|
||||||
|
import SingleImageUploadInput, { ImageType } from 'src/Components/Inputs/FilesInputs/SingleImageUploadInput/SingleImageUploadInput'
|
||||||
|
import { AiOutlineCloudUpload } from 'react-icons/ai'
|
||||||
|
|
||||||
|
interface Props extends ModalCard {
|
||||||
|
callbackAction: PayloadAction<{ src: string, alt?: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function InsertImageModal({ onClose, direction, callbackAction, ...props }: Props) {
|
||||||
|
|
||||||
|
const [uploadedImage, setUploadedImage] = useState<ImageType | null>(null)
|
||||||
|
const [altInput, setAltInput] = useState("")
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const dropAreaRef = useRef<HTMLDivElement>(null!)
|
||||||
|
const isDragging = useIsDraggingOnElement({ ref: dropAreaRef });
|
||||||
|
|
||||||
|
const handleSubmit = (e: FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
console.log(uploadedImage?.url);
|
||||||
|
|
||||||
|
if (uploadedImage?.url) {
|
||||||
|
// onInsert({ src: urlInput, alt: altInput })
|
||||||
|
const action = Object.assign({}, callbackAction);
|
||||||
|
action.payload = { src: uploadedImage.url, alt: altInput }
|
||||||
|
dispatch(action)
|
||||||
|
onClose?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
custom={direction}
|
||||||
|
variants={modalCardVariants}
|
||||||
|
initial='initial'
|
||||||
|
animate="animate"
|
||||||
|
exit='exit'
|
||||||
|
className="modal-card max-w-[660px] p-24 rounded-xl relative"
|
||||||
|
>
|
||||||
|
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
|
||||||
|
<h2 className='text-h5 font-bold'>Add Image</h2>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{/* <div className="grid md:grid-cols-3 gap-16 mt-32">
|
||||||
|
<div className='md:col-span-2'>
|
||||||
|
<p className="text-body5">
|
||||||
|
Image URL
|
||||||
|
</p>
|
||||||
|
<div className="input-wrapper mt-8 relative">
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
className="input-text"
|
||||||
|
value={urlInput}
|
||||||
|
onChange={e => setUrlInput(e.target.value)}
|
||||||
|
placeholder='https://images.com/my-image'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-body5">
|
||||||
|
Alt Text
|
||||||
|
</p>
|
||||||
|
<div className="input-wrapper mt-8 relative">
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
className="input-text"
|
||||||
|
value={altInput}
|
||||||
|
onChange={e => setAltInput(e.target.value)}
|
||||||
|
placeholder=''
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-32 w-1/2 mx-auto aspect-video bg-gray-200 rounded-10">
|
||||||
|
{urlInput && <img
|
||||||
|
src={urlInput}
|
||||||
|
className='w-full h-full object-cover rounded-10'
|
||||||
|
alt={altInput}
|
||||||
|
/>}
|
||||||
|
</div> */}
|
||||||
|
<SingleImageUploadInput
|
||||||
|
value={uploadedImage}
|
||||||
|
onChange={setUploadedImage}
|
||||||
|
wrapperClass='h-full mt-32'
|
||||||
|
render={({ img, isUploading, isDraggingOnWindow }) => <div ref={dropAreaRef} className="w-full group aspect-video bg-gray-100 cursor-pointer rounded-16 border-2 border-gray200 overflow-hidden relative flex flex-col justify-center items-center">
|
||||||
|
{img && <>
|
||||||
|
<img src={img.url} className='w-full h-full object-cover rounded-16' alt="" />
|
||||||
|
{!isUploading &&
|
||||||
|
<button type='button' className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 py-16 px-24 rounded-12 bg-black bg-opacity-70 opacity-0 group-hover:opacity-100 hover:bg-opacity-90 transition-opacity text-white text-h3'>
|
||||||
|
<FaExchangeAlt />
|
||||||
|
</button>}
|
||||||
|
</>}
|
||||||
|
{!img &&
|
||||||
|
<>
|
||||||
|
<p className="text-center text-gray-700 text-body1 md:text-h1 mb-8"><FaImage /></p>
|
||||||
|
<div className={`text-gray-600 text-center text-body4`}>
|
||||||
|
Drop an <span className="font-bold">IMAGE</span> here or <br /> <span className="text-blue-500 underline">Click to browse</span>
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
{isUploading &&
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-gray-400 bg-opacity-60 flex flex-col justify-center items-center text-white font-bold transition-transform"
|
||||||
|
>
|
||||||
|
<RotatingLines
|
||||||
|
strokeColor="#fff"
|
||||||
|
strokeWidth="3"
|
||||||
|
animationDuration="0.75"
|
||||||
|
width="48"
|
||||||
|
visible={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{isDraggingOnWindow &&
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
`absolute inset-0 ${isDragging ? 'bg-primary-600' : 'bg-primary-400'} bg-opacity-80 flex flex-col justify-center items-center text-white font-bold transition-transform`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: 0 }}
|
||||||
|
animate={
|
||||||
|
isDragging ? {
|
||||||
|
y: 5,
|
||||||
|
transition: {
|
||||||
|
duration: .4,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: 'mirror',
|
||||||
|
}
|
||||||
|
} : {
|
||||||
|
y: 0
|
||||||
|
}}
|
||||||
|
className='text-center text-body1'
|
||||||
|
>
|
||||||
|
<AiOutlineCloudUpload className="scale-150 text-h1 mb-16" />
|
||||||
|
<br />
|
||||||
|
Drop here to upload
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>}
|
||||||
|
/>
|
||||||
|
<div className='mt-24'>
|
||||||
|
<p className="text-body5">
|
||||||
|
Alternative Text
|
||||||
|
</p>
|
||||||
|
<div className="input-wrapper mt-8 relative">
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
className="input-text"
|
||||||
|
value={altInput}
|
||||||
|
onChange={e => setAltInput(e.target.value)}
|
||||||
|
placeholder='A description for the content of this image'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-16 justify-end mt-32">
|
||||||
|
<Button onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button type='submit' color='primary' >
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</motion.div>
|
||||||
|
)
|
||||||
|
}
|
||||||
21
src/api/auth.ts
Normal file
21
src/api/auth.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
import { CONSTS } from "src/utils";
|
||||||
|
|
||||||
|
export async function fetchLnurlAuth() {
|
||||||
|
const res = await fetch(CONSTS.apiEndpoint + '/get-login-url', {
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchIsLoggedIn(session_token: string) {
|
||||||
|
const res = await fetch(CONSTS.apiEndpoint + '/is-logged-in', {
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
session_token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
return data.logged_in;
|
||||||
|
}
|
||||||
12
src/api/uploading.ts
Normal file
12
src/api/uploading.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
import axios from "axios";
|
||||||
|
import { CONSTS } from "src/utils";
|
||||||
|
|
||||||
|
export async function fetchUploadImageUrl({ filename }: { filename: string }) {
|
||||||
|
const res = await axios.post(CONSTS.apiEndpoint + '/upload-image-url', {
|
||||||
|
filename
|
||||||
|
}, {
|
||||||
|
withCredentials: true
|
||||||
|
})
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import Button from "src/Components/Button/Button";
|
|||||||
import { FiCopy } from "react-icons/fi";
|
import { FiCopy } from "react-icons/fi";
|
||||||
import useCopyToClipboard from "src/utils/hooks/useCopyToClipboard";
|
import useCopyToClipboard from "src/utils/hooks/useCopyToClipboard";
|
||||||
import { getPropertyFromUnknown, trimText, } from "src/utils/helperFunctions";
|
import { getPropertyFromUnknown, trimText, } from "src/utils/helperFunctions";
|
||||||
|
import { fetchIsLoggedIn, fetchLnurlAuth } from "src/api/auth";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
|||||||
import { Controller, FormProvider, NestedValue, Resolver, SubmitHandler, useForm } from "react-hook-form";
|
import { Controller, FormProvider, NestedValue, Resolver, SubmitHandler, useForm } from "react-hook-form";
|
||||||
import Button from "src/Components/Button/Button";
|
import Button from "src/Components/Button/Button";
|
||||||
import DatePicker from "src/Components/Inputs/DatePicker/DatePicker";
|
import DatePicker from "src/Components/Inputs/DatePicker/DatePicker";
|
||||||
import FilesInput from "src/Components/Inputs/FilesInput/FilesInput";
|
|
||||||
import TagsInput from "src/Components/Inputs/TagsInput/TagsInput";
|
import TagsInput from "src/Components/Inputs/TagsInput/TagsInput";
|
||||||
|
import { Tag } from "src/graphql";
|
||||||
|
import { imageSchema } from "src/utils/validation";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import ContentEditor from "../ContentEditor/ContentEditor";
|
import ContentEditor from "../ContentEditor/ContentEditor";
|
||||||
|
|
||||||
@@ -31,29 +32,14 @@ const schema = yup.object({
|
|||||||
.string()
|
.string()
|
||||||
.required()
|
.required()
|
||||||
.min(50, 'you have to write at least 10 words'),
|
.min(50, 'you have to write at least 10 words'),
|
||||||
cover_image: yup
|
cover_image: imageSchema,
|
||||||
.lazy((value: string | File[]) => {
|
|
||||||
switch (typeof value) {
|
|
||||||
case 'object':
|
|
||||||
return yup
|
|
||||||
.array()
|
|
||||||
.test("fileSize", "File Size is too large", (files) => (files as File[]).every(file => file.size <= 5242880))
|
|
||||||
.test("fileType", "Unsupported File Format, only png/jpg/jpeg images are allowed",
|
|
||||||
(files) => (files as File[]).every((file: File) =>
|
|
||||||
["image/jpeg", "image/png", "image/jpg"].includes(file.type)))
|
|
||||||
case 'string':
|
|
||||||
return yup.string().url();
|
|
||||||
default:
|
|
||||||
return yup.mixed()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}).required();
|
}).required();
|
||||||
|
|
||||||
interface IFormInputs {
|
interface IFormInputs {
|
||||||
title: string
|
title: string
|
||||||
deadline: Date
|
deadline: Date
|
||||||
bounty_amount: number
|
bounty_amount: number
|
||||||
tags: NestedValue<object[]>
|
tags: NestedValue<Tag[]>
|
||||||
cover_image: NestedValue<File[]> | string
|
cover_image: NestedValue<File[]> | string
|
||||||
body: string
|
body: string
|
||||||
}
|
}
|
||||||
@@ -86,7 +72,7 @@ export default function BountyForm() {
|
|||||||
<div
|
<div
|
||||||
className='bg-white shadow-lg rounded-8 overflow-hidden'>
|
className='bg-white shadow-lg rounded-8 overflow-hidden'>
|
||||||
<div className="p-32">
|
<div className="p-32">
|
||||||
<Controller
|
{/* <Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="cover_image"
|
name="cover_image"
|
||||||
render={({ field: { onChange, value, onBlur } }) => (
|
render={({ field: { onChange, value, onBlur } }) => (
|
||||||
@@ -97,7 +83,7 @@ export default function BountyForm() {
|
|||||||
uploadText='Add a cover image'
|
uploadText='Add a cover image'
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/> */}
|
||||||
<p className='input-error'>{errors.cover_image?.message}</p>
|
<p className='input-error'>{errors.cover_image?.message}</p>
|
||||||
|
|
||||||
|
|
||||||
@@ -155,10 +141,20 @@ export default function BountyForm() {
|
|||||||
<p className="text-body5 mt-16">
|
<p className="text-body5 mt-16">
|
||||||
Tags
|
Tags
|
||||||
</p>
|
</p>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="tags"
|
||||||
|
render={({ field: { onChange, value, onBlur } }) => (
|
||||||
<TagsInput
|
<TagsInput
|
||||||
placeholder="Enter your tag and click enter. You can add multiple tags to your post"
|
placeholder="Add up to 5 popular tags..."
|
||||||
classes={{ container: 'mt-8' }}
|
classes={{ container: 'mt-16' }}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
onBlur={onBlur}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
{errors.tags && <p className="input-error">
|
{errors.tags && <p className="input-error">
|
||||||
{errors.tags.message}
|
{errors.tags.message}
|
||||||
</p>}
|
</p>}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export default {
|
|||||||
decorators: [WithModals, WrapForm<IStoryFormInputs>({
|
decorators: [WithModals, WrapForm<IStoryFormInputs>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
tags: [],
|
tags: [],
|
||||||
cover_image: [],
|
|
||||||
}
|
}
|
||||||
})]
|
})]
|
||||||
} as ComponentMeta<typeof DraftsContainer>;
|
} as ComponentMeta<typeof DraftsContainer>;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { NotificationsService } from 'src/services';
|
|||||||
import { getDateDifference } from 'src/utils/helperFunctions';
|
import { getDateDifference } from 'src/utils/helperFunctions';
|
||||||
import { useAppDispatch } from 'src/utils/hooks';
|
import { useAppDispatch } from 'src/utils/hooks';
|
||||||
import { useReduxEffect } from 'src/utils/hooks/useReduxEffect';
|
import { useReduxEffect } from 'src/utils/hooks/useReduxEffect';
|
||||||
import { IStoryFormInputs } from '../../CreateStoryPage/CreateStoryPage';
|
import { CreateStoryType, IStoryFormInputs } from '../../CreateStoryPage/CreateStoryPage';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -28,7 +28,7 @@ export default function DraftsContainer({ id, type, onDraftLoad }: Props) {
|
|||||||
const [deleteStory] = useDeleteStoryMutation({
|
const [deleteStory] = useDeleteStoryMutation({
|
||||||
refetchQueries: ['GetMyDrafts']
|
refetchQueries: ['GetMyDrafts']
|
||||||
})
|
})
|
||||||
const { setValue } = useFormContext<IStoryFormInputs>()
|
const { setValue } = useFormContext<CreateStoryType>()
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
@@ -45,7 +45,7 @@ export default function DraftsContainer({ id, type, onDraftLoad }: Props) {
|
|||||||
setValue('title', data.getPostById.title);
|
setValue('title', data.getPostById.title);
|
||||||
setValue('tags', data.getPostById.tags);
|
setValue('tags', data.getPostById.tags);
|
||||||
setValue('body', data.getPostById.body);
|
setValue('body', data.getPostById.body);
|
||||||
setValue('cover_image', data.getPostById.cover_image ? [data.getPostById.cover_image] : []);
|
setValue('cover_image', data.getPostById.cover_image ? { url: data.getPostById.cover_image, id: null, name: null } : null);
|
||||||
setValue('is_published', data.getPostById.is_published);
|
setValue('is_published', data.getPostById.is_published);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import { Controller, FormProvider, NestedValue, Resolver, SubmitHandler, useForm } from "react-hook-form";
|
import { Controller, FormProvider, NestedValue, Resolver, SubmitHandler, useForm } from "react-hook-form";
|
||||||
import Button from "src/Components/Button/Button";
|
import Button from "src/Components/Button/Button";
|
||||||
import FilesInput from "src/Components/Inputs/FilesInput/FilesInput";
|
|
||||||
import TagsInput from "src/Components/Inputs/TagsInput/TagsInput";
|
import TagsInput from "src/Components/Inputs/TagsInput/TagsInput";
|
||||||
|
import { Tag } from "src/graphql";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import ContentEditor from "../ContentEditor/ContentEditor";
|
import ContentEditor from "../ContentEditor/ContentEditor";
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ const schema = yup.object({
|
|||||||
|
|
||||||
interface IFormInputs {
|
interface IFormInputs {
|
||||||
title: string
|
title: string
|
||||||
tags: NestedValue<object[]>
|
tags: NestedValue<Tag[]>
|
||||||
cover_image: NestedValue<File[]> | string
|
cover_image: NestedValue<File[]> | string
|
||||||
body: string
|
body: string
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ export default function QuestionForm() {
|
|||||||
<div
|
<div
|
||||||
className='bg-white shadow-lg rounded-8 overflow-hidden'>
|
className='bg-white shadow-lg rounded-8 overflow-hidden'>
|
||||||
<div className="p-32">
|
<div className="p-32">
|
||||||
<Controller
|
{/* <Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="cover_image"
|
name="cover_image"
|
||||||
render={({ field: { onChange, value, onBlur } }) => (
|
render={({ field: { onChange, value, onBlur } }) => (
|
||||||
@@ -71,7 +71,7 @@ export default function QuestionForm() {
|
|||||||
uploadText='Add a cover image'
|
uploadText='Add a cover image'
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/> */}
|
||||||
<p className='input-error'>{errors.cover_image?.message}</p>
|
<p className='input-error'>{errors.cover_image?.message}</p>
|
||||||
|
|
||||||
|
|
||||||
@@ -95,9 +95,18 @@ export default function QuestionForm() {
|
|||||||
<p className="text-body5 mt-16">
|
<p className="text-body5 mt-16">
|
||||||
Tags
|
Tags
|
||||||
</p>
|
</p>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="tags"
|
||||||
|
render={({ field: { onChange, value, onBlur } }) => (
|
||||||
<TagsInput
|
<TagsInput
|
||||||
placeholder="Enter your tag and click enter. You can add multiple tags to your post"
|
placeholder="Add up to 5 popular tags..."
|
||||||
classes={{ container: 'mt-8' }}
|
classes={{ container: 'mt-16' }}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
onBlur={onBlur}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
{errors.tags && <p className="input-error">
|
{errors.tags && <p className="input-error">
|
||||||
{errors.tags.message}
|
{errors.tags.message}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export default {
|
|||||||
decorators: [WithModals, WrapForm<IStoryFormInputs>({
|
decorators: [WithModals, WrapForm<IStoryFormInputs>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
tags: [],
|
tags: [],
|
||||||
cover_image: [],
|
|
||||||
}
|
}
|
||||||
})]
|
})]
|
||||||
} as ComponentMeta<typeof StoryForm>;
|
} as ComponentMeta<typeof StoryForm>;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { Controller, useFormContext } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
import Button from "src/Components/Button/Button";
|
import Button from "src/Components/Button/Button";
|
||||||
import FilesInput from "src/Components/Inputs/FilesInput/FilesInput";
|
|
||||||
import TagsInput from "src/Components/Inputs/TagsInput/TagsInput";
|
import TagsInput from "src/Components/Inputs/TagsInput/TagsInput";
|
||||||
import ContentEditor from "../ContentEditor/ContentEditor";
|
import ContentEditor from "../ContentEditor/ContentEditor";
|
||||||
import { useCreateStoryMutation } from 'src/graphql'
|
import { useCreateStoryMutation } from 'src/graphql'
|
||||||
@@ -13,7 +12,8 @@ import { createRoute } from 'src/utils/routing';
|
|||||||
import PreviewPostCard from '../PreviewPostCard/PreviewPostCard'
|
import PreviewPostCard from '../PreviewPostCard/PreviewPostCard'
|
||||||
import { StorageService } from 'src/services';
|
import { StorageService } from 'src/services';
|
||||||
import { useThrottledCallback } from '@react-hookz/web';
|
import { useThrottledCallback } from '@react-hookz/web';
|
||||||
import { CreateStoryType, IStoryFormInputs } from '../../CreateStoryPage/CreateStoryPage';
|
import { CreateStoryType } from '../../CreateStoryPage/CreateStoryPage';
|
||||||
|
import CoverImageInput from 'src/Components/Inputs/FilesInputs/CoverImageInput/CoverImageInput';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isUpdating?: boolean;
|
isUpdating?: boolean;
|
||||||
@@ -29,7 +29,7 @@ export default function StoryForm(props: Props) {
|
|||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { handleSubmit, control, register, trigger, getValues, watch, reset } = useFormContext<IStoryFormInputs>();
|
const { handleSubmit, control, register, trigger, getValues, watch, reset } = useFormContext<CreateStoryType>();
|
||||||
|
|
||||||
|
|
||||||
const [editMode, setEditMode] = useState(true)
|
const [editMode, setEditMode] = useState(true)
|
||||||
@@ -80,7 +80,7 @@ export default function StoryForm(props: Props) {
|
|||||||
refetchQueries: ['GetMyDrafts']
|
refetchQueries: ['GetMyDrafts']
|
||||||
});
|
});
|
||||||
|
|
||||||
const clickSubmit = (publish_now: boolean) => handleSubmit<IStoryFormInputs>(data => {
|
const clickSubmit = (publish_now: boolean) => handleSubmit<CreateStoryType>(data => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
createStory({
|
createStory({
|
||||||
variables: {
|
variables: {
|
||||||
@@ -90,7 +90,7 @@ export default function StoryForm(props: Props) {
|
|||||||
body: data.body,
|
body: data.body,
|
||||||
tags: data.tags.map(t => t.title),
|
tags: data.tags.map(t => t.title),
|
||||||
is_published: publish_now,
|
is_published: publish_now,
|
||||||
cover_image: (data.cover_image[0] ?? null) as string | null,
|
cover_image: data.cover_image,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -103,6 +103,8 @@ export default function StoryForm(props: Props) {
|
|||||||
const { ref: registerTitleRef, ...titleRegisteration } = register('title');
|
const { ref: registerTitleRef, ...titleRegisteration } = register('title');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div id='preview-switch' className="flex gap-16">
|
<div id='preview-switch' className="flex gap-16">
|
||||||
@@ -117,19 +119,21 @@ export default function StoryForm(props: Props) {
|
|||||||
<div
|
<div
|
||||||
className='bg-white border-2 border-gray-200 rounded-16 overflow-hidden'>
|
className='bg-white border-2 border-gray-200 rounded-16 overflow-hidden'>
|
||||||
<div className="p-16 md:p-24 lg:p-32">
|
<div className="p-16 md:p-24 lg:p-32">
|
||||||
|
<div className="w-full h-[120px] md:h-[240px] rounded-12 mb-16 overflow-hidden">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="cover_image"
|
name="cover_image"
|
||||||
render={({ field: { onChange, value, onBlur, ref } }) => (
|
render={({ field: { onChange, value, onBlur, ref } }) => <CoverImageInput
|
||||||
<FilesInput
|
|
||||||
ref={ref}
|
|
||||||
value={value}
|
value={value}
|
||||||
onBlur={onBlur}
|
onChange={e => {
|
||||||
onChange={onChange}
|
onChange(e)
|
||||||
uploadText='Add a cover image'
|
}}
|
||||||
|
// uploadText='Add a cover image'
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -153,10 +157,20 @@ export default function StoryForm(props: Props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="tags"
|
||||||
|
render={({ field: { onChange, value, onBlur } }) => (
|
||||||
<TagsInput
|
<TagsInput
|
||||||
placeholder="Add up to 5 popular tags..."
|
placeholder="Add up to 5 popular tags..."
|
||||||
classes={{ container: 'mt-16' }}
|
classes={{ container: 'mt-16' }}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
onBlur={onBlur}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<ContentEditor
|
<ContentEditor
|
||||||
@@ -167,7 +181,7 @@ export default function StoryForm(props: Props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>}
|
</>}
|
||||||
{!editMode && <PreviewPostCard post={{ ...getValues(), cover_image: getValues().cover_image[0] }} />}
|
{!editMode && <PreviewPostCard post={{ ...getValues(), cover_image: getValues('cover_image.url') }} />}
|
||||||
<div className="flex gap-16 mt-32">
|
<div className="flex gap-16 mt-32">
|
||||||
<Button
|
<Button
|
||||||
type='submit'
|
type='submit'
|
||||||
|
|||||||
@@ -2,54 +2,42 @@
|
|||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { FormProvider, NestedValue, Resolver, useForm } from "react-hook-form";
|
import { FormProvider, NestedValue, Resolver, useForm } from "react-hook-form";
|
||||||
import { Post_Type } from "src/graphql";
|
import { CreateStoryMutationVariables, Post_Type } from "src/graphql";
|
||||||
import { StorageService } from "src/services";
|
import { StorageService } from "src/services";
|
||||||
import { useAppSelector } from "src/utils/hooks";
|
import { useAppSelector } from "src/utils/hooks";
|
||||||
import { Override } from "src/utils/interfaces";
|
import { Override } from "src/utils/interfaces";
|
||||||
|
import { imageSchema, tagSchema } from "src/utils/validation";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import DraftsContainer from "../Components/DraftsContainer/DraftsContainer";
|
import DraftsContainer from "../Components/DraftsContainer/DraftsContainer";
|
||||||
import ErrorsContainer from "../Components/ErrorsContainer/ErrorsContainer";
|
import ErrorsContainer from "../Components/ErrorsContainer/ErrorsContainer";
|
||||||
import StoryForm from "../Components/StoryForm/StoryForm";
|
import StoryForm from "../Components/StoryForm/StoryForm";
|
||||||
import styles from './styles.module.scss'
|
import styles from './styles.module.scss'
|
||||||
|
|
||||||
const FileSchema = yup.lazy((value: string | File[]) => {
|
|
||||||
switch (typeof value) {
|
|
||||||
case 'object':
|
|
||||||
return yup.mixed()
|
|
||||||
.test("fileSize", "File Size is too large", file => file.size <= 5242880)
|
|
||||||
.test("fileType", "Unsupported File Format, only png/jpg/jpeg images are allowed",
|
|
||||||
(file: File) =>
|
|
||||||
["image/jpeg", "image/png", "image/jpg"].includes(file.type))
|
|
||||||
case 'string':
|
|
||||||
return yup.string().url();
|
|
||||||
default:
|
|
||||||
return yup.mixed()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
title: yup.string().trim().required().min(10, 'Story title must be 2+ words').transform(v => v.replace(/(\r\n|\n|\r)/gm, "")),
|
title: yup.string().trim().required().min(10, 'Story title must be 2+ words').transform(v => v.replace(/(\r\n|\n|\r)/gm, "")),
|
||||||
tags: yup.array().required().min(1, 'Add at least one tag'),
|
tags: yup.array().of(tagSchema).required().min(1, 'Add at least one tag'),
|
||||||
body: yup.string().required().min(50, 'Post must contain at least 10+ words'),
|
body: yup.string().required("Write some content in the post").min(50, 'Post must contain at least 10+ words'),
|
||||||
cover_image: yup.array().of(FileSchema as any)
|
cover_image: imageSchema.nullable(true),
|
||||||
|
|
||||||
}).required();
|
}).required();
|
||||||
|
|
||||||
|
|
||||||
export interface IStoryFormInputs {
|
type ApiStoryInput = NonNullable<CreateStoryMutationVariables['data']>;
|
||||||
id: number | null
|
|
||||||
title: string
|
export type IStoryFormInputs = {
|
||||||
tags: NestedValue<{ title: string }[]>
|
id: ApiStoryInput['id']
|
||||||
cover_image: NestedValue<File[]> | NestedValue<string[]>
|
title: ApiStoryInput['title']
|
||||||
body: string
|
body: ApiStoryInput['body']
|
||||||
is_published: boolean | null
|
cover_image: NestedValue<NonNullable<ApiStoryInput['cover_image']>> | null
|
||||||
|
tags: NestedValue<ApiStoryInput['tags']>
|
||||||
|
is_published: ApiStoryInput['is_published']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export type CreateStoryType = Override<IStoryFormInputs, {
|
export type CreateStoryType = Override<IStoryFormInputs, {
|
||||||
|
cover_image: ApiStoryInput['cover_image'],
|
||||||
tags: { title: string }[]
|
tags: { title: string }[]
|
||||||
cover_image: File[] | string[]
|
|
||||||
}>
|
}>
|
||||||
|
|
||||||
const storageService = new StorageService<CreateStoryType>('story-edit');
|
const storageService = new StorageService<CreateStoryType>('story-edit');
|
||||||
@@ -62,13 +50,13 @@ export default function CreateStoryPage() {
|
|||||||
story: state.staging.story || storageService.get()
|
story: state.staging.story || storageService.get()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const formMethods = useForm<IStoryFormInputs>({
|
const formMethods = useForm<CreateStoryType>({
|
||||||
resolver: yupResolver(schema) as Resolver<IStoryFormInputs>,
|
resolver: yupResolver(schema) as Resolver<CreateStoryType>,
|
||||||
shouldFocusError: false,
|
shouldFocusError: false,
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: story?.id ?? null,
|
id: story?.id ?? null,
|
||||||
title: story?.title ?? '',
|
title: story?.title ?? '',
|
||||||
cover_image: story?.cover_image ?? [],
|
cover_image: story?.cover_image,
|
||||||
tags: story?.tags ?? [],
|
tags: story?.tags ?? [],
|
||||||
body: story?.body ?? '',
|
body: story?.body ?? '',
|
||||||
is_published: story?.is_published ?? false,
|
is_published: story?.is_published ?? false,
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ export default function StoryPageContent({ story }: Props) {
|
|||||||
<Card id="content" onlyMd className="relative max">
|
<Card id="content" onlyMd className="relative max">
|
||||||
{story.cover_image &&
|
{story.cover_image &&
|
||||||
<img src={story.cover_image}
|
<img src={story.cover_image}
|
||||||
className='w-full object-cover rounded-12 md:rounded-16 mb-16'
|
className='w-full h-[120px] md:h-[240px] object-cover rounded-12 mb-16'
|
||||||
|
// className='w-full object-cover rounded-12 md:rounded-16 mb-16'
|
||||||
alt="" />}
|
alt="" />}
|
||||||
<div className="flex flex-col gap-24 relative">
|
<div className="flex flex-col gap-24 relative">
|
||||||
{curUser?.id === story.author.id && <Menu
|
{curUser?.id === story.author.id && <Menu
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const useUpdateStory = (story: Story) => {
|
|||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
dispatch(stageStory({
|
dispatch(stageStory({
|
||||||
...story,
|
...story,
|
||||||
cover_image: story.cover_image ? [story.cover_image] : []
|
cover_image: story.cover_image ? { id: null, name: null, url: story.cover_image } : null,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
navigate("/blog/create-post?type=story")
|
navigate("/blog/create-post?type=story")
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { SubmitHandler, useForm } from "react-hook-form"
|
import { Controller, SubmitHandler, useForm } from "react-hook-form"
|
||||||
import { useUpdateProfileAboutMutation, useMyProfileAboutQuery, UpdateProfileAboutMutationVariables, UserBasicInfoFragmentDoc } from "src/graphql";
|
import { useUpdateProfileAboutMutation, useMyProfileAboutQuery, UpdateProfileAboutMutationVariables, UserBasicInfoFragmentDoc } from "src/graphql";
|
||||||
import { NotificationsService } from "src/services/notifications.service";
|
import { NotificationsService } from "src/services/notifications.service";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import Avatar from "src/features/Profiles/Components/Avatar/Avatar";
|
|
||||||
import { useAppDispatch, usePrompt } from "src/utils/hooks";
|
import { useAppDispatch, usePrompt } from "src/utils/hooks";
|
||||||
import SaveChangesCard from "../SaveChangesCard/SaveChangesCard";
|
import SaveChangesCard from "../SaveChangesCard/SaveChangesCard";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
@@ -12,15 +11,18 @@ import NotFoundPage from "src/features/Shared/pages/NotFoundPage/NotFoundPage";
|
|||||||
import { setUser } from "src/redux/features/user.slice";
|
import { setUser } from "src/redux/features/user.slice";
|
||||||
import UpdateProfileAboutTabSkeleton from "./BasicProfileInfoTab.Skeleton";
|
import UpdateProfileAboutTabSkeleton from "./BasicProfileInfoTab.Skeleton";
|
||||||
import { useApolloClient } from "@apollo/client";
|
import { useApolloClient } from "@apollo/client";
|
||||||
|
import AvatarInput from "src/Components/Inputs/FilesInputs/AvatarInput/AvatarInput";
|
||||||
|
import { imageSchema } from "src/utils/validation";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
type IFormInputs = NonNullable<UpdateProfileAboutMutationVariables['data']>;
|
type IFormInputs = NonNullable<UpdateProfileAboutMutationVariables['data']>;
|
||||||
|
|
||||||
|
|
||||||
const schema: yup.SchemaOf<IFormInputs> = yup.object({
|
const schema: yup.SchemaOf<IFormInputs> = yup.object({
|
||||||
name: yup.string().trim().required().min(2),
|
name: yup.string().trim().required().min(2),
|
||||||
avatar: yup.string().url().required(),
|
avatar: imageSchema.required(),
|
||||||
bio: yup.string().ensure(),
|
bio: yup.string().ensure(),
|
||||||
email: yup.string().email().ensure(),
|
email: yup.string().email().ensure(),
|
||||||
github: yup.string().ensure(),
|
github: yup.string().ensure(),
|
||||||
@@ -55,8 +57,10 @@ const schema: yup.SchemaOf<IFormInputs> = yup.object({
|
|||||||
|
|
||||||
export default function BasicProfileInfoTab() {
|
export default function BasicProfileInfoTab() {
|
||||||
|
|
||||||
const { register, formState: { errors, isDirty, }, handleSubmit, reset } = useForm<IFormInputs>({
|
const { register, formState: { errors, isDirty, }, handleSubmit, reset, control } = useForm<IFormInputs>({
|
||||||
defaultValues: {},
|
defaultValues: {
|
||||||
|
|
||||||
|
},
|
||||||
resolver: yupResolver(schema),
|
resolver: yupResolver(schema),
|
||||||
mode: 'onBlur',
|
mode: 'onBlur',
|
||||||
});
|
});
|
||||||
@@ -65,7 +69,7 @@ export default function BasicProfileInfoTab() {
|
|||||||
const profileQuery = useMyProfileAboutQuery({
|
const profileQuery = useMyProfileAboutQuery({
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
if (data.me)
|
if (data.me)
|
||||||
reset(data.me)
|
reset({ ...data.me, avatar: { url: data.me.avatar } })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const [mutate, mutationStatus] = useUpdateProfileAboutMutation();
|
const [mutate, mutationStatus] = useUpdateProfileAboutMutation();
|
||||||
@@ -107,7 +111,7 @@ export default function BasicProfileInfoTab() {
|
|||||||
onCompleted: ({ updateProfileDetails: data }) => {
|
onCompleted: ({ updateProfileDetails: data }) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
dispatch(setUser(data))
|
dispatch(setUser(data))
|
||||||
reset(data);
|
reset({ ...data, avatar: { url: data.avatar } });
|
||||||
apolloClient.writeFragment({
|
apolloClient.writeFragment({
|
||||||
id: `User:${data?.id}`,
|
id: `User:${data?.id}`,
|
||||||
data,
|
data,
|
||||||
@@ -123,12 +127,21 @@ export default function BasicProfileInfoTab() {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-24">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-24">
|
||||||
<Card className="md:col-span-2" defaultPadding={false}>
|
<Card className="md:col-span-2" defaultPadding={false}>
|
||||||
<div className="bg-gray-600 relative h-[160px] rounded-t-16">
|
<div className="bg-gray-600 relative h-[160px] rounded-t-16">
|
||||||
<div className="absolute left-24 bottom-0 translate-y-1/2">
|
<div className="absolute left-24 bottom-0 translate-y-1/2">
|
||||||
<Avatar src={profileQuery.data.me.avatar} width={120} />
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="avatar"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<AvatarInput value={value} onChange={onChange} width={120} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-16 md:p-24 mt-64">
|
<div className="p-16 md:p-24 mt-64">
|
||||||
@@ -148,29 +161,14 @@ export default function BasicProfileInfoTab() {
|
|||||||
{errors.name && <p className="input-error">
|
{errors.name && <p className="input-error">
|
||||||
{errors.name.message}
|
{errors.name.message}
|
||||||
</p>}
|
</p>}
|
||||||
<p className="text-body5 mt-16 font-medium">
|
|
||||||
Avatar
|
|
||||||
</p>
|
|
||||||
<div className="input-wrapper mt-8 relative">
|
|
||||||
<input
|
|
||||||
|
|
||||||
type='text'
|
|
||||||
className="input-text"
|
|
||||||
placeholder='https://images.com/my-avatar.jpg'
|
|
||||||
{...register("avatar")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{errors.avatar && <p className="input-error">
|
|
||||||
{errors.avatar.message}
|
|
||||||
</p>}
|
|
||||||
<p className="text-body5 mt-16 font-medium">
|
<p className="text-body5 mt-16 font-medium">
|
||||||
Bio
|
Bio
|
||||||
</p>
|
</p>
|
||||||
<div className="input-wrapper mt-8 relative">
|
<div className="input-wrapper mt-8 relative">
|
||||||
<textarea
|
<textarea
|
||||||
|
|
||||||
rows={3}
|
rows={4}
|
||||||
className="input-text !p-20"
|
className="input-text"
|
||||||
placeholder='Tell others a little bit about yourself'
|
placeholder='Tell others a little bit about yourself'
|
||||||
{...register("bio")}
|
{...register("bio")}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,23 +2,16 @@ import { motion } from 'framer-motion'
|
|||||||
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
|
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { Grid } from "react-loader-spinner";
|
import { Grid } from "react-loader-spinner";
|
||||||
import { CONSTS } from "src/utils";
|
|
||||||
import { QRCodeSVG } from 'qrcode.react';
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
import Button from "src/Components/Button/Button";
|
import Button from "src/Components/Button/Button";
|
||||||
import { FiCopy } from "react-icons/fi";
|
import { FiCopy } from "react-icons/fi";
|
||||||
import useCopyToClipboard from "src/utils/hooks/useCopyToClipboard";
|
import useCopyToClipboard from "src/utils/hooks/useCopyToClipboard";
|
||||||
import { useApolloClient } from '@apollo/client';
|
import { useApolloClient } from '@apollo/client';
|
||||||
import { IoClose } from 'react-icons/io5';
|
import { IoClose } from 'react-icons/io5';
|
||||||
|
import { fetchLnurlAuth } from 'src/api/auth';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const fetchLnurlAuth = async () => {
|
|
||||||
const res = await fetch(CONSTS.apiEndpoint + '/get-login-url?action=link', {
|
|
||||||
credentials: 'include'
|
|
||||||
})
|
|
||||||
const data = await res.json()
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useLnurlQuery = () => {
|
const useLnurlQuery = () => {
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
|||||||
@@ -141,6 +141,12 @@ export type Hackathon = {
|
|||||||
website: Scalars['String'];
|
website: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ImageInput = {
|
||||||
|
id: InputMaybe<Scalars['String']>;
|
||||||
|
name: InputMaybe<Scalars['String']>;
|
||||||
|
url: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
export type LnurlDetails = {
|
export type LnurlDetails = {
|
||||||
__typename?: 'LnurlDetails';
|
__typename?: 'LnurlDetails';
|
||||||
commentAllowed: Maybe<Scalars['Int']>;
|
commentAllowed: Maybe<Scalars['Int']>;
|
||||||
@@ -1031,13 +1037,13 @@ export const OfficialTagsDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useOfficialTagsQuery(baseOptions?: Apollo.QueryHookOptions<OfficialTagsQuery, OfficialTagsQueryVariables>) {
|
export function useOfficialTagsQuery(baseOptions?: Apollo.QueryHookOptions<OfficialTagsQuery, OfficialTagsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<OfficialTagsQuery, OfficialTagsQueryVariables>(OfficialTagsDocument, options);
|
return Apollo.useQuery<OfficialTagsQuery, OfficialTagsQueryVariables>(OfficialTagsDocument, options);
|
||||||
}
|
}
|
||||||
export function useOfficialTagsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<OfficialTagsQuery, OfficialTagsQueryVariables>) {
|
export function useOfficialTagsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<OfficialTagsQuery, OfficialTagsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<OfficialTagsQuery, OfficialTagsQueryVariables>(OfficialTagsDocument, options);
|
return Apollo.useLazyQuery<OfficialTagsQuery, OfficialTagsQueryVariables>(OfficialTagsDocument, options);
|
||||||
}
|
}
|
||||||
export type OfficialTagsQueryHookResult = ReturnType<typeof useOfficialTagsQuery>;
|
export type OfficialTagsQueryHookResult = ReturnType<typeof useOfficialTagsQuery>;
|
||||||
export type OfficialTagsLazyQueryHookResult = ReturnType<typeof useOfficialTagsLazyQuery>;
|
export type OfficialTagsLazyQueryHookResult = ReturnType<typeof useOfficialTagsLazyQuery>;
|
||||||
export type OfficialTagsQueryResult = Apollo.QueryResult<OfficialTagsQuery, OfficialTagsQueryVariables>;
|
export type OfficialTagsQueryResult = Apollo.QueryResult<OfficialTagsQuery, OfficialTagsQueryVariables>;
|
||||||
@@ -1068,13 +1074,13 @@ export const NavCategoriesDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useNavCategoriesQuery(baseOptions?: Apollo.QueryHookOptions<NavCategoriesQuery, NavCategoriesQueryVariables>) {
|
export function useNavCategoriesQuery(baseOptions?: Apollo.QueryHookOptions<NavCategoriesQuery, NavCategoriesQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<NavCategoriesQuery, NavCategoriesQueryVariables>(NavCategoriesDocument, options);
|
return Apollo.useQuery<NavCategoriesQuery, NavCategoriesQueryVariables>(NavCategoriesDocument, options);
|
||||||
}
|
}
|
||||||
export function useNavCategoriesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<NavCategoriesQuery, NavCategoriesQueryVariables>) {
|
export function useNavCategoriesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<NavCategoriesQuery, NavCategoriesQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<NavCategoriesQuery, NavCategoriesQueryVariables>(NavCategoriesDocument, options);
|
return Apollo.useLazyQuery<NavCategoriesQuery, NavCategoriesQueryVariables>(NavCategoriesDocument, options);
|
||||||
}
|
}
|
||||||
export type NavCategoriesQueryHookResult = ReturnType<typeof useNavCategoriesQuery>;
|
export type NavCategoriesQueryHookResult = ReturnType<typeof useNavCategoriesQuery>;
|
||||||
export type NavCategoriesLazyQueryHookResult = ReturnType<typeof useNavCategoriesLazyQuery>;
|
export type NavCategoriesLazyQueryHookResult = ReturnType<typeof useNavCategoriesLazyQuery>;
|
||||||
export type NavCategoriesQueryResult = Apollo.QueryResult<NavCategoriesQuery, NavCategoriesQueryVariables>;
|
export type NavCategoriesQueryResult = Apollo.QueryResult<NavCategoriesQuery, NavCategoriesQueryVariables>;
|
||||||
@@ -1109,13 +1115,13 @@ export const SearchProjectsDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useSearchProjectsQuery(baseOptions: Apollo.QueryHookOptions<SearchProjectsQuery, SearchProjectsQueryVariables>) {
|
export function useSearchProjectsQuery(baseOptions: Apollo.QueryHookOptions<SearchProjectsQuery, SearchProjectsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<SearchProjectsQuery, SearchProjectsQueryVariables>(SearchProjectsDocument, options);
|
return Apollo.useQuery<SearchProjectsQuery, SearchProjectsQueryVariables>(SearchProjectsDocument, options);
|
||||||
}
|
}
|
||||||
export function useSearchProjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<SearchProjectsQuery, SearchProjectsQueryVariables>) {
|
export function useSearchProjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<SearchProjectsQuery, SearchProjectsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<SearchProjectsQuery, SearchProjectsQueryVariables>(SearchProjectsDocument, options);
|
return Apollo.useLazyQuery<SearchProjectsQuery, SearchProjectsQueryVariables>(SearchProjectsDocument, options);
|
||||||
}
|
}
|
||||||
export type SearchProjectsQueryHookResult = ReturnType<typeof useSearchProjectsQuery>;
|
export type SearchProjectsQueryHookResult = ReturnType<typeof useSearchProjectsQuery>;
|
||||||
export type SearchProjectsLazyQueryHookResult = ReturnType<typeof useSearchProjectsLazyQuery>;
|
export type SearchProjectsLazyQueryHookResult = ReturnType<typeof useSearchProjectsLazyQuery>;
|
||||||
export type SearchProjectsQueryResult = Apollo.QueryResult<SearchProjectsQuery, SearchProjectsQueryVariables>;
|
export type SearchProjectsQueryResult = Apollo.QueryResult<SearchProjectsQuery, SearchProjectsQueryVariables>;
|
||||||
@@ -1148,13 +1154,13 @@ export const MeDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useMeQuery(baseOptions?: Apollo.QueryHookOptions<MeQuery, MeQueryVariables>) {
|
export function useMeQuery(baseOptions?: Apollo.QueryHookOptions<MeQuery, MeQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<MeQuery, MeQueryVariables>(MeDocument, options);
|
return Apollo.useQuery<MeQuery, MeQueryVariables>(MeDocument, options);
|
||||||
}
|
}
|
||||||
export function useMeLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MeQuery, MeQueryVariables>) {
|
export function useMeLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MeQuery, MeQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<MeQuery, MeQueryVariables>(MeDocument, options);
|
return Apollo.useLazyQuery<MeQuery, MeQueryVariables>(MeDocument, options);
|
||||||
}
|
}
|
||||||
export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
|
export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
|
||||||
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
|
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
|
||||||
export type MeQueryResult = Apollo.QueryResult<MeQuery, MeQueryVariables>;
|
export type MeQueryResult = Apollo.QueryResult<MeQuery, MeQueryVariables>;
|
||||||
@@ -1185,13 +1191,13 @@ export const DonationsStatsDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useDonationsStatsQuery(baseOptions?: Apollo.QueryHookOptions<DonationsStatsQuery, DonationsStatsQueryVariables>) {
|
export function useDonationsStatsQuery(baseOptions?: Apollo.QueryHookOptions<DonationsStatsQuery, DonationsStatsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<DonationsStatsQuery, DonationsStatsQueryVariables>(DonationsStatsDocument, options);
|
return Apollo.useQuery<DonationsStatsQuery, DonationsStatsQueryVariables>(DonationsStatsDocument, options);
|
||||||
}
|
}
|
||||||
export function useDonationsStatsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<DonationsStatsQuery, DonationsStatsQueryVariables>) {
|
export function useDonationsStatsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<DonationsStatsQuery, DonationsStatsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<DonationsStatsQuery, DonationsStatsQueryVariables>(DonationsStatsDocument, options);
|
return Apollo.useLazyQuery<DonationsStatsQuery, DonationsStatsQueryVariables>(DonationsStatsDocument, options);
|
||||||
}
|
}
|
||||||
export type DonationsStatsQueryHookResult = ReturnType<typeof useDonationsStatsQuery>;
|
export type DonationsStatsQueryHookResult = ReturnType<typeof useDonationsStatsQuery>;
|
||||||
export type DonationsStatsLazyQueryHookResult = ReturnType<typeof useDonationsStatsLazyQuery>;
|
export type DonationsStatsLazyQueryHookResult = ReturnType<typeof useDonationsStatsLazyQuery>;
|
||||||
export type DonationsStatsQueryResult = Apollo.QueryResult<DonationsStatsQuery, DonationsStatsQueryVariables>;
|
export type DonationsStatsQueryResult = Apollo.QueryResult<DonationsStatsQuery, DonationsStatsQueryVariables>;
|
||||||
@@ -1225,9 +1231,9 @@ export type DonateMutationFn = Apollo.MutationFunction<DonateMutation, DonateMut
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useDonateMutation(baseOptions?: Apollo.MutationHookOptions<DonateMutation, DonateMutationVariables>) {
|
export function useDonateMutation(baseOptions?: Apollo.MutationHookOptions<DonateMutation, DonateMutationVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useMutation<DonateMutation, DonateMutationVariables>(DonateDocument, options);
|
return Apollo.useMutation<DonateMutation, DonateMutationVariables>(DonateDocument, options);
|
||||||
}
|
}
|
||||||
export type DonateMutationHookResult = ReturnType<typeof useDonateMutation>;
|
export type DonateMutationHookResult = ReturnType<typeof useDonateMutation>;
|
||||||
export type DonateMutationResult = Apollo.MutationResult<DonateMutation>;
|
export type DonateMutationResult = Apollo.MutationResult<DonateMutation>;
|
||||||
export type DonateMutationOptions = Apollo.BaseMutationOptions<DonateMutation, DonateMutationVariables>;
|
export type DonateMutationOptions = Apollo.BaseMutationOptions<DonateMutation, DonateMutationVariables>;
|
||||||
@@ -1261,9 +1267,9 @@ export type ConfirmDonationMutationFn = Apollo.MutationFunction<ConfirmDonationM
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useConfirmDonationMutation(baseOptions?: Apollo.MutationHookOptions<ConfirmDonationMutation, ConfirmDonationMutationVariables>) {
|
export function useConfirmDonationMutation(baseOptions?: Apollo.MutationHookOptions<ConfirmDonationMutation, ConfirmDonationMutationVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useMutation<ConfirmDonationMutation, ConfirmDonationMutationVariables>(ConfirmDonationDocument, options);
|
return Apollo.useMutation<ConfirmDonationMutation, ConfirmDonationMutationVariables>(ConfirmDonationDocument, options);
|
||||||
}
|
}
|
||||||
export type ConfirmDonationMutationHookResult = ReturnType<typeof useConfirmDonationMutation>;
|
export type ConfirmDonationMutationHookResult = ReturnType<typeof useConfirmDonationMutation>;
|
||||||
export type ConfirmDonationMutationResult = Apollo.MutationResult<ConfirmDonationMutation>;
|
export type ConfirmDonationMutationResult = Apollo.MutationResult<ConfirmDonationMutation>;
|
||||||
export type ConfirmDonationMutationOptions = Apollo.BaseMutationOptions<ConfirmDonationMutation, ConfirmDonationMutationVariables>;
|
export type ConfirmDonationMutationOptions = Apollo.BaseMutationOptions<ConfirmDonationMutation, ConfirmDonationMutationVariables>;
|
||||||
@@ -1305,13 +1311,13 @@ export const GetHackathonsDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useGetHackathonsQuery(baseOptions?: Apollo.QueryHookOptions<GetHackathonsQuery, GetHackathonsQueryVariables>) {
|
export function useGetHackathonsQuery(baseOptions?: Apollo.QueryHookOptions<GetHackathonsQuery, GetHackathonsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<GetHackathonsQuery, GetHackathonsQueryVariables>(GetHackathonsDocument, options);
|
return Apollo.useQuery<GetHackathonsQuery, GetHackathonsQueryVariables>(GetHackathonsDocument, options);
|
||||||
}
|
}
|
||||||
export function useGetHackathonsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetHackathonsQuery, GetHackathonsQueryVariables>) {
|
export function useGetHackathonsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetHackathonsQuery, GetHackathonsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<GetHackathonsQuery, GetHackathonsQueryVariables>(GetHackathonsDocument, options);
|
return Apollo.useLazyQuery<GetHackathonsQuery, GetHackathonsQueryVariables>(GetHackathonsDocument, options);
|
||||||
}
|
}
|
||||||
export type GetHackathonsQueryHookResult = ReturnType<typeof useGetHackathonsQuery>;
|
export type GetHackathonsQueryHookResult = ReturnType<typeof useGetHackathonsQuery>;
|
||||||
export type GetHackathonsLazyQueryHookResult = ReturnType<typeof useGetHackathonsLazyQuery>;
|
export type GetHackathonsLazyQueryHookResult = ReturnType<typeof useGetHackathonsLazyQuery>;
|
||||||
export type GetHackathonsQueryResult = Apollo.QueryResult<GetHackathonsQuery, GetHackathonsQueryVariables>;
|
export type GetHackathonsQueryResult = Apollo.QueryResult<GetHackathonsQuery, GetHackathonsQueryVariables>;
|
||||||
@@ -1362,13 +1368,13 @@ export const TrendingPostsDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useTrendingPostsQuery(baseOptions?: Apollo.QueryHookOptions<TrendingPostsQuery, TrendingPostsQueryVariables>) {
|
export function useTrendingPostsQuery(baseOptions?: Apollo.QueryHookOptions<TrendingPostsQuery, TrendingPostsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<TrendingPostsQuery, TrendingPostsQueryVariables>(TrendingPostsDocument, options);
|
return Apollo.useQuery<TrendingPostsQuery, TrendingPostsQueryVariables>(TrendingPostsDocument, options);
|
||||||
}
|
}
|
||||||
export function useTrendingPostsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<TrendingPostsQuery, TrendingPostsQueryVariables>) {
|
export function useTrendingPostsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<TrendingPostsQuery, TrendingPostsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<TrendingPostsQuery, TrendingPostsQueryVariables>(TrendingPostsDocument, options);
|
return Apollo.useLazyQuery<TrendingPostsQuery, TrendingPostsQueryVariables>(TrendingPostsDocument, options);
|
||||||
}
|
}
|
||||||
export type TrendingPostsQueryHookResult = ReturnType<typeof useTrendingPostsQuery>;
|
export type TrendingPostsQueryHookResult = ReturnType<typeof useTrendingPostsQuery>;
|
||||||
export type TrendingPostsLazyQueryHookResult = ReturnType<typeof useTrendingPostsLazyQuery>;
|
export type TrendingPostsLazyQueryHookResult = ReturnType<typeof useTrendingPostsLazyQuery>;
|
||||||
export type TrendingPostsQueryResult = Apollo.QueryResult<TrendingPostsQuery, TrendingPostsQueryVariables>;
|
export type TrendingPostsQueryResult = Apollo.QueryResult<TrendingPostsQuery, TrendingPostsQueryVariables>;
|
||||||
@@ -1411,13 +1417,13 @@ export const GetMyDraftsDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useGetMyDraftsQuery(baseOptions: Apollo.QueryHookOptions<GetMyDraftsQuery, GetMyDraftsQueryVariables>) {
|
export function useGetMyDraftsQuery(baseOptions: Apollo.QueryHookOptions<GetMyDraftsQuery, GetMyDraftsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<GetMyDraftsQuery, GetMyDraftsQueryVariables>(GetMyDraftsDocument, options);
|
return Apollo.useQuery<GetMyDraftsQuery, GetMyDraftsQueryVariables>(GetMyDraftsDocument, options);
|
||||||
}
|
}
|
||||||
export function useGetMyDraftsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetMyDraftsQuery, GetMyDraftsQueryVariables>) {
|
export function useGetMyDraftsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetMyDraftsQuery, GetMyDraftsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<GetMyDraftsQuery, GetMyDraftsQueryVariables>(GetMyDraftsDocument, options);
|
return Apollo.useLazyQuery<GetMyDraftsQuery, GetMyDraftsQueryVariables>(GetMyDraftsDocument, options);
|
||||||
}
|
}
|
||||||
export type GetMyDraftsQueryHookResult = ReturnType<typeof useGetMyDraftsQuery>;
|
export type GetMyDraftsQueryHookResult = ReturnType<typeof useGetMyDraftsQuery>;
|
||||||
export type GetMyDraftsLazyQueryHookResult = ReturnType<typeof useGetMyDraftsLazyQuery>;
|
export type GetMyDraftsLazyQueryHookResult = ReturnType<typeof useGetMyDraftsLazyQuery>;
|
||||||
export type GetMyDraftsQueryResult = Apollo.QueryResult<GetMyDraftsQuery, GetMyDraftsQueryVariables>;
|
export type GetMyDraftsQueryResult = Apollo.QueryResult<GetMyDraftsQuery, GetMyDraftsQueryVariables>;
|
||||||
@@ -1459,9 +1465,9 @@ export type CreateStoryMutationFn = Apollo.MutationFunction<CreateStoryMutation,
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useCreateStoryMutation(baseOptions?: Apollo.MutationHookOptions<CreateStoryMutation, CreateStoryMutationVariables>) {
|
export function useCreateStoryMutation(baseOptions?: Apollo.MutationHookOptions<CreateStoryMutation, CreateStoryMutationVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useMutation<CreateStoryMutation, CreateStoryMutationVariables>(CreateStoryDocument, options);
|
return Apollo.useMutation<CreateStoryMutation, CreateStoryMutationVariables>(CreateStoryDocument, options);
|
||||||
}
|
}
|
||||||
export type CreateStoryMutationHookResult = ReturnType<typeof useCreateStoryMutation>;
|
export type CreateStoryMutationHookResult = ReturnType<typeof useCreateStoryMutation>;
|
||||||
export type CreateStoryMutationResult = Apollo.MutationResult<CreateStoryMutation>;
|
export type CreateStoryMutationResult = Apollo.MutationResult<CreateStoryMutation>;
|
||||||
export type CreateStoryMutationOptions = Apollo.BaseMutationOptions<CreateStoryMutation, CreateStoryMutationVariables>;
|
export type CreateStoryMutationOptions = Apollo.BaseMutationOptions<CreateStoryMutation, CreateStoryMutationVariables>;
|
||||||
@@ -1492,9 +1498,9 @@ export type DeleteStoryMutationFn = Apollo.MutationFunction<DeleteStoryMutation,
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useDeleteStoryMutation(baseOptions?: Apollo.MutationHookOptions<DeleteStoryMutation, DeleteStoryMutationVariables>) {
|
export function useDeleteStoryMutation(baseOptions?: Apollo.MutationHookOptions<DeleteStoryMutation, DeleteStoryMutationVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useMutation<DeleteStoryMutation, DeleteStoryMutationVariables>(DeleteStoryDocument, options);
|
return Apollo.useMutation<DeleteStoryMutation, DeleteStoryMutationVariables>(DeleteStoryDocument, options);
|
||||||
}
|
}
|
||||||
export type DeleteStoryMutationHookResult = ReturnType<typeof useDeleteStoryMutation>;
|
export type DeleteStoryMutationHookResult = ReturnType<typeof useDeleteStoryMutation>;
|
||||||
export type DeleteStoryMutationResult = Apollo.MutationResult<DeleteStoryMutation>;
|
export type DeleteStoryMutationResult = Apollo.MutationResult<DeleteStoryMutation>;
|
||||||
export type DeleteStoryMutationOptions = Apollo.BaseMutationOptions<DeleteStoryMutation, DeleteStoryMutationVariables>;
|
export type DeleteStoryMutationOptions = Apollo.BaseMutationOptions<DeleteStoryMutation, DeleteStoryMutationVariables>;
|
||||||
@@ -1524,13 +1530,13 @@ export const PopularTagsDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function usePopularTagsQuery(baseOptions?: Apollo.QueryHookOptions<PopularTagsQuery, PopularTagsQueryVariables>) {
|
export function usePopularTagsQuery(baseOptions?: Apollo.QueryHookOptions<PopularTagsQuery, PopularTagsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<PopularTagsQuery, PopularTagsQueryVariables>(PopularTagsDocument, options);
|
return Apollo.useQuery<PopularTagsQuery, PopularTagsQueryVariables>(PopularTagsDocument, options);
|
||||||
}
|
}
|
||||||
export function usePopularTagsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<PopularTagsQuery, PopularTagsQueryVariables>) {
|
export function usePopularTagsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<PopularTagsQuery, PopularTagsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<PopularTagsQuery, PopularTagsQueryVariables>(PopularTagsDocument, options);
|
return Apollo.useLazyQuery<PopularTagsQuery, PopularTagsQueryVariables>(PopularTagsDocument, options);
|
||||||
}
|
}
|
||||||
export type PopularTagsQueryHookResult = ReturnType<typeof usePopularTagsQuery>;
|
export type PopularTagsQueryHookResult = ReturnType<typeof usePopularTagsQuery>;
|
||||||
export type PopularTagsLazyQueryHookResult = ReturnType<typeof usePopularTagsLazyQuery>;
|
export type PopularTagsLazyQueryHookResult = ReturnType<typeof usePopularTagsLazyQuery>;
|
||||||
export type PopularTagsQueryResult = Apollo.QueryResult<PopularTagsQuery, PopularTagsQueryVariables>;
|
export type PopularTagsQueryResult = Apollo.QueryResult<PopularTagsQuery, PopularTagsQueryVariables>;
|
||||||
@@ -1621,13 +1627,13 @@ export const FeedDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useFeedQuery(baseOptions?: Apollo.QueryHookOptions<FeedQuery, FeedQueryVariables>) {
|
export function useFeedQuery(baseOptions?: Apollo.QueryHookOptions<FeedQuery, FeedQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<FeedQuery, FeedQueryVariables>(FeedDocument, options);
|
return Apollo.useQuery<FeedQuery, FeedQueryVariables>(FeedDocument, options);
|
||||||
}
|
}
|
||||||
export function useFeedLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FeedQuery, FeedQueryVariables>) {
|
export function useFeedLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FeedQuery, FeedQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<FeedQuery, FeedQueryVariables>(FeedDocument, options);
|
return Apollo.useLazyQuery<FeedQuery, FeedQueryVariables>(FeedDocument, options);
|
||||||
}
|
}
|
||||||
export type FeedQueryHookResult = ReturnType<typeof useFeedQuery>;
|
export type FeedQueryHookResult = ReturnType<typeof useFeedQuery>;
|
||||||
export type FeedLazyQueryHookResult = ReturnType<typeof useFeedLazyQuery>;
|
export type FeedLazyQueryHookResult = ReturnType<typeof useFeedLazyQuery>;
|
||||||
export type FeedQueryResult = Apollo.QueryResult<FeedQuery, FeedQueryVariables>;
|
export type FeedQueryResult = Apollo.QueryResult<FeedQuery, FeedQueryVariables>;
|
||||||
@@ -1726,13 +1732,13 @@ export const PostDetailsDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function usePostDetailsQuery(baseOptions: Apollo.QueryHookOptions<PostDetailsQuery, PostDetailsQueryVariables>) {
|
export function usePostDetailsQuery(baseOptions: Apollo.QueryHookOptions<PostDetailsQuery, PostDetailsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<PostDetailsQuery, PostDetailsQueryVariables>(PostDetailsDocument, options);
|
return Apollo.useQuery<PostDetailsQuery, PostDetailsQueryVariables>(PostDetailsDocument, options);
|
||||||
}
|
}
|
||||||
export function usePostDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<PostDetailsQuery, PostDetailsQueryVariables>) {
|
export function usePostDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<PostDetailsQuery, PostDetailsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<PostDetailsQuery, PostDetailsQueryVariables>(PostDetailsDocument, options);
|
return Apollo.useLazyQuery<PostDetailsQuery, PostDetailsQueryVariables>(PostDetailsDocument, options);
|
||||||
}
|
}
|
||||||
export type PostDetailsQueryHookResult = ReturnType<typeof usePostDetailsQuery>;
|
export type PostDetailsQueryHookResult = ReturnType<typeof usePostDetailsQuery>;
|
||||||
export type PostDetailsLazyQueryHookResult = ReturnType<typeof usePostDetailsLazyQuery>;
|
export type PostDetailsLazyQueryHookResult = ReturnType<typeof usePostDetailsLazyQuery>;
|
||||||
export type PostDetailsQueryResult = Apollo.QueryResult<PostDetailsQuery, PostDetailsQueryVariables>;
|
export type PostDetailsQueryResult = Apollo.QueryResult<PostDetailsQuery, PostDetailsQueryVariables>;
|
||||||
@@ -1761,13 +1767,13 @@ export const MyProfileAboutDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useMyProfileAboutQuery(baseOptions?: Apollo.QueryHookOptions<MyProfileAboutQuery, MyProfileAboutQueryVariables>) {
|
export function useMyProfileAboutQuery(baseOptions?: Apollo.QueryHookOptions<MyProfileAboutQuery, MyProfileAboutQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<MyProfileAboutQuery, MyProfileAboutQueryVariables>(MyProfileAboutDocument, options);
|
return Apollo.useQuery<MyProfileAboutQuery, MyProfileAboutQueryVariables>(MyProfileAboutDocument, options);
|
||||||
}
|
}
|
||||||
export function useMyProfileAboutLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MyProfileAboutQuery, MyProfileAboutQueryVariables>) {
|
export function useMyProfileAboutLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MyProfileAboutQuery, MyProfileAboutQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<MyProfileAboutQuery, MyProfileAboutQueryVariables>(MyProfileAboutDocument, options);
|
return Apollo.useLazyQuery<MyProfileAboutQuery, MyProfileAboutQueryVariables>(MyProfileAboutDocument, options);
|
||||||
}
|
}
|
||||||
export type MyProfileAboutQueryHookResult = ReturnType<typeof useMyProfileAboutQuery>;
|
export type MyProfileAboutQueryHookResult = ReturnType<typeof useMyProfileAboutQuery>;
|
||||||
export type MyProfileAboutLazyQueryHookResult = ReturnType<typeof useMyProfileAboutLazyQuery>;
|
export type MyProfileAboutLazyQueryHookResult = ReturnType<typeof useMyProfileAboutLazyQuery>;
|
||||||
export type MyProfileAboutQueryResult = Apollo.QueryResult<MyProfileAboutQuery, MyProfileAboutQueryVariables>;
|
export type MyProfileAboutQueryResult = Apollo.QueryResult<MyProfileAboutQuery, MyProfileAboutQueryVariables>;
|
||||||
@@ -1799,9 +1805,9 @@ export type UpdateProfileAboutMutationFn = Apollo.MutationFunction<UpdateProfile
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useUpdateProfileAboutMutation(baseOptions?: Apollo.MutationHookOptions<UpdateProfileAboutMutation, UpdateProfileAboutMutationVariables>) {
|
export function useUpdateProfileAboutMutation(baseOptions?: Apollo.MutationHookOptions<UpdateProfileAboutMutation, UpdateProfileAboutMutationVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useMutation<UpdateProfileAboutMutation, UpdateProfileAboutMutationVariables>(UpdateProfileAboutDocument, options);
|
return Apollo.useMutation<UpdateProfileAboutMutation, UpdateProfileAboutMutationVariables>(UpdateProfileAboutDocument, options);
|
||||||
}
|
}
|
||||||
export type UpdateProfileAboutMutationHookResult = ReturnType<typeof useUpdateProfileAboutMutation>;
|
export type UpdateProfileAboutMutationHookResult = ReturnType<typeof useUpdateProfileAboutMutation>;
|
||||||
export type UpdateProfileAboutMutationResult = Apollo.MutationResult<UpdateProfileAboutMutation>;
|
export type UpdateProfileAboutMutationResult = Apollo.MutationResult<UpdateProfileAboutMutation>;
|
||||||
export type UpdateProfileAboutMutationOptions = Apollo.BaseMutationOptions<UpdateProfileAboutMutation, UpdateProfileAboutMutationVariables>;
|
export type UpdateProfileAboutMutationOptions = Apollo.BaseMutationOptions<UpdateProfileAboutMutation, UpdateProfileAboutMutationVariables>;
|
||||||
@@ -1836,13 +1842,13 @@ export const MyProfilePreferencesDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useMyProfilePreferencesQuery(baseOptions?: Apollo.QueryHookOptions<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>) {
|
export function useMyProfilePreferencesQuery(baseOptions?: Apollo.QueryHookOptions<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>(MyProfilePreferencesDocument, options);
|
return Apollo.useQuery<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>(MyProfilePreferencesDocument, options);
|
||||||
}
|
}
|
||||||
export function useMyProfilePreferencesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>) {
|
export function useMyProfilePreferencesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>(MyProfilePreferencesDocument, options);
|
return Apollo.useLazyQuery<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>(MyProfilePreferencesDocument, options);
|
||||||
}
|
}
|
||||||
export type MyProfilePreferencesQueryHookResult = ReturnType<typeof useMyProfilePreferencesQuery>;
|
export type MyProfilePreferencesQueryHookResult = ReturnType<typeof useMyProfilePreferencesQuery>;
|
||||||
export type MyProfilePreferencesLazyQueryHookResult = ReturnType<typeof useMyProfilePreferencesLazyQuery>;
|
export type MyProfilePreferencesLazyQueryHookResult = ReturnType<typeof useMyProfilePreferencesLazyQuery>;
|
||||||
export type MyProfilePreferencesQueryResult = Apollo.QueryResult<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>;
|
export type MyProfilePreferencesQueryResult = Apollo.QueryResult<MyProfilePreferencesQuery, MyProfilePreferencesQueryVariables>;
|
||||||
@@ -1879,9 +1885,9 @@ export type UpdateUserPreferencesMutationFn = Apollo.MutationFunction<UpdateUser
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useUpdateUserPreferencesMutation(baseOptions?: Apollo.MutationHookOptions<UpdateUserPreferencesMutation, UpdateUserPreferencesMutationVariables>) {
|
export function useUpdateUserPreferencesMutation(baseOptions?: Apollo.MutationHookOptions<UpdateUserPreferencesMutation, UpdateUserPreferencesMutationVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useMutation<UpdateUserPreferencesMutation, UpdateUserPreferencesMutationVariables>(UpdateUserPreferencesDocument, options);
|
return Apollo.useMutation<UpdateUserPreferencesMutation, UpdateUserPreferencesMutationVariables>(UpdateUserPreferencesDocument, options);
|
||||||
}
|
}
|
||||||
export type UpdateUserPreferencesMutationHookResult = ReturnType<typeof useUpdateUserPreferencesMutation>;
|
export type UpdateUserPreferencesMutationHookResult = ReturnType<typeof useUpdateUserPreferencesMutation>;
|
||||||
export type UpdateUserPreferencesMutationResult = Apollo.MutationResult<UpdateUserPreferencesMutation>;
|
export type UpdateUserPreferencesMutationResult = Apollo.MutationResult<UpdateUserPreferencesMutation>;
|
||||||
export type UpdateUserPreferencesMutationOptions = Apollo.BaseMutationOptions<UpdateUserPreferencesMutation, UpdateUserPreferencesMutationVariables>;
|
export type UpdateUserPreferencesMutationOptions = Apollo.BaseMutationOptions<UpdateUserPreferencesMutation, UpdateUserPreferencesMutationVariables>;
|
||||||
@@ -1919,13 +1925,13 @@ export const MyProfileRolesSkillsDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useMyProfileRolesSkillsQuery(baseOptions?: Apollo.QueryHookOptions<MyProfileRolesSkillsQuery, MyProfileRolesSkillsQueryVariables>) {
|
export function useMyProfileRolesSkillsQuery(baseOptions?: Apollo.QueryHookOptions<MyProfileRolesSkillsQuery, MyProfileRolesSkillsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<MyProfileRolesSkillsQuery, MyProfileRolesSkillsQueryVariables>(MyProfileRolesSkillsDocument, options);
|
return Apollo.useQuery<MyProfileRolesSkillsQuery, MyProfileRolesSkillsQueryVariables>(MyProfileRolesSkillsDocument, options);
|
||||||
}
|
}
|
||||||
export function useMyProfileRolesSkillsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MyProfileRolesSkillsQuery, MyProfileRolesSkillsQueryVariables>) {
|
export function useMyProfileRolesSkillsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MyProfileRolesSkillsQuery, MyProfileRolesSkillsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<MyProfileRolesSkillsQuery, MyProfileRolesSkillsQueryVariables>(MyProfileRolesSkillsDocument, options);
|
return Apollo.useLazyQuery<MyProfileRolesSkillsQuery, MyProfileRolesSkillsQueryVariables>(MyProfileRolesSkillsDocument, options);
|
||||||
}
|
}
|
||||||
export type MyProfileRolesSkillsQueryHookResult = ReturnType<typeof useMyProfileRolesSkillsQuery>;
|
export type MyProfileRolesSkillsQueryHookResult = ReturnType<typeof useMyProfileRolesSkillsQuery>;
|
||||||
export type MyProfileRolesSkillsLazyQueryHookResult = ReturnType<typeof useMyProfileRolesSkillsLazyQuery>;
|
export type MyProfileRolesSkillsLazyQueryHookResult = ReturnType<typeof useMyProfileRolesSkillsLazyQuery>;
|
||||||
export type MyProfileRolesSkillsQueryResult = Apollo.QueryResult<MyProfileRolesSkillsQuery, MyProfileRolesSkillsQueryVariables>;
|
export type MyProfileRolesSkillsQueryResult = Apollo.QueryResult<MyProfileRolesSkillsQuery, MyProfileRolesSkillsQueryVariables>;
|
||||||
@@ -1966,9 +1972,9 @@ export type UpdateUserRolesSkillsMutationFn = Apollo.MutationFunction<UpdateUser
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useUpdateUserRolesSkillsMutation(baseOptions?: Apollo.MutationHookOptions<UpdateUserRolesSkillsMutation, UpdateUserRolesSkillsMutationVariables>) {
|
export function useUpdateUserRolesSkillsMutation(baseOptions?: Apollo.MutationHookOptions<UpdateUserRolesSkillsMutation, UpdateUserRolesSkillsMutationVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useMutation<UpdateUserRolesSkillsMutation, UpdateUserRolesSkillsMutationVariables>(UpdateUserRolesSkillsDocument, options);
|
return Apollo.useMutation<UpdateUserRolesSkillsMutation, UpdateUserRolesSkillsMutationVariables>(UpdateUserRolesSkillsDocument, options);
|
||||||
}
|
}
|
||||||
export type UpdateUserRolesSkillsMutationHookResult = ReturnType<typeof useUpdateUserRolesSkillsMutation>;
|
export type UpdateUserRolesSkillsMutationHookResult = ReturnType<typeof useUpdateUserRolesSkillsMutation>;
|
||||||
export type UpdateUserRolesSkillsMutationResult = Apollo.MutationResult<UpdateUserRolesSkillsMutation>;
|
export type UpdateUserRolesSkillsMutationResult = Apollo.MutationResult<UpdateUserRolesSkillsMutation>;
|
||||||
export type UpdateUserRolesSkillsMutationOptions = Apollo.BaseMutationOptions<UpdateUserRolesSkillsMutation, UpdateUserRolesSkillsMutationVariables>;
|
export type UpdateUserRolesSkillsMutationOptions = Apollo.BaseMutationOptions<UpdateUserRolesSkillsMutation, UpdateUserRolesSkillsMutationVariables>;
|
||||||
@@ -2022,13 +2028,13 @@ ${UserRolesSkillsFragmentDoc}`;
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useProfileQuery(baseOptions: Apollo.QueryHookOptions<ProfileQuery, ProfileQueryVariables>) {
|
export function useProfileQuery(baseOptions: Apollo.QueryHookOptions<ProfileQuery, ProfileQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<ProfileQuery, ProfileQueryVariables>(ProfileDocument, options);
|
return Apollo.useQuery<ProfileQuery, ProfileQueryVariables>(ProfileDocument, options);
|
||||||
}
|
}
|
||||||
export function useProfileLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ProfileQuery, ProfileQueryVariables>) {
|
export function useProfileLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ProfileQuery, ProfileQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<ProfileQuery, ProfileQueryVariables>(ProfileDocument, options);
|
return Apollo.useLazyQuery<ProfileQuery, ProfileQueryVariables>(ProfileDocument, options);
|
||||||
}
|
}
|
||||||
export type ProfileQueryHookResult = ReturnType<typeof useProfileQuery>;
|
export type ProfileQueryHookResult = ReturnType<typeof useProfileQuery>;
|
||||||
export type ProfileLazyQueryHookResult = ReturnType<typeof useProfileLazyQuery>;
|
export type ProfileLazyQueryHookResult = ReturnType<typeof useProfileLazyQuery>;
|
||||||
export type ProfileQueryResult = Apollo.QueryResult<ProfileQuery, ProfileQueryVariables>;
|
export type ProfileQueryResult = Apollo.QueryResult<ProfileQuery, ProfileQueryVariables>;
|
||||||
@@ -2070,13 +2076,13 @@ export const CategoryPageDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useCategoryPageQuery(baseOptions: Apollo.QueryHookOptions<CategoryPageQuery, CategoryPageQueryVariables>) {
|
export function useCategoryPageQuery(baseOptions: Apollo.QueryHookOptions<CategoryPageQuery, CategoryPageQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<CategoryPageQuery, CategoryPageQueryVariables>(CategoryPageDocument, options);
|
return Apollo.useQuery<CategoryPageQuery, CategoryPageQueryVariables>(CategoryPageDocument, options);
|
||||||
}
|
}
|
||||||
export function useCategoryPageLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<CategoryPageQuery, CategoryPageQueryVariables>) {
|
export function useCategoryPageLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<CategoryPageQuery, CategoryPageQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<CategoryPageQuery, CategoryPageQueryVariables>(CategoryPageDocument, options);
|
return Apollo.useLazyQuery<CategoryPageQuery, CategoryPageQueryVariables>(CategoryPageDocument, options);
|
||||||
}
|
}
|
||||||
export type CategoryPageQueryHookResult = ReturnType<typeof useCategoryPageQuery>;
|
export type CategoryPageQueryHookResult = ReturnType<typeof useCategoryPageQuery>;
|
||||||
export type CategoryPageLazyQueryHookResult = ReturnType<typeof useCategoryPageLazyQuery>;
|
export type CategoryPageLazyQueryHookResult = ReturnType<typeof useCategoryPageLazyQuery>;
|
||||||
export type CategoryPageQueryResult = Apollo.QueryResult<CategoryPageQuery, CategoryPageQueryVariables>;
|
export type CategoryPageQueryResult = Apollo.QueryResult<CategoryPageQuery, CategoryPageQueryVariables>;
|
||||||
@@ -2106,13 +2112,13 @@ export const AllCategoriesDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useAllCategoriesQuery(baseOptions?: Apollo.QueryHookOptions<AllCategoriesQuery, AllCategoriesQueryVariables>) {
|
export function useAllCategoriesQuery(baseOptions?: Apollo.QueryHookOptions<AllCategoriesQuery, AllCategoriesQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<AllCategoriesQuery, AllCategoriesQueryVariables>(AllCategoriesDocument, options);
|
return Apollo.useQuery<AllCategoriesQuery, AllCategoriesQueryVariables>(AllCategoriesDocument, options);
|
||||||
}
|
}
|
||||||
export function useAllCategoriesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<AllCategoriesQuery, AllCategoriesQueryVariables>) {
|
export function useAllCategoriesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<AllCategoriesQuery, AllCategoriesQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<AllCategoriesQuery, AllCategoriesQueryVariables>(AllCategoriesDocument, options);
|
return Apollo.useLazyQuery<AllCategoriesQuery, AllCategoriesQueryVariables>(AllCategoriesDocument, options);
|
||||||
}
|
}
|
||||||
export type AllCategoriesQueryHookResult = ReturnType<typeof useAllCategoriesQuery>;
|
export type AllCategoriesQueryHookResult = ReturnType<typeof useAllCategoriesQuery>;
|
||||||
export type AllCategoriesLazyQueryHookResult = ReturnType<typeof useAllCategoriesLazyQuery>;
|
export type AllCategoriesLazyQueryHookResult = ReturnType<typeof useAllCategoriesLazyQuery>;
|
||||||
export type AllCategoriesQueryResult = Apollo.QueryResult<AllCategoriesQuery, AllCategoriesQueryVariables>;
|
export type AllCategoriesQueryResult = Apollo.QueryResult<AllCategoriesQuery, AllCategoriesQueryVariables>;
|
||||||
@@ -2171,13 +2177,13 @@ export const ExploreProjectsDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useExploreProjectsQuery(baseOptions?: Apollo.QueryHookOptions<ExploreProjectsQuery, ExploreProjectsQueryVariables>) {
|
export function useExploreProjectsQuery(baseOptions?: Apollo.QueryHookOptions<ExploreProjectsQuery, ExploreProjectsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<ExploreProjectsQuery, ExploreProjectsQueryVariables>(ExploreProjectsDocument, options);
|
return Apollo.useQuery<ExploreProjectsQuery, ExploreProjectsQueryVariables>(ExploreProjectsDocument, options);
|
||||||
}
|
}
|
||||||
export function useExploreProjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExploreProjectsQuery, ExploreProjectsQueryVariables>) {
|
export function useExploreProjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExploreProjectsQuery, ExploreProjectsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<ExploreProjectsQuery, ExploreProjectsQueryVariables>(ExploreProjectsDocument, options);
|
return Apollo.useLazyQuery<ExploreProjectsQuery, ExploreProjectsQueryVariables>(ExploreProjectsDocument, options);
|
||||||
}
|
}
|
||||||
export type ExploreProjectsQueryHookResult = ReturnType<typeof useExploreProjectsQuery>;
|
export type ExploreProjectsQueryHookResult = ReturnType<typeof useExploreProjectsQuery>;
|
||||||
export type ExploreProjectsLazyQueryHookResult = ReturnType<typeof useExploreProjectsLazyQuery>;
|
export type ExploreProjectsLazyQueryHookResult = ReturnType<typeof useExploreProjectsLazyQuery>;
|
||||||
export type ExploreProjectsQueryResult = Apollo.QueryResult<ExploreProjectsQuery, ExploreProjectsQueryVariables>;
|
export type ExploreProjectsQueryResult = Apollo.QueryResult<ExploreProjectsQuery, ExploreProjectsQueryVariables>;
|
||||||
@@ -2212,13 +2218,13 @@ export const HottestProjectsDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useHottestProjectsQuery(baseOptions?: Apollo.QueryHookOptions<HottestProjectsQuery, HottestProjectsQueryVariables>) {
|
export function useHottestProjectsQuery(baseOptions?: Apollo.QueryHookOptions<HottestProjectsQuery, HottestProjectsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<HottestProjectsQuery, HottestProjectsQueryVariables>(HottestProjectsDocument, options);
|
return Apollo.useQuery<HottestProjectsQuery, HottestProjectsQueryVariables>(HottestProjectsDocument, options);
|
||||||
}
|
}
|
||||||
export function useHottestProjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<HottestProjectsQuery, HottestProjectsQueryVariables>) {
|
export function useHottestProjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<HottestProjectsQuery, HottestProjectsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<HottestProjectsQuery, HottestProjectsQueryVariables>(HottestProjectsDocument, options);
|
return Apollo.useLazyQuery<HottestProjectsQuery, HottestProjectsQueryVariables>(HottestProjectsDocument, options);
|
||||||
}
|
}
|
||||||
export type HottestProjectsQueryHookResult = ReturnType<typeof useHottestProjectsQuery>;
|
export type HottestProjectsQueryHookResult = ReturnType<typeof useHottestProjectsQuery>;
|
||||||
export type HottestProjectsLazyQueryHookResult = ReturnType<typeof useHottestProjectsLazyQuery>;
|
export type HottestProjectsLazyQueryHookResult = ReturnType<typeof useHottestProjectsLazyQuery>;
|
||||||
export type HottestProjectsQueryResult = Apollo.QueryResult<HottestProjectsQuery, HottestProjectsQueryVariables>;
|
export type HottestProjectsQueryResult = Apollo.QueryResult<HottestProjectsQuery, HottestProjectsQueryVariables>;
|
||||||
@@ -2270,13 +2276,13 @@ export const ProjectDetailsDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useProjectDetailsQuery(baseOptions: Apollo.QueryHookOptions<ProjectDetailsQuery, ProjectDetailsQueryVariables>) {
|
export function useProjectDetailsQuery(baseOptions: Apollo.QueryHookOptions<ProjectDetailsQuery, ProjectDetailsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<ProjectDetailsQuery, ProjectDetailsQueryVariables>(ProjectDetailsDocument, options);
|
return Apollo.useQuery<ProjectDetailsQuery, ProjectDetailsQueryVariables>(ProjectDetailsDocument, options);
|
||||||
}
|
}
|
||||||
export function useProjectDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ProjectDetailsQuery, ProjectDetailsQueryVariables>) {
|
export function useProjectDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ProjectDetailsQuery, ProjectDetailsQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<ProjectDetailsQuery, ProjectDetailsQueryVariables>(ProjectDetailsDocument, options);
|
return Apollo.useLazyQuery<ProjectDetailsQuery, ProjectDetailsQueryVariables>(ProjectDetailsDocument, options);
|
||||||
}
|
}
|
||||||
export type ProjectDetailsQueryHookResult = ReturnType<typeof useProjectDetailsQuery>;
|
export type ProjectDetailsQueryHookResult = ReturnType<typeof useProjectDetailsQuery>;
|
||||||
export type ProjectDetailsLazyQueryHookResult = ReturnType<typeof useProjectDetailsLazyQuery>;
|
export type ProjectDetailsLazyQueryHookResult = ReturnType<typeof useProjectDetailsLazyQuery>;
|
||||||
export type ProjectDetailsQueryResult = Apollo.QueryResult<ProjectDetailsQuery, ProjectDetailsQueryVariables>;
|
export type ProjectDetailsQueryResult = Apollo.QueryResult<ProjectDetailsQuery, ProjectDetailsQueryVariables>;
|
||||||
@@ -2306,13 +2312,13 @@ export const GetAllRolesDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useGetAllRolesQuery(baseOptions?: Apollo.QueryHookOptions<GetAllRolesQuery, GetAllRolesQueryVariables>) {
|
export function useGetAllRolesQuery(baseOptions?: Apollo.QueryHookOptions<GetAllRolesQuery, GetAllRolesQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<GetAllRolesQuery, GetAllRolesQueryVariables>(GetAllRolesDocument, options);
|
return Apollo.useQuery<GetAllRolesQuery, GetAllRolesQueryVariables>(GetAllRolesDocument, options);
|
||||||
}
|
}
|
||||||
export function useGetAllRolesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetAllRolesQuery, GetAllRolesQueryVariables>) {
|
export function useGetAllRolesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetAllRolesQuery, GetAllRolesQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<GetAllRolesQuery, GetAllRolesQueryVariables>(GetAllRolesDocument, options);
|
return Apollo.useLazyQuery<GetAllRolesQuery, GetAllRolesQueryVariables>(GetAllRolesDocument, options);
|
||||||
}
|
}
|
||||||
export type GetAllRolesQueryHookResult = ReturnType<typeof useGetAllRolesQuery>;
|
export type GetAllRolesQueryHookResult = ReturnType<typeof useGetAllRolesQuery>;
|
||||||
export type GetAllRolesLazyQueryHookResult = ReturnType<typeof useGetAllRolesLazyQuery>;
|
export type GetAllRolesLazyQueryHookResult = ReturnType<typeof useGetAllRolesLazyQuery>;
|
||||||
export type GetAllRolesQueryResult = Apollo.QueryResult<GetAllRolesQuery, GetAllRolesQueryVariables>;
|
export type GetAllRolesQueryResult = Apollo.QueryResult<GetAllRolesQuery, GetAllRolesQueryVariables>;
|
||||||
@@ -2376,13 +2382,13 @@ export const GetMakersInTournamentDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useGetMakersInTournamentQuery(baseOptions: Apollo.QueryHookOptions<GetMakersInTournamentQuery, GetMakersInTournamentQueryVariables>) {
|
export function useGetMakersInTournamentQuery(baseOptions: Apollo.QueryHookOptions<GetMakersInTournamentQuery, GetMakersInTournamentQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<GetMakersInTournamentQuery, GetMakersInTournamentQueryVariables>(GetMakersInTournamentDocument, options);
|
return Apollo.useQuery<GetMakersInTournamentQuery, GetMakersInTournamentQueryVariables>(GetMakersInTournamentDocument, options);
|
||||||
}
|
}
|
||||||
export function useGetMakersInTournamentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetMakersInTournamentQuery, GetMakersInTournamentQueryVariables>) {
|
export function useGetMakersInTournamentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetMakersInTournamentQuery, GetMakersInTournamentQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<GetMakersInTournamentQuery, GetMakersInTournamentQueryVariables>(GetMakersInTournamentDocument, options);
|
return Apollo.useLazyQuery<GetMakersInTournamentQuery, GetMakersInTournamentQueryVariables>(GetMakersInTournamentDocument, options);
|
||||||
}
|
}
|
||||||
export type GetMakersInTournamentQueryHookResult = ReturnType<typeof useGetMakersInTournamentQuery>;
|
export type GetMakersInTournamentQueryHookResult = ReturnType<typeof useGetMakersInTournamentQuery>;
|
||||||
export type GetMakersInTournamentLazyQueryHookResult = ReturnType<typeof useGetMakersInTournamentLazyQuery>;
|
export type GetMakersInTournamentLazyQueryHookResult = ReturnType<typeof useGetMakersInTournamentLazyQuery>;
|
||||||
export type GetMakersInTournamentQueryResult = Apollo.QueryResult<GetMakersInTournamentQuery, GetMakersInTournamentQueryVariables>;
|
export type GetMakersInTournamentQueryResult = Apollo.QueryResult<GetMakersInTournamentQuery, GetMakersInTournamentQueryVariables>;
|
||||||
@@ -2439,13 +2445,13 @@ export const GetProjectsInTournamentDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useGetProjectsInTournamentQuery(baseOptions: Apollo.QueryHookOptions<GetProjectsInTournamentQuery, GetProjectsInTournamentQueryVariables>) {
|
export function useGetProjectsInTournamentQuery(baseOptions: Apollo.QueryHookOptions<GetProjectsInTournamentQuery, GetProjectsInTournamentQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<GetProjectsInTournamentQuery, GetProjectsInTournamentQueryVariables>(GetProjectsInTournamentDocument, options);
|
return Apollo.useQuery<GetProjectsInTournamentQuery, GetProjectsInTournamentQueryVariables>(GetProjectsInTournamentDocument, options);
|
||||||
}
|
}
|
||||||
export function useGetProjectsInTournamentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetProjectsInTournamentQuery, GetProjectsInTournamentQueryVariables>) {
|
export function useGetProjectsInTournamentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetProjectsInTournamentQuery, GetProjectsInTournamentQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<GetProjectsInTournamentQuery, GetProjectsInTournamentQueryVariables>(GetProjectsInTournamentDocument, options);
|
return Apollo.useLazyQuery<GetProjectsInTournamentQuery, GetProjectsInTournamentQueryVariables>(GetProjectsInTournamentDocument, options);
|
||||||
}
|
}
|
||||||
export type GetProjectsInTournamentQueryHookResult = ReturnType<typeof useGetProjectsInTournamentQuery>;
|
export type GetProjectsInTournamentQueryHookResult = ReturnType<typeof useGetProjectsInTournamentQuery>;
|
||||||
export type GetProjectsInTournamentLazyQueryHookResult = ReturnType<typeof useGetProjectsInTournamentLazyQuery>;
|
export type GetProjectsInTournamentLazyQueryHookResult = ReturnType<typeof useGetProjectsInTournamentLazyQuery>;
|
||||||
export type GetProjectsInTournamentQueryResult = Apollo.QueryResult<GetProjectsInTournamentQuery, GetProjectsInTournamentQueryVariables>;
|
export type GetProjectsInTournamentQueryResult = Apollo.QueryResult<GetProjectsInTournamentQuery, GetProjectsInTournamentQueryVariables>;
|
||||||
@@ -2479,9 +2485,9 @@ export type UpdateTournamentRegistrationMutationFn = Apollo.MutationFunction<Upd
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useUpdateTournamentRegistrationMutation(baseOptions?: Apollo.MutationHookOptions<UpdateTournamentRegistrationMutation, UpdateTournamentRegistrationMutationVariables>) {
|
export function useUpdateTournamentRegistrationMutation(baseOptions?: Apollo.MutationHookOptions<UpdateTournamentRegistrationMutation, UpdateTournamentRegistrationMutationVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useMutation<UpdateTournamentRegistrationMutation, UpdateTournamentRegistrationMutationVariables>(UpdateTournamentRegistrationDocument, options);
|
return Apollo.useMutation<UpdateTournamentRegistrationMutation, UpdateTournamentRegistrationMutationVariables>(UpdateTournamentRegistrationDocument, options);
|
||||||
}
|
}
|
||||||
export type UpdateTournamentRegistrationMutationHookResult = ReturnType<typeof useUpdateTournamentRegistrationMutation>;
|
export type UpdateTournamentRegistrationMutationHookResult = ReturnType<typeof useUpdateTournamentRegistrationMutation>;
|
||||||
export type UpdateTournamentRegistrationMutationResult = Apollo.MutationResult<UpdateTournamentRegistrationMutation>;
|
export type UpdateTournamentRegistrationMutationResult = Apollo.MutationResult<UpdateTournamentRegistrationMutation>;
|
||||||
export type UpdateTournamentRegistrationMutationOptions = Apollo.BaseMutationOptions<UpdateTournamentRegistrationMutation, UpdateTournamentRegistrationMutationVariables>;
|
export type UpdateTournamentRegistrationMutationOptions = Apollo.BaseMutationOptions<UpdateTournamentRegistrationMutation, UpdateTournamentRegistrationMutationVariables>;
|
||||||
@@ -2514,9 +2520,9 @@ export type RegisterInTournamentMutationFn = Apollo.MutationFunction<RegisterInT
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useRegisterInTournamentMutation(baseOptions?: Apollo.MutationHookOptions<RegisterInTournamentMutation, RegisterInTournamentMutationVariables>) {
|
export function useRegisterInTournamentMutation(baseOptions?: Apollo.MutationHookOptions<RegisterInTournamentMutation, RegisterInTournamentMutationVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useMutation<RegisterInTournamentMutation, RegisterInTournamentMutationVariables>(RegisterInTournamentDocument, options);
|
return Apollo.useMutation<RegisterInTournamentMutation, RegisterInTournamentMutationVariables>(RegisterInTournamentDocument, options);
|
||||||
}
|
}
|
||||||
export type RegisterInTournamentMutationHookResult = ReturnType<typeof useRegisterInTournamentMutation>;
|
export type RegisterInTournamentMutationHookResult = ReturnType<typeof useRegisterInTournamentMutation>;
|
||||||
export type RegisterInTournamentMutationResult = Apollo.MutationResult<RegisterInTournamentMutation>;
|
export type RegisterInTournamentMutationResult = Apollo.MutationResult<RegisterInTournamentMutation>;
|
||||||
export type RegisterInTournamentMutationOptions = Apollo.BaseMutationOptions<RegisterInTournamentMutation, RegisterInTournamentMutationVariables>;
|
export type RegisterInTournamentMutationOptions = Apollo.BaseMutationOptions<RegisterInTournamentMutation, RegisterInTournamentMutationVariables>;
|
||||||
@@ -2556,13 +2562,13 @@ export const MeTournamentDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useMeTournamentQuery(baseOptions: Apollo.QueryHookOptions<MeTournamentQuery, MeTournamentQueryVariables>) {
|
export function useMeTournamentQuery(baseOptions: Apollo.QueryHookOptions<MeTournamentQuery, MeTournamentQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<MeTournamentQuery, MeTournamentQueryVariables>(MeTournamentDocument, options);
|
return Apollo.useQuery<MeTournamentQuery, MeTournamentQueryVariables>(MeTournamentDocument, options);
|
||||||
}
|
}
|
||||||
export function useMeTournamentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MeTournamentQuery, MeTournamentQueryVariables>) {
|
export function useMeTournamentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MeTournamentQuery, MeTournamentQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<MeTournamentQuery, MeTournamentQueryVariables>(MeTournamentDocument, options);
|
return Apollo.useLazyQuery<MeTournamentQuery, MeTournamentQueryVariables>(MeTournamentDocument, options);
|
||||||
}
|
}
|
||||||
export type MeTournamentQueryHookResult = ReturnType<typeof useMeTournamentQuery>;
|
export type MeTournamentQueryHookResult = ReturnType<typeof useMeTournamentQuery>;
|
||||||
export type MeTournamentLazyQueryHookResult = ReturnType<typeof useMeTournamentLazyQuery>;
|
export type MeTournamentLazyQueryHookResult = ReturnType<typeof useMeTournamentLazyQuery>;
|
||||||
export type MeTournamentQueryResult = Apollo.QueryResult<MeTournamentQuery, MeTournamentQueryVariables>;
|
export type MeTournamentQueryResult = Apollo.QueryResult<MeTournamentQuery, MeTournamentQueryVariables>;
|
||||||
@@ -2650,13 +2656,13 @@ export const GetTournamentByIdDocument = gql`
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useGetTournamentByIdQuery(baseOptions: Apollo.QueryHookOptions<GetTournamentByIdQuery, GetTournamentByIdQueryVariables>) {
|
export function useGetTournamentByIdQuery(baseOptions: Apollo.QueryHookOptions<GetTournamentByIdQuery, GetTournamentByIdQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useQuery<GetTournamentByIdQuery, GetTournamentByIdQueryVariables>(GetTournamentByIdDocument, options);
|
return Apollo.useQuery<GetTournamentByIdQuery, GetTournamentByIdQueryVariables>(GetTournamentByIdDocument, options);
|
||||||
}
|
}
|
||||||
export function useGetTournamentByIdLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetTournamentByIdQuery, GetTournamentByIdQueryVariables>) {
|
export function useGetTournamentByIdLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetTournamentByIdQuery, GetTournamentByIdQueryVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useLazyQuery<GetTournamentByIdQuery, GetTournamentByIdQueryVariables>(GetTournamentByIdDocument, options);
|
return Apollo.useLazyQuery<GetTournamentByIdQuery, GetTournamentByIdQueryVariables>(GetTournamentByIdDocument, options);
|
||||||
}
|
}
|
||||||
export type GetTournamentByIdQueryHookResult = ReturnType<typeof useGetTournamentByIdQuery>;
|
export type GetTournamentByIdQueryHookResult = ReturnType<typeof useGetTournamentByIdQuery>;
|
||||||
export type GetTournamentByIdLazyQueryHookResult = ReturnType<typeof useGetTournamentByIdLazyQuery>;
|
export type GetTournamentByIdLazyQueryHookResult = ReturnType<typeof useGetTournamentByIdLazyQuery>;
|
||||||
export type GetTournamentByIdQueryResult = Apollo.QueryResult<GetTournamentByIdQuery, GetTournamentByIdQueryVariables>;
|
export type GetTournamentByIdQueryResult = Apollo.QueryResult<GetTournamentByIdQuery, GetTournamentByIdQueryVariables>;
|
||||||
@@ -2695,9 +2701,9 @@ export type VoteMutationFn = Apollo.MutationFunction<VoteMutation, VoteMutationV
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useVoteMutation(baseOptions?: Apollo.MutationHookOptions<VoteMutation, VoteMutationVariables>) {
|
export function useVoteMutation(baseOptions?: Apollo.MutationHookOptions<VoteMutation, VoteMutationVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useMutation<VoteMutation, VoteMutationVariables>(VoteDocument, options);
|
return Apollo.useMutation<VoteMutation, VoteMutationVariables>(VoteDocument, options);
|
||||||
}
|
}
|
||||||
export type VoteMutationHookResult = ReturnType<typeof useVoteMutation>;
|
export type VoteMutationHookResult = ReturnType<typeof useVoteMutation>;
|
||||||
export type VoteMutationResult = Apollo.MutationResult<VoteMutation>;
|
export type VoteMutationResult = Apollo.MutationResult<VoteMutation>;
|
||||||
export type VoteMutationOptions = Apollo.BaseMutationOptions<VoteMutation, VoteMutationVariables>;
|
export type VoteMutationOptions = Apollo.BaseMutationOptions<VoteMutation, VoteMutationVariables>;
|
||||||
@@ -2735,9 +2741,9 @@ export type ConfirmVoteMutationFn = Apollo.MutationFunction<ConfirmVoteMutation,
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useConfirmVoteMutation(baseOptions?: Apollo.MutationHookOptions<ConfirmVoteMutation, ConfirmVoteMutationVariables>) {
|
export function useConfirmVoteMutation(baseOptions?: Apollo.MutationHookOptions<ConfirmVoteMutation, ConfirmVoteMutationVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = { ...defaultOptions, ...baseOptions }
|
||||||
return Apollo.useMutation<ConfirmVoteMutation, ConfirmVoteMutationVariables>(ConfirmVoteDocument, options);
|
return Apollo.useMutation<ConfirmVoteMutation, ConfirmVoteMutationVariables>(ConfirmVoteDocument, options);
|
||||||
}
|
}
|
||||||
export type ConfirmVoteMutationHookResult = ReturnType<typeof useConfirmVoteMutation>;
|
export type ConfirmVoteMutationHookResult = ReturnType<typeof useConfirmVoteMutation>;
|
||||||
export type ConfirmVoteMutationResult = Apollo.MutationResult<ConfirmVoteMutation>;
|
export type ConfirmVoteMutationResult = Apollo.MutationResult<ConfirmVoteMutation>;
|
||||||
export type ConfirmVoteMutationOptions = Apollo.BaseMutationOptions<ConfirmVoteMutation, ConfirmVoteMutationVariables>;
|
export type ConfirmVoteMutationOptions = Apollo.BaseMutationOptions<ConfirmVoteMutation, ConfirmVoteMutationVariables>;
|
||||||
@@ -2,9 +2,9 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
|||||||
import { Login_ScanningWalletCard, Login_ExternalWalletCard, Login_NativeWalletCard, Login_SuccessCard } from "src/Components/Modals/Login";
|
import { Login_ScanningWalletCard, Login_ExternalWalletCard, Login_NativeWalletCard, Login_SuccessCard } from "src/Components/Modals/Login";
|
||||||
import { ProjectDetailsCard } from "src/features/Projects/pages/ProjectPage/ProjectDetailsCard";
|
import { ProjectDetailsCard } from "src/features/Projects/pages/ProjectPage/ProjectDetailsCard";
|
||||||
import VoteCard from "src/features/Projects/pages/ProjectPage/VoteCard/VoteCard";
|
import VoteCard from "src/features/Projects/pages/ProjectPage/VoteCard/VoteCard";
|
||||||
import { InsertImageModal } from 'src/Components/Inputs/TextEditor/InsertImageModal'
|
|
||||||
import { InsertVideoModal } from 'src/Components/Inputs/TextEditor/InsertVideoModal'
|
import { InsertVideoModal } from 'src/Components/Inputs/TextEditor/InsertVideoModal'
|
||||||
import { InsertLinkModal } from 'src/Components/Inputs/TextEditor/InsertLinkModal'
|
import { InsertLinkModal } from 'src/Components/Inputs/TextEditor/InsertLinkModal'
|
||||||
|
|
||||||
import { Claim_FundWithdrawCard, Claim_CopySignatureCard, Claim_GenerateSignatureCard, Claim_SubmittedCard } from "src/features/Projects/pages/ProjectPage/ClaimProject";
|
import { Claim_FundWithdrawCard, Claim_CopySignatureCard, Claim_GenerateSignatureCard, Claim_SubmittedCard } from "src/features/Projects/pages/ProjectPage/ClaimProject";
|
||||||
import { ModalCard } from "src/Components/Modals/ModalsContainer/ModalsContainer";
|
import { ModalCard } from "src/Components/Modals/ModalsContainer/ModalsContainer";
|
||||||
import { ConfirmModal } from "src/Components/Modals/ConfirmModal";
|
import { ConfirmModal } from "src/Components/Modals/ConfirmModal";
|
||||||
@@ -18,6 +18,7 @@ import { ConnectToMakerModal } from "src/features/Tournaments/pages/MakersPage/C
|
|||||||
import { RegistrationModals } from "src/features/Tournaments/pages/OverviewPage/RegisterationModals";
|
import { RegistrationModals } from "src/features/Tournaments/pages/OverviewPage/RegisterationModals";
|
||||||
|
|
||||||
|
|
||||||
|
import { InsertImageModal } from "src/Components/Modals/InsertImageModal";
|
||||||
|
|
||||||
export enum Direction {
|
export enum Direction {
|
||||||
START,
|
START,
|
||||||
|
|||||||
@@ -54,6 +54,8 @@
|
|||||||
img {
|
img {
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
outline: 1px solid #e4e7ec;
|
outline: 1px solid #e4e7ec;
|
||||||
|
max-width: 100%;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ export * from './useCurrentSection'
|
|||||||
export * from './usePreload'
|
export * from './usePreload'
|
||||||
export * from './useCarousel'
|
export * from './useCarousel'
|
||||||
export * from './usePrompt'
|
export * from './usePrompt'
|
||||||
|
export * from './useIsDraggingOnElement'
|
||||||
export * from './useCountdown'
|
export * from './useCountdown'
|
||||||
|
|
||||||
|
|||||||
76
src/utils/hooks/useIsDraggingOnElement.ts
Normal file
76
src/utils/hooks/useIsDraggingOnElement.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function addEventListener<K extends keyof HTMLElementEventMap>(element: HTMLElement, type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions) {
|
||||||
|
element.addEventListener(type, listener, options);
|
||||||
|
return () => element.removeEventListener(type, listener, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setImmediate(callback: (...args: any[]) => void, ...args: any[]) {
|
||||||
|
let cancelled = false;
|
||||||
|
Promise.resolve().then(() => cancelled || callback(...args));
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function noop() { }
|
||||||
|
|
||||||
|
function handleDragOver(ev: DragEvent) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.dataTransfer!.dropEffect = 'copy';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const useIsDraggingOnElement = (options?: Partial<{
|
||||||
|
ref: MutableRefObject<HTMLElement>
|
||||||
|
}>) => {
|
||||||
|
const listenersRef = useRef<any[]>([]);
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let count = 0;
|
||||||
|
let cancelImmediate = noop;
|
||||||
|
|
||||||
|
const element = options?.ref?.current ?? document as unknown as HTMLElement;
|
||||||
|
|
||||||
|
listenersRef.current = [
|
||||||
|
addEventListener(element, 'dragover', handleDragOver),
|
||||||
|
addEventListener(element, 'dragenter', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if (count === 0) {
|
||||||
|
setIsDragging(true)
|
||||||
|
}
|
||||||
|
++count;
|
||||||
|
}),
|
||||||
|
addEventListener(element, 'dragleave', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
cancelImmediate = setImmediate(() => {
|
||||||
|
--count;
|
||||||
|
if (count === 0) {
|
||||||
|
setIsDragging(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}),
|
||||||
|
addEventListener(element, 'drop', ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
cancelImmediate();
|
||||||
|
if (count > 0) {
|
||||||
|
count = 0;
|
||||||
|
setIsDragging(false)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
listenersRef.current.forEach(f => f());
|
||||||
|
}
|
||||||
|
}, [options?.ref])
|
||||||
|
|
||||||
|
|
||||||
|
return isDragging
|
||||||
|
}
|
||||||
@@ -16,8 +16,10 @@ import "src/styles/index.scss";
|
|||||||
import 'react-loading-skeleton/dist/skeleton.css'
|
import 'react-loading-skeleton/dist/skeleton.css'
|
||||||
import { ApolloProvider } from '@apollo/client';
|
import { ApolloProvider } from '@apollo/client';
|
||||||
import { apolloClient } from '../apollo';
|
import { apolloClient } from '../apollo';
|
||||||
import { FormProvider, useForm, UseFormProps } from 'react-hook-form';
|
import { Controller, FormProvider, useForm, UseFormProps } from 'react-hook-form';
|
||||||
import ModalsContainer from 'src/Components/Modals/ModalsContainer/ModalsContainer';
|
import ModalsContainer from 'src/Components/Modals/ModalsContainer/ModalsContainer';
|
||||||
|
import { ToastContainer } from 'react-toastify';
|
||||||
|
import { NotificationsService } from 'src/services';
|
||||||
|
|
||||||
|
|
||||||
// Enable the Mocks Service Worker
|
// Enable the Mocks Service Worker
|
||||||
@@ -63,6 +65,11 @@ export const WrapperDecorator: DecoratorFn = (Story, options) => {
|
|||||||
effect='solid'
|
effect='solid'
|
||||||
delayShow={1000}
|
delayShow={1000}
|
||||||
/>
|
/>
|
||||||
|
<ToastContainer
|
||||||
|
{...NotificationsService.defaultOptions}
|
||||||
|
newestOnTop={false}
|
||||||
|
limit={2}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -112,16 +119,42 @@ export const centerDecorator: DecoratorFn = (Story) => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WrapForm<T = any>(options?: Partial<UseFormProps<T>>): DecoratorFn {
|
export function WrapForm<T = any>(options?: Partial<UseFormProps<T> & { logValues: boolean }>): DecoratorFn {
|
||||||
const Func: DecoratorFn = (Story) => {
|
const Func: DecoratorFn = (Story) => {
|
||||||
const methods = useForm<T>(options);
|
const methods = useForm<T>(options);
|
||||||
|
|
||||||
|
if (options?.logValues) {
|
||||||
|
console.log(methods.watch())
|
||||||
|
}
|
||||||
|
|
||||||
return <FormProvider {...methods} >
|
return <FormProvider {...methods} >
|
||||||
<Story />
|
<Story onChang />
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
}
|
}
|
||||||
return Func
|
return Func
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function WrapFormController<T = any>(options: Partial<UseFormProps<T> & { logValues: boolean }> & { name: string }): DecoratorFn {
|
||||||
|
const Func: DecoratorFn = (Story) => {
|
||||||
|
|
||||||
|
const methods = useForm<T>(options);
|
||||||
|
|
||||||
|
if (options?.logValues) {
|
||||||
|
console.log(methods.watch(options.name as any))
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Controller
|
||||||
|
control={methods.control}
|
||||||
|
name={options.name as any}
|
||||||
|
render={({ field: { value, onChange, onBlur } }) =>
|
||||||
|
<Story controller={{ value, onChange, onBlur }} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
return Func
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const WithModals: DecoratorFn = (Component) => <>
|
export const WithModals: DecoratorFn = (Component) => <>
|
||||||
<Component />
|
<Component />
|
||||||
|
|||||||
1
src/utils/validation/index.ts
Normal file
1
src/utils/validation/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './misc';
|
||||||
12
src/utils/validation/misc.ts
Normal file
12
src/utils/validation/misc.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import * as yup from "yup";
|
||||||
|
|
||||||
|
export const imageSchema = yup.object().shape({
|
||||||
|
id: yup.string().nullable(true),
|
||||||
|
name: yup.string().nullable(true),
|
||||||
|
url: yup.string().trim().required().url(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tagSchema = yup.object().shape({
|
||||||
|
title: yup.string().trim().min(2).required(),
|
||||||
|
});
|
||||||
|
|
||||||
Reference in New Issue
Block a user