Merge pull request #41 from StevanFreeborn/stevanfreeborn/feat/add-search
feat: add search
This commit is contained in:
+4
-2
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals",
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["next/core-web-vitals", "plugin:prettier/recommended"],
|
||||
"rules": {
|
||||
"no-console": "warn"
|
||||
"no-console": "warn",
|
||||
"prettier/prettier": "error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"printWidth": 80,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid",
|
||||
"embeddedLanguageFormatting": "auto",
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
Vendored
+1
@@ -4,6 +4,7 @@
|
||||
"codemirror",
|
||||
"conve",
|
||||
"doesn",
|
||||
"Dtos",
|
||||
"Keymap",
|
||||
"lezer",
|
||||
"nextjs",
|
||||
|
||||
+70
-102
@@ -1,9 +1,9 @@
|
||||
import { PaginationResult, paginationOptsValidator } from 'convex/server';
|
||||
import { paginationOptsValidator } from 'convex/server';
|
||||
import { v } from 'convex/values';
|
||||
import { PostWithUserDto } from '../src/app/types';
|
||||
import { Id } from './_generated/dataModel';
|
||||
import { mutation, query } from './_generated/server';
|
||||
import { userQuery } from './users';
|
||||
import { Doc } from './_generated/dataModel.d';
|
||||
import { QueryCtx, mutation, query } from './_generated/server';
|
||||
import { createUserDto, userQuery } from './users';
|
||||
|
||||
export const createOrUpdatePost = mutation({
|
||||
args: {
|
||||
@@ -28,8 +28,10 @@ export const createOrUpdatePost = mutation({
|
||||
return 'CANNOT_POST_ON_BEHALF_OF_ANOTHER_USER';
|
||||
}
|
||||
|
||||
const contentText = args.content.join('\n');
|
||||
|
||||
if (args.id) {
|
||||
await ctx.db.patch(args.id, { content: args.content });
|
||||
await ctx.db.patch(args.id, { content: args.content, contentText });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -37,6 +39,7 @@ export const createOrUpdatePost = mutation({
|
||||
userId: user._id,
|
||||
content: args.content,
|
||||
parentPostId: args.parentPostId,
|
||||
contentText,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -44,12 +47,16 @@ export const createOrUpdatePost = mutation({
|
||||
export const getUsersPostById = query({
|
||||
args: { userId: v.id('users'), paginationOpts: paginationOptsValidator },
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db
|
||||
const posts = await ctx.db
|
||||
.query('posts')
|
||||
.withIndex('by_user_id', q => q.eq('userId', args.userId))
|
||||
.filter(q => q.eq(q.field('parentPostId'), undefined))
|
||||
.order('desc')
|
||||
.paginate(args.paginationOpts);
|
||||
|
||||
const postDtos = posts.page.map(createPostDto);
|
||||
|
||||
return { ...posts, page: postDtos };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -81,14 +88,8 @@ export const getPostById = query({
|
||||
}
|
||||
|
||||
return {
|
||||
...post,
|
||||
user: {
|
||||
_id: user._id,
|
||||
_creationTime: user._creationTime,
|
||||
clerkUsername: user.clerkUser.username,
|
||||
clerkImageUrl: user.clerkUser.image_url,
|
||||
clerkUserId: user.clerkUser.id,
|
||||
},
|
||||
...createPostDto(post),
|
||||
user: createUserDto(user),
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -102,35 +103,10 @@ export const getRepliesByParentId = query({
|
||||
.order('desc')
|
||||
.paginate(args.paginationOpts);
|
||||
|
||||
const repliesWithUserData = await Promise.all(
|
||||
replies.page.map(async reply => {
|
||||
const user = await ctx.db.get(reply.userId);
|
||||
|
||||
if (user === null) {
|
||||
return {
|
||||
...reply,
|
||||
user: {
|
||||
_id: '' as Id<'users'>,
|
||||
_creationTime: 0,
|
||||
clerkUsername: null,
|
||||
clerkImageUrl: '',
|
||||
clerkUserId: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...reply,
|
||||
user: {
|
||||
_id: user._id,
|
||||
_creationTime: user._creationTime,
|
||||
clerkUsername: user.clerkUser.username,
|
||||
clerkImageUrl: user.clerkUser.image_url,
|
||||
clerkUserId: user.clerkUser.id,
|
||||
},
|
||||
};
|
||||
})
|
||||
);
|
||||
const repliesWithUserData = await getPostsWithUsers({
|
||||
ctx,
|
||||
posts: replies.page,
|
||||
});
|
||||
|
||||
return { ...replies, page: repliesWithUserData };
|
||||
},
|
||||
@@ -170,35 +146,7 @@ export const getAllPostsWithUser = query({
|
||||
.order('desc')
|
||||
.paginate(args.paginationOpts);
|
||||
|
||||
const postsWithUser = await Promise.all(
|
||||
posts.page.map(async post => {
|
||||
const user = await ctx.db.get(post.userId);
|
||||
|
||||
if (user === null) {
|
||||
return {
|
||||
...post,
|
||||
user: {
|
||||
_id: '' as Id<'users'>,
|
||||
_creationTime: 0,
|
||||
clerkUsername: null,
|
||||
clerkImageUrl: '',
|
||||
clerkUserId: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...post,
|
||||
user: {
|
||||
_id: user._id,
|
||||
_creationTime: user._creationTime,
|
||||
clerkUsername: user.clerkUser.username,
|
||||
clerkImageUrl: user.clerkUser.image_url,
|
||||
clerkUserId: user.clerkUser.id,
|
||||
},
|
||||
};
|
||||
})
|
||||
);
|
||||
const postsWithUser = await getPostsWithUsers({ ctx, posts: posts.page });
|
||||
|
||||
return { ...posts, page: postsWithUser };
|
||||
},
|
||||
@@ -206,7 +154,7 @@ export const getAllPostsWithUser = query({
|
||||
|
||||
export const getAllPostsForFollowings = query({
|
||||
args: { paginationOpts: paginationOptsValidator },
|
||||
handler: async (ctx, args): Promise<PaginationResult<PostWithUserDto>> => {
|
||||
handler: async (ctx, args) => {
|
||||
const currentUser = await ctx.auth.getUserIdentity();
|
||||
|
||||
if (currentUser === null) {
|
||||
@@ -239,36 +187,56 @@ export const getAllPostsForFollowings = query({
|
||||
.order('desc')
|
||||
.paginate(args.paginationOpts);
|
||||
|
||||
const postsWithUser = await Promise.all(
|
||||
posts.page.map(async post => {
|
||||
const user = await ctx.db.get(post.userId);
|
||||
|
||||
if (user === null) {
|
||||
return {
|
||||
...post,
|
||||
user: {
|
||||
_id: '' as Id<'users'>,
|
||||
_creationTime: 0,
|
||||
clerkUsername: null,
|
||||
clerkImageUrl: '',
|
||||
clerkUserId: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...post,
|
||||
user: {
|
||||
_id: user._id,
|
||||
_creationTime: user._creationTime,
|
||||
clerkUsername: user.clerkUser.username,
|
||||
clerkImageUrl: user.clerkUser.image_url,
|
||||
clerkUserId: user.clerkUser.id,
|
||||
},
|
||||
};
|
||||
})
|
||||
);
|
||||
const postsWithUser = await getPostsWithUsers({ ctx, posts: posts.page });
|
||||
|
||||
return { ...posts, page: postsWithUser };
|
||||
},
|
||||
});
|
||||
|
||||
export const getPostsBySearchTerm = query({
|
||||
args: { term: v.string(), paginationOpts: paginationOptsValidator },
|
||||
handler: async (ctx, args) => {
|
||||
const posts = await ctx.db
|
||||
.query('posts')
|
||||
.withSearchIndex('search_by_content', q =>
|
||||
q.search('contentText', args.term)
|
||||
)
|
||||
.paginate(args.paginationOpts);
|
||||
|
||||
const postsWithUser = await getPostsWithUsers({ ctx, posts: posts.page });
|
||||
|
||||
return { ...posts, page: postsWithUser };
|
||||
},
|
||||
});
|
||||
|
||||
async function getPostsWithUsers({
|
||||
ctx,
|
||||
posts,
|
||||
}: {
|
||||
ctx: QueryCtx;
|
||||
posts: Doc<'posts'>[];
|
||||
}) {
|
||||
return await Promise.all(
|
||||
posts.map(async post => {
|
||||
const postDto = createPostDto(post);
|
||||
const user = await ctx.db.get(post.userId);
|
||||
|
||||
if (user === null) {
|
||||
return {
|
||||
...postDto,
|
||||
user: createUserDto(user),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...postDto,
|
||||
user: createUserDto(user),
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function createPostDto(post: Doc<'posts'>) {
|
||||
const { contentText, ...postDto } = post;
|
||||
return postDto;
|
||||
}
|
||||
|
||||
+12
-2
@@ -34,14 +34,24 @@ export default defineSchema(
|
||||
created_at: v.number(),
|
||||
updated_at: v.number(),
|
||||
}),
|
||||
}).index('by_clerk_id', ['clerkUser.id']),
|
||||
})
|
||||
.index('by_clerk_id', ['clerkUser.id'])
|
||||
.searchIndex('search_by_username', {
|
||||
searchField: 'clerkUser.username',
|
||||
filterFields: [],
|
||||
}),
|
||||
posts: defineTable({
|
||||
parentPostId: v.optional(v.id('posts')),
|
||||
userId: v.id('users'),
|
||||
content: v.array(v.string()),
|
||||
contentText: v.string(),
|
||||
})
|
||||
.index('by_user_id', ['userId'])
|
||||
.index('by_parent_id', ['parentPostId']),
|
||||
.index('by_parent_id', ['parentPostId'])
|
||||
.searchIndex('search_by_content', {
|
||||
searchField: 'contentText',
|
||||
filterFields: ['parentPostId'],
|
||||
}),
|
||||
likes: defineTable({
|
||||
postId: v.id('posts'),
|
||||
userId: v.id('users'),
|
||||
|
||||
+40
-14
@@ -1,5 +1,7 @@
|
||||
import { paginationOptsValidator } from 'convex/server';
|
||||
import { v } from 'convex/values';
|
||||
import { UserDto } from './../src/app/types/index';
|
||||
import { Doc, Id } from './_generated/dataModel';
|
||||
import {
|
||||
QueryCtx,
|
||||
internalMutation,
|
||||
@@ -23,13 +25,7 @@ export const getUserByClerkId = query({
|
||||
return 'USER_NOT_FOUND';
|
||||
}
|
||||
|
||||
return {
|
||||
_id: user._id,
|
||||
_creationTime: user._creationTime,
|
||||
clerkUsername: user.clerkUser.username,
|
||||
clerkImageUrl: user.clerkUser.image_url,
|
||||
clerkUserId: user.clerkUser.id,
|
||||
};
|
||||
return createUserDto(user);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -42,13 +38,7 @@ export const getUserById = query({
|
||||
return 'USER_NOT_FOUND';
|
||||
}
|
||||
|
||||
return {
|
||||
_id: user._id,
|
||||
_creationTime: user._creationTime,
|
||||
clerkUsername: user.clerkUser.username,
|
||||
clerkImageUrl: user.clerkUser.image_url,
|
||||
clerkUserId: user.clerkUser.id,
|
||||
};
|
||||
return createUserDto(user);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -121,3 +111,39 @@ export const deleteUser = internalMutation({
|
||||
await ctx.db.delete(userRecord._id);
|
||||
},
|
||||
});
|
||||
|
||||
export const getUsersBySearchTerm = query({
|
||||
args: { term: v.string(), paginationOpts: paginationOptsValidator },
|
||||
handler: async (ctx, args) => {
|
||||
const users = await ctx.db
|
||||
.query('users')
|
||||
.withSearchIndex('search_by_username', q =>
|
||||
q.search('clerkUser.username', args.term)
|
||||
)
|
||||
.paginate(args.paginationOpts);
|
||||
|
||||
const userDtos = users.page.map(createUserDto);
|
||||
|
||||
return { ...users, page: userDtos };
|
||||
},
|
||||
});
|
||||
|
||||
export function createUserDto(user: Doc<'users'> | null) {
|
||||
if (user === null) {
|
||||
return {
|
||||
_id: '' as Id<'users'>,
|
||||
_creationTime: 0,
|
||||
clerkUsername: null,
|
||||
clerkImageUrl: '',
|
||||
clerkUserId: '',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
_id: user._id,
|
||||
_creationTime: user._creationTime,
|
||||
clerkUsername: user.clerkUser.username,
|
||||
clerkImageUrl: user.clerkUser.image_url,
|
||||
clerkUserId: user.clerkUser.id,
|
||||
};
|
||||
}
|
||||
|
||||
Generated
+496
@@ -26,11 +26,13 @@
|
||||
"encoding": "^0.1.13",
|
||||
"eslint": "8.49.0",
|
||||
"eslint-config-next": "13.4.19",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"next": "13.4.19",
|
||||
"next-themes": "^0.2.1",
|
||||
"nextjs-toploader": "^1.4.2",
|
||||
"nprogress": "^0.2.0",
|
||||
"postcss": "8.4.29",
|
||||
"prettier": "^3.0.3",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-icons": "^4.11.0",
|
||||
@@ -41,6 +43,9 @@
|
||||
"svix": "^1.11.0",
|
||||
"tailwindcss": "3.3.3",
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-config-prettier": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
@@ -1320,6 +1325,25 @@
|
||||
"node": ">=10.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgr/utils": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz",
|
||||
"integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.3",
|
||||
"fast-glob": "^3.3.0",
|
||||
"is-glob": "^4.0.3",
|
||||
"open": "^9.1.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"tslib": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/eslint-patch": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz",
|
||||
@@ -1955,6 +1979,14 @@
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/big-integer": {
|
||||
"version": "1.6.51",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
|
||||
"integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
@@ -1963,6 +1995,17 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/bplist-parser": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz",
|
||||
"integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==",
|
||||
"dependencies": {
|
||||
"big-integer": "^1.6.44"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 5.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@@ -2014,6 +2057,20 @@
|
||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/bundle-name": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
|
||||
"integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==",
|
||||
"dependencies": {
|
||||
"run-applescript": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
@@ -2404,6 +2461,49 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/default-browser": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz",
|
||||
"integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==",
|
||||
"dependencies": {
|
||||
"bundle-name": "^3.0.0",
|
||||
"default-browser-id": "^3.0.0",
|
||||
"execa": "^7.1.1",
|
||||
"titleize": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/default-browser-id": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz",
|
||||
"integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==",
|
||||
"dependencies": {
|
||||
"bplist-parser": "^0.2.0",
|
||||
"untildify": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/define-lazy-prop": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
|
||||
"integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/define-properties": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
|
||||
@@ -2776,6 +2876,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-prettier": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz",
|
||||
"integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-import-resolver-node": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
|
||||
@@ -2936,6 +3048,34 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-prettier": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz",
|
||||
"integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==",
|
||||
"dependencies": {
|
||||
"prettier-linter-helpers": "^1.0.0",
|
||||
"synckit": "^0.8.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/prettier"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/eslint": ">=8.0.0",
|
||||
"eslint": ">=8.0.0",
|
||||
"prettier": ">=3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/eslint": {
|
||||
"optional": true
|
||||
},
|
||||
"eslint-config-prettier": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react": {
|
||||
"version": "7.33.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz",
|
||||
@@ -3091,6 +3231,28 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
|
||||
"integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.3",
|
||||
"get-stream": "^6.0.1",
|
||||
"human-signals": "^4.3.0",
|
||||
"is-stream": "^3.0.0",
|
||||
"merge-stream": "^2.0.0",
|
||||
"npm-run-path": "^5.1.0",
|
||||
"onetime": "^6.0.0",
|
||||
"signal-exit": "^3.0.7",
|
||||
"strip-final-newline": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || ^16.14.0 || >=18.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
@@ -3101,6 +3263,11 @@
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
|
||||
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
|
||||
@@ -3320,6 +3487,17 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/get-symbol-description": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
|
||||
@@ -3595,6 +3773,14 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
|
||||
"integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
@@ -3822,6 +4008,20 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/is-docker": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
|
||||
"integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
|
||||
"bin": {
|
||||
"is-docker": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@@ -3875,6 +4075,23 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/is-inside-container": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
|
||||
"integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
|
||||
"dependencies": {
|
||||
"is-docker": "^3.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"is-inside-container": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-map": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
|
||||
@@ -3969,6 +4186,17 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
|
||||
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-string": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
|
||||
@@ -4042,6 +4270,31 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-wsl": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
||||
"dependencies": {
|
||||
"is-docker": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-wsl/node_modules/is-docker": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
||||
"bin": {
|
||||
"is-docker": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||
@@ -4499,6 +4752,11 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
@@ -5073,6 +5331,17 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@@ -5277,6 +5546,31 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
|
||||
"integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
|
||||
"dependencies": {
|
||||
"path-key": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path/node_modules/path-key": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
|
||||
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/nprogress": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
|
||||
@@ -5407,6 +5701,37 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/onetime": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
|
||||
"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
|
||||
"dependencies": {
|
||||
"mimic-fn": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/open": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz",
|
||||
"integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==",
|
||||
"dependencies": {
|
||||
"default-browser": "^4.0.0",
|
||||
"define-lazy-prop": "^3.0.0",
|
||||
"is-inside-container": "^1.0.0",
|
||||
"is-wsl": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
|
||||
@@ -5694,6 +6019,31 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
|
||||
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-linter-helpers": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
|
||||
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
|
||||
"dependencies": {
|
||||
"fast-diff": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
|
||||
@@ -6125,6 +6475,102 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/run-applescript": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
|
||||
"integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==",
|
||||
"dependencies": {
|
||||
"execa": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/run-applescript/node_modules/execa": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
||||
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.3",
|
||||
"get-stream": "^6.0.0",
|
||||
"human-signals": "^2.1.0",
|
||||
"is-stream": "^2.0.0",
|
||||
"merge-stream": "^2.0.0",
|
||||
"npm-run-path": "^4.0.1",
|
||||
"onetime": "^5.1.2",
|
||||
"signal-exit": "^3.0.3",
|
||||
"strip-final-newline": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/run-applescript/node_modules/human-signals": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
||||
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
|
||||
"engines": {
|
||||
"node": ">=10.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/run-applescript/node_modules/is-stream": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/run-applescript/node_modules/mimic-fn": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/run-applescript/node_modules/npm-run-path": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
|
||||
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
|
||||
"dependencies": {
|
||||
"path-key": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/run-applescript/node_modules/onetime": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
|
||||
"dependencies": {
|
||||
"mimic-fn": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/run-applescript/node_modules/strip-final-newline": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
|
||||
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
@@ -6247,6 +6693,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
||||
},
|
||||
"node_modules/skin-tone": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz",
|
||||
@@ -6403,6 +6854,17 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-final-newline": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
|
||||
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||
@@ -6562,6 +7024,21 @@
|
||||
"react": "^16.11.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
|
||||
"integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==",
|
||||
"dependencies": {
|
||||
"@pkgr/utils": "^2.3.1",
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz",
|
||||
@@ -6630,6 +7107,17 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/titleize": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
|
||||
"integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/to-no-case": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz",
|
||||
@@ -6924,6 +7412,14 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/untildify": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
|
||||
"integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
|
||||
|
||||
@@ -27,11 +27,13 @@
|
||||
"encoding": "^0.1.13",
|
||||
"eslint": "8.49.0",
|
||||
"eslint-config-next": "13.4.19",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"next": "13.4.19",
|
||||
"next-themes": "^0.2.1",
|
||||
"nextjs-toploader": "^1.4.2",
|
||||
"nprogress": "^0.2.0",
|
||||
"postcss": "8.4.29",
|
||||
"prettier": "^3.0.3",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-icons": "^4.11.0",
|
||||
@@ -42,5 +44,8 @@
|
||||
"svix": "^1.11.0",
|
||||
"tailwindcss": "3.3.3",
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-config-prettier": "^9.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { UserProfile } from '@clerk/nextjs';
|
||||
|
||||
export default function AccountPage() {
|
||||
return (
|
||||
<main className='flex flex-col items-center'>
|
||||
<main className="flex flex-col items-center">
|
||||
<UserProfile
|
||||
appearance={{
|
||||
elements: {
|
||||
|
||||
@@ -2,9 +2,9 @@ import FollowingPosts from '@/components/FollowingPosts';
|
||||
|
||||
export default function FollowingPage() {
|
||||
return (
|
||||
<main className='flex flex-col items-center flex-1'>
|
||||
<div className='flex flex-col w-full max-w-4xl gap-4'>
|
||||
<h1 className='text-4xl font-bold'>Following</h1>
|
||||
<main className="flex flex-col items-center flex-1">
|
||||
<div className="flex flex-col w-full max-w-4xl gap-4">
|
||||
<h1 className="text-4xl font-bold">Following</h1>
|
||||
<FollowingPosts />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
+3
-10
@@ -19,11 +19,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html
|
||||
lang='en'
|
||||
className='w-full h-full'
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<html lang="en" className="w-full h-full" suppressHydrationWarning>
|
||||
<body
|
||||
className={`w-full h-full flex flex-col bg-white text-primary-gray dark:bg-primary-gray dark:text-white ${inter.className}`}
|
||||
>
|
||||
@@ -32,12 +28,9 @@ export default function RootLayout({
|
||||
>
|
||||
<ConvexProvider>
|
||||
<NextThemesProvider>
|
||||
<NextTopLoader
|
||||
color='#3743e5'
|
||||
showSpinner={false}
|
||||
/>
|
||||
<NextTopLoader color="#3743e5" showSpinner={false} />
|
||||
<Navbar />
|
||||
<div className='flex flex-col p-4 w-full h-full overflow-y-auto'>
|
||||
<div className="flex flex-col p-4 w-full h-full overflow-y-auto">
|
||||
{children}
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { SignIn } from '@clerk/nextjs';
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<main className='flex flex-col flex-1 items-center'>
|
||||
<main className="flex flex-col flex-1 items-center">
|
||||
<SignIn
|
||||
appearance={{
|
||||
elements: {
|
||||
|
||||
+3
-3
@@ -2,9 +2,9 @@ import AllPosts from '@/components/AllPosts';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className='flex flex-col items-center flex-1'>
|
||||
<div className='flex flex-col w-full max-w-4xl gap-4'>
|
||||
<h1 className='text-4xl font-bold'>Home</h1>
|
||||
<main className="flex flex-col items-center flex-1">
|
||||
<div className="flex flex-col w-full max-w-4xl gap-4">
|
||||
<h1 className="text-4xl font-bold">Home</h1>
|
||||
<AllPosts />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function EditPostPage({ params }: { params: { id: string } }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<main className='flex flex-col flex-1 w-full items-center'>
|
||||
<main className="flex flex-col flex-1 w-full items-center">
|
||||
{user.isLoaded && post !== undefined ? (
|
||||
<Editor
|
||||
clerkUserId={user.user.id}
|
||||
@@ -59,8 +59,8 @@ export default function EditPostPage({ params }: { params: { id: string } }) {
|
||||
submitAction={submitAction}
|
||||
/>
|
||||
) : (
|
||||
<div className='flex gap-2 items-center'>
|
||||
<SpinningLoader className='animate-spin w-5 h-5' />
|
||||
<div className="flex gap-2 items-center">
|
||||
<SpinningLoader className="animate-spin w-5 h-5" />
|
||||
Loading...
|
||||
</div>
|
||||
)}
|
||||
|
||||
+11
-18
@@ -68,34 +68,27 @@ export default function PostPage({
|
||||
}
|
||||
|
||||
return (
|
||||
<main className='flex flex-col flex-1 w-full items-center'>
|
||||
<div className='flex flex-col w-full max-w-4xl gap-4'>
|
||||
<main className="flex flex-col flex-1 w-full items-center">
|
||||
<div className="flex flex-col w-full max-w-4xl gap-4">
|
||||
{post !== undefined && user.isLoaded ? (
|
||||
<>
|
||||
<div className='flex flex-col min-w-0 p-1 rounded-md border border-gray-600'>
|
||||
<Post
|
||||
post={post}
|
||||
limit={false}
|
||||
showReply={false}
|
||||
/>
|
||||
<div className="flex flex-col min-w-0 p-1 rounded-md border border-gray-600">
|
||||
<Post post={post} limit={false} showReply={false} />
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<div className="w-full">
|
||||
<PostReplies parentId={post._id} />
|
||||
</div>
|
||||
<div className='flex w-full h-80 gap-4'>
|
||||
<div className="flex w-full h-80 gap-4">
|
||||
<div>
|
||||
<Image
|
||||
alt='user profile image'
|
||||
alt="user profile image"
|
||||
src={user.user.imageUrl}
|
||||
width={40}
|
||||
height={40}
|
||||
className='rounded-full object-cover border-4 border-primary-accent'
|
||||
className="rounded-full object-cover border-4 border-primary-accent"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
ref={replyRef}
|
||||
className='flex w-full'
|
||||
>
|
||||
<div ref={replyRef} className="flex w-full">
|
||||
<Editor
|
||||
clerkUserId={user.user.id}
|
||||
parentPostId={post._id}
|
||||
@@ -106,8 +99,8 @@ export default function PostPage({
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className='flex w-full justify-center gap-2'>
|
||||
<SpinningLoader className='animate-spin w-5 h-5' /> Loading post...
|
||||
<div className="flex w-full justify-center gap-2">
|
||||
<SpinningLoader className="animate-spin w-5 h-5" /> Loading post...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -43,15 +43,12 @@ export default function AddPost() {
|
||||
}
|
||||
|
||||
return (
|
||||
<main className='flex flex-col flex-1 w-full items-center'>
|
||||
<main className="flex flex-col flex-1 w-full items-center">
|
||||
{user.isLoaded ? (
|
||||
<Editor
|
||||
clerkUserId={user.user.id}
|
||||
submitAction={submitAction}
|
||||
/>
|
||||
<Editor clerkUserId={user.user.id} submitAction={submitAction} />
|
||||
) : (
|
||||
<div className='flex gap-2 items-center'>
|
||||
<SpinningLoader className='animate-spin w-5 h-5' />
|
||||
<div className="flex gap-2 items-center">
|
||||
<SpinningLoader className="animate-spin w-5 h-5" />
|
||||
Loading...
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -19,8 +19,8 @@ export default async function UserProfilePage({
|
||||
}
|
||||
|
||||
return (
|
||||
<main className='flex flex-col flex-1 w-full items-center'>
|
||||
<div className='w-full max-w-4xl'>
|
||||
<main className="flex flex-col flex-1 w-full items-center">
|
||||
<div className="w-full max-w-4xl">
|
||||
<UserProfile user={user} />
|
||||
<UserPosts user={user} />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import SearchResults from '@/components/SearchResults';
|
||||
|
||||
export default function SearchPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
}) {
|
||||
let term = searchParams.term;
|
||||
|
||||
if (term === undefined) {
|
||||
term = '';
|
||||
}
|
||||
|
||||
if (Array.isArray(term)) {
|
||||
term = term.length > 0 ? term[0] : '';
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex flex-col items-center flex-1">
|
||||
<div className="flex flex-col w-full max-w-4xl gap-4">
|
||||
<h1 className="text-4xl font-bold">Here's what we found</h1>
|
||||
<SearchResults term={term} />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { SignUp } from '@clerk/nextjs';
|
||||
|
||||
export default function SignUpPage() {
|
||||
return (
|
||||
<main className='flex flex-col flex-1 items-center'>
|
||||
<main className="flex flex-col flex-1 items-center">
|
||||
<SignUp
|
||||
appearance={{
|
||||
elements: {
|
||||
|
||||
@@ -8,4 +8,6 @@ export type UserDto = {
|
||||
clerkUserId: string;
|
||||
};
|
||||
|
||||
export type PostWithUserDto = Doc<'posts'> & { user: UserDto };
|
||||
export type PostWithUserDto = Omit<Doc<'posts'>, 'contentText'> & {
|
||||
user: UserDto;
|
||||
};
|
||||
|
||||
@@ -19,8 +19,8 @@ export default function AllPosts() {
|
||||
|
||||
if (pager.isLoading === false && pager.results.length == 0) {
|
||||
return (
|
||||
<div className='flex flex-col justify-center items-center gap-2'>
|
||||
<h2 className='text-2xl'>
|
||||
<div className="flex flex-col justify-center items-center gap-2">
|
||||
<h2 className="text-2xl">
|
||||
Hmmm it doesn't look like anyone has posted.
|
||||
</h2>
|
||||
</div>
|
||||
@@ -28,12 +28,12 @@ export default function AllPosts() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-center w-full'>
|
||||
<div className='flex flex-col items-center w-full'>
|
||||
<div className="flex flex-col items-center w-full">
|
||||
<div className="flex flex-col items-center w-full">
|
||||
{pager.results.map(post => (
|
||||
<div
|
||||
key={post._id}
|
||||
className='w-full border border-t-0 border-gray-600 first:border-t last:rounded-b-md p-1 last:mb-4'
|
||||
className="w-full border border-t-0 border-gray-600 first:border-t last:rounded-b-md p-1 last:mb-4"
|
||||
>
|
||||
<Post post={post} />
|
||||
</div>
|
||||
|
||||
+12
-15
@@ -92,30 +92,30 @@ export default function Editor({
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className='flex flex-col flex-1 w-full shadow-md rounded-md dark:bg-secondary-gray border border-gray-600'
|
||||
className="flex flex-col flex-1 w-full shadow-md rounded-md dark:bg-secondary-gray border border-gray-600"
|
||||
>
|
||||
<div className='flex items-center justify-between rounded-t-md p-4 pb-0 border-b border-gray-600 dark:bg-primary-gray'>
|
||||
<div className='flex items-center'>
|
||||
<div className="flex items-center justify-between rounded-t-md p-4 pb-0 border-b border-gray-600 dark:bg-primary-gray">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
className='flex items-center justify-center px-3 py-2 rounded-t-md -mb-[1px] disabled:bg-white disabled:border disabled:border-b-0 disabled:border-gray-600 disabled:dark:bg-secondary-gray'
|
||||
className="flex items-center justify-center px-3 py-2 rounded-t-md -mb-[1px] disabled:bg-white disabled:border disabled:border-b-0 disabled:border-gray-600 disabled:dark:bg-secondary-gray"
|
||||
disabled={mode === 'write'}
|
||||
onClick={() => setMode('write')}
|
||||
type='button'
|
||||
type="button"
|
||||
>
|
||||
Write
|
||||
</button>
|
||||
<button
|
||||
className='flex items-center justify-center px-3 py-2 rounded-t-md -mb-[1px] disabled:bg-white disabled:border disabled:border-b-0 disabled:border-gray-600 disabled:dark:bg-secondary-gray'
|
||||
className="flex items-center justify-center px-3 py-2 rounded-t-md -mb-[1px] disabled:bg-white disabled:border disabled:border-b-0 disabled:border-gray-600 disabled:dark:bg-secondary-gray"
|
||||
disabled={mode === 'preview'}
|
||||
onClick={() => setMode('preview')}
|
||||
type='button'
|
||||
type="button"
|
||||
>
|
||||
Preview
|
||||
</button>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div className='flex flex-col pt-4 px-4 h-0 flex-grow overflow-auto'>
|
||||
<div className="flex flex-col pt-4 px-4 h-0 flex-grow overflow-auto">
|
||||
<div
|
||||
className={`${mode === 'write' ? '' : 'hidden'} flex-1 overflow-auto`}
|
||||
ref={editorRef}
|
||||
@@ -124,21 +124,18 @@ export default function Editor({
|
||||
<PostContent content={Text.of(currentDoc).toString()} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center justify-end p-4 gap-4'>
|
||||
<button
|
||||
onClick={() => router.back()}
|
||||
type='button'
|
||||
>
|
||||
<div className="flex items-center justify-end p-4 gap-4">
|
||||
<button onClick={() => router.back()} type="button">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type='submit'
|
||||
type="submit"
|
||||
disabled={
|
||||
!currentDoc.join() ||
|
||||
!currentDoc.join().trim() ||
|
||||
creatingOrUpdating
|
||||
}
|
||||
className='py-1 px-4 bg-primary-accent text-white rounded-md disabled:opacity-50 flex items-center justify-center gap-2'
|
||||
className="py-1 px-4 bg-primary-accent text-white rounded-md disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
|
||||
@@ -20,14 +20,14 @@ export default function FollowingPosts() {
|
||||
|
||||
if (pager.isLoading === false && pager.results.length == 0) {
|
||||
return (
|
||||
<div className='flex flex-col justify-center items-center gap-2'>
|
||||
<h2 className='text-2xl'>
|
||||
<div className="flex flex-col justify-center items-center gap-2">
|
||||
<h2 className="text-2xl">
|
||||
Hmmm it doesn't look like you follow anyone or anyone who has
|
||||
made a post yet.
|
||||
</h2>
|
||||
<Link
|
||||
href='/'
|
||||
className='max-w-max py-1 px-3 text-white bg-primary-accent rounded-md'
|
||||
href="/"
|
||||
className="max-w-max py-1 px-3 text-white bg-primary-accent rounded-md"
|
||||
>
|
||||
Find someone to follow
|
||||
</Link>
|
||||
@@ -36,12 +36,12 @@ export default function FollowingPosts() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-center w-full'>
|
||||
<div className='flex flex-col items-center w-full'>
|
||||
<div className="flex flex-col items-center w-full">
|
||||
<div className="flex flex-col items-center w-full">
|
||||
{pager.results.map(post => (
|
||||
<div
|
||||
key={post._id}
|
||||
className='w-full border border-t-0 border-gray-600 first:border-t last:rounded-b-md p-1 last:mb-4'
|
||||
className="w-full border border-t-0 border-gray-600 first:border-t last:rounded-b-md p-1 last:mb-4"
|
||||
>
|
||||
<Post post={post} />
|
||||
</div>
|
||||
|
||||
+11
-23
@@ -2,43 +2,31 @@ import Link from 'next/link';
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<div className='flex flex-col w-full items-center justify-center px-4 mt-4'>
|
||||
<footer className='flex flex-col items-center justify-between gap-2 w-full max-w-4xl text-sm text-primary-white border-t border-gray-600 py-4'>
|
||||
<div className='flex items-center justify-center w-full flex-wrap gap-4'>
|
||||
<Link
|
||||
href='/about'
|
||||
className='hover:text-primary-orange'
|
||||
>
|
||||
<div className="flex flex-col w-full items-center justify-center px-4 mt-4">
|
||||
<footer className="flex flex-col items-center justify-between gap-2 w-full max-w-4xl text-sm text-primary-white border-t border-gray-600 py-4">
|
||||
<div className="flex items-center justify-center w-full flex-wrap gap-4">
|
||||
<Link href="/about" className="hover:text-primary-orange">
|
||||
About
|
||||
</Link>
|
||||
<Link
|
||||
href='/contact'
|
||||
className='hover:text-primary-orange'
|
||||
>
|
||||
<Link href="/contact" className="hover:text-primary-orange">
|
||||
Contact
|
||||
</Link>
|
||||
<Link
|
||||
href='/terms'
|
||||
className='hover:text-primary-orange'
|
||||
>
|
||||
<Link href="/terms" className="hover:text-primary-orange">
|
||||
Terms
|
||||
</Link>
|
||||
<Link
|
||||
href='/privacy'
|
||||
className='hover:text-primary-orange'
|
||||
>
|
||||
<Link href="/privacy" className="hover:text-primary-orange">
|
||||
Privacy
|
||||
</Link>
|
||||
<Link
|
||||
href='https://github.com/StevanFreeborn/conve-x'
|
||||
className='hover:text-primary-orange'
|
||||
href="https://github.com/StevanFreeborn/conve-x"
|
||||
className="hover:text-primary-orange"
|
||||
>
|
||||
Code
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<span className='self-center whitespace-nowrap italic'>
|
||||
conve<span className='font-bold text-primary-accent'>X</span>
|
||||
<span className="self-center whitespace-nowrap italic">
|
||||
conve<span className="font-bold text-primary-accent">X</span>
|
||||
</span>
|
||||
<span> © 2023</span>
|
||||
</div>
|
||||
|
||||
@@ -11,8 +11,8 @@ export default function Loader({
|
||||
}) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='flex w-full items-center justify-center gap-2 p-5 border-t border-gray-600 text-sm'>
|
||||
<SpinningLoader className='animate-spin w-5 h-5' />
|
||||
<div className="flex w-full items-center justify-center gap-2 p-5 text-sm">
|
||||
<SpinningLoader className="animate-spin w-5 h-5" />
|
||||
Loading...
|
||||
</div>
|
||||
);
|
||||
@@ -23,15 +23,15 @@ export default function Loader({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex items-center'>
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
className='bg-primary-accent text-white px-3 py-1 rounded-full text-sm'
|
||||
className="bg-primary-accent text-white px-3 py-1 rounded-full text-sm"
|
||||
onClick={loadButtonClickHandler}
|
||||
disabled={status !== 'CanLoadMore'}
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<SpinningLoader className='animate-spin w-5 h-5' />
|
||||
<SpinningLoader className="animate-spin w-5 h-5" />
|
||||
Loading...
|
||||
</>
|
||||
) : (
|
||||
|
||||
+63
-63
@@ -1,11 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { useRouter } from '@/hooks';
|
||||
import { SignedIn, SignedOut, useUser } from '@clerk/nextjs';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { AiFillHome } from 'react-icons/ai';
|
||||
import { BiLogIn, BiPlus } from 'react-icons/bi';
|
||||
import { BsPersonPlusFill } from 'react-icons/bs';
|
||||
import { BsPersonPlusFill, BsSearch } from 'react-icons/bs';
|
||||
import { ImProfile } from 'react-icons/im';
|
||||
import {
|
||||
RiMenuFoldLine,
|
||||
@@ -16,8 +17,10 @@ import ThemeButton from './ThemeButton';
|
||||
import UserButton from './UserButton';
|
||||
|
||||
export default function Navbar() {
|
||||
const [term, setTerm] = useState('');
|
||||
const { user } = useUser();
|
||||
const [navOpen, setNavOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
function handler() {
|
||||
@@ -33,68 +36,74 @@ export default function Navbar() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<nav className='flex flex-col items-center gap-4 p-5 shadow-md dark:bg-secondary-gray md:flex-row md:justify-between'>
|
||||
<div className='flex items-center justify-between w-full md:max-w-min'>
|
||||
<Link
|
||||
href='/'
|
||||
className='italic'
|
||||
>
|
||||
<nav className="flex flex-col items-center gap-4 p-5 shadow-md dark:bg-secondary-gray md:flex-row md:justify-between">
|
||||
<div className="flex items-center justify-between w-full md:max-w-min">
|
||||
<Link href="/" className="italic">
|
||||
conve
|
||||
<span className='font-bold text-4xl text-primary-accent'>X</span>
|
||||
<span className="font-bold text-4xl text-primary-accent">X</span>
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => setNavOpen(!navOpen)}
|
||||
type='button'
|
||||
className='flex items-center justify-center md:hidden'
|
||||
type="button"
|
||||
className="flex items-center justify-center md:hidden"
|
||||
>
|
||||
{navOpen ? <RiMenuUnfoldLine /> : <RiMenuFoldLine />}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className='hidden flex-1 flex-col gap-4 w-full items-center md:flex md:flex-row md:justify-between'
|
||||
className="hidden flex-1 flex-col gap-4 w-full items-center md:flex md:flex-row md:justify-between"
|
||||
style={{ display: navOpen ? 'flex' : '' }}
|
||||
>
|
||||
<ul className='flex flex-col w-full items-start gap-4 md:flex-row md:items-center md:max-w-min'>
|
||||
<ul className="flex flex-col w-full items-start gap-4 md:flex-row md:items-center md:max-w-min">
|
||||
<SignedIn>
|
||||
<li className='flex w-full'>
|
||||
<div className='flex w-full'>
|
||||
<li className="flex w-full">
|
||||
<form
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
router.push(`/search?term=${term}`);
|
||||
}}
|
||||
className="flex w-full relative"
|
||||
>
|
||||
<input
|
||||
type='text'
|
||||
placeholder='Search'
|
||||
className='w-full md:w-[unset] py-1 px-2 rounded-md border border-gray-600 dark:bg-primary-gray dark:border-0'
|
||||
onChange={e => setTerm(e.target.value)}
|
||||
value={term}
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
className="w-full md:w-[unset] py-1 px-2 rounded-md border border-gray-600 dark:bg-primary-gray dark:border-0"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="flex items-center justify-center absolute right-0 top-1/2 transform -translate-x-1/2 -translate-y-1/2"
|
||||
disabled={!term || !term.trim()}
|
||||
type="submit"
|
||||
>
|
||||
<BsSearch />
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='p-1 md:hidden'>
|
||||
<AiFillHome className='w-6 h-6' />
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-1 md:hidden">
|
||||
<AiFillHome className="w-6 h-6" />
|
||||
</div>
|
||||
<Link
|
||||
onClick={() => setNavOpen(false)}
|
||||
href='/'
|
||||
>
|
||||
<Link onClick={() => setNavOpen(false)} href="/">
|
||||
Home
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='p-1 md:hidden'>
|
||||
<RiUserFollowFill className='w-6 h-6' />
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-1 md:hidden">
|
||||
<RiUserFollowFill className="w-6 h-6" />
|
||||
</div>
|
||||
<Link
|
||||
onClick={() => setNavOpen(false)}
|
||||
href='/following'
|
||||
>
|
||||
<Link onClick={() => setNavOpen(false)} href="/following">
|
||||
Following
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='p-1 md:hidden'>
|
||||
<ImProfile className='w-6 h-6' />
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-1 md:hidden">
|
||||
<ImProfile className="w-6 h-6" />
|
||||
</div>
|
||||
<Link
|
||||
onClick={() => setNavOpen(false)}
|
||||
@@ -104,55 +113,46 @@ export default function Navbar() {
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
<li className='flex items-center gap-2 max-w-min whitespace-nowrap'>
|
||||
<div className='p-1 bg-primary-accent rounded-md rounded-bl-none'>
|
||||
<Link
|
||||
onClick={() => setNavOpen(false)}
|
||||
href='/posts/add'
|
||||
>
|
||||
<BiPlus className='w-6 h-6 text-white' />
|
||||
<li className="flex items-center gap-2 max-w-min whitespace-nowrap">
|
||||
<div className="p-1 bg-primary-accent rounded-md rounded-bl-none">
|
||||
<Link onClick={() => setNavOpen(false)} href="/posts/add">
|
||||
<BiPlus className="w-6 h-6 text-white" />
|
||||
</Link>
|
||||
</div>
|
||||
<span className='md:hidden'>Add Post</span>
|
||||
<span className="md:hidden">Add Post</span>
|
||||
</li>
|
||||
</SignedIn>
|
||||
</ul>
|
||||
<ul className='flex flex-col w-full items-start gap-4 md:flex-row md:items-center md:max-w-min'>
|
||||
<li className='flex items-center gap-2'>
|
||||
<div className='p-1'>
|
||||
<ul className="flex flex-col w-full items-start gap-4 md:flex-row md:items-center md:max-w-min">
|
||||
<li className="flex items-center gap-2">
|
||||
<div className="p-1">
|
||||
<ThemeButton />
|
||||
</div>
|
||||
<span className='md:hidden'>Change Theme</span>
|
||||
<span className="md:hidden">Change Theme</span>
|
||||
</li>
|
||||
<SignedIn>
|
||||
<li className='flex items-center gap-2'>
|
||||
<li className="flex items-center gap-2">
|
||||
<UserButton />
|
||||
<span className='md:hidden'>Manage Account</span>
|
||||
<span className="md:hidden">Manage Account</span>
|
||||
</li>
|
||||
</SignedIn>
|
||||
<SignedOut>
|
||||
<li>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='p-1 md:hidden'>
|
||||
<BiLogIn className='w-6 h-6' />
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-1 md:hidden">
|
||||
<BiLogIn className="w-6 h-6" />
|
||||
</div>
|
||||
<Link
|
||||
href='/login'
|
||||
className='whitespace-nowrap'
|
||||
>
|
||||
<Link href="/login" className="whitespace-nowrap">
|
||||
Sign in
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='p-1 md:hidden'>
|
||||
<BsPersonPlusFill className='w-6 h-6' />
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-1 md:hidden">
|
||||
<BsPersonPlusFill className="w-6 h-6" />
|
||||
</div>
|
||||
<Link
|
||||
href='/signup'
|
||||
className='whitespace-nowrap'
|
||||
>
|
||||
<Link href="/signup" className="whitespace-nowrap">
|
||||
Sign up
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
+13
-22
@@ -44,30 +44,27 @@ export default function Post({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex w-full gap-4 p-8 bg-white dark:bg-secondary-gray flex-wrap'>
|
||||
<div className='flex-shrink-0'>
|
||||
<div className="flex w-full gap-4 p-8 bg-white dark:bg-secondary-gray flex-wrap">
|
||||
<div className="flex-shrink-0">
|
||||
<Link href={`/profile/${post.user.clerkUserId}`}>
|
||||
<Image
|
||||
alt='user profile image'
|
||||
alt="user profile image"
|
||||
src={post.user.clerkImageUrl}
|
||||
width={40}
|
||||
height={40}
|
||||
className='rounded-full object-cover border-4 border-primary-accent'
|
||||
className="rounded-full object-cover border-4 border-primary-accent"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className='flex flex-1 flex-col gap-2 min-w-0'>
|
||||
<div className='flex items-center justify-between gap-2'>
|
||||
<div className='flex items-center gap-2 flex-wrap min-w-0'>
|
||||
<div
|
||||
className='overflow-hidden text-ellipsis'
|
||||
title={username}
|
||||
>
|
||||
<div className="flex flex-1 flex-col gap-2 min-w-0">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2 flex-wrap min-w-0">
|
||||
<div className="overflow-hidden text-ellipsis" title={username}>
|
||||
{username}
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<GoDotFill className='w-3 h-3' />
|
||||
<div className='text-xs'>
|
||||
<div className="flex items-center gap-2">
|
||||
<GoDotFill className="w-3 h-3" />
|
||||
<div className="text-xs">
|
||||
{`${postMonth} ${dayOfMonth}, ${postYear}`}
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,18 +80,12 @@ export default function Post({
|
||||
</div>
|
||||
<div>
|
||||
{limit && post.content.length > 10 ? (
|
||||
<Link
|
||||
className='font-semibold text-primary-accent'
|
||||
href={postLink}
|
||||
>
|
||||
<Link className="font-semibold text-primary-accent" href={postLink}>
|
||||
Show more
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
<PostActionButton
|
||||
postId={post._id}
|
||||
showReply={showReply}
|
||||
/>
|
||||
<PostActionButton postId={post._id} showReply={showReply} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -29,14 +29,14 @@ export default function PostActionButton({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-4'>
|
||||
<div className="flex items-center gap-4">
|
||||
{showReply && replyCount !== undefined ? (
|
||||
<Link
|
||||
className='flex items-center justify-center gap-2 px-3 py-1 border border-gray-600 rounded-md hover:bg-primary-accent hover:text-white'
|
||||
className="flex items-center justify-center gap-2 px-3 py-1 border border-gray-600 rounded-md hover:bg-primary-accent hover:text-white"
|
||||
href={{ pathname: `/posts/${postId}`, query: { reply: true } }}
|
||||
>
|
||||
<BsFillReplyFill />
|
||||
<div className='flex items-center justify-center h-6 w-6 rounded-full text-xs bg-primary-gray text-white'>
|
||||
<div className="flex items-center justify-center h-6 w-6 rounded-full text-xs bg-primary-gray text-white">
|
||||
{replyCount}
|
||||
</div>
|
||||
</Link>
|
||||
@@ -44,13 +44,13 @@ export default function PostActionButton({
|
||||
{like !== undefined && likeCount !== undefined ? (
|
||||
<button
|
||||
onClick={handleLikeClick}
|
||||
type='button'
|
||||
type="button"
|
||||
className={`flex items-center justify-center gap-2 px-3 py-1 border border-gray-600 rounded-md ${
|
||||
isLiked ? 'text-primary-accent' : ''
|
||||
}`}
|
||||
>
|
||||
<BiSolidLike className='flex-shrink-0' />
|
||||
<div className='flex items-center justify-center h-6 w-6 rounded-full text-xs bg-primary-gray text-white'>
|
||||
<BiSolidLike className="flex-shrink-0" />
|
||||
<div className="flex items-center justify-center h-6 w-6 rounded-full text-xs bg-primary-gray text-white">
|
||||
{likeCount}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function PostActionModal({ postId }: { postId: Id<'posts'> }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<div className="relative">
|
||||
<button onClick={() => setModalOpen(!modalOpen)}>
|
||||
<BiDotsHorizontalRounded />
|
||||
</button>
|
||||
@@ -55,11 +55,8 @@ export default function PostActionModal({ postId }: { postId: Id<'posts'> }) {
|
||||
Edit post
|
||||
</Link>
|
||||
</li>
|
||||
<li className='text-red-600'>
|
||||
<button
|
||||
onClick={handleDeleteButtonClick}
|
||||
type='button'
|
||||
>
|
||||
<li className="text-red-600">
|
||||
<button onClick={handleDeleteButtonClick} type="button">
|
||||
Delete post
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -7,7 +7,7 @@ import './MarkdownContent.css';
|
||||
|
||||
export default function PostContent({ content }: { content: string }) {
|
||||
return (
|
||||
<div className='markdown-content'>
|
||||
<div className="markdown-content">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkEmoji]}
|
||||
components={{
|
||||
@@ -19,15 +19,12 @@ export default function PostContent({ content }: { content: string }) {
|
||||
{...props}
|
||||
style={oneDark}
|
||||
language={match[1]}
|
||||
PreTag='div'
|
||||
PreTag="div"
|
||||
>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code
|
||||
{...props}
|
||||
className={className}
|
||||
>
|
||||
<code {...props} className={className}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
|
||||
@@ -21,13 +21,10 @@ export default function PostReplies({ parentId }: { parentId: Id<'posts'> }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col-reverse items-center w-full gap-4'>
|
||||
<div className='flex flex-col-reverse items-center w-full gap-4'>
|
||||
<div className="flex flex-col-reverse items-center w-full gap-4">
|
||||
<div className="flex flex-col-reverse items-center w-full gap-4">
|
||||
{pager.results.map(reply => (
|
||||
<div
|
||||
key={reply._id}
|
||||
className='w-full'
|
||||
>
|
||||
<div key={reply._id} className="w-full">
|
||||
<Reply post={reply} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
+12
-15
@@ -16,39 +16,36 @@ export default function Reply({ post }: { post: PostWithUserDto }) {
|
||||
const year = created.getFullYear();
|
||||
|
||||
return (
|
||||
<div className='flex w-full gap-4'>
|
||||
<div className="flex w-full gap-4">
|
||||
<div>
|
||||
<Link href={`/profile/${post.user.clerkUserId}`}>
|
||||
<Image
|
||||
alt='user profile image'
|
||||
alt="user profile image"
|
||||
src={post.user.clerkImageUrl}
|
||||
width={40}
|
||||
height={40}
|
||||
className='rounded-full object-cover border-4 border-primary-accent'
|
||||
className="rounded-full object-cover border-4 border-primary-accent"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className='flex flex-col flex-1 w-full border border-gray-600 rounded-md'>
|
||||
<div className='flex flex-col w-full p-1'>
|
||||
<div className='flex justify-between dark:bg-primary-gray p-2'>
|
||||
<div className='text-sm'>
|
||||
<div className="flex flex-col flex-1 w-full border border-gray-600 rounded-md">
|
||||
<div className="flex flex-col w-full p-1">
|
||||
<div className="flex justify-between dark:bg-primary-gray p-2">
|
||||
<div className="text-sm">
|
||||
{`${username} `}
|
||||
<span className='text-[#7a838f]'>{`replied on ${month} ${day}, ${year}`}</span>
|
||||
<span className="text-[#7a838f]">{`replied on ${month} ${day}, ${year}`}</span>
|
||||
</div>
|
||||
{isLoaded && isSignedIn && user.id === post.user.clerkUserId ? (
|
||||
<PostActionModal postId={post._id} />
|
||||
) : null}
|
||||
</div>
|
||||
<div className='p-1'>
|
||||
<div className='p-2 rounded-md dark:bg-secondary-gray'>
|
||||
<div className="p-1">
|
||||
<div className="p-2 rounded-md dark:bg-secondary-gray">
|
||||
<PostContent content={Text.of(post.content).toString()} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='p-1'>
|
||||
<PostActionButton
|
||||
postId={post._id}
|
||||
showReply={false}
|
||||
/>
|
||||
<div className="p-1">
|
||||
<PostActionButton postId={post._id} showReply={false} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
'use client';
|
||||
|
||||
import { PostWithUserDto, UserDto } from '@/app/types';
|
||||
import { UsePaginatedQueryReturnType, usePaginatedQuery } from 'convex/react';
|
||||
import { FunctionReference } from 'convex/server';
|
||||
import { useState } from 'react';
|
||||
import { api } from '../../convex/_generated/api';
|
||||
import Loader from './Loader';
|
||||
import Post from './Post';
|
||||
import UserProfile from './UserProfile';
|
||||
|
||||
type Pager<T> = UsePaginatedQueryReturnType<
|
||||
FunctionReference<
|
||||
'query',
|
||||
'public',
|
||||
{
|
||||
term: string;
|
||||
paginationOpts: {
|
||||
id?: number | undefined;
|
||||
numItems: number;
|
||||
cursor: string | null;
|
||||
};
|
||||
},
|
||||
{
|
||||
page: T[];
|
||||
isDone: boolean;
|
||||
continueCursor: string;
|
||||
}
|
||||
>
|
||||
>;
|
||||
|
||||
function NoResults() {
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center gap-2">
|
||||
<h2 className="text-xl">
|
||||
Hmmm it doesn't look like we could find anything.
|
||||
</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function UserSearchResults({
|
||||
pager,
|
||||
pageSize,
|
||||
}: {
|
||||
pager: Pager<UserDto>;
|
||||
pageSize: number;
|
||||
}) {
|
||||
const { status, results, isLoading, loadMore } = pager;
|
||||
|
||||
if (isLoading === false && results.length == 0) {
|
||||
return <NoResults />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{results.map(user => (
|
||||
<div key={user._id}>
|
||||
<UserProfile user={user} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Loader
|
||||
isLoading={isLoading}
|
||||
status={status}
|
||||
loadButtonClickHandler={() => loadMore(pageSize)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PostSearchResults({
|
||||
pager,
|
||||
pageSize,
|
||||
}: {
|
||||
pager: Pager<PostWithUserDto>;
|
||||
pageSize: number;
|
||||
}) {
|
||||
const { status, results, isLoading, loadMore } = pager;
|
||||
|
||||
if (isLoading === false && results.length == 0) {
|
||||
return <NoResults />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center w-full">
|
||||
<div className="flex flex-col items-center w-full">
|
||||
{results.map(post => (
|
||||
<div
|
||||
key={post._id}
|
||||
className="w-full border border-t-0 border-gray-600 first:border-t last:rounded-b-md p-1 last:mb-4"
|
||||
>
|
||||
<Post post={post} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Loader
|
||||
isLoading={isLoading}
|
||||
status={status}
|
||||
loadButtonClickHandler={() => loadMore(pageSize)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SearchResults({ term }: { term: string }) {
|
||||
const PAGE_SIZE = 10;
|
||||
const [current, setCurrent] = useState<'posts' | 'users'>('posts');
|
||||
|
||||
const postsPager = usePaginatedQuery(
|
||||
api.posts.getPostsBySearchTerm,
|
||||
{ term },
|
||||
{ initialNumItems: PAGE_SIZE }
|
||||
);
|
||||
const usersPager = usePaginatedQuery(
|
||||
api.users.getUsersBySearchTerm,
|
||||
{ term },
|
||||
{ initialNumItems: PAGE_SIZE }
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex gap-2 border-b border-gray-600">
|
||||
<button
|
||||
onClick={() => setCurrent('posts')}
|
||||
disabled={current === 'posts'}
|
||||
type="button"
|
||||
className="py-1 px-2 border-b-4 border-transparent disabled:border-primary-accent"
|
||||
>
|
||||
Posts
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrent('users')}
|
||||
disabled={current === 'users'}
|
||||
type="button"
|
||||
className="py-1 px-2 border-b-4 border-transparent disabled:border-primary-accent"
|
||||
>
|
||||
Users
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{current === 'posts' ? (
|
||||
<PostSearchResults pager={postsPager} pageSize={PAGE_SIZE} />
|
||||
) : (
|
||||
<UserSearchResults pager={usersPager} pageSize={PAGE_SIZE} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -13,15 +13,15 @@ export default function ThemeButton() {
|
||||
|
||||
return (
|
||||
<button
|
||||
className='flex items-center justify-center'
|
||||
className="flex items-center justify-center"
|
||||
onClick={() => setTheme(isDark ? 'light' : 'dark')}
|
||||
>
|
||||
{mounted === false ? (
|
||||
<Loader className='animate-spin w-6 h-6' />
|
||||
<Loader className="animate-spin w-6 h-6" />
|
||||
) : isDark ? (
|
||||
<BsMoonStarsFill className='w-6 h-6' />
|
||||
<BsMoonStarsFill className="w-6 h-6" />
|
||||
) : (
|
||||
<BsSunFill className='w-6 h-6' />
|
||||
<BsSunFill className="w-6 h-6" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -19,10 +19,10 @@ export default function UserButton({ children }: { children?: ReactNode }) {
|
||||
'border-solid border-2 border-primary-accent focus:shadow-none',
|
||||
},
|
||||
}}
|
||||
userProfileMode='navigation'
|
||||
userProfileUrl='/account'
|
||||
signInUrl='/login'
|
||||
afterSignOutUrl='/'
|
||||
userProfileMode="navigation"
|
||||
userProfileUrl="/account"
|
||||
signInUrl="/login"
|
||||
afterSignOutUrl="/"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@ export default function UserPosts({ user }: { user: UserDto }) {
|
||||
|
||||
if (pager.isLoading === false && postsWithUser.length === 0) {
|
||||
return (
|
||||
<div className='flex flex-col items-center justify-center border-t border-gray-600 w-full h-full'>
|
||||
<div className='flex flex-col items-center gap-2'>
|
||||
<h2 className='text-xl font-semibold'>
|
||||
<div className="flex flex-col items-center justify-center border-t border-gray-600 w-full h-full">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<h2 className="text-xl font-semibold">
|
||||
Hmm...it doesn't look like you've posted anything.
|
||||
</h2>
|
||||
<Link
|
||||
href='/posts/add'
|
||||
className='inline-flex max-w-max items-center justify-center px-3 py-1 bg-primary-accent rounded-md text-white'
|
||||
href="/posts/add"
|
||||
className="inline-flex max-w-max items-center justify-center px-3 py-1 bg-primary-accent rounded-md text-white"
|
||||
>
|
||||
Make a post
|
||||
</Link>
|
||||
@@ -42,12 +42,12 @@ export default function UserPosts({ user }: { user: UserDto }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-center w-full'>
|
||||
<div className='flex flex-col items-center w-full'>
|
||||
<div className="flex flex-col items-center w-full">
|
||||
<div className="flex flex-col items-center w-full">
|
||||
{postsWithUser.map(post => (
|
||||
<div
|
||||
key={post._id}
|
||||
className='w-full border border-t-0 border-gray-600 first:border-t last:rounded-b-md p-1 last:mb-4'
|
||||
className="w-full border border-t-0 border-gray-600 last:rounded-b-md p-1 last:mb-4"
|
||||
>
|
||||
<Post post={post} />
|
||||
</div>
|
||||
|
||||
@@ -49,28 +49,28 @@ export default function UserProfile({ user }: { user: UserDto }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
<div className='shadow-md bg-gradient-to-r from-violet-600 via-violet-600 to-indigo-600 rounded-t-md pt-20 px-5'>
|
||||
<div className='flex items-center gap-4 -mb-[25px]'>
|
||||
<div className='flex-shrink-0'>
|
||||
<div className="w-full">
|
||||
<div className="shadow-md bg-gradient-to-r from-violet-600 via-violet-600 to-indigo-600 rounded-t-md pt-20 px-5">
|
||||
<div className="flex items-center gap-4 -mb-[25px]">
|
||||
<div className="flex-shrink-0">
|
||||
<Image
|
||||
alt='user profile image'
|
||||
alt="user profile image"
|
||||
src={user.clerkImageUrl}
|
||||
width={100}
|
||||
height={100}
|
||||
className='rounded-full object-cover border-4 border-primary-accent'
|
||||
className="rounded-full object-cover border-4 border-primary-accent"
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col-reverse mb-[25px] flex-1 min-w-0 gap-0.5 md:flex-row md:mb-0 md:gap-2'>
|
||||
<div className='flex flex-col min-w-0 text-white'>
|
||||
<div className="flex flex-col-reverse mb-[25px] flex-1 min-w-0 gap-0.5 md:flex-row md:mb-0 md:gap-2">
|
||||
<div className="flex flex-col min-w-0 text-white">
|
||||
<h1
|
||||
className='font-bold overflow-hidden text-ellipsis'
|
||||
className="font-bold overflow-hidden text-ellipsis"
|
||||
title={username}
|
||||
>
|
||||
{username}
|
||||
</h1>
|
||||
<div className='flex gap-2 items-center text-sm'>
|
||||
<AiFillCalendar className='w-4 h-4' />
|
||||
<div className="flex gap-2 items-center text-sm">
|
||||
<AiFillCalendar className="w-4 h-4" />
|
||||
{`Joined ${monthJoined} ${yearJoined}`}
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,7 +78,7 @@ export default function UserProfile({ user }: { user: UserDto }) {
|
||||
{isLoaded && isSignedIn && currentUser.id !== user.clerkUserId ? (
|
||||
<button
|
||||
onClick={handleFollowButtonClick}
|
||||
type='button'
|
||||
type="button"
|
||||
className={`py-0.5 px-3 rounded-full text-sm border border-white text-white ${
|
||||
isFollowing ? 'bg-primary-accent' : 'o'
|
||||
}`}
|
||||
@@ -90,22 +90,22 @@ export default function UserProfile({ user }: { user: UserDto }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-1 bg-white dark:bg-primary-gray pl-5 border border-gray-600 border-b-0'>
|
||||
<div className='flex flex-col min-h-[25px] items-start gap-1 py-1 pl-[116px] md:flex-row md:items-center md:gap-4'>
|
||||
<div className="flex flex-1 bg-white dark:bg-primary-gray pl-5 border border-gray-600">
|
||||
<div className="flex flex-col min-h-[25px] items-start gap-1 py-1 pl-[116px] md:flex-row md:items-center md:gap-4">
|
||||
{countDataLoaded ? (
|
||||
<>
|
||||
<div>
|
||||
<span className='font-bold whitespace-nowrap'>{postCount}</span>{' '}
|
||||
<span className="font-bold whitespace-nowrap">{postCount}</span>{' '}
|
||||
posts
|
||||
</div>
|
||||
<div className='min-w-0'>
|
||||
<span className='font-bold whitespace-nowrap'>
|
||||
<div className="min-w-0">
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
{followerCount}
|
||||
</span>{' '}
|
||||
followers
|
||||
</div>
|
||||
<div>
|
||||
<span className='font-bold whitespace-nowrap'>
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
{followingCount}
|
||||
</span>{' '}
|
||||
following
|
||||
|
||||
@@ -16,10 +16,7 @@ const client = new ConvexReactClient(CONVEX_URL);
|
||||
|
||||
export function ConvexProvider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<ConvexProviderWithClerk
|
||||
client={client}
|
||||
useAuth={useAuth}
|
||||
>
|
||||
<ConvexProviderWithClerk client={client} useAuth={useAuth}>
|
||||
{children}
|
||||
</ConvexProviderWithClerk>
|
||||
);
|
||||
@@ -27,11 +24,7 @@ export function ConvexProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
export function NextThemesProvider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<ThemeProvider
|
||||
attribute='class'
|
||||
defaultTheme='light'
|
||||
enableSystem
|
||||
>
|
||||
<ThemeProvider attribute="class" defaultTheme="light" enableSystem>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user