diff --git a/api-schema.graphql b/api-schema.graphql index 3ed6625..12cbd81 100644 --- a/api-schema.graphql +++ b/api-schema.graphql @@ -81,6 +81,76 @@ type AppConfig { authTwitterEnabled: Boolean! } +type Claim { + amount: String! + communityId: String! + createdAt: DateTime + id: String! + minter: String! + name: String! + provider: IdentityProvider! + providerId: String! + signature: String + status: ClaimStatus! + updatedAt: DateTime +} + +input ClaimAdminCreateInput { + amount: String + communityId: String! + minter: String! + provider: IdentityProvider! + providerId: String! +} + +input ClaimAdminFindManyInput { + communityId: String! + limit: Int = 10 + page: Int = 1 + search: String +} + +input ClaimAdminUpdateInput { + amount: String + signature: String + status: ClaimStatus +} + +type ClaimPaging { + data: [Claim!]! + meta: PagingMeta! +} + +enum ClaimStatus { + Claimed + Pending +} + +input ClaimUserCreateInput { + amount: String + communityId: String! + minter: String! + provider: IdentityProvider! + providerId: String! +} + +input ClaimUserFindManyInput { + communityId: String! + limit: Int = 10 + minter: String + page: Int = 1 + provider: IdentityProvider + providerId: String + search: String + status: ClaimStatus +} + +input ClaimUserUpdateInput { + amount: String + signature: String + status: ClaimStatus +} + type Community { createdAt: DateTime description: String! @@ -184,12 +254,14 @@ input LoginInput { } type Mutation { + adminCreateClaim(input: ClaimAdminCreateInput!): Claim adminCreateCommunityMember(input: AdminCreateCommunityMemberInput!): CommunityMember adminCreateIdentity(input: AdminCreateIdentityInput!): Identity adminCreatePreset(input: PresetAdminCreateInput!): Preset adminCreatePrice(input: PriceAdminCreateInput!): Price adminCreateUser(input: AdminCreateUserInput!): User adminCreateWallet(input: WalletAdminCreateInput!): Wallet + adminDeleteClaim(claimId: String!): Boolean adminDeleteCommunity(communityId: String!): Boolean adminDeleteCommunityMember(communityMemberId: String!): Boolean adminDeleteIdentity(identityId: String!): Boolean @@ -197,6 +269,7 @@ type Mutation { adminDeletePrice(priceId: String!): Boolean adminDeleteUser(userId: String!): Boolean adminDeleteWallet(walletId: String!): Boolean + adminUpdateClaim(claimId: String!, input: ClaimAdminUpdateInput!): Claim adminUpdateCommunity(communityId: String!, input: AdminUpdateCommunityInput!): Community adminUpdateCommunityMember(communityMemberId: String!, input: AdminUpdateCommunityMemberInput!): CommunityMember adminUpdatePreset(input: PresetAdminUpdateInput!, presetId: String!): Preset @@ -208,17 +281,21 @@ type Mutation { logout: Boolean register(input: RegisterInput!): User solanaRequestAirdrop(account: String!): JSON + userCreateClaim(input: ClaimUserCreateInput!): Claim userCreateCommunity(input: UserCreateCommunityInput!): Community userCreateCommunityMember(input: UserCreateCommunityMemberInput!): CommunityMember userCreateMintFromMinter(account: String!, communitySlug: String!): String userCreateMintFromPreset(communitySlug: String!, presetId: String!): String userCreateWallet(input: WalletUserCreateInput!): Wallet + userDeleteClaim(claimId: String!): Boolean userDeleteCommunity(communityId: String!): Boolean userDeleteCommunityMember(communityMemberId: String!): Boolean userDeleteIdentity(identityId: String!): Boolean + userDeleteMinter(account: String!): Boolean userDeleteWallet(publicKey: String!): Boolean userLinkIdentity(input: LinkIdentityInput!): Identity userSetWalletFeepayer(publicKey: String!): Wallet + userUpdateClaim(claimId: String!, input: ClaimUserUpdateInput!): Claim userUpdateCommunity(communityId: String!, input: UserUpdateCommunityInput!): Community userUpdateCommunityMember(communityMemberId: String!, input: UserUpdateCommunityMemberInput!): CommunityMember userUpdateUser(input: UserUpdateUserInput!): User @@ -314,6 +391,7 @@ input PriceUserFindManyInput { } type Query { + adminFindManyClaim(input: ClaimAdminFindManyInput!): ClaimPaging! adminFindManyCommunity(input: AdminFindManyCommunityInput!): CommunityPaging! adminFindManyCommunityMember(input: AdminFindManyCommunityMemberInput!): CommunityMemberPaging! adminFindManyIdentity(input: AdminFindManyIdentityInput!): [Identity!] @@ -321,6 +399,7 @@ type Query { adminFindManyPrice(input: PriceAdminFindManyInput!): [Price!]! adminFindManyUser(input: AdminFindManyUserInput!): UserPaging! adminFindManyWallet(input: WalletAdminFindManyInput!): WalletPaging! + adminFindOneClaim(claimId: String!): Claim adminFindOneCommunity(communityId: String!): Community adminFindOneCommunityMember(communityMemberId: String!): CommunityMember adminFindOnePreset(presetId: String!): Preset @@ -337,6 +416,7 @@ type Query { solanaGetTokenAccounts(account: String!): JSON solanaGetTransactions(account: String!): JSON uptime: Float! + userFindManyClaim(input: ClaimUserFindManyInput!): ClaimPaging! userFindManyCommunity(input: UserFindManyCommunityInput!): CommunityPaging! userFindManyCommunityMember(input: UserFindManyCommunityMemberInput!): CommunityMemberPaging! userFindManyIdentity(input: UserFindManyIdentityInput!): [Identity!] @@ -344,6 +424,7 @@ type Query { userFindManyPrice(input: PriceUserFindManyInput!): [Price!]! userFindManyUser(input: UserFindManyUserInput!): UserPaging! userFindManyWallet(input: WalletUserFindManyInput!): WalletPaging! + userFindOneClaim(claimId: String!): Claim userFindOneCommunity(slug: String!): Community userFindOneCommunityMember(communityMemberId: String!): CommunityMember userFindOnePreset(presetId: String!): Preset diff --git a/apps/api-e2e/src/api/api-claim-admin-feature.spec.ts b/apps/api-e2e/src/api/api-claim-admin-feature.spec.ts new file mode 100644 index 0000000..33cdfa3 --- /dev/null +++ b/apps/api-e2e/src/api/api-claim-admin-feature.spec.ts @@ -0,0 +1,150 @@ +import { ClaimAdminCreateInput, ClaimAdminFindManyInput, ClaimAdminUpdateInput, Claim } from '@tokengator-mint/sdk' +import { getAliceCookie, getBobCookie, sdk, uniqueId } from '../support' + +describe('api-claim-feature', () => { + describe('api-claim-admin-resolver', () => { + const claimName = uniqueId('acme-claim') + + let claimId: string + let cookie: string + + beforeAll(async () => { + cookie = await getAliceCookie() + const created = await sdk.adminCreateClaim({ input: { name: claimName } }, { cookie }) + claimId = created.data.created.id + }) + + describe('authorized', () => { + beforeAll(async () => { + cookie = await getAliceCookie() + }) + + it('should create a claim', async () => { + const input: ClaimAdminCreateInput = { + name: uniqueId('claim'), + } + + const res = await sdk.adminCreateClaim({ input }, { cookie }) + + const item: Claim = res.data.created + expect(item.name).toBe(input.name) + expect(item.id).toBeDefined() + expect(item.createdAt).toBeDefined() + expect(item.updatedAt).toBeDefined() + }) + + it('should update a claim', async () => { + const createInput: ClaimAdminCreateInput = { + name: uniqueId('claim'), + } + const createdRes = await sdk.adminCreateClaim({ input: createInput }, { cookie }) + const claimId = createdRes.data.created.id + const input: ClaimAdminUpdateInput = { + name: uniqueId('claim'), + } + + const res = await sdk.adminUpdateClaim({ claimId, input }, { cookie }) + + const item: Claim = res.data.updated + expect(item.name).toBe(input.name) + }) + + it('should find a list of claims (find all)', async () => { + const createInput: ClaimAdminCreateInput = { + name: uniqueId('claim'), + } + const createdRes = await sdk.adminCreateClaim({ input: createInput }, { cookie }) + const claimId = createdRes.data.created.id + + const input: ClaimAdminFindManyInput = {} + + const res = await sdk.adminFindManyClaim({ input }, { cookie }) + + expect(res.data.paging.meta.totalCount).toBeGreaterThan(1) + expect(res.data.paging.data.length).toBeGreaterThan(1) + // First item should be the one we created above + expect(res.data.paging.data[0].id).toBe(claimId) + }) + + it('should find a list of claims (find new one)', async () => { + const createInput: ClaimAdminCreateInput = { + name: uniqueId('claim'), + } + const createdRes = await sdk.adminCreateClaim({ input: createInput }, { cookie }) + const claimId = createdRes.data.created.id + + const input: ClaimAdminFindManyInput = { + search: claimId, + } + + const res = await sdk.adminFindManyClaim({ input }, { cookie }) + + expect(res.data.paging.meta.totalCount).toBe(1) + expect(res.data.paging.data.length).toBe(1) + expect(res.data.paging.data[0].id).toBe(claimId) + }) + + it('should find a claim by id', async () => { + const createInput: ClaimAdminCreateInput = { + name: uniqueId('claim'), + } + const createdRes = await sdk.adminCreateClaim({ input: createInput }, { cookie }) + const claimId = createdRes.data.created.id + + const res = await sdk.adminFindOneClaim({ claimId }, { cookie }) + + expect(res.data.item.id).toBe(claimId) + }) + + it('should delete a claim', async () => { + const createInput: ClaimAdminCreateInput = { + name: uniqueId('claim'), + } + const createdRes = await sdk.adminCreateClaim({ input: createInput }, { cookie }) + const claimId = createdRes.data.created.id + + const res = await sdk.adminDeleteClaim({ claimId }, { cookie }) + + expect(res.data.deleted).toBe(true) + + const findRes = await sdk.adminFindManyClaim({ input: { search: claimId } }, { cookie }) + expect(findRes.data.paging.meta.totalCount).toBe(0) + expect(findRes.data.paging.data.length).toBe(0) + }) + }) + + describe('unauthorized', () => { + let cookie: string + beforeAll(async () => { + cookie = await getBobCookie() + }) + + it('should not update a claim', async () => { + expect.assertions(1) + try { + await sdk.adminUpdateClaim({ claimId, input: {} }, { cookie }) + } catch (e) { + expect(e.message).toBe('Unauthorized: User is not Admin') + } + }) + + it('should not find a claim by id', async () => { + expect.assertions(1) + try { + await sdk.adminFindOneClaim({ claimId }, { cookie }) + } catch (e) { + expect(e.message).toBe('Unauthorized: User is not Admin') + } + }) + + it('should not delete a claim', async () => { + expect.assertions(1) + try { + await sdk.adminDeleteClaim({ claimId }, { cookie }) + } catch (e) { + expect(e.message).toBe('Unauthorized: User is not Admin') + } + }) + }) + }) +}) diff --git a/apps/api-e2e/src/api/api-claim-user-feature.spec.ts b/apps/api-e2e/src/api/api-claim-user-feature.spec.ts new file mode 100644 index 0000000..faee71f --- /dev/null +++ b/apps/api-e2e/src/api/api-claim-user-feature.spec.ts @@ -0,0 +1,150 @@ +import { ClaimUserCreateInput, ClaimUserFindManyInput, ClaimUserUpdateInput, Claim } from '@tokengator-mint/sdk' +import { getAliceCookie, getBobCookie, sdk, uniqueId } from '../support' + +describe('api-claim-feature', () => { + describe('api-claim-user-resolver', () => { + const claimName = uniqueId('acme-claim') + + let claimId: string + let cookie: string + + beforeAll(async () => { + cookie = await getAliceCookie() + const created = await sdk.userCreateClaim({ input: { name: claimName } }, { cookie }) + claimId = created.data.created.id + }) + + describe('authorized', () => { + beforeAll(async () => { + cookie = await getAliceCookie() + }) + + it('should create a claim', async () => { + const input: ClaimUserCreateInput = { + name: uniqueId('claim'), + } + + const res = await sdk.userCreateClaim({ input }, { cookie }) + + const item: Claim = res.data.created + expect(item.name).toBe(input.name) + expect(item.id).toBeDefined() + expect(item.createdAt).toBeDefined() + expect(item.updatedAt).toBeDefined() + }) + + it('should update a claim', async () => { + const createInput: ClaimUserCreateInput = { + name: uniqueId('claim'), + } + const createdRes = await sdk.userCreateClaim({ input: createInput }, { cookie }) + const claimId = createdRes.data.created.id + const input: ClaimUserUpdateInput = { + name: uniqueId('claim'), + } + + const res = await sdk.userUpdateClaim({ claimId, input }, { cookie }) + + const item: Claim = res.data.updated + expect(item.name).toBe(input.name) + }) + + it('should find a list of claims (find all)', async () => { + const createInput: ClaimUserCreateInput = { + name: uniqueId('claim'), + } + const createdRes = await sdk.userCreateClaim({ input: createInput }, { cookie }) + const claimId = createdRes.data.created.id + + const input: ClaimUserFindManyInput = {} + + const res = await sdk.userFindManyClaim({ input }, { cookie }) + + expect(res.data.paging.meta.totalCount).toBeGreaterThan(1) + expect(res.data.paging.data.length).toBeGreaterThan(1) + // First item should be the one we created above + expect(res.data.paging.data[0].id).toBe(claimId) + }) + + it('should find a list of claims (find new one)', async () => { + const createInput: ClaimUserCreateInput = { + name: uniqueId('claim'), + } + const createdRes = await sdk.userCreateClaim({ input: createInput }, { cookie }) + const claimId = createdRes.data.created.id + + const input: ClaimUserFindManyInput = { + search: claimId, + } + + const res = await sdk.userFindManyClaim({ input }, { cookie }) + + expect(res.data.paging.meta.totalCount).toBe(1) + expect(res.data.paging.data.length).toBe(1) + expect(res.data.paging.data[0].id).toBe(claimId) + }) + + it('should find a claim by id', async () => { + const createInput: ClaimUserCreateInput = { + name: uniqueId('claim'), + } + const createdRes = await sdk.userCreateClaim({ input: createInput }, { cookie }) + const claimId = createdRes.data.created.id + + const res = await sdk.userFindOneClaim({ claimId }, { cookie }) + + expect(res.data.item.id).toBe(claimId) + }) + + it('should delete a claim', async () => { + const createInput: ClaimUserCreateInput = { + name: uniqueId('claim'), + } + const createdRes = await sdk.userCreateClaim({ input: createInput }, { cookie }) + const claimId = createdRes.data.created.id + + const res = await sdk.userDeleteClaim({ claimId }, { cookie }) + + expect(res.data.deleted).toBe(true) + + const findRes = await sdk.userFindManyClaim({ input: { search: claimId } }, { cookie }) + expect(findRes.data.paging.meta.totalCount).toBe(0) + expect(findRes.data.paging.data.length).toBe(0) + }) + }) + + describe('unauthorized', () => { + let cookie: string + beforeAll(async () => { + cookie = await getBobCookie() + }) + + it('should not update a claim', async () => { + expect.assertions(1) + try { + await sdk.userUpdateClaim({ claimId, input: {} }, { cookie }) + } catch (e) { + expect(e.message).toBe('You are not authorized to update this Claim') + } + }) + + it('should not find a claim by id', async () => { + expect.assertions(1) + try { + await sdk.userFindOneClaim({ claimId }, { cookie }) + } catch (e) { + expect(e.message).toBe('You are not authorized to view this Claim') + } + }) + + it('should not delete a claim', async () => { + expect.assertions(1) + try { + await sdk.userDeleteClaim({ claimId }, { cookie }) + } catch (e) { + expect(e.message).toBe('You are not authorized to delete this Claim') + } + }) + }) + }) +}) diff --git a/libs/api/claim/data-access/.eslintrc.json b/libs/api/claim/data-access/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/libs/api/claim/data-access/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/api/claim/data-access/README.md b/libs/api/claim/data-access/README.md new file mode 100644 index 0000000..ed480c8 --- /dev/null +++ b/libs/api/claim/data-access/README.md @@ -0,0 +1,7 @@ +# api-claim-data-access + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test api-claim-data-access` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/api/claim/data-access/jest.config.ts b/libs/api/claim/data-access/jest.config.ts new file mode 100644 index 0000000..b155ee2 --- /dev/null +++ b/libs/api/claim/data-access/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'api-claim-data-access', + preset: '../../../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../../../coverage/libs/api/claim/data-access', +} diff --git a/libs/api/claim/data-access/project.json b/libs/api/claim/data-access/project.json new file mode 100644 index 0000000..1a18b0c --- /dev/null +++ b/libs/api/claim/data-access/project.json @@ -0,0 +1,20 @@ +{ + "name": "api-claim-data-access", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/api/claim/data-access/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/api/claim/data-access/jest.config.ts" + } + } + }, + "tags": ["app:api", "type:data-access"] +} diff --git a/libs/api/claim/data-access/src/index.ts b/libs/api/claim/data-access/src/index.ts new file mode 100644 index 0000000..1e0df2c --- /dev/null +++ b/libs/api/claim/data-access/src/index.ts @@ -0,0 +1,11 @@ +export * from './lib/api-claim.data-access.module' +export * from './lib/api-claim.service' +export * from './lib/entity/claim.entity' +export * from './lib/dto/claim-admin-create.input' +export * from './lib/dto/claim-admin-find-many.input' +export * from './lib/dto/claim-admin-update.input' +export * from './lib/dto/claim-user-create.input' +export * from './lib/dto/claim-user-find-many.input' +export * from './lib/dto/claim-user-update.input' + +export * from './lib/entity/claim-status.enum' \ No newline at end of file diff --git a/libs/api/claim/data-access/src/lib/api-claim-data-admin.service.ts b/libs/api/claim/data-access/src/lib/api-claim-data-admin.service.ts new file mode 100644 index 0000000..979e7d6 --- /dev/null +++ b/libs/api/claim/data-access/src/lib/api-claim-data-admin.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@nestjs/common' +import { ClaimAdminCreateInput } from './dto/claim-admin-create.input' +import { ClaimAdminFindManyInput } from './dto/claim-admin-find-many.input' +import { ClaimAdminUpdateInput } from './dto/claim-admin-update.input' +import { ClaimPaging } from './entity/claim.entity' +import { getClaimWhereAdminInput } from './helpers/get-claim-where-admin.input' +import { ApiClaimDataService } from './api-claim-data.service' + +@Injectable() +export class ApiClaimDataAdminService { + constructor(private readonly data: ApiClaimDataService) {} + + async createClaim(input: ClaimAdminCreateInput) { + return this.data.create(input) + } + + async deleteClaim(claimId: string) { + return this.data.delete(claimId) + } + + async findManyClaim(input: ClaimAdminFindManyInput): Promise { + return this.data.findMany({ + orderBy: { createdAt: 'desc' }, + where: getClaimWhereAdminInput(input), + limit: input.limit, + page: input.page, + }) + } + + async findOneClaim(claimId: string) { + return this.data.findOne(claimId) + } + + async updateClaim(claimId: string, input: ClaimAdminUpdateInput) { + return this.data.update(claimId, input) + } +} diff --git a/libs/api/claim/data-access/src/lib/api-claim-data-user.service.ts b/libs/api/claim/data-access/src/lib/api-claim-data-user.service.ts new file mode 100644 index 0000000..13ffff9 --- /dev/null +++ b/libs/api/claim/data-access/src/lib/api-claim-data-user.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@nestjs/common' +import { ApiClaimDataService } from './api-claim-data.service' +import { ClaimUserCreateInput } from './dto/claim-user-create.input' +import { ClaimUserFindManyInput } from './dto/claim-user-find-many.input' +import { ClaimUserUpdateInput } from './dto/claim-user-update.input' +import { ClaimPaging } from './entity/claim.entity' +import { getClaimWhereUserInput } from './helpers/get-claim-where-user.input' + +@Injectable() +export class ApiClaimDataUserService { + constructor(private readonly data: ApiClaimDataService) {} + + async createClaim(input: ClaimUserCreateInput) { + return this.data.create(input) + } + + async deleteClaim(claimId: string) { + return this.data.delete(claimId) + } + + async findManyClaim(input: ClaimUserFindManyInput): Promise { + return this.data.findMany({ + orderBy: { createdAt: 'desc' }, + where: getClaimWhereUserInput(input), + limit: input.limit, + page: input.page, + }) + } + + async findOneClaim(claimId: string) { + return this.data.findOne(claimId) + } + + async updateClaim(claimId: string, input: ClaimUserUpdateInput) { + return this.data.update(claimId, input) + } +} diff --git a/libs/api/claim/data-access/src/lib/api-claim-data.service.ts b/libs/api/claim/data-access/src/lib/api-claim-data.service.ts new file mode 100644 index 0000000..aeb2c51 --- /dev/null +++ b/libs/api/claim/data-access/src/lib/api-claim-data.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@nestjs/common' +import { ClaimStatus, Prisma } from '@prisma/client' +import { ApiCoreService, PagingInputFields } from '@tokengator-mint/api-core-data-access' +import { ClaimPaging } from './entity/claim.entity' + +@Injectable() +export class ApiClaimDataService { + constructor(private readonly core: ApiCoreService) {} + + async create(input: Omit & { amount?: string }) { + return this.core.data.claim.create({ + data: { ...input, amount: input.amount?.trim().length ? input.amount : '1', status: ClaimStatus.Pending }, + }) + } + + async delete(claimId: string) { + await this.findOne(claimId) + const deleted = await this.core.data.claim.delete({ where: { id: claimId } }) + return !!deleted + } + + async findMany({ + limit = 10, + page = 1, + ...input + }: Prisma.ClaimFindManyArgs & PagingInputFields): Promise { + return this.core.data.claim + .paginate(input) + .withPages({ limit, page }) + .then(([data, meta]) => ({ data, meta })) + } + + async findOne(claimId: string) { + const found = await this.core.data.claim.findUnique({ where: { id: claimId } }) + if (!found) { + throw new Error('Claim not found') + } + return found + } + + async update(claimId: string, input: Prisma.ClaimUpdateInput) { + return this.core.data.claim.update({ where: { id: claimId }, data: input }) + } +} diff --git a/libs/api/claim/data-access/src/lib/api-claim.data-access.module.ts b/libs/api/claim/data-access/src/lib/api-claim.data-access.module.ts new file mode 100644 index 0000000..e436460 --- /dev/null +++ b/libs/api/claim/data-access/src/lib/api-claim.data-access.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common' +import { ApiCoreDataAccessModule } from '@tokengator-mint/api-core-data-access' +import { ApiClaimService } from './api-claim.service' +import { ApiClaimDataService } from './api-claim-data.service' +import { ApiClaimDataAdminService } from './api-claim-data-admin.service' +import { ApiClaimDataUserService } from './api-claim-data-user.service' + +@Module({ + imports: [ApiCoreDataAccessModule], + providers: [ApiClaimService, ApiClaimDataService, ApiClaimDataAdminService, ApiClaimDataUserService], + exports: [ApiClaimService], +}) +export class ApiClaimDataAccessModule {} diff --git a/libs/api/claim/data-access/src/lib/api-claim.service.ts b/libs/api/claim/data-access/src/lib/api-claim.service.ts new file mode 100644 index 0000000..729b2b1 --- /dev/null +++ b/libs/api/claim/data-access/src/lib/api-claim.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@nestjs/common' +import { ApiClaimDataService } from './api-claim-data.service' +import { ApiClaimDataAdminService } from './api-claim-data-admin.service' +import { ApiClaimDataUserService } from './api-claim-data-user.service' + +@Injectable() +export class ApiClaimService { + constructor( + readonly data: ApiClaimDataService, + readonly admin: ApiClaimDataAdminService, + readonly user: ApiClaimDataUserService, + ) {} +} diff --git a/libs/api/claim/data-access/src/lib/dto/claim-admin-create.input.ts b/libs/api/claim/data-access/src/lib/dto/claim-admin-create.input.ts new file mode 100644 index 0000000..485bab4 --- /dev/null +++ b/libs/api/claim/data-access/src/lib/dto/claim-admin-create.input.ts @@ -0,0 +1,16 @@ +import { Field, InputType } from '@nestjs/graphql' +import { IdentityProvider } from '@tokengator-mint/api-identity-data-access' + +@InputType() +export class ClaimAdminCreateInput { + @Field({ nullable: true }) + amount?: string + @Field() + communityId!: string + @Field() + minter!: string + @Field(() => IdentityProvider) + provider!: IdentityProvider + @Field() + providerId!: string +} diff --git a/libs/api/claim/data-access/src/lib/dto/claim-admin-find-many.input.ts b/libs/api/claim/data-access/src/lib/dto/claim-admin-find-many.input.ts new file mode 100644 index 0000000..21a199d --- /dev/null +++ b/libs/api/claim/data-access/src/lib/dto/claim-admin-find-many.input.ts @@ -0,0 +1,10 @@ +import { Field, InputType } from '@nestjs/graphql' +import { PagingInput } from '@tokengator-mint/api-core-data-access' + +@InputType() +export class ClaimAdminFindManyInput extends PagingInput() { + @Field() + communityId!: string + @Field({ nullable: true }) + search?: string +} diff --git a/libs/api/claim/data-access/src/lib/dto/claim-admin-update.input.ts b/libs/api/claim/data-access/src/lib/dto/claim-admin-update.input.ts new file mode 100644 index 0000000..6fcfbe6 --- /dev/null +++ b/libs/api/claim/data-access/src/lib/dto/claim-admin-update.input.ts @@ -0,0 +1,14 @@ +import { Field, InputType } from '@nestjs/graphql' +import { ClaimStatus } from '../entity/claim-status.enum' + +@InputType() +export class ClaimAdminUpdateInput { + @Field({ nullable: true }) + amount?: string + + @Field({ nullable: true }) + signature?: string + + @Field(() => ClaimStatus, { nullable: true }) + status?: ClaimStatus +} diff --git a/libs/api/claim/data-access/src/lib/dto/claim-user-create.input.ts b/libs/api/claim/data-access/src/lib/dto/claim-user-create.input.ts new file mode 100644 index 0000000..1e7a1d2 --- /dev/null +++ b/libs/api/claim/data-access/src/lib/dto/claim-user-create.input.ts @@ -0,0 +1,16 @@ +import { Field, InputType } from '@nestjs/graphql' +import { IdentityProvider } from '@tokengator-mint/api-identity-data-access' + +@InputType() +export class ClaimUserCreateInput { + @Field() + communityId!: string + @Field({ nullable: true }) + amount?: string + @Field() + minter!: string + @Field(() => IdentityProvider) + provider!: IdentityProvider + @Field() + providerId!: string +} diff --git a/libs/api/claim/data-access/src/lib/dto/claim-user-find-many.input.ts b/libs/api/claim/data-access/src/lib/dto/claim-user-find-many.input.ts new file mode 100644 index 0000000..f1b7164 --- /dev/null +++ b/libs/api/claim/data-access/src/lib/dto/claim-user-find-many.input.ts @@ -0,0 +1,20 @@ +import { Field, InputType } from '@nestjs/graphql' +import { PagingInput } from '@tokengator-mint/api-core-data-access' +import { IdentityProvider } from '@tokengator-mint/api-identity-data-access' +import { ClaimStatus } from '../entity/claim-status.enum' + +@InputType() +export class ClaimUserFindManyInput extends PagingInput() { + @Field() + communityId!: string + @Field({ nullable: true }) + search?: string + @Field({ nullable: true }) + minter?: string + @Field(() => IdentityProvider, { nullable: true }) + provider?: IdentityProvider + @Field({ nullable: true }) + providerId?: string + @Field(() => ClaimStatus, { nullable: true }) + status?: ClaimStatus +} diff --git a/libs/api/claim/data-access/src/lib/dto/claim-user-update.input.ts b/libs/api/claim/data-access/src/lib/dto/claim-user-update.input.ts new file mode 100644 index 0000000..672154a --- /dev/null +++ b/libs/api/claim/data-access/src/lib/dto/claim-user-update.input.ts @@ -0,0 +1,14 @@ +import { Field, InputType } from '@nestjs/graphql' +import { ClaimStatus } from '../entity/claim-status.enum' + +@InputType() +export class ClaimUserUpdateInput { + @Field({ nullable: true }) + amount?: string + + @Field({ nullable: true }) + signature?: string + + @Field(() => ClaimStatus, { nullable: true }) + status?: ClaimStatus +} diff --git a/libs/api/claim/data-access/src/lib/entity/claim-status.enum.ts b/libs/api/claim/data-access/src/lib/entity/claim-status.enum.ts new file mode 100644 index 0000000..41fdc3a --- /dev/null +++ b/libs/api/claim/data-access/src/lib/entity/claim-status.enum.ts @@ -0,0 +1,5 @@ +import { registerEnumType } from '@nestjs/graphql' +import { ClaimStatus } from '@prisma/client' +export { ClaimStatus } + +registerEnumType(ClaimStatus, { name: 'ClaimStatus' }) \ No newline at end of file diff --git a/libs/api/claim/data-access/src/lib/entity/claim.entity.ts b/libs/api/claim/data-access/src/lib/entity/claim.entity.ts new file mode 100644 index 0000000..8cee374 --- /dev/null +++ b/libs/api/claim/data-access/src/lib/entity/claim.entity.ts @@ -0,0 +1,31 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { PagingResponse } from '@tokengator-mint/api-core-data-access' +import { IdentityProvider } from '@tokengator-mint/api-identity-data-access' +import { ClaimStatus } from './claim-status.enum' + +@ObjectType() +export class Claim { + @Field() + id!: string + @Field({ nullable: true }) + createdAt?: Date + @Field({ nullable: true }) + updatedAt?: Date + @Field() + amount!: string + @Field() + communityId!: string + @Field() + minter!: string + @Field({ nullable: true }) + signature?: string | null + @Field(() => IdentityProvider) + provider!: IdentityProvider + @Field() + providerId!: string + @Field(() => ClaimStatus) + status!: ClaimStatus +} + +@ObjectType() +export class ClaimPaging extends PagingResponse(Claim) {} diff --git a/libs/api/claim/data-access/src/lib/helpers/get-claim-where-admin.input.ts b/libs/api/claim/data-access/src/lib/helpers/get-claim-where-admin.input.ts new file mode 100644 index 0000000..e66e417 --- /dev/null +++ b/libs/api/claim/data-access/src/lib/helpers/get-claim-where-admin.input.ts @@ -0,0 +1,17 @@ +import { Prisma } from '@prisma/client' +import { ClaimAdminFindManyInput } from '../dto/claim-admin-find-many.input' + +export function getClaimWhereAdminInput(input: ClaimAdminFindManyInput): Prisma.ClaimWhereInput { + const where: Prisma.ClaimWhereInput = { + communityId: input.communityId, + } + + if (input.search) { + where.OR = [ + { id: { contains: input.search, mode: 'insensitive' } }, + { providerId: { contains: input.search, mode: 'insensitive' } }, + ] + } + + return where +} diff --git a/libs/api/claim/data-access/src/lib/helpers/get-claim-where-user.input.ts b/libs/api/claim/data-access/src/lib/helpers/get-claim-where-user.input.ts new file mode 100644 index 0000000..cebcdce --- /dev/null +++ b/libs/api/claim/data-access/src/lib/helpers/get-claim-where-user.input.ts @@ -0,0 +1,22 @@ +import { Prisma } from '@prisma/client' +import { ClaimUserFindManyInput } from '../dto/claim-user-find-many.input' + +export function getClaimWhereUserInput(input: ClaimUserFindManyInput): Prisma.ClaimWhereInput { + const where: Prisma.ClaimWhereInput = { + communityId: input.communityId, + minter: input.minter ?? undefined, + provider: input.provider ?? undefined, + providerId: input.providerId ?? undefined, + status: input.status ?? undefined, + } + + if (input.search) { + where.OR = [ + { id: { contains: input.search, mode: 'insensitive' } }, + { providerId: { contains: input.search, mode: 'insensitive' } }, + { signature: { contains: input.search, mode: 'insensitive' } }, + ] + } + + return where +} diff --git a/libs/api/claim/data-access/tsconfig.json b/libs/api/claim/data-access/tsconfig.json new file mode 100644 index 0000000..4022fd4 --- /dev/null +++ b/libs/api/claim/data-access/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/api/claim/data-access/tsconfig.lib.json b/libs/api/claim/data-access/tsconfig.lib.json new file mode 100644 index 0000000..c6b908a --- /dev/null +++ b/libs/api/claim/data-access/tsconfig.lib.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"], + "target": "es6", + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/api/claim/data-access/tsconfig.spec.json b/libs/api/claim/data-access/tsconfig.spec.json new file mode 100644 index 0000000..56497b8 --- /dev/null +++ b/libs/api/claim/data-access/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/libs/api/claim/feature/.eslintrc.json b/libs/api/claim/feature/.eslintrc.json new file mode 100644 index 0000000..632e9b0 --- /dev/null +++ b/libs/api/claim/feature/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/api/claim/feature/README.md b/libs/api/claim/feature/README.md new file mode 100644 index 0000000..0ebc64e --- /dev/null +++ b/libs/api/claim/feature/README.md @@ -0,0 +1,7 @@ +# api-claim-feature + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test api-claim-feature` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/api/claim/feature/jest.config.ts b/libs/api/claim/feature/jest.config.ts new file mode 100644 index 0000000..938f8e4 --- /dev/null +++ b/libs/api/claim/feature/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'api-claim-feature', + preset: '../../../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../../../coverage/libs/api/claim/feature', +} diff --git a/libs/api/claim/feature/project.json b/libs/api/claim/feature/project.json new file mode 100644 index 0000000..9ec5d23 --- /dev/null +++ b/libs/api/claim/feature/project.json @@ -0,0 +1,20 @@ +{ + "name": "api-claim-feature", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/api/claim/feature/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/api/claim/feature/jest.config.ts" + } + } + }, + "tags": ["app:api", "type:feature"] +} diff --git a/libs/api/claim/feature/src/index.ts b/libs/api/claim/feature/src/index.ts new file mode 100644 index 0000000..1ed2f72 --- /dev/null +++ b/libs/api/claim/feature/src/index.ts @@ -0,0 +1 @@ +export * from './lib/api-claim.feature.module' diff --git a/libs/api/claim/feature/src/lib/api-claim-admin.resolver.ts b/libs/api/claim/feature/src/lib/api-claim-admin.resolver.ts new file mode 100644 index 0000000..ae29d5c --- /dev/null +++ b/libs/api/claim/feature/src/lib/api-claim-admin.resolver.ts @@ -0,0 +1,43 @@ +import { Resolver } from '@nestjs/graphql' +import { ApiClaimService } from '@tokengator-mint/api-claim-data-access' +import { ApiAuthGraphQLAdminGuard } from '@tokengator-mint/api-auth-data-access' +import { Mutation, Query, Args } from '@nestjs/graphql' +import { UseGuards } from '@nestjs/common' +import { + ClaimAdminCreateInput, + ClaimAdminFindManyInput, + Claim, + ClaimPaging, + ClaimAdminUpdateInput, +} from '@tokengator-mint/api-claim-data-access' + +@Resolver() +@UseGuards(ApiAuthGraphQLAdminGuard) +export class ApiClaimAdminResolver { + constructor(private readonly service: ApiClaimService) {} + + @Mutation(() => Claim, { nullable: true }) + adminCreateClaim(@Args('input') input: ClaimAdminCreateInput) { + return this.service.admin.createClaim(input) + } + + @Mutation(() => Boolean, { nullable: true }) + adminDeleteClaim(@Args('claimId') claimId: string) { + return this.service.admin.deleteClaim(claimId) + } + + @Query(() => ClaimPaging) + adminFindManyClaim(@Args('input') input: ClaimAdminFindManyInput) { + return this.service.admin.findManyClaim(input) + } + + @Query(() => Claim, { nullable: true }) + adminFindOneClaim(@Args('claimId') claimId: string) { + return this.service.admin.findOneClaim(claimId) + } + + @Mutation(() => Claim, { nullable: true }) + adminUpdateClaim(@Args('claimId') claimId: string, @Args('input') input: ClaimAdminUpdateInput) { + return this.service.admin.updateClaim(claimId, input) + } +} diff --git a/libs/api/claim/feature/src/lib/api-claim-user.resolver.ts b/libs/api/claim/feature/src/lib/api-claim-user.resolver.ts new file mode 100644 index 0000000..9d2f652 --- /dev/null +++ b/libs/api/claim/feature/src/lib/api-claim-user.resolver.ts @@ -0,0 +1,43 @@ +import { Resolver } from '@nestjs/graphql' +import { ApiClaimService } from '@tokengator-mint/api-claim-data-access' +import { ApiAuthGraphQLUserGuard } from '@tokengator-mint/api-auth-data-access' +import { Mutation, Query, Args } from '@nestjs/graphql' +import { UseGuards } from '@nestjs/common' +import { + ClaimUserCreateInput, + ClaimUserFindManyInput, + Claim, + ClaimPaging, + ClaimUserUpdateInput, +} from '@tokengator-mint/api-claim-data-access' + +@Resolver() +@UseGuards(ApiAuthGraphQLUserGuard) +export class ApiClaimUserResolver { + constructor(private readonly service: ApiClaimService) {} + + @Mutation(() => Claim, { nullable: true }) + userCreateClaim(@Args('input') input: ClaimUserCreateInput) { + return this.service.user.createClaim(input) + } + + @Mutation(() => Boolean, { nullable: true }) + userDeleteClaim(@Args('claimId') claimId: string) { + return this.service.user.deleteClaim(claimId) + } + + @Query(() => ClaimPaging) + userFindManyClaim(@Args('input') input: ClaimUserFindManyInput) { + return this.service.user.findManyClaim(input) + } + + @Query(() => Claim, { nullable: true }) + userFindOneClaim(@Args('claimId') claimId: string) { + return this.service.user.findOneClaim(claimId) + } + + @Mutation(() => Claim, { nullable: true }) + userUpdateClaim(@Args('claimId') claimId: string, @Args('input') input: ClaimUserUpdateInput) { + return this.service.user.updateClaim(claimId, input) + } +} diff --git a/libs/api/claim/feature/src/lib/api-claim.feature.module.ts b/libs/api/claim/feature/src/lib/api-claim.feature.module.ts new file mode 100644 index 0000000..96ae48d --- /dev/null +++ b/libs/api/claim/feature/src/lib/api-claim.feature.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common' +import { ApiClaimDataAccessModule } from '@tokengator-mint/api-claim-data-access' +import { ApiClaimResolver } from './api-claim.resolver' +import { ApiClaimAdminResolver } from './api-claim-admin.resolver' +import { ApiClaimUserResolver } from './api-claim-user.resolver' + +@Module({ + imports: [ApiClaimDataAccessModule], + providers: [ApiClaimResolver, ApiClaimAdminResolver, ApiClaimUserResolver], +}) +export class ApiClaimFeatureModule {} diff --git a/libs/api/claim/feature/src/lib/api-claim.resolver.ts b/libs/api/claim/feature/src/lib/api-claim.resolver.ts new file mode 100644 index 0000000..b3e4715 --- /dev/null +++ b/libs/api/claim/feature/src/lib/api-claim.resolver.ts @@ -0,0 +1,13 @@ +import { Parent, ResolveField, Resolver } from '@nestjs/graphql' +import { ApiClaimService, Claim } from '@tokengator-mint/api-claim-data-access' +import { ellipsify } from '@tokengator-mint/api-core-data-access' + +@Resolver(() => Claim) +export class ApiClaimResolver { + constructor(private readonly service: ApiClaimService) {} + + @ResolveField(() => String) + name(@Parent() claim: Claim) { + return `${claim.provider}: ${ellipsify(claim.providerId)} ` + } +} diff --git a/libs/api/claim/feature/tsconfig.json b/libs/api/claim/feature/tsconfig.json new file mode 100644 index 0000000..4022fd4 --- /dev/null +++ b/libs/api/claim/feature/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/api/claim/feature/tsconfig.lib.json b/libs/api/claim/feature/tsconfig.lib.json new file mode 100644 index 0000000..c6b908a --- /dev/null +++ b/libs/api/claim/feature/tsconfig.lib.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"], + "target": "es6", + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/api/claim/feature/tsconfig.spec.json b/libs/api/claim/feature/tsconfig.spec.json new file mode 100644 index 0000000..56497b8 --- /dev/null +++ b/libs/api/claim/feature/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/libs/api/community/data-access/src/lib/api-community-provision.service.ts b/libs/api/community/data-access/src/lib/api-community-provision.service.ts index eae80f6..d79ea21 100644 --- a/libs/api/community/data-access/src/lib/api-community-provision.service.ts +++ b/libs/api/community/data-access/src/lib/api-community-provision.service.ts @@ -1,6 +1,6 @@ import { Injectable, Logger } from '@nestjs/common' import { OnEvent } from '@nestjs/event-emitter' -import { CommunityMemberRole, Prisma } from '@prisma/client' +import { CommunityMemberRole, IdentityProvider, Prisma } from '@prisma/client' import { ApiCoreService, slugifyId } from '@tokengator-mint/api-core-data-access' import { USER_PROVISIONED } from '@tokengator-mint/api-user-data-access' import { ApiCommunityDataService } from './api-community-data.service' @@ -52,6 +52,17 @@ export const provisionCommunities: ProvisionCommunityInput[] = [ }, ], }, + claims: { + create: [ + { provider: IdentityProvider.Discord, providerId: '185307556032413697' }, + { provider: IdentityProvider.Discord, providerId: '386584531353862154' }, + { provider: IdentityProvider.GitHub, providerId: '32637757' }, + { provider: IdentityProvider.GitHub, providerId: '36491' }, + { provider: IdentityProvider.Solana, providerId: '81sWMLg1EgYps3nMwyeSW1JfjKgFqkGYPP85vTnkFzRn' }, + { provider: IdentityProvider.Solana, providerId: 'BEEMANPx2jdmfR7jpn1hRdMuM2Vj4E3azBLb6RUBrCDY' }, + { provider: IdentityProvider.Twitter, providerId: 'beeman_nl' }, + ].map((i) => ({ ...i, minter: '9u6HpBdFd1yZzQ8JszBoSRtcgbvJF5uk5pv7zcvrL3Se' })), + }, }, { name: 'COLOSSEUM', diff --git a/libs/api/core/feature/src/lib/api-core-feature.module.ts b/libs/api/core/feature/src/lib/api-core-feature.module.ts index 3a5d61c..6d32b71 100644 --- a/libs/api/core/feature/src/lib/api-core-feature.module.ts +++ b/libs/api/core/feature/src/lib/api-core-feature.module.ts @@ -12,6 +12,7 @@ import { ApiUserFeatureModule } from '@tokengator-mint/api-user-feature' import { ApiWalletFeatureModule } from '@tokengator-mint/api-wallet-feature' import { ApiCoreController } from './api-core.controller' import { ApiCoreResolver } from './api-core.resolver' +import { ApiClaimFeatureModule } from '@tokengator-mint/api-claim-feature' const imports = [ // The api-feature generator will add the imports here @@ -26,6 +27,7 @@ const imports = [ ApiSolanaFeatureModule, ApiUserFeatureModule, ApiWalletFeatureModule, + ApiClaimFeatureModule, ] @Module({ diff --git a/libs/api/preset/data-access/src/lib/api-preset-data-user.service.ts b/libs/api/preset/data-access/src/lib/api-preset-data-user.service.ts index 141d985..40f520c 100644 --- a/libs/api/preset/data-access/src/lib/api-preset-data-user.service.ts +++ b/libs/api/preset/data-access/src/lib/api-preset-data-user.service.ts @@ -48,4 +48,11 @@ export class ApiPresetDataUserService { async getMinterAssets(account: string) { return this.minter.getMinterAssets(account) } + + async deleteMinter(userId: string, account: string) { + const find = await this.minter.getMinter(account) + await this.data.core.ensureCommunityAdminBySlug({ communitySlug: find.communityId, userId }) + // TODO: delete minter + return false + } } diff --git a/libs/api/preset/data-access/src/lib/api-preset-minter.service.ts b/libs/api/preset/data-access/src/lib/api-preset-minter.service.ts index 4b4cf77..e485b5b 100644 --- a/libs/api/preset/data-access/src/lib/api-preset-minter.service.ts +++ b/libs/api/preset/data-access/src/lib/api-preset-minter.service.ts @@ -17,10 +17,8 @@ import { getWNSMemberPda, IdentityProvider, MINT_USDC, - TokengatorMinter, TokengatorMinterIDL, WEN_NEW_STANDARD_PROGRAM_ID, - WenNewStandard, WenNewStandardIDL, } from '@tokengator-mint/api-solana-util' import { ApiPresetDataService } from './api-preset-data.service' @@ -31,30 +29,30 @@ import { formatTokenGatorMinter } from './helpers/format-token-gator-minter' export class ApiPresetMinterService { private readonly logger = new Logger(ApiPresetMinterService.name) private readonly feePayer: Keypair - private readonly provider: AnchorProvider - private readonly programTokenMinter: Program - private readonly programWns: Program - private readonly programId: PublicKey + private readonly programId = getTokengatorMinterProgramId('devnet') constructor(readonly data: ApiPresetDataService, readonly core: ApiCoreService, readonly solana: ApiSolanaService) { this.feePayer = this.core.config.solanaFeePayer - this.provider = this.solana.getAnchorProvider(this.feePayer) - this.programId = getTokengatorMinterProgramId('devnet') - this.programTokenMinter = new Program(TokengatorMinterIDL, this.programId, this.provider) - - this.programWns = new Program(WenNewStandardIDL, WEN_NEW_STANDARD_PROGRAM_ID, this.provider) this.logger.debug(`Program ID: ${this.programId.toString()}`) } + getProgramTokenMinter(provider: AnchorProvider = this.solana.getAnchorProvider()) { + return new Program(TokengatorMinterIDL, this.programId, provider) + } + + getProgramWns(provider: AnchorProvider = this.solana.getAnchorProvider()) { + return new Program(WenNewStandardIDL, WEN_NEW_STANDARD_PROGRAM_ID, provider) + } + async mintFromPreset(presetId: string, communitySlug: string) { const preset = await this.data.findOne(presetId) - const communityFeePayer = await this.getKeypairFromCommunity(communitySlug) - const authority = communityFeePayer + const authority = await this.getKeypairFromCommunity(communitySlug) + const programTokenMinter = this.getProgramTokenMinter(this.solana.getAnchorProvider(authority)) const remoteFeePayer = this.feePayer this.logger.debug( - `Minting from preset: ${presetId} for community: ${communitySlug}, fee payer: ${communityFeePayer.publicKey.toString()}`, + `Minting from preset: ${presetId} for community: ${communitySlug}, fee payer: ${remoteFeePayer.publicKey.toString()}`, ) const mintKeypair = Keypair.generate() @@ -85,7 +83,7 @@ export class ApiPresetMinterService { const identities = getIdentityProviders([IdentityProvider.Discord]) - const signature = await this.programTokenMinter.methods + const signature = await programTokenMinter.methods .createMinterWns({ community: communitySlug, name, @@ -135,14 +133,15 @@ export class ApiPresetMinterService { async mintFromMinter(minterAccount: string, communitySlug: string) { const minter = new PublicKey(minterAccount) + const authority = await this.getKeypairFromCommunity(communitySlug) + const programTokenMinter = this.getProgramTokenMinter(this.solana.getAnchorProvider(authority)) - const found = await this.programTokenMinter.account.minter.fetch(minter) + const found = await programTokenMinter.account.minter.fetch(minter) if (!found) { throw new Error(`Minter not found: ${minterAccount}`) } - const authority = await this.getKeypairFromCommunity(communitySlug) - const remoteFeePayer = this.feePayer + const feePayer = this.feePayer const groupMintPublicKey = found.minterConfig.mint const memberMintKeypair = Keypair.generate() @@ -167,7 +166,7 @@ export class ApiPresetMinterService { ASSOCIATED_TOKEN_PROGRAM_ID, ) - const signature = await this.programTokenMinter.methods + const signature = await programTokenMinter.methods .mintMinterWns({ name, symbol, uri }) .accounts({ minter, @@ -176,7 +175,7 @@ export class ApiPresetMinterService { member, authorityTokenAccount, authority: authority.publicKey, - feePayer: remoteFeePayer.publicKey, + feePayer: feePayer.publicKey, mint: memberMintKeypair.publicKey, rent: SYSVAR_RENT_PUBKEY, wnsProgram: WEN_NEW_STANDARD_PROGRAM_ID, @@ -184,7 +183,7 @@ export class ApiPresetMinterService { tokenProgram: TOKEN_2022_PROGRAM_ID, systemProgram: SystemProgram.programId, }) - .signers([authority, memberMintKeypair]) + .signers([feePayer, authority, memberMintKeypair]) .rpc({ commitment: 'confirmed', skipPreflight: true }) this.logger.debug(`Signature: ${signature}`) @@ -193,32 +192,38 @@ export class ApiPresetMinterService { } async getMinters(): Promise { - return this.programTokenMinter.account.minter.all().then((res) => - res.map(({ account: { minterConfig, paymentConfig, ...account }, publicKey }) => - formatTokenGatorMinter({ - account, - publicKey, - paymentConfig, - minterConfig, - }), - ), - ) + return this.getProgramTokenMinter() + .account.minter.all() + .then((res) => + res + .map(({ account: { minterConfig, paymentConfig, ...account }, publicKey }) => + formatTokenGatorMinter({ + account, + publicKey, + paymentConfig, + minterConfig, + }), + ) + .sort((a, b) => a.name.localeCompare(b.name)), + ) } async getMintersByCommunity(communitySlug: string): Promise { const [account] = getCommunityPda(communitySlug, this.programId) - return this.programTokenMinter.account.minter - .all([{ memcmp: { offset: 8 + 1, bytes: account.toBase58() } }]) + return this.getProgramTokenMinter() + .account.minter.all([{ memcmp: { offset: 8 + 1, bytes: account.toBase58() } }]) .then((res) => - res.map(({ account: { minterConfig, paymentConfig, ...account }, publicKey }) => - formatTokenGatorMinter({ - account, - publicKey, - paymentConfig, - minterConfig, - }), - ), + res + .map(({ account: { minterConfig, paymentConfig, ...account }, publicKey }) => + formatTokenGatorMinter({ + account, + publicKey, + paymentConfig, + minterConfig, + }), + ) + .sort((a, b) => a.name.localeCompare(b.name)), ) } @@ -233,8 +238,8 @@ export class ApiPresetMinterService { } async getMinter(publicKey: string): Promise { - return this.programTokenMinter.account.minter - .fetch(new PublicKey(publicKey)) + return this.getProgramTokenMinter() + .account.minter.fetch(new PublicKey(publicKey)) .then(({ minterConfig, paymentConfig, ...account }) => formatTokenGatorMinter({ account, @@ -261,8 +266,8 @@ export class ApiPresetMinterService { } private async getGroupMembers({ account }: { account: PublicKey }) { - return this.programWns.account.tokenGroupMember - .all([{ memcmp: { offset: 32 + 8, bytes: account.toBase58() } }]) + return this.getProgramWns() + .account.tokenGroupMember.all([{ memcmp: { offset: 32 + 8, bytes: account.toBase58() } }]) .then((res) => res.sort((a, b) => a.account.memberNumber - b.account.memberNumber)) } } diff --git a/libs/api/preset/feature/src/lib/api-preset-user.resolver.ts b/libs/api/preset/feature/src/lib/api-preset-user.resolver.ts index 9a35b41..80c1537 100644 --- a/libs/api/preset/feature/src/lib/api-preset-user.resolver.ts +++ b/libs/api/preset/feature/src/lib/api-preset-user.resolver.ts @@ -33,6 +33,11 @@ export class ApiPresetUserResolver { return this.service.user.createMintFromMinter(userId, account, communitySlug) } + @Mutation(() => Boolean, { nullable: true }) + userDeleteMinter(@CtxUserId() userId: string, @Args('account') account: string) { + return this.service.user.deleteMinter(userId, account) + } + @Query(() => TokenGatorMinter) userGetMinters() { return this.service.user.getMinters() diff --git a/libs/api/user/data-access/src/lib/api-user-provision-data.ts b/libs/api/user/data-access/src/lib/api-user-provision-data.ts index b30cf53..fff3124 100644 --- a/libs/api/user/data-access/src/lib/api-user-provision-data.ts +++ b/libs/api/user/data-access/src/lib/api-user-provision-data.ts @@ -8,9 +8,10 @@ export const provisionUsers: Prisma.UserCreateInput[] = [ developer: true, identities: { create: [ - { provider: IdentityProvider.GitHub, providerId: '36491' }, { provider: IdentityProvider.Discord, providerId: '386584531353862154' }, + { provider: IdentityProvider.GitHub, providerId: '36491' }, { provider: IdentityProvider.Solana, providerId: 'BEEMANPx2jdmfR7jpn1hRdMuM2Vj4E3azBLb6RUBrCDY' }, + { provider: IdentityProvider.Twitter, providerId: '11481502' }, ], }, }, diff --git a/libs/sdk/src/generated/graphql-sdk.ts b/libs/sdk/src/generated/graphql-sdk.ts index 99717c3..261b2a5 100644 --- a/libs/sdk/src/generated/graphql-sdk.ts +++ b/libs/sdk/src/generated/graphql-sdk.ts @@ -104,6 +104,78 @@ export type AppConfig = { authTwitterEnabled: Scalars['Boolean']['output'] } +export type Claim = { + __typename?: 'Claim' + amount: Scalars['String']['output'] + communityId: Scalars['String']['output'] + createdAt?: Maybe + id: Scalars['String']['output'] + minter: Scalars['String']['output'] + name: Scalars['String']['output'] + provider: IdentityProvider + providerId: Scalars['String']['output'] + signature?: Maybe + status: ClaimStatus + updatedAt?: Maybe +} + +export type ClaimAdminCreateInput = { + amount?: InputMaybe + communityId: Scalars['String']['input'] + minter: Scalars['String']['input'] + provider: IdentityProvider + providerId: Scalars['String']['input'] +} + +export type ClaimAdminFindManyInput = { + communityId: Scalars['String']['input'] + limit?: InputMaybe + page?: InputMaybe + search?: InputMaybe +} + +export type ClaimAdminUpdateInput = { + amount?: InputMaybe + signature?: InputMaybe + status?: InputMaybe +} + +export type ClaimPaging = { + __typename?: 'ClaimPaging' + data: Array + meta: PagingMeta +} + +export enum ClaimStatus { + Claimed = 'Claimed', + Pending = 'Pending', +} + +export type ClaimUserCreateInput = { + amount?: InputMaybe + communityId: Scalars['String']['input'] + minter: Scalars['String']['input'] + provider: IdentityProvider + providerId: Scalars['String']['input'] +} + +export type ClaimUserFindManyInput = { + communityId: Scalars['String']['input'] + limit?: InputMaybe + minter?: InputMaybe + page?: InputMaybe + provider?: InputMaybe + providerId?: InputMaybe + search?: InputMaybe + status?: InputMaybe +} + +export type ClaimUserUpdateInput = { + amount?: InputMaybe + signature?: InputMaybe + status?: InputMaybe +} + export type Community = { __typename?: 'Community' createdAt?: Maybe @@ -205,12 +277,14 @@ export type LoginInput = { export type Mutation = { __typename?: 'Mutation' + adminCreateClaim?: Maybe adminCreateCommunityMember?: Maybe adminCreateIdentity?: Maybe adminCreatePreset?: Maybe adminCreatePrice?: Maybe adminCreateUser?: Maybe adminCreateWallet?: Maybe + adminDeleteClaim?: Maybe adminDeleteCommunity?: Maybe adminDeleteCommunityMember?: Maybe adminDeleteIdentity?: Maybe @@ -218,6 +292,7 @@ export type Mutation = { adminDeletePrice?: Maybe adminDeleteUser?: Maybe adminDeleteWallet?: Maybe + adminUpdateClaim?: Maybe adminUpdateCommunity?: Maybe adminUpdateCommunityMember?: Maybe adminUpdatePreset?: Maybe @@ -229,17 +304,21 @@ export type Mutation = { logout?: Maybe register?: Maybe solanaRequestAirdrop?: Maybe + userCreateClaim?: Maybe userCreateCommunity?: Maybe userCreateCommunityMember?: Maybe userCreateMintFromMinter?: Maybe userCreateMintFromPreset?: Maybe userCreateWallet?: Maybe + userDeleteClaim?: Maybe userDeleteCommunity?: Maybe userDeleteCommunityMember?: Maybe userDeleteIdentity?: Maybe + userDeleteMinter?: Maybe userDeleteWallet?: Maybe userLinkIdentity?: Maybe userSetWalletFeepayer?: Maybe + userUpdateClaim?: Maybe userUpdateCommunity?: Maybe userUpdateCommunityMember?: Maybe userUpdateUser?: Maybe @@ -247,6 +326,10 @@ export type Mutation = { userVerifyIdentityChallenge?: Maybe } +export type MutationAdminCreateClaimArgs = { + input: ClaimAdminCreateInput +} + export type MutationAdminCreateCommunityMemberArgs = { input: AdminCreateCommunityMemberInput } @@ -271,6 +354,10 @@ export type MutationAdminCreateWalletArgs = { input: WalletAdminCreateInput } +export type MutationAdminDeleteClaimArgs = { + claimId: Scalars['String']['input'] +} + export type MutationAdminDeleteCommunityArgs = { communityId: Scalars['String']['input'] } @@ -299,6 +386,11 @@ export type MutationAdminDeleteWalletArgs = { walletId: Scalars['String']['input'] } +export type MutationAdminUpdateClaimArgs = { + claimId: Scalars['String']['input'] + input: ClaimAdminUpdateInput +} + export type MutationAdminUpdateCommunityArgs = { communityId: Scalars['String']['input'] input: AdminUpdateCommunityInput @@ -345,6 +437,10 @@ export type MutationSolanaRequestAirdropArgs = { account: Scalars['String']['input'] } +export type MutationUserCreateClaimArgs = { + input: ClaimUserCreateInput +} + export type MutationUserCreateCommunityArgs = { input: UserCreateCommunityInput } @@ -367,6 +463,10 @@ export type MutationUserCreateWalletArgs = { input: WalletUserCreateInput } +export type MutationUserDeleteClaimArgs = { + claimId: Scalars['String']['input'] +} + export type MutationUserDeleteCommunityArgs = { communityId: Scalars['String']['input'] } @@ -379,6 +479,10 @@ export type MutationUserDeleteIdentityArgs = { identityId: Scalars['String']['input'] } +export type MutationUserDeleteMinterArgs = { + account: Scalars['String']['input'] +} + export type MutationUserDeleteWalletArgs = { publicKey: Scalars['String']['input'] } @@ -391,6 +495,11 @@ export type MutationUserSetWalletFeepayerArgs = { publicKey: Scalars['String']['input'] } +export type MutationUserUpdateClaimArgs = { + claimId: Scalars['String']['input'] + input: ClaimUserUpdateInput +} + export type MutationUserUpdateCommunityArgs = { communityId: Scalars['String']['input'] input: UserUpdateCommunityInput @@ -507,6 +616,7 @@ export type PriceUserFindManyInput = { export type Query = { __typename?: 'Query' + adminFindManyClaim: ClaimPaging adminFindManyCommunity: CommunityPaging adminFindManyCommunityMember: CommunityMemberPaging adminFindManyIdentity?: Maybe> @@ -514,6 +624,7 @@ export type Query = { adminFindManyPrice: Array adminFindManyUser: UserPaging adminFindManyWallet: WalletPaging + adminFindOneClaim?: Maybe adminFindOneCommunity?: Maybe adminFindOneCommunityMember?: Maybe adminFindOnePreset?: Maybe @@ -530,6 +641,7 @@ export type Query = { solanaGetTokenAccounts?: Maybe solanaGetTransactions?: Maybe uptime: Scalars['Float']['output'] + userFindManyClaim: ClaimPaging userFindManyCommunity: CommunityPaging userFindManyCommunityMember: CommunityMemberPaging userFindManyIdentity?: Maybe> @@ -537,6 +649,7 @@ export type Query = { userFindManyPrice: Array userFindManyUser: UserPaging userFindManyWallet: WalletPaging + userFindOneClaim?: Maybe userFindOneCommunity?: Maybe userFindOneCommunityMember?: Maybe userFindOnePreset?: Maybe @@ -549,6 +662,10 @@ export type Query = { userRequestIdentityChallenge?: Maybe } +export type QueryAdminFindManyClaimArgs = { + input: ClaimAdminFindManyInput +} + export type QueryAdminFindManyCommunityArgs = { input: AdminFindManyCommunityInput } @@ -577,6 +694,10 @@ export type QueryAdminFindManyWalletArgs = { input: WalletAdminFindManyInput } +export type QueryAdminFindOneClaimArgs = { + claimId: Scalars['String']['input'] +} + export type QueryAdminFindOneCommunityArgs = { communityId: Scalars['String']['input'] } @@ -625,6 +746,10 @@ export type QuerySolanaGetTransactionsArgs = { account: Scalars['String']['input'] } +export type QueryUserFindManyClaimArgs = { + input: ClaimUserFindManyInput +} + export type QueryUserFindManyCommunityArgs = { input: UserFindManyCommunityInput } @@ -653,6 +778,10 @@ export type QueryUserFindManyWalletArgs = { input: WalletUserFindManyInput } +export type QueryUserFindOneClaimArgs = { + claimId: Scalars['String']['input'] +} + export type QueryUserFindOneCommunityArgs = { slug: Scalars['String']['input'] } @@ -962,6 +1091,237 @@ export type MeQuery = { } | null } +export type ClaimDetailsFragment = { + __typename?: 'Claim' + createdAt?: Date | null + id: string + communityId: string + amount: string + minter: string + signature?: string | null + provider: IdentityProvider + providerId: string + status: ClaimStatus + name: string + updatedAt?: Date | null +} + +export type AdminFindManyClaimQueryVariables = Exact<{ + input: ClaimAdminFindManyInput +}> + +export type AdminFindManyClaimQuery = { + __typename?: 'Query' + paging: { + __typename?: 'ClaimPaging' + data: Array<{ + __typename?: 'Claim' + createdAt?: Date | null + id: string + communityId: string + amount: string + minter: string + signature?: string | null + provider: IdentityProvider + providerId: string + status: ClaimStatus + name: string + updatedAt?: Date | null + }> + meta: { + __typename?: 'PagingMeta' + currentPage: number + isFirstPage: boolean + isLastPage: boolean + nextPage?: number | null + pageCount?: number | null + previousPage?: number | null + totalCount?: number | null + } + } +} + +export type AdminFindOneClaimQueryVariables = Exact<{ + claimId: Scalars['String']['input'] +}> + +export type AdminFindOneClaimQuery = { + __typename?: 'Query' + item?: { + __typename?: 'Claim' + createdAt?: Date | null + id: string + communityId: string + amount: string + minter: string + signature?: string | null + provider: IdentityProvider + providerId: string + status: ClaimStatus + name: string + updatedAt?: Date | null + } | null +} + +export type AdminCreateClaimMutationVariables = Exact<{ + input: ClaimAdminCreateInput +}> + +export type AdminCreateClaimMutation = { + __typename?: 'Mutation' + created?: { + __typename?: 'Claim' + createdAt?: Date | null + id: string + communityId: string + amount: string + minter: string + signature?: string | null + provider: IdentityProvider + providerId: string + status: ClaimStatus + name: string + updatedAt?: Date | null + } | null +} + +export type AdminUpdateClaimMutationVariables = Exact<{ + claimId: Scalars['String']['input'] + input: ClaimAdminUpdateInput +}> + +export type AdminUpdateClaimMutation = { + __typename?: 'Mutation' + updated?: { + __typename?: 'Claim' + createdAt?: Date | null + id: string + communityId: string + amount: string + minter: string + signature?: string | null + provider: IdentityProvider + providerId: string + status: ClaimStatus + name: string + updatedAt?: Date | null + } | null +} + +export type AdminDeleteClaimMutationVariables = Exact<{ + claimId: Scalars['String']['input'] +}> + +export type AdminDeleteClaimMutation = { __typename?: 'Mutation'; deleted?: boolean | null } + +export type UserFindManyClaimQueryVariables = Exact<{ + input: ClaimUserFindManyInput +}> + +export type UserFindManyClaimQuery = { + __typename?: 'Query' + paging: { + __typename?: 'ClaimPaging' + data: Array<{ + __typename?: 'Claim' + createdAt?: Date | null + id: string + communityId: string + amount: string + minter: string + signature?: string | null + provider: IdentityProvider + providerId: string + status: ClaimStatus + name: string + updatedAt?: Date | null + }> + meta: { + __typename?: 'PagingMeta' + currentPage: number + isFirstPage: boolean + isLastPage: boolean + nextPage?: number | null + pageCount?: number | null + previousPage?: number | null + totalCount?: number | null + } + } +} + +export type UserFindOneClaimQueryVariables = Exact<{ + claimId: Scalars['String']['input'] +}> + +export type UserFindOneClaimQuery = { + __typename?: 'Query' + item?: { + __typename?: 'Claim' + createdAt?: Date | null + id: string + communityId: string + amount: string + minter: string + signature?: string | null + provider: IdentityProvider + providerId: string + status: ClaimStatus + name: string + updatedAt?: Date | null + } | null +} + +export type UserCreateClaimMutationVariables = Exact<{ + input: ClaimUserCreateInput +}> + +export type UserCreateClaimMutation = { + __typename?: 'Mutation' + created?: { + __typename?: 'Claim' + createdAt?: Date | null + id: string + communityId: string + amount: string + minter: string + signature?: string | null + provider: IdentityProvider + providerId: string + status: ClaimStatus + name: string + updatedAt?: Date | null + } | null +} + +export type UserUpdateClaimMutationVariables = Exact<{ + claimId: Scalars['String']['input'] + input: ClaimUserUpdateInput +}> + +export type UserUpdateClaimMutation = { + __typename?: 'Mutation' + updated?: { + __typename?: 'Claim' + createdAt?: Date | null + id: string + communityId: string + amount: string + minter: string + signature?: string | null + provider: IdentityProvider + providerId: string + status: ClaimStatus + name: string + updatedAt?: Date | null + } | null +} + +export type UserDeleteClaimMutationVariables = Exact<{ + claimId: Scalars['String']['input'] +}> + +export type UserDeleteClaimMutation = { __typename?: 'Mutation'; deleted?: boolean | null } + export type CommunityMemberDetailsFragment = { __typename?: 'CommunityMember' createdAt?: Date | null @@ -2841,6 +3201,21 @@ export type UserSetWalletFeepayerMutation = { } | null } +export const ClaimDetailsFragmentDoc = gql` + fragment ClaimDetails on Claim { + createdAt + id + communityId + amount + minter + signature + provider + providerId + status + name + updatedAt + } +` export const UserDetailsFragmentDoc = gql` fragment UserDetails on User { avatarUrl @@ -3072,6 +3447,92 @@ export const MeDocument = gql` ${UserDetailsFragmentDoc} ${IdentityDetailsFragmentDoc} ` +export const AdminFindManyClaimDocument = gql` + query adminFindManyClaim($input: ClaimAdminFindManyInput!) { + paging: adminFindManyClaim(input: $input) { + data { + ...ClaimDetails + } + meta { + ...PagingMetaDetails + } + } + } + ${ClaimDetailsFragmentDoc} + ${PagingMetaDetailsFragmentDoc} +` +export const AdminFindOneClaimDocument = gql` + query adminFindOneClaim($claimId: String!) { + item: adminFindOneClaim(claimId: $claimId) { + ...ClaimDetails + } + } + ${ClaimDetailsFragmentDoc} +` +export const AdminCreateClaimDocument = gql` + mutation adminCreateClaim($input: ClaimAdminCreateInput!) { + created: adminCreateClaim(input: $input) { + ...ClaimDetails + } + } + ${ClaimDetailsFragmentDoc} +` +export const AdminUpdateClaimDocument = gql` + mutation adminUpdateClaim($claimId: String!, $input: ClaimAdminUpdateInput!) { + updated: adminUpdateClaim(claimId: $claimId, input: $input) { + ...ClaimDetails + } + } + ${ClaimDetailsFragmentDoc} +` +export const AdminDeleteClaimDocument = gql` + mutation adminDeleteClaim($claimId: String!) { + deleted: adminDeleteClaim(claimId: $claimId) + } +` +export const UserFindManyClaimDocument = gql` + query userFindManyClaim($input: ClaimUserFindManyInput!) { + paging: userFindManyClaim(input: $input) { + data { + ...ClaimDetails + } + meta { + ...PagingMetaDetails + } + } + } + ${ClaimDetailsFragmentDoc} + ${PagingMetaDetailsFragmentDoc} +` +export const UserFindOneClaimDocument = gql` + query userFindOneClaim($claimId: String!) { + item: userFindOneClaim(claimId: $claimId) { + ...ClaimDetails + } + } + ${ClaimDetailsFragmentDoc} +` +export const UserCreateClaimDocument = gql` + mutation userCreateClaim($input: ClaimUserCreateInput!) { + created: userCreateClaim(input: $input) { + ...ClaimDetails + } + } + ${ClaimDetailsFragmentDoc} +` +export const UserUpdateClaimDocument = gql` + mutation userUpdateClaim($claimId: String!, $input: ClaimUserUpdateInput!) { + updated: userUpdateClaim(claimId: $claimId, input: $input) { + ...ClaimDetails + } + } + ${ClaimDetailsFragmentDoc} +` +export const UserDeleteClaimDocument = gql` + mutation userDeleteClaim($claimId: String!) { + deleted: userDeleteClaim(claimId: $claimId) + } +` export const UserFindManyCommunityMemberDocument = gql` query userFindManyCommunityMember($input: UserFindManyCommunityMemberInput!) { paging: userFindManyCommunityMember(input: $input) { @@ -3719,6 +4180,16 @@ const LoginDocumentString = print(LoginDocument) const LogoutDocumentString = print(LogoutDocument) const RegisterDocumentString = print(RegisterDocument) const MeDocumentString = print(MeDocument) +const AdminFindManyClaimDocumentString = print(AdminFindManyClaimDocument) +const AdminFindOneClaimDocumentString = print(AdminFindOneClaimDocument) +const AdminCreateClaimDocumentString = print(AdminCreateClaimDocument) +const AdminUpdateClaimDocumentString = print(AdminUpdateClaimDocument) +const AdminDeleteClaimDocumentString = print(AdminDeleteClaimDocument) +const UserFindManyClaimDocumentString = print(UserFindManyClaimDocument) +const UserFindOneClaimDocumentString = print(UserFindOneClaimDocument) +const UserCreateClaimDocumentString = print(UserCreateClaimDocument) +const UserUpdateClaimDocumentString = print(UserUpdateClaimDocument) +const UserDeleteClaimDocumentString = print(UserDeleteClaimDocument) const UserFindManyCommunityMemberDocumentString = print(UserFindManyCommunityMemberDocument) const UserFindOneCommunityMemberDocumentString = print(UserFindOneCommunityMemberDocument) const UserCreateCommunityMemberDocumentString = print(UserCreateCommunityMemberDocument) @@ -3860,6 +4331,216 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = variables, ) }, + adminFindManyClaim( + variables: AdminFindManyClaimQueryVariables, + requestHeaders?: GraphQLClientRequestHeaders, + ): Promise<{ + data: AdminFindManyClaimQuery + errors?: GraphQLError[] + extensions?: any + headers: Headers + status: number + }> { + return withWrapper( + (wrappedRequestHeaders) => + client.rawRequest(AdminFindManyClaimDocumentString, variables, { + ...requestHeaders, + ...wrappedRequestHeaders, + }), + 'adminFindManyClaim', + 'query', + variables, + ) + }, + adminFindOneClaim( + variables: AdminFindOneClaimQueryVariables, + requestHeaders?: GraphQLClientRequestHeaders, + ): Promise<{ + data: AdminFindOneClaimQuery + errors?: GraphQLError[] + extensions?: any + headers: Headers + status: number + }> { + return withWrapper( + (wrappedRequestHeaders) => + client.rawRequest(AdminFindOneClaimDocumentString, variables, { + ...requestHeaders, + ...wrappedRequestHeaders, + }), + 'adminFindOneClaim', + 'query', + variables, + ) + }, + adminCreateClaim( + variables: AdminCreateClaimMutationVariables, + requestHeaders?: GraphQLClientRequestHeaders, + ): Promise<{ + data: AdminCreateClaimMutation + errors?: GraphQLError[] + extensions?: any + headers: Headers + status: number + }> { + return withWrapper( + (wrappedRequestHeaders) => + client.rawRequest(AdminCreateClaimDocumentString, variables, { + ...requestHeaders, + ...wrappedRequestHeaders, + }), + 'adminCreateClaim', + 'mutation', + variables, + ) + }, + adminUpdateClaim( + variables: AdminUpdateClaimMutationVariables, + requestHeaders?: GraphQLClientRequestHeaders, + ): Promise<{ + data: AdminUpdateClaimMutation + errors?: GraphQLError[] + extensions?: any + headers: Headers + status: number + }> { + return withWrapper( + (wrappedRequestHeaders) => + client.rawRequest(AdminUpdateClaimDocumentString, variables, { + ...requestHeaders, + ...wrappedRequestHeaders, + }), + 'adminUpdateClaim', + 'mutation', + variables, + ) + }, + adminDeleteClaim( + variables: AdminDeleteClaimMutationVariables, + requestHeaders?: GraphQLClientRequestHeaders, + ): Promise<{ + data: AdminDeleteClaimMutation + errors?: GraphQLError[] + extensions?: any + headers: Headers + status: number + }> { + return withWrapper( + (wrappedRequestHeaders) => + client.rawRequest(AdminDeleteClaimDocumentString, variables, { + ...requestHeaders, + ...wrappedRequestHeaders, + }), + 'adminDeleteClaim', + 'mutation', + variables, + ) + }, + userFindManyClaim( + variables: UserFindManyClaimQueryVariables, + requestHeaders?: GraphQLClientRequestHeaders, + ): Promise<{ + data: UserFindManyClaimQuery + errors?: GraphQLError[] + extensions?: any + headers: Headers + status: number + }> { + return withWrapper( + (wrappedRequestHeaders) => + client.rawRequest(UserFindManyClaimDocumentString, variables, { + ...requestHeaders, + ...wrappedRequestHeaders, + }), + 'userFindManyClaim', + 'query', + variables, + ) + }, + userFindOneClaim( + variables: UserFindOneClaimQueryVariables, + requestHeaders?: GraphQLClientRequestHeaders, + ): Promise<{ + data: UserFindOneClaimQuery + errors?: GraphQLError[] + extensions?: any + headers: Headers + status: number + }> { + return withWrapper( + (wrappedRequestHeaders) => + client.rawRequest(UserFindOneClaimDocumentString, variables, { + ...requestHeaders, + ...wrappedRequestHeaders, + }), + 'userFindOneClaim', + 'query', + variables, + ) + }, + userCreateClaim( + variables: UserCreateClaimMutationVariables, + requestHeaders?: GraphQLClientRequestHeaders, + ): Promise<{ + data: UserCreateClaimMutation + errors?: GraphQLError[] + extensions?: any + headers: Headers + status: number + }> { + return withWrapper( + (wrappedRequestHeaders) => + client.rawRequest(UserCreateClaimDocumentString, variables, { + ...requestHeaders, + ...wrappedRequestHeaders, + }), + 'userCreateClaim', + 'mutation', + variables, + ) + }, + userUpdateClaim( + variables: UserUpdateClaimMutationVariables, + requestHeaders?: GraphQLClientRequestHeaders, + ): Promise<{ + data: UserUpdateClaimMutation + errors?: GraphQLError[] + extensions?: any + headers: Headers + status: number + }> { + return withWrapper( + (wrappedRequestHeaders) => + client.rawRequest(UserUpdateClaimDocumentString, variables, { + ...requestHeaders, + ...wrappedRequestHeaders, + }), + 'userUpdateClaim', + 'mutation', + variables, + ) + }, + userDeleteClaim( + variables: UserDeleteClaimMutationVariables, + requestHeaders?: GraphQLClientRequestHeaders, + ): Promise<{ + data: UserDeleteClaimMutation + errors?: GraphQLError[] + extensions?: any + headers: Headers + status: number + }> { + return withWrapper( + (wrappedRequestHeaders) => + client.rawRequest(UserDeleteClaimDocumentString, variables, { + ...requestHeaders, + ...wrappedRequestHeaders, + }), + 'userDeleteClaim', + 'mutation', + variables, + ) + }, userFindManyCommunityMember( variables: UserFindManyCommunityMemberQueryVariables, requestHeaders?: GraphQLClientRequestHeaders, @@ -5452,6 +6133,8 @@ export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== und export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)) +export const ClaimStatusSchema = z.nativeEnum(ClaimStatus) + export const CommunityMemberRoleSchema = z.nativeEnum(CommunityMemberRole) export const IdentityProviderSchema = z.nativeEnum(IdentityProvider) @@ -5551,6 +6234,64 @@ export function AnonFindManyCommunityInputSchema(): z.ZodObject> { + return z.object({ + amount: z.string().nullish(), + communityId: z.string(), + minter: z.string(), + provider: IdentityProviderSchema, + providerId: z.string(), + }) +} + +export function ClaimAdminFindManyInputSchema(): z.ZodObject> { + return z.object({ + communityId: z.string(), + limit: z.number().nullish(), + page: z.number().nullish(), + search: z.string().nullish(), + }) +} + +export function ClaimAdminUpdateInputSchema(): z.ZodObject> { + return z.object({ + amount: z.string().nullish(), + signature: z.string().nullish(), + status: ClaimStatusSchema.nullish(), + }) +} + +export function ClaimUserCreateInputSchema(): z.ZodObject> { + return z.object({ + amount: z.string().nullish(), + communityId: z.string(), + minter: z.string(), + provider: IdentityProviderSchema, + providerId: z.string(), + }) +} + +export function ClaimUserFindManyInputSchema(): z.ZodObject> { + return z.object({ + communityId: z.string(), + limit: z.number().nullish(), + minter: z.string().nullish(), + page: z.number().nullish(), + provider: IdentityProviderSchema.nullish(), + providerId: z.string().nullish(), + search: z.string().nullish(), + status: ClaimStatusSchema.nullish(), + }) +} + +export function ClaimUserUpdateInputSchema(): z.ZodObject> { + return z.object({ + amount: z.string().nullish(), + signature: z.string().nullish(), + status: ClaimStatusSchema.nullish(), + }) +} + export function LinkIdentityInputSchema(): z.ZodObject> { return z.object({ provider: IdentityProviderSchema, diff --git a/libs/sdk/src/graphql/feature-claim.graphql b/libs/sdk/src/graphql/feature-claim.graphql new file mode 100644 index 0000000..3816bd4 --- /dev/null +++ b/libs/sdk/src/graphql/feature-claim.graphql @@ -0,0 +1,79 @@ +fragment ClaimDetails on Claim { + createdAt + id + communityId + amount + minter + signature + provider + providerId + status + name + updatedAt +} + +query adminFindManyClaim($input: ClaimAdminFindManyInput!) { + paging: adminFindManyClaim(input: $input) { + data { + ...ClaimDetails + } + meta { + ...PagingMetaDetails + } + } +} + +query adminFindOneClaim($claimId: String!) { + item: adminFindOneClaim(claimId: $claimId) { + ...ClaimDetails + } +} + +mutation adminCreateClaim($input: ClaimAdminCreateInput!) { + created: adminCreateClaim(input: $input) { + ...ClaimDetails + } +} + +mutation adminUpdateClaim($claimId: String!, $input: ClaimAdminUpdateInput!) { + updated: adminUpdateClaim(claimId: $claimId, input: $input) { + ...ClaimDetails + } +} + +mutation adminDeleteClaim($claimId: String!) { + deleted: adminDeleteClaim(claimId: $claimId) +} + +query userFindManyClaim($input: ClaimUserFindManyInput!) { + paging: userFindManyClaim(input: $input) { + data { + ...ClaimDetails + } + meta { + ...PagingMetaDetails + } + } +} + +query userFindOneClaim($claimId: String!) { + item: userFindOneClaim(claimId: $claimId) { + ...ClaimDetails + } +} + +mutation userCreateClaim($input: ClaimUserCreateInput!) { + created: userCreateClaim(input: $input) { + ...ClaimDetails + } +} + +mutation userUpdateClaim($claimId: String!, $input: ClaimUserUpdateInput!) { + updated: userUpdateClaim(claimId: $claimId, input: $input) { + ...ClaimDetails + } +} + +mutation userDeleteClaim($claimId: String!) { + deleted: userDeleteClaim(claimId: $claimId) +} diff --git a/libs/web/claim/data-access/.babelrc b/libs/web/claim/data-access/.babelrc new file mode 100644 index 0000000..1ea870e --- /dev/null +++ b/libs/web/claim/data-access/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/web/claim/data-access/.eslintrc.json b/libs/web/claim/data-access/.eslintrc.json new file mode 100644 index 0000000..772a43d --- /dev/null +++ b/libs/web/claim/data-access/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nx/react", "../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/web/claim/data-access/README.md b/libs/web/claim/data-access/README.md new file mode 100644 index 0000000..d062cfc --- /dev/null +++ b/libs/web/claim/data-access/README.md @@ -0,0 +1,7 @@ +# web-claim-data-access + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test web-claim-data-access` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/web/claim/data-access/project.json b/libs/web/claim/data-access/project.json new file mode 100644 index 0000000..5f76c6f --- /dev/null +++ b/libs/web/claim/data-access/project.json @@ -0,0 +1,13 @@ +{ + "name": "web-claim-data-access", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/web/claim/data-access/src", + "projectType": "library", + "tags": ["app:web", "type:data-access"], + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + } + } +} diff --git a/libs/web/claim/data-access/src/index.ts b/libs/web/claim/data-access/src/index.ts new file mode 100644 index 0000000..fe04262 --- /dev/null +++ b/libs/web/claim/data-access/src/index.ts @@ -0,0 +1,4 @@ +export * from './lib/use-admin-find-many-claim' +export * from './lib/use-admin-find-one-claim' +export * from './lib/use-user-find-many-claim' +export * from './lib/use-user-find-one-claim' diff --git a/libs/web/claim/data-access/src/lib/use-admin-find-many-claim.ts b/libs/web/claim/data-access/src/lib/use-admin-find-many-claim.ts new file mode 100644 index 0000000..3f49b61 --- /dev/null +++ b/libs/web/claim/data-access/src/lib/use-admin-find-many-claim.ts @@ -0,0 +1,54 @@ +import { toastError, toastSuccess } from '@pubkey-ui/core' +import { useQuery } from '@tanstack/react-query' +import { ClaimAdminCreateInput, ClaimAdminFindManyInput } from '@tokengator-mint/sdk' +import { useSdk } from '@tokengator-mint/web-core-data-access' +import { useState } from 'react' + +export function useAdminFindManyClaim(props: Partial & { communityId: string }) { + const sdk = useSdk() + const [limit, setLimit] = useState(props?.limit ?? 10) + const [page, setPage] = useState(props?.page ?? 1) + const [search, setSearch] = useState(props?.search ?? '') + + const input: ClaimAdminFindManyInput = { page, limit, search, communityId: props.communityId } + const query = useQuery({ + queryKey: ['admin', 'find-many-claim', input], + queryFn: () => sdk.adminFindManyClaim({ input }).then((res) => res.data), + }) + const total = query.data?.paging?.meta?.totalCount ?? 0 + const items = query.data?.paging.data ?? [] + + return { + items, + query, + pagination: { + page, + setPage, + limit, + setLimit, + total, + }, + setSearch, + createClaim: (input: ClaimAdminCreateInput) => + sdk + .adminCreateClaim({ input: { ...input, communityId: props.communityId } }) + .then((res) => res.data) + .then((res) => { + if (res.created) { + toastSuccess(`Claim created`) + } else { + toastError(`Claim not created`) + } + return res.created + }) + .catch((err) => { + toastError(err.message) + return undefined + }), + deleteClaim: (claimId: string) => + sdk.adminDeleteClaim({ claimId }).then(() => { + toastSuccess('Claim deleted') + return query.refetch() + }), + } +} diff --git a/libs/web/claim/data-access/src/lib/use-admin-find-one-claim.ts b/libs/web/claim/data-access/src/lib/use-admin-find-one-claim.ts new file mode 100644 index 0000000..ad710dc --- /dev/null +++ b/libs/web/claim/data-access/src/lib/use-admin-find-one-claim.ts @@ -0,0 +1,36 @@ +import { ClaimAdminUpdateInput } from '@tokengator-mint/sdk' +import { useSdk } from '@tokengator-mint/web-core-data-access' +import { toastError, toastSuccess } from '@pubkey-ui/core' +import { useQuery } from '@tanstack/react-query' + +export function useAdminFindOneClaim({ claimId }: { claimId: string }) { + const sdk = useSdk() + const query = useQuery({ + queryKey: ['admin', 'find-one-claim', claimId], + queryFn: () => sdk.adminFindOneClaim({ claimId }).then((res) => res.data), + retry: 0, + }) + const item = query.data?.item ?? undefined + + return { + item, + query, + updateClaim: async (input: ClaimAdminUpdateInput) => + sdk + .adminUpdateClaim({ claimId, input }) + .then((res) => res.data) + .then(async (res) => { + if (res) { + toastSuccess('Claim updated') + await query.refetch() + return true + } + toastError('Claim not updated') + return false + }) + .catch((err) => { + toastError(err.message) + return false + }), + } +} diff --git a/libs/web/claim/data-access/src/lib/use-user-find-many-claim.ts b/libs/web/claim/data-access/src/lib/use-user-find-many-claim.ts new file mode 100644 index 0000000..3ea7e86 --- /dev/null +++ b/libs/web/claim/data-access/src/lib/use-user-find-many-claim.ts @@ -0,0 +1,54 @@ +import { toastError, toastSuccess } from '@pubkey-ui/core' +import { useQuery } from '@tanstack/react-query' +import { ClaimUserCreateInput, ClaimUserFindManyInput } from '@tokengator-mint/sdk' +import { useSdk } from '@tokengator-mint/web-core-data-access' +import { useState } from 'react' + +export function useUserFindManyClaim(props: Partial & { communityId: string; minter: string }) { + const sdk = useSdk() + const [limit, setLimit] = useState(props?.limit ?? 10) + const [page, setPage] = useState(props?.page ?? 1) + const [search, setSearch] = useState(props?.search ?? '') + + const input: ClaimUserFindManyInput = { page, limit, search, communityId: props.communityId } + const query = useQuery({ + queryKey: ['user', 'find-many-claim', input], + queryFn: () => sdk.userFindManyClaim({ input }).then((res) => res.data), + }) + const total = query.data?.paging?.meta?.totalCount ?? 0 + const items = query.data?.paging.data ?? [] + + return { + items, + query, + pagination: { + page, + setPage, + limit, + setLimit, + total, + }, + setSearch, + createClaim: (input: ClaimUserCreateInput) => + sdk + .userCreateClaim({ input: { ...input, minter: props.minter, communityId: props.communityId } }) + .then((res) => res.data) + .then((res) => { + if (res.created) { + toastSuccess(`Claim created`) + } else { + toastError(`Claim not created`) + } + return res.created + }) + .catch((err) => { + toastError(err.message) + return undefined + }), + deleteClaim: (claimId: string) => + sdk.userDeleteClaim({ claimId }).then(() => { + toastSuccess('Claim deleted') + return query.refetch() + }), + } +} diff --git a/libs/web/claim/data-access/src/lib/use-user-find-one-claim.ts b/libs/web/claim/data-access/src/lib/use-user-find-one-claim.ts new file mode 100644 index 0000000..0cb9cee --- /dev/null +++ b/libs/web/claim/data-access/src/lib/use-user-find-one-claim.ts @@ -0,0 +1,36 @@ +import { ClaimUserUpdateInput } from '@tokengator-mint/sdk' +import { useSdk } from '@tokengator-mint/web-core-data-access' +import { toastError, toastSuccess } from '@pubkey-ui/core' +import { useQuery } from '@tanstack/react-query' + +export function useUserFindOneClaim({ claimId }: { claimId: string }) { + const sdk = useSdk() + const query = useQuery({ + queryKey: ['user', 'find-one-claim', claimId], + queryFn: () => sdk.userFindOneClaim({ claimId }).then((res) => res.data), + retry: 0, + }) + const item = query.data?.item ?? undefined + + return { + item, + query, + updateClaim: async (input: ClaimUserUpdateInput) => + sdk + .userUpdateClaim({ claimId, input }) + .then((res) => res.data) + .then(async (res) => { + if (res) { + toastSuccess('Claim updated') + await query.refetch() + return true + } + toastError('Claim not updated') + return false + }) + .catch((err) => { + toastError(err.message) + return false + }), + } +} diff --git a/libs/web/claim/data-access/tsconfig.json b/libs/web/claim/data-access/tsconfig.json new file mode 100644 index 0000000..d8c59fe --- /dev/null +++ b/libs/web/claim/data-access/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "extends": "../../../../tsconfig.base.json" +} diff --git a/libs/web/claim/data-access/tsconfig.lib.json b/libs/web/claim/data-access/tsconfig.lib.json new file mode 100644 index 0000000..45b2297 --- /dev/null +++ b/libs/web/claim/data-access/tsconfig.lib.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "types": ["node", "@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/image.d.ts"] + }, + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/libs/web/claim/feature/.babelrc b/libs/web/claim/feature/.babelrc new file mode 100644 index 0000000..1ea870e --- /dev/null +++ b/libs/web/claim/feature/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/web/claim/feature/.eslintrc.json b/libs/web/claim/feature/.eslintrc.json new file mode 100644 index 0000000..772a43d --- /dev/null +++ b/libs/web/claim/feature/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nx/react", "../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/web/claim/feature/README.md b/libs/web/claim/feature/README.md new file mode 100644 index 0000000..26fa302 --- /dev/null +++ b/libs/web/claim/feature/README.md @@ -0,0 +1,7 @@ +# web-claim-feature + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test web-claim-feature` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/web/claim/feature/project.json b/libs/web/claim/feature/project.json new file mode 100644 index 0000000..c19133d --- /dev/null +++ b/libs/web/claim/feature/project.json @@ -0,0 +1,13 @@ +{ + "name": "web-claim-feature", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/web/claim/feature/src", + "projectType": "library", + "tags": ["app:web", "type:feature"], + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + } + } +} diff --git a/libs/web/claim/feature/src/index.ts b/libs/web/claim/feature/src/index.ts new file mode 100644 index 0000000..3ea59ac --- /dev/null +++ b/libs/web/claim/feature/src/index.ts @@ -0,0 +1,4 @@ +import { lazy } from 'react' +export const AdminClaimFeature = lazy(() => import('./lib/admin-claim.routes')) + +export const UserClaimFeature = lazy(() => import('./lib/user-claim.routes')) diff --git a/libs/web/claim/feature/src/lib/admin-claim-create.feature.tsx b/libs/web/claim/feature/src/lib/admin-claim-create.feature.tsx new file mode 100644 index 0000000..c979b46 --- /dev/null +++ b/libs/web/claim/feature/src/lib/admin-claim-create.feature.tsx @@ -0,0 +1,32 @@ +import { toastError, UiBack, UiCard, UiPage } from '@pubkey-ui/core' +import { ClaimAdminCreateInput } from '@tokengator-mint/sdk' +import { useAdminFindManyClaim } from '@tokengator-mint/web-claim-data-access' +import { AdminClaimUiCreateForm } from '@tokengator-mint/web-claim-ui' +import { useNavigate } from 'react-router-dom' + +export default function AdminClaimCreateFeature({ communityId }: { communityId: string }) { + const navigate = useNavigate() + const { createClaim } = useAdminFindManyClaim({ communityId }) + + async function submit(input: ClaimAdminCreateInput) { + return createClaim(input) + .then((res) => { + if (res) { + navigate(`../${res?.id}`) + } + }) + .then(() => true) + .catch((err) => { + toastError(err.message) + return false + }) + } + + return ( + } title="Create Claim"> + + + + + ) +} diff --git a/libs/web/claim/feature/src/lib/admin-claim-detail-info.tab.tsx b/libs/web/claim/feature/src/lib/admin-claim-detail-info.tab.tsx new file mode 100644 index 0000000..ac8db43 --- /dev/null +++ b/libs/web/claim/feature/src/lib/admin-claim-detail-info.tab.tsx @@ -0,0 +1,20 @@ +import { useAdminFindOneClaim } from '@tokengator-mint/web-claim-data-access' +import { ClaimUiInfo } from '@tokengator-mint/web-claim-ui' +import { UiCard, UiError, UiLoader } from '@pubkey-ui/core' + +export function AdminClaimDetailInfoTab({ claimId }: { claimId: string }) { + const { item, query } = useAdminFindOneClaim({ claimId }) + + if (query.isLoading) { + return + } + if (!item) { + return + } + + return ( + + + + ) +} diff --git a/libs/web/claim/feature/src/lib/admin-claim-detail-settings.tab.tsx b/libs/web/claim/feature/src/lib/admin-claim-detail-settings.tab.tsx new file mode 100644 index 0000000..a4a3bd3 --- /dev/null +++ b/libs/web/claim/feature/src/lib/admin-claim-detail-settings.tab.tsx @@ -0,0 +1,20 @@ +import { useAdminFindOneClaim } from '@tokengator-mint/web-claim-data-access' +import { AdminClaimUiUpdateForm } from '@tokengator-mint/web-claim-ui' +import { UiCard, UiError, UiLoader } from '@pubkey-ui/core' + +export function AdminClaimDetailSettingsTab({ claimId }: { claimId: string }) { + const { item, query, updateClaim } = useAdminFindOneClaim({ claimId }) + + if (query.isLoading) { + return + } + if (!item) { + return + } + + return ( + + + + ) +} diff --git a/libs/web/claim/feature/src/lib/admin-claim-detail.feature.tsx b/libs/web/claim/feature/src/lib/admin-claim-detail.feature.tsx new file mode 100644 index 0000000..21451e1 --- /dev/null +++ b/libs/web/claim/feature/src/lib/admin-claim-detail.feature.tsx @@ -0,0 +1,46 @@ +import { Group } from '@mantine/core' +import { UiBack, UiDebugModal, UiError, UiLoader, UiPage, UiTabRoute, UiTabRoutes } from '@pubkey-ui/core' +import { useAdminFindOneClaim } from '@tokengator-mint/web-claim-data-access' +import { ClaimUiItem } from '@tokengator-mint/web-claim-ui' +import { useParams } from 'react-router-dom' +import { AdminClaimDetailInfoTab } from './admin-claim-detail-info.tab' +import { AdminClaimDetailSettingsTab } from './admin-claim-detail-settings.tab' + +export default function AdminClaimDetailFeature() { + const { claimId } = useParams<{ claimId: string }>() as { claimId: string } + const { item, query } = useAdminFindOneClaim({ claimId }) + + if (query.isLoading) { + return + } + if (!item) { + return + } + + const tabs: UiTabRoute[] = [ + { + path: 'info', + label: 'Info', + element: , + }, + { + path: 'settings', + label: 'Settings', + element: , + }, + ] + + return ( + } + leftAction={} + rightAction={ + + + + } + > + + + ) +} diff --git a/libs/web/claim/feature/src/lib/admin-claim-list.feature.tsx b/libs/web/claim/feature/src/lib/admin-claim-list.feature.tsx new file mode 100644 index 0000000..5fd0729 --- /dev/null +++ b/libs/web/claim/feature/src/lib/admin-claim-list.feature.tsx @@ -0,0 +1,51 @@ +import { Button, Group } from '@mantine/core' +import { UiBack, UiDebugModal, UiInfo, UiLoader, UiPage } from '@pubkey-ui/core' +import { useAdminFindManyClaim } from '@tokengator-mint/web-claim-data-access' +import { AdminClaimUiTable } from '@tokengator-mint/web-claim-ui' +import { UiPageLimit, UiSearchField } from '@tokengator-mint/web-core-ui' +import { Link } from 'react-router-dom' + +export default function AdminClaimListFeature({ communityId }: { communityId: string }) { + const { deleteClaim, items, pagination, query, setSearch } = useAdminFindManyClaim({ + limit: 10, + communityId, + }) + + return ( + } + rightAction={ + + + + + } + > + + + + + + {query.isLoading ? ( + + ) : items?.length ? ( + { + if (!window.confirm('Are you sure?')) return + return deleteClaim(claim.id) + }} + claims={items} + page={pagination.page} + totalRecords={pagination.total} + recordsPerPage={pagination.limit} + onPageChange={(page) => void pagination.setPage(page)} + /> + ) : ( + + )} + + ) +} diff --git a/libs/web/claim/feature/src/lib/admin-claim.routes.tsx b/libs/web/claim/feature/src/lib/admin-claim.routes.tsx new file mode 100644 index 0000000..d6d7b13 --- /dev/null +++ b/libs/web/claim/feature/src/lib/admin-claim.routes.tsx @@ -0,0 +1,14 @@ +import { lazy } from 'react' +import { useRoutes } from 'react-router-dom' + +const Create = lazy(() => import('./admin-claim-create.feature')) +const Detail = lazy(() => import('./admin-claim-detail.feature')) +const List = lazy(() => import('./admin-claim-list.feature')) + +export default function AdminClaimRoutes({ communityId }: { communityId: string }) { + return useRoutes([ + { path: '', element: }, + { path: 'create', element: }, + { path: ':claimId/*', element: }, + ]) +} diff --git a/libs/web/claim/feature/src/lib/user-claim-create.feature.tsx b/libs/web/claim/feature/src/lib/user-claim-create.feature.tsx new file mode 100644 index 0000000..67465ec --- /dev/null +++ b/libs/web/claim/feature/src/lib/user-claim-create.feature.tsx @@ -0,0 +1,37 @@ +import { Group } from '@mantine/core' +import { toastError, UiBack, UiCard, UiCardTitle, UiStack } from '@pubkey-ui/core' +import { ClaimUserCreateInput } from '@tokengator-mint/sdk' +import { useUserFindManyClaim } from '@tokengator-mint/web-claim-data-access' +import { UserClaimUiCreateForm } from '@tokengator-mint/web-claim-ui' +import { useNavigate } from 'react-router-dom' + +export default function UserClaimCreateFeature({ communityId, minter }: { communityId: string; minter: string }) { + const navigate = useNavigate() + const { createClaim } = useUserFindManyClaim({ communityId, minter }) + + async function submit(input: ClaimUserCreateInput) { + return createClaim(input) + .then((res) => { + if (res) { + navigate(`../${res?.id}`) + } + }) + .then(() => true) + .catch((err) => { + toastError(err.message) + return false + }) + } + + return ( + + + + Create Claim + + + + + + ) +} diff --git a/libs/web/claim/feature/src/lib/user-claim-detail-settings.tab.tsx b/libs/web/claim/feature/src/lib/user-claim-detail-settings.tab.tsx new file mode 100644 index 0000000..c6e3287 --- /dev/null +++ b/libs/web/claim/feature/src/lib/user-claim-detail-settings.tab.tsx @@ -0,0 +1,20 @@ +import { useUserFindOneClaim } from '@tokengator-mint/web-claim-data-access' +import { UserClaimUiUpdateForm } from '@tokengator-mint/web-claim-ui' +import { UiCard, UiError, UiLoader } from '@pubkey-ui/core' + +export function UserClaimDetailSettingsTab({ claimId }: { claimId: string }) { + const { item, query, updateClaim } = useUserFindOneClaim({ claimId }) + + if (query.isLoading) { + return + } + if (!item) { + return + } + + return ( + + + + ) +} diff --git a/libs/web/claim/feature/src/lib/user-claim-detail.feature.tsx b/libs/web/claim/feature/src/lib/user-claim-detail.feature.tsx new file mode 100644 index 0000000..848069d --- /dev/null +++ b/libs/web/claim/feature/src/lib/user-claim-detail.feature.tsx @@ -0,0 +1,34 @@ +import { Group } from '@mantine/core' +import { UiBack, UiCard, UiDebugModal, UiError, UiGroup, UiLoader, UiStack } from '@pubkey-ui/core' +import { useUserFindOneClaim } from '@tokengator-mint/web-claim-data-access' +import { ClaimUiInfo, ClaimUiItem } from '@tokengator-mint/web-claim-ui' +import { useParams } from 'react-router-dom' +import { UserClaimDetailSettingsTab } from './user-claim-detail-settings.tab' + +export default function UserClaimDetailFeature() { + const { claimId } = useParams<{ claimId: string }>() as { claimId: string } + const { item, query } = useUserFindOneClaim({ claimId }) + + if (query.isLoading) { + return + } + if (!item) { + return + } + + return ( + + + + + + + + + + + + + + ) +} diff --git a/libs/web/claim/feature/src/lib/user-claim-list.feature.tsx b/libs/web/claim/feature/src/lib/user-claim-list.feature.tsx new file mode 100644 index 0000000..747cc98 --- /dev/null +++ b/libs/web/claim/feature/src/lib/user-claim-list.feature.tsx @@ -0,0 +1,44 @@ +import { Button, Group } from '@mantine/core' +import { UiDebugModal, UiInfo, UiLoader, UiStack } from '@pubkey-ui/core' +import { useUserFindManyClaim } from '@tokengator-mint/web-claim-data-access' +import { UserClaimUiTable } from '@tokengator-mint/web-claim-ui' +import { UiSearchField } from '@tokengator-mint/web-core-ui' +import { Link } from 'react-router-dom' + +export default function UserClaimListFeature({ communityId, minter }: { communityId: string; minter: string }) { + const { deleteClaim, items, pagination, query, setSearch } = useUserFindManyClaim({ + limit: 100, + communityId, + minter, + }) + + return ( + + + + + + + + {query.isLoading ? ( + + ) : items?.length ? ( + { + if (!window.confirm('Are you sure?')) return + return deleteClaim(claim.id) + }} + claims={items} + page={pagination.page} + totalRecords={pagination.total} + recordsPerPage={pagination.limit} + onPageChange={(page) => void pagination.setPage(page)} + /> + ) : ( + + )} + + ) +} diff --git a/libs/web/claim/feature/src/lib/user-claim.routes.tsx b/libs/web/claim/feature/src/lib/user-claim.routes.tsx new file mode 100644 index 0000000..a64ceae --- /dev/null +++ b/libs/web/claim/feature/src/lib/user-claim.routes.tsx @@ -0,0 +1,14 @@ +import { lazy } from 'react' +import { useRoutes } from 'react-router-dom' + +const Create = lazy(() => import('./user-claim-create.feature')) +const Detail = lazy(() => import('./user-claim-detail.feature')) +const List = lazy(() => import('./user-claim-list.feature')) + +export default function UserClaimRoutes({ communityId, minter }: { communityId: string; minter: string }) { + return useRoutes([ + { path: '', element: }, + { path: 'create', element: }, + { path: ':claimId/*', element: }, + ]) +} diff --git a/libs/web/claim/feature/tsconfig.json b/libs/web/claim/feature/tsconfig.json new file mode 100644 index 0000000..d8c59fe --- /dev/null +++ b/libs/web/claim/feature/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "extends": "../../../../tsconfig.base.json" +} diff --git a/libs/web/claim/feature/tsconfig.lib.json b/libs/web/claim/feature/tsconfig.lib.json new file mode 100644 index 0000000..45b2297 --- /dev/null +++ b/libs/web/claim/feature/tsconfig.lib.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "types": ["node", "@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/image.d.ts"] + }, + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/libs/web/claim/ui/.babelrc b/libs/web/claim/ui/.babelrc new file mode 100644 index 0000000..1ea870e --- /dev/null +++ b/libs/web/claim/ui/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/web/claim/ui/.eslintrc.json b/libs/web/claim/ui/.eslintrc.json new file mode 100644 index 0000000..772a43d --- /dev/null +++ b/libs/web/claim/ui/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nx/react", "../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/web/claim/ui/README.md b/libs/web/claim/ui/README.md new file mode 100644 index 0000000..2be001f --- /dev/null +++ b/libs/web/claim/ui/README.md @@ -0,0 +1,7 @@ +# web-claim-ui + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test web-claim-ui` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/web/claim/ui/project.json b/libs/web/claim/ui/project.json new file mode 100644 index 0000000..12e5b4c --- /dev/null +++ b/libs/web/claim/ui/project.json @@ -0,0 +1,13 @@ +{ + "name": "web-claim-ui", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/web/claim/ui/src", + "projectType": "library", + "tags": ["app:web", "type:ui"], + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + } + } +} diff --git a/libs/web/claim/ui/src/index.ts b/libs/web/claim/ui/src/index.ts new file mode 100644 index 0000000..d7e8aec --- /dev/null +++ b/libs/web/claim/ui/src/index.ts @@ -0,0 +1,11 @@ +export * from './lib/admin-claim-ui-create-form' +export * from './lib/admin-claim-ui-table' +export * from './lib/admin-claim-ui-update-form' +export * from './lib/claim-ui-avatar' +export * from './lib/claim-ui-grid' +export * from './lib/claim-ui-grid-item' +export * from './lib/claim-ui-info' +export * from './lib/claim-ui-item' +export * from './lib/user-claim-ui-create-form' +export * from './lib/user-claim-ui-table' +export * from './lib/user-claim-ui-update-form' diff --git a/libs/web/claim/ui/src/lib/admin-claim-ui-create-form.tsx b/libs/web/claim/ui/src/lib/admin-claim-ui-create-form.tsx new file mode 100644 index 0000000..ec7e8ab --- /dev/null +++ b/libs/web/claim/ui/src/lib/admin-claim-ui-create-form.tsx @@ -0,0 +1,35 @@ +import { Button, Group, Select, TextInput } from '@mantine/core' +import { useForm } from '@mantine/form' +import { getEnumOptions, UiStack } from '@pubkey-ui/core' +import { ClaimAdminCreateInput, IdentityProvider } from '@tokengator-mint/sdk' + +export function AdminClaimUiCreateForm({ submit }: { submit: (res: ClaimAdminCreateInput) => Promise }) { + const form = useForm({ + initialValues: { + communityId: '', + amount: '', + minter: '', + provider: IdentityProvider.Solana, + providerId: '', + }, + }) + + return ( +
submit(values))}> + + + + + + + +
+ ) +} diff --git a/libs/web/claim/ui/src/lib/claim-ui-avatar.tsx b/libs/web/claim/ui/src/lib/claim-ui-avatar.tsx new file mode 100644 index 0000000..49cb188 --- /dev/null +++ b/libs/web/claim/ui/src/lib/claim-ui-avatar.tsx @@ -0,0 +1,10 @@ +import { Claim } from '@tokengator-mint/sdk' +import { UiAvatar, UiAvatarProps } from '@pubkey-ui/core' + +export type ClaimUiAvatarProps = UiAvatarProps & { + claim?: Claim +} + +export function ClaimUiAvatar({ claim, ...props }: ClaimUiAvatarProps) { + return +} diff --git a/libs/web/claim/ui/src/lib/claim-ui-grid-item.tsx b/libs/web/claim/ui/src/lib/claim-ui-grid-item.tsx new file mode 100644 index 0000000..dd8eb56 --- /dev/null +++ b/libs/web/claim/ui/src/lib/claim-ui-grid-item.tsx @@ -0,0 +1,15 @@ +import { Paper } from '@mantine/core' +import { Claim } from '@tokengator-mint/sdk' +import { UiDebugModal, UiGroup } from '@pubkey-ui/core' +import { ClaimUiItem } from './claim-ui-item' + +export function ClaimUiGridItem({ claim, to }: { claim: Claim; to?: string }) { + return ( + + + + + + + ) +} diff --git a/libs/web/claim/ui/src/lib/claim-ui-grid.tsx b/libs/web/claim/ui/src/lib/claim-ui-grid.tsx new file mode 100644 index 0000000..d43a5fc --- /dev/null +++ b/libs/web/claim/ui/src/lib/claim-ui-grid.tsx @@ -0,0 +1,42 @@ +import { Group, Pagination, SimpleGrid } from '@mantine/core' +import { Claim } from '@tokengator-mint/sdk' +import { gridLimits, UiPageLimit } from '@tokengator-mint/web-core-ui' +import { UiDebugModal, UiGroup, UiStack } from '@pubkey-ui/core' +import { DataTableProps } from 'mantine-datatable' +import { ClaimUiGridItem } from './claim-ui-grid-item' + +export function ClaimUiGrid({ + claims = [], + onPageChange, + page, + totalRecords, + limit, + setLimit, + setPage, +}: { + claims: Claim[] + page: DataTableProps['page'] + totalRecords: number + onPageChange: (page: number) => void + limit: number + setLimit: (limit: number) => void + setPage: (page: number) => void +}) { + const totalPages = totalRecords / limit + 1 + return ( + + + {claims.map((claim) => ( + + ))} + + + + + + + + + + ) +} diff --git a/libs/web/claim/ui/src/lib/claim-ui-info.tsx b/libs/web/claim/ui/src/lib/claim-ui-info.tsx new file mode 100644 index 0000000..edd1299 --- /dev/null +++ b/libs/web/claim/ui/src/lib/claim-ui-info.tsx @@ -0,0 +1,19 @@ +import { UiInfoItems, UiInfoTable, UiTime } from '@pubkey-ui/core' +import { Claim } from '@tokengator-mint/sdk' + +export function ClaimUiInfo({ claim }: { claim?: Claim }) { + if (!claim) return null + + const items: UiInfoItems = [ + ['status', claim.status], + ['amount', claim.amount], + ['provider', claim.provider], + ['providerId', claim.providerId], + ['minter', claim.minter], + ['signature', claim.signature], + ['Created At', ], + ['Updated At', ], + ] + + return +} diff --git a/libs/web/claim/ui/src/lib/claim-ui-item.tsx b/libs/web/claim/ui/src/lib/claim-ui-item.tsx new file mode 100644 index 0000000..21995c7 --- /dev/null +++ b/libs/web/claim/ui/src/lib/claim-ui-item.tsx @@ -0,0 +1,33 @@ +import { AvatarProps, Group, GroupProps, Stack, Text } from '@mantine/core' +import { Claim } from '@tokengator-mint/sdk' +import { UiAnchor, UiAnchorProps } from '@pubkey-ui/core' +import { ClaimUiAvatar } from './claim-ui-avatar' + +export function ClaimUiItem({ + anchorProps, + avatarProps, + groupProps, + claim, + to, +}: { + anchorProps?: UiAnchorProps + avatarProps?: Omit + groupProps?: GroupProps + claim?: Claim + to?: string | null +}) { + if (!claim) return null + + return ( + + + + + + {claim?.name} + + + + + ) +} diff --git a/libs/web/claim/ui/src/lib/user-claim-ui-create-form.tsx b/libs/web/claim/ui/src/lib/user-claim-ui-create-form.tsx new file mode 100644 index 0000000..43b202d --- /dev/null +++ b/libs/web/claim/ui/src/lib/user-claim-ui-create-form.tsx @@ -0,0 +1,34 @@ +import { Button, Group, Select, TextInput } from '@mantine/core' +import { useForm } from '@mantine/form' +import { getEnumOptions, UiStack } from '@pubkey-ui/core' +import { ClaimUserCreateInput, IdentityProvider } from '@tokengator-mint/sdk' + +export function UserClaimUiCreateForm({ submit }: { submit: (res: ClaimUserCreateInput) => Promise }) { + const form = useForm({ + initialValues: { + communityId: '', + amount: '', + minter: '', + provider: IdentityProvider.Solana, + providerId: '', + }, + }) + + return ( +
submit(values))}> + + + + + + +
+ ) +} diff --git a/libs/web/claim/ui/tsconfig.json b/libs/web/claim/ui/tsconfig.json new file mode 100644 index 0000000..d8c59fe --- /dev/null +++ b/libs/web/claim/ui/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "extends": "../../../../tsconfig.base.json" +} diff --git a/libs/web/claim/ui/tsconfig.lib.json b/libs/web/claim/ui/tsconfig.lib.json new file mode 100644 index 0000000..45b2297 --- /dev/null +++ b/libs/web/claim/ui/tsconfig.lib.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/out-tsc", + "types": ["node", "@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/image.d.ts"] + }, + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/libs/web/community/feature/src/lib/admin-community-detail.feature.tsx b/libs/web/community/feature/src/lib/admin-community-detail.feature.tsx index ff99b23..856a0ba 100644 --- a/libs/web/community/feature/src/lib/admin-community-detail.feature.tsx +++ b/libs/web/community/feature/src/lib/admin-community-detail.feature.tsx @@ -1,5 +1,6 @@ import { Group } from '@mantine/core' import { UiBack, UiDebugModal, UiError, UiLoader, UiPage, UiTabRoutes } from '@pubkey-ui/core' +import { AdminClaimFeature } from '@tokengator-mint/web-claim-feature' import { useAdminFindOneCommunity } from '@tokengator-mint/web-community-data-access' import { AdminCommunityMemberFeature } from '@tokengator-mint/web-community-member-feature' import { CommunityUiItem } from '@tokengator-mint/web-community-ui' @@ -36,6 +37,7 @@ export function AdminCommunityDetailFeature() { label: 'Info', element: , }, + { path: 'claims', label: 'Claims', element: }, { path: 'members', label: 'Members', element: }, { path: 'wallets', label: 'Wallets', element: }, { diff --git a/libs/web/community/feature/src/lib/user-community-detail-minter-detail-tab.tsx b/libs/web/community/feature/src/lib/user-community-detail-minter-detail-tab.tsx index d4f94c1..473fa74 100644 --- a/libs/web/community/feature/src/lib/user-community-detail-minter-detail-tab.tsx +++ b/libs/web/community/feature/src/lib/user-community-detail-minter-detail-tab.tsx @@ -1,7 +1,8 @@ import { Button, Group } from '@mantine/core' -import { UiLoader, UiStack, UiWarning } from '@pubkey-ui/core' +import { UiInfo, UiLoader, UiStack, UiTabRoute, UiTabRoutes, UiWarning } from '@pubkey-ui/core' import { AccountInfo, ParsedAccountData } from '@solana/web3.js' import { Community } from '@tokengator-mint/sdk' +import { UserClaimFeature } from '@tokengator-mint/web-claim-feature' import { useUserCreateMintFromMinter, useUserGetMinter, @@ -18,23 +19,39 @@ export function UserCommunityDetailMinterDetailTab({ community }: { community: C const queryAssets = useUserGetMinterAssets({ account }) const items: AccountInfo[] = useMemo(() => queryAssets.data ?? [], [queryAssets.data]) + const tabs: UiTabRoute[] = [ + { + path: 'assets', + label: 'Assets', + element: items?.length ? : , + }, + { + path: 'claims', + label: 'Claims', + element: , + }, + ] + return ( {query.isLoading ? ( ) : query.data ? ( - - - - + + + + + - - + + + + ) : ( )} diff --git a/libs/web/core/feature/src/lib/web-core-routes-admin.tsx b/libs/web/core/feature/src/lib/web-core-routes-admin.tsx index 833829e..15a0e99 100644 --- a/libs/web/core/feature/src/lib/web-core-routes-admin.tsx +++ b/libs/web/core/feature/src/lib/web-core-routes-admin.tsx @@ -12,6 +12,7 @@ const links: UiDashboardItem[] = [ { label: 'Mints', icon: IconSettings, to: '/admin/mints' }, { label: 'Presets', icon: IconAdjustmentsX, to: '/admin/presets' }, { label: 'Users', icon: IconUsers, to: '/admin/users' }, + { label: 'Claims', icon: IconSettings, to: '/admin/claims' }, ] const routes: RouteObject[] = [ diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a57aded..e1c43b2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -7,6 +7,20 @@ datasource db { url = env("DATABASE_URL") } +model Claim { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + community Community @relation(fields: [communityId], references: [id], onDelete: Cascade) + communityId String + amount String @default("1") + minter String + signature String? + provider IdentityProvider + providerId String + status ClaimStatus @default(Pending) +} + model Community { id String @id @default(cuid()) createdAt DateTime @default(now()) @@ -15,6 +29,7 @@ model Community { slug String @unique description String imageUrl String? + claims Claim[] members CommunityMember[] wallets Wallet[] } @@ -126,6 +141,11 @@ model Wallet { secretKey String } +enum ClaimStatus { + Claimed + Pending +} + enum CommunityMemberRole { Admin Member diff --git a/tsconfig.base.json b/tsconfig.base.json index ce5d0e3..23972f5 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -18,6 +18,8 @@ "@tokengator-mint/anchor": ["libs/anchor/src/index.ts"], "@tokengator-mint/api-auth-data-access": ["libs/api/auth/data-access/src/index.ts"], "@tokengator-mint/api-auth-feature": ["libs/api/auth/feature/src/index.ts"], + "@tokengator-mint/api-claim-data-access": ["libs/api/claim/data-access/src/index.ts"], + "@tokengator-mint/api-claim-feature": ["libs/api/claim/feature/src/index.ts"], "@tokengator-mint/api-community-data-access": ["libs/api/community/data-access/src/index.ts"], "@tokengator-mint/api-community-feature": ["libs/api/community/feature/src/index.ts"], "@tokengator-mint/api-community-member-data-access": ["libs/api/community-member/data-access/src/index.ts"], @@ -45,6 +47,9 @@ "@tokengator-mint/web-auth-data-access": ["libs/web/auth/data-access/src/index.ts"], "@tokengator-mint/web-auth-feature": ["libs/web/auth/feature/src/index.ts"], "@tokengator-mint/web-auth-ui": ["libs/web/auth/ui/src/index.ts"], + "@tokengator-mint/web-claim-data-access": ["libs/web/claim/data-access/src/index.ts"], + "@tokengator-mint/web-claim-feature": ["libs/web/claim/feature/src/index.ts"], + "@tokengator-mint/web-claim-ui": ["libs/web/claim/ui/src/index.ts"], "@tokengator-mint/web-community-data-access": ["libs/web/community/data-access/src/index.ts"], "@tokengator-mint/web-community-feature": ["libs/web/community/feature/src/index.ts"], "@tokengator-mint/web-community-member-data-access": ["libs/web/community-member/data-access/src/index.ts"],