diff --git a/backend/src/auth/INALIENABLE_RIGHTS.ts b/backend/src/auth/INALIENABLE_RIGHTS.ts index c3c96b95e..19865608f 100644 --- a/backend/src/auth/INALIENABLE_RIGHTS.ts +++ b/backend/src/auth/INALIENABLE_RIGHTS.ts @@ -7,6 +7,7 @@ export const INALIENABLE_RIGHTS = [ RIGHTS.SEND_RESET_PASSWORD_EMAIL, RIGHTS.SET_PASSWORD, RIGHTS.QUERY_TRANSACTION_LINK, + RIGHTS.QUERY_REDEEM_JWT, RIGHTS.QUERY_OPT_IN, RIGHTS.CHECK_USERNAME, RIGHTS.PROJECT_BRANDING_BANNER, diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index 0ccb9695f..4775c07e2 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -6,6 +6,7 @@ export enum RIGHTS { SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL', SET_PASSWORD = 'SET_PASSWORD', QUERY_TRANSACTION_LINK = 'QUERY_TRANSACTION_LINK', + QUERY_REDEEM_JWT = 'QUERY_REDEEM_JWT', QUERY_OPT_IN = 'QUERY_OPT_IN', CHECK_USERNAME = 'CHECK_USERNAME', PROJECT_BRANDING_BANNER = 'PROJECT_BRANDING_BANNER', diff --git a/backend/src/auth/jwt/JWT.ts b/backend/src/auth/jwt/JWT.ts new file mode 100644 index 000000000..f36dd10fc --- /dev/null +++ b/backend/src/auth/jwt/JWT.ts @@ -0,0 +1,37 @@ +import { SignJWT, jwtVerify } from 'jose' + +import { LogError } from '@/server/LogError' + +import { JwtPayloadType } from './payloadtypes/JwtPayloadType' + +export const decode = async (token: string, signkey: Buffer): Promise => { + if (!token) throw new LogError('401 Unauthorized') + + try { + const secret = new TextEncoder().encode(signkey.toString()) + const { payload } = await jwtVerify(token, secret, { + issuer: 'urn:gradido:issuer', + audience: 'urn:gradido:audience', + }) + return payload as unknown as JwtPayloadType + } catch (err) { + return null + } +} + +export const encode = async (payload: JwtPayloadType, signkey: Buffer): Promise => { + const secret = new TextEncoder().encode(signkey.toString()) + const token = await new SignJWT({ payload, 'urn:gradido:claim': true }) + .setProtectedHeader({ alg: 'HS256' }) + .setIssuedAt() + .setIssuer('urn:gradido:issuer') + .setAudience('urn:gradido:audience') + .setExpirationTime(payload.expiration) + .sign(secret) + return token +} + +export const decodeJwtType = async (token: string, signkey: Buffer): Promise => { + const payload = await decode(token, signkey) + return payload ? payload.tokentype : 'unknown token type' +} diff --git a/backend/src/auth/jwt/payloadtypes/DisbursementJwtPayloadType.ts b/backend/src/auth/jwt/payloadtypes/DisbursementJwtPayloadType.ts new file mode 100644 index 000000000..f8e0094b9 --- /dev/null +++ b/backend/src/auth/jwt/payloadtypes/DisbursementJwtPayloadType.ts @@ -0,0 +1,33 @@ +// import { JWTPayload } from 'jose' +import { JwtPayloadType } from './JwtPayloadType' + +export class DisbursementJwtPayloadType extends JwtPayloadType { + static REDEEM_ACTIVATION_TYPE = 'redeem-activation' + + sendercommunityuuid: string + sendergradidoid: string + sendername: string // alias or firstname + redeemcode: string + amount: string + memo: string + + constructor( + senderCom: string, + senderUser: string, + sendername: string, + code: string, + amount: string, + memo: string, + ) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + super() + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + this.tokentype = DisbursementJwtPayloadType.REDEEM_ACTIVATION_TYPE + this.sendercommunityuuid = senderCom + this.sendergradidoid = senderUser + this.sendername = sendername + this.redeemcode = code + this.amount = amount + this.memo = memo + } +} diff --git a/backend/src/auth/jwt/payloadtypes/JwtPayloadType.ts b/backend/src/auth/jwt/payloadtypes/JwtPayloadType.ts new file mode 100644 index 000000000..23b6c3fe8 --- /dev/null +++ b/backend/src/auth/jwt/payloadtypes/JwtPayloadType.ts @@ -0,0 +1,19 @@ +import { JWTPayload } from 'jose' + +export class JwtPayloadType implements JWTPayload { + iat?: number | undefined + exp?: number | undefined + nbf?: number | undefined + jti?: string | undefined + aud?: string | string[] | undefined + sub?: string | undefined + iss?: string | undefined; + [propName: string]: unknown + + tokentype: string + expiration: string // in minutes (format: 10m for ten minutes) + constructor() { + this.tokentype = 'unknown jwt type' + this.expiration = '10m' + } +} diff --git a/backend/src/federation/client/1_0/FederationClient.ts b/backend/src/federation/client/1_0/FederationClient.ts index 0c2b4101b..1b95cb1a3 100644 --- a/backend/src/federation/client/1_0/FederationClient.ts +++ b/backend/src/federation/client/1_0/FederationClient.ts @@ -78,6 +78,7 @@ export class FederationClient { ) return data.getPublicCommunityInfo } catch (err) { + logger.warn(' err', err) const errorString = JSON.stringify(err) logger.warn('Federation: getPublicCommunityInfo failed for endpoint', { endpoint: this.endpoint, diff --git a/backend/src/federation/client/1_0/logging/PublicCommunityInfoLogging.view.ts b/backend/src/federation/client/1_0/logging/PublicCommunityInfoLogging.view.ts index 3151bbb31..1cb9270f5 100644 --- a/backend/src/federation/client/1_0/logging/PublicCommunityInfoLogging.view.ts +++ b/backend/src/federation/client/1_0/logging/PublicCommunityInfoLogging.view.ts @@ -12,7 +12,7 @@ export class PublicCommunityInfoLoggingView extends AbstractLoggingView { return { name: this.self.name, description: this.self.description, - creationDate: this.dateToString(this.self.creationDate), + creationDate: this.self.creationDate, publicKey: this.self.publicKey, } } diff --git a/backend/src/graphql/arg/RedeemJwtArgs.ts b/backend/src/graphql/arg/RedeemJwtArgs.ts new file mode 100644 index 000000000..bd3feb1d6 --- /dev/null +++ b/backend/src/graphql/arg/RedeemJwtArgs.ts @@ -0,0 +1,37 @@ +import { MaxLength, MinLength } from 'class-validator' +import { Decimal } from 'decimal.js-light' +import { Field, ArgsType, InputType } from 'type-graphql' + +import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '@/graphql/resolver/const/const' +import { IsPositiveDecimal } from '@/graphql/validator/Decimal' + +@InputType() +@ArgsType() +export class RedeemJwtArgs { + @Field(() => String, { nullable: false }) + gradidoID: string + + @Field(() => String, { nullable: true }) + alias?: string | null + + @Field(() => String, { nullable: true }) + firstName?: string | null + + @Field(() => String, { nullable: false }) + communityUuid: string + + @Field(() => String, { nullable: false }) + communityName: string + + @Field(() => String, { nullable: false }) + code: string + + @Field(() => Decimal, { nullable: false }) + @IsPositiveDecimal() + amount: Decimal + + @Field(() => String, { nullable: false }) + @MaxLength(MEMO_MAX_CHARS) + @MinLength(MEMO_MIN_CHARS) + memo: string +} diff --git a/backend/src/graphql/model/TransactionLink.ts b/backend/src/graphql/model/TransactionLink.ts index 995fe5d50..210420bb9 100644 --- a/backend/src/graphql/model/TransactionLink.ts +++ b/backend/src/graphql/model/TransactionLink.ts @@ -1,26 +1,42 @@ -import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' +import { Community as DbCommunity } from '@entity/Community' +import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink' import { Decimal } from 'decimal.js-light' import { ObjectType, Field, Int } from 'type-graphql' import { CONFIG } from '@/config' +import { Community } from './Community' import { User } from './User' @ObjectType() export class TransactionLink { - constructor(transactionLink: dbTransactionLink, user: User, redeemedBy: User | null = null) { - this.id = transactionLink.id - this.user = user - this.amount = transactionLink.amount - this.holdAvailableAmount = transactionLink.holdAvailableAmount - this.memo = transactionLink.memo - this.code = transactionLink.code - this.createdAt = transactionLink.createdAt - this.validUntil = transactionLink.validUntil - this.deletedAt = transactionLink.deletedAt - this.redeemedAt = transactionLink.redeemedAt - this.redeemedBy = redeemedBy - this.link = CONFIG.COMMUNITY_REDEEM_URL + this.code + constructor( + dbTransactionLink?: DbTransactionLink, + user?: User, + redeemedBy?: User, + dbCommunities?: DbCommunity[], + ) { + if (dbTransactionLink !== undefined) { + this.id = dbTransactionLink.id + this.amount = dbTransactionLink.amount + this.holdAvailableAmount = dbTransactionLink.holdAvailableAmount + this.memo = dbTransactionLink.memo + this.code = dbTransactionLink.code + this.link = CONFIG.COMMUNITY_REDEEM_URL + this.code + this.createdAt = dbTransactionLink.createdAt + this.validUntil = dbTransactionLink.validUntil + this.deletedAt = dbTransactionLink.deletedAt + this.redeemedAt = dbTransactionLink.redeemedAt + } + if (user !== undefined) { + this.user = user + } + if (redeemedBy !== undefined) { + this.redeemedBy = redeemedBy + } + if (dbCommunities !== undefined) { + this.communities = dbCommunities.map((dbCom: DbCommunity) => new Community(dbCom)) + } } @Field(() => Int) @@ -58,6 +74,12 @@ export class TransactionLink { @Field(() => String) link: string + + @Field(() => String) + communityName: string + + @Field(() => [Community]) + communities: Community[] } @ObjectType() diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index b665bde98..73c6c3b61 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -1,5 +1,5 @@ import { Point } from '@dbTools/typeorm' -import { User as dbUser } from '@entity/User' +import { User as DbUser } from '@entity/User' import { ObjectType, Field, Int } from 'type-graphql' import { GmsPublishLocationType } from '@enum/GmsPublishLocationType' @@ -14,41 +14,43 @@ import { UserContact } from './UserContact' @ObjectType() export class User { - constructor(user: dbUser | null) { - if (user) { - this.id = user.id - this.foreign = user.foreign - this.communityUuid = user.communityUuid - if (user.community) { - this.communityName = user.community.name + constructor(dbUser: DbUser | null) { + if (dbUser) { + this.id = dbUser.id + this.foreign = dbUser.foreign + this.communityUuid = dbUser.communityUuid + if (dbUser.community) { + this.communityName = dbUser.community.name } - this.gradidoID = user.gradidoID - this.alias = user.alias + this.gradidoID = dbUser.gradidoID + this.alias = dbUser.alias - const publishNameLogic = new PublishNameLogic(user) - this.humhubUsername = publishNameLogic.getUsername(user.humhubPublishName as PublishNameType) + const publishNameLogic = new PublishNameLogic(dbUser) + this.humhubUsername = publishNameLogic.getUsername( + dbUser.humhubPublishName as PublishNameType, + ) - if (user.emailContact) { - this.emailChecked = user.emailContact.emailChecked - this.emailContact = new UserContact(user.emailContact) + if (dbUser.emailContact) { + this.emailChecked = dbUser.emailContact.emailChecked + this.emailContact = new UserContact(dbUser.emailContact) } - this.firstName = user.firstName - this.lastName = user.lastName - this.deletedAt = user.deletedAt - this.createdAt = user.createdAt - this.language = user.language - this.publisherId = user.publisherId - this.roles = user.userRoles?.map((userRole) => userRole.role) ?? [] + this.firstName = dbUser.firstName + this.lastName = dbUser.lastName + this.deletedAt = dbUser.deletedAt + this.createdAt = dbUser.createdAt + this.language = dbUser.language + this.publisherId = dbUser.publisherId + this.roles = dbUser.userRoles?.map((userRole) => userRole.role) ?? [] this.klickTipp = null this.hasElopage = null - this.hideAmountGDD = user.hideAmountGDD - this.hideAmountGDT = user.hideAmountGDT - this.humhubAllowed = user.humhubAllowed - this.gmsAllowed = user.gmsAllowed - this.gmsPublishName = user.gmsPublishName - this.humhubPublishName = user.humhubPublishName - this.gmsPublishLocation = user.gmsPublishLocation - this.userLocation = user.location ? Point2Location(user.location as Point) : null + this.hideAmountGDD = dbUser.hideAmountGDD + this.hideAmountGDT = dbUser.hideAmountGDT + this.humhubAllowed = dbUser.humhubAllowed + this.gmsAllowed = dbUser.gmsAllowed + this.gmsPublishName = dbUser.gmsPublishName + this.humhubPublishName = dbUser.humhubPublishName + this.gmsPublishLocation = dbUser.gmsPublishLocation + this.userLocation = dbUser.location ? Point2Location(dbUser.location as Point) : null } } diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 63134a9a8..f5ccb03f0 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -1,6 +1,7 @@ import { randomBytes } from 'crypto' import { getConnection } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' import { Contribution as DbContribution } from '@entity/Contribution' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' import { Transaction as DbTransaction } from '@entity/Transaction' @@ -10,6 +11,7 @@ import { Decimal } from 'decimal.js-light' import { Resolver, Args, Arg, Authorized, Ctx, Mutation, Query, Int } from 'type-graphql' import { Paginated } from '@arg/Paginated' +import { RedeemJwtArgs } from '@arg/RedeemJwtArgs' import { TransactionLinkArgs } from '@arg/TransactionLinkArgs' import { TransactionLinkFilters } from '@arg/TransactionLinkFilters' import { ContributionCycleType } from '@enum/ContributionCycleType' @@ -22,6 +24,8 @@ import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink' import { User } from '@model/User' import { QueryLinkResult } from '@union/QueryLinkResult' +import { decode, encode } from '@/auth/jwt/JWT' +import { DisbursementJwtPayloadType } from '@/auth/jwt/payloadtypes/DisbursementJwtPayloadType' import { RIGHTS } from '@/auth/RIGHTS' import { EVENT_CONTRIBUTION_LINK_REDEEM, @@ -39,6 +43,7 @@ import { fullName } from '@/util/utilities' import { calculateBalance } from '@/util/validate' import { executeTransaction } from './TransactionResolver' +import { getAuthenticatedCommunities, getHomeCommunity } from './util/communities' import { getUserCreation, validateContribution } from './util/creations' import { getLastTransaction } from './util/getLastTransaction' import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector' @@ -136,6 +141,8 @@ export class TransactionLinkResolver { @Authorized([RIGHTS.QUERY_TRANSACTION_LINK]) @Query(() => QueryLinkResult) async queryTransactionLink(@Arg('code') code: string): Promise { + logger.debug('TransactionLinkResolver.queryTransactionLink... code=', code) + const transactionLink = new TransactionLink() if (code.match(/^CL-/)) { const contributionLink = await DbContributionLink.findOneOrFail({ where: { code: code.replace('CL-', '') }, @@ -143,19 +150,48 @@ export class TransactionLinkResolver { }) return new ContributionLink(contributionLink) } else { - const transactionLink = await DbTransactionLink.findOneOrFail({ - where: { code }, - withDeleted: true, - }) - const user = await DbUser.findOneOrFail({ where: { id: transactionLink.userId } }) - let redeemedBy: User | null = null - if (transactionLink?.redeemedBy) { - redeemedBy = new User( - await DbUser.findOneOrFail({ where: { id: transactionLink.redeemedBy } }), - ) + let txLinkFound = false + let dbTransactionLink!: DbTransactionLink + try { + dbTransactionLink = await DbTransactionLink.findOneOrFail({ + where: { code }, + withDeleted: true, + }) + txLinkFound = true + } catch (err) { + txLinkFound = false + } + // normal redeem code + if (txLinkFound) { + const user = await DbUser.findOneOrFail({ where: { id: dbTransactionLink.userId } }) + let redeemedBy + if (dbTransactionLink.redeemedBy) { + redeemedBy = new User( + await DbUser.findOneOrFail({ where: { id: dbTransactionLink.redeemedBy } }), + ) + } + const communities = await getAuthenticatedCommunities() + return new TransactionLink(dbTransactionLink, new User(user), redeemedBy, communities) + } else { + // disbursement jwt-token + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment + const homeCom = await getHomeCommunity() + const jwtPayload = decode(code, homeCom.publicKey) + if (jwtPayload !== null && jwtPayload instanceof DisbursementJwtPayloadType) { + const disburseJwtPayload: DisbursementJwtPayloadType = jwtPayload + transactionLink.communityName = homeCom.name !== null ? homeCom.name : 'unknown' + // transactionLink.user = new User() + transactionLink.user.alias = disburseJwtPayload.sendername + transactionLink.amount = new Decimal(disburseJwtPayload.amount) + transactionLink.memo = disburseJwtPayload.memo + transactionLink.code = disburseJwtPayload.redeemcode + return transactionLink + } else { + throw new LogError('Redeem with wrong type of JWT-Token! jwtType=', jwtPayload) + } } - return new TransactionLink(transactionLink, new User(user), redeemedBy) } + return transactionLink } @Authorized([RIGHTS.REDEEM_TRANSACTION_LINK]) @@ -364,6 +400,36 @@ export class TransactionLinkResolver { } } + @Authorized([RIGHTS.QUERY_REDEEM_JWT]) + @Mutation(() => String) + async createRedeemJwt(@Args() redeemJwtArgs: RedeemJwtArgs): Promise { + logger.debug('TransactionLinkResolver.queryRedeemJwt... args=', { + gradidoID: redeemJwtArgs.gradidoID, + alias: redeemJwtArgs.alias, + firstName: redeemJwtArgs.firstName, + communityUuid: redeemJwtArgs.communityUuid, + communityName: redeemJwtArgs.communityName, + code: redeemJwtArgs.code, + amount: redeemJwtArgs.amount, + memo: redeemJwtArgs.memo, + }) + + const disbursementJwtPayloadType = new DisbursementJwtPayloadType( + redeemJwtArgs.communityUuid, + redeemJwtArgs.gradidoID, + redeemJwtArgs.alias ?? redeemJwtArgs.firstName ?? '', + redeemJwtArgs.code, + redeemJwtArgs.amount.toString(), + redeemJwtArgs.memo, + ) + const homeCom = await getHomeCommunity() + if (!homeCom.privateKey) { + throw new LogError('Home community private key is not set') + } + const redeemJwt = await encode(disbursementJwtPayloadType, homeCom.privateKey) + return redeemJwt + } + @Authorized([RIGHTS.LIST_TRANSACTION_LINKS]) @Query(() => TransactionLinkResult) async listTransactionLinks( diff --git a/backend/src/graphql/resolver/util/communities.ts b/backend/src/graphql/resolver/util/communities.ts index 87cf31610..90087bd10 100644 --- a/backend/src/graphql/resolver/util/communities.ts +++ b/backend/src/graphql/resolver/util/communities.ts @@ -1,4 +1,4 @@ -import { FindOneOptions } from '@dbTools/typeorm' +import { FindOneOptions, IsNull, Not } from '@dbTools/typeorm' import { Community as DbCommunity } from '@entity/Community' import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' @@ -87,6 +87,17 @@ export async function getCommunityByUuid(communityUuid: string): Promise { + const dbCommunities: DbCommunity[] = await DbCommunity.find({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + where: { communityUuid: Not(IsNull()) }, //, authenticatedAt: Not(IsNull()) }, + order: { + name: 'ASC', + }, + }) + return dbCommunities +} + export async function getCommunityByIdentifier( communityIdentifier: string, ): Promise { diff --git a/docu/Concepts/TechnicalRequirements/image/redeemlink_without_community-switch.png b/docu/Concepts/TechnicalRequirements/image/redeemlink_without_community-switch.png new file mode 100644 index 000000000..a929d5ff7 Binary files /dev/null and b/docu/Concepts/TechnicalRequirements/image/redeemlink_without_community-switch.png differ diff --git a/frontend/src/components/CommunitySwitch.vue b/frontend/src/components/CommunitySwitch.vue index cd50789dd..16f1a4b7b 100644 --- a/frontend/src/components/CommunitySwitch.vue +++ b/frontend/src/components/CommunitySwitch.vue @@ -46,6 +46,7 @@ const validCommunityIdentifier = ref(false) const { onResult } = useQuery(selectCommunities) onResult(({ data }) => { + console.log('CommunitySwitch.onResult...data=', data) if (data) { communities.value = data.communities setDefaultCommunity() @@ -55,22 +56,29 @@ onResult(({ data }) => { const communityIdentifier = computed(() => route.params.communityIdentifier) function updateCommunity(community) { + console.log('CommunitySwitch.updateCommunity...community=', community) emit('update:model-value', community) } function setDefaultCommunity() { + console.log('CommunitySwitch.setDefaultCommunity... communityIdentifier= communities=', communityIdentifier, communities) if (communityIdentifier.value && communities.value.length >= 1) { + console.log('CommunitySwitch.setDefaultCommunity... communities.value.length=', communities.value.length) const foundCommunity = communities.value.find((community) => { + console.log('CommunitySwitch.setDefaultCommunity... community=', community) if ( community.uuid === communityIdentifier.value || community.name === communityIdentifier.value ) { validCommunityIdentifier.value = true + console.log('CommunitySwitch.setDefaultCommunity...true validCommunityIdentifier=', validCommunityIdentifier) return true } + console.log('CommunitySwitch.setDefaultCommunity...false validCommunityIdentifier=', validCommunityIdentifier) return false }) if (foundCommunity) { + console.log('CommunitySwitch.setDefaultCommunity...foundCommunity=', foundCommunity) updateCommunity(foundCommunity) return } @@ -79,10 +87,13 @@ function setDefaultCommunity() { if (validCommunityIdentifier.value && !communityIdentifier.value) { validCommunityIdentifier.value = false + console.log('CommunitySwitch.setDefaultCommunity...validCommunityIdentifier=', validCommunityIdentifier) } if (props.modelValue?.uuid === '' && communities.value.length) { + console.log('CommunitySwitch.setDefaultCommunity...props.modelValue= communities=', props.modelValue, communities.value.length) const foundCommunity = communities.value.find((community) => !community.foreign) + console.log('CommunitySwitch.setDefaultCommunity...foundCommunity=', foundCommunity) if (foundCommunity) { updateCommunity(foundCommunity) } diff --git a/frontend/src/components/LinkInformations/RedeemCommunitySelection.vue b/frontend/src/components/LinkInformations/RedeemCommunitySelection.vue new file mode 100644 index 000000000..3d2febe66 --- /dev/null +++ b/frontend/src/components/LinkInformations/RedeemCommunitySelection.vue @@ -0,0 +1,103 @@ + + diff --git a/frontend/src/components/LinkInformations/RedeemSelectCommunity.vue b/frontend/src/components/LinkInformations/RedeemSelectCommunity.vue new file mode 100644 index 000000000..d956dce1a --- /dev/null +++ b/frontend/src/components/LinkInformations/RedeemSelectCommunity.vue @@ -0,0 +1,49 @@ + + + diff --git a/frontend/src/graphql/mutations.js b/frontend/src/graphql/mutations.js index e1e3bf7db..0a5a4652b 100644 --- a/frontend/src/graphql/mutations.js +++ b/frontend/src/graphql/mutations.js @@ -198,3 +198,27 @@ export const logout = gql` logout } ` + +export const createRedeemJwtMutation = gql` + mutation ( + $gradidoID: String! + $firstName: String! + $alias: String! + $communityUuid: String! + $communityName: String! + $code: String! + $amount: Decimal! + $memo: String! + ) { + createRedeemJwt( + gradidoID: $gradidoID + firstName: $firstName + alias: $alias + communityUuid: $communityUuid + communityName: $communityName + code: $code + amount: $amount + memo: $memo + ) + } +` diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index b1ae7bed8..bc4755776 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -110,6 +110,7 @@ export const selectCommunities = gql` name description foreign + url } } ` @@ -142,6 +143,13 @@ export const queryTransactionLink = gql` firstName publisherId } + communities { + foreign + name + description + url + uuid + } } ... on ContributionLink { id diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 0aa5e843c..c98ede881 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -251,12 +251,15 @@ "no-account": "Du besitzt noch kein Gradido Konto?", "no-redeem": "Du darfst deinen eigenen Link nicht einlösen!", "not-copied": "Dein Gerät lässt das Kopieren leider nicht zu! Bitte kopiere den Link von Hand!", + "recipientCommunity": "Wähle deine Gemeinschaft zum Einlösen des Link-Guthabens...", "redeem": "Einlösen", "redeemed": "Erfolgreich eingelöst! Deinem Konto wurden {n} GDD gutgeschrieben.", "redeemed-at": "Der Link wurde bereits am {date} eingelöst.", "redeemlink-error": "Dieser Einlöse-Link ist nicht vollständig.", + "switchCommunity": "Du hast eine andere Gemeinschaft ausgewählt...", "to-login": "Log dich ein", "to-register": "Registriere ein neues Konto.", + "to-switch": "Wechsle zur Gemeinschaft", "validUntil": "Gültig bis", "validUntilDate": "Der Link ist bis zum {date} gültig." }, diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index d241f5cc2..4e6052ab7 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -251,12 +251,15 @@ "no-account": "You don't have a Gradido account yet?", "no-redeem": "You not allowed to redeem your own link!", "not-copied": "Unfortunately, your device does not allow copying! Please copy the link by hand!", + "recipientCommunity": "Select your Community to redeem the link-deposit...", "redeem": "Redeem", "redeemed": "Successfully redeemed! Your account has been credited with {n} GDD.", "redeemed-at": "The link was already redeemed on {date}.", "redeemlink-error": "This redemption link is not complete.", + "switchCommunity": "You have selected a foreign Community...", "to-login": "Log in", "to-register": "Register a new account.", + "to-switch": "Switch to Community", "validUntil": "Valid until", "validUntilDate": "The link is valid until {date}." }, diff --git a/frontend/src/locales/es.json b/frontend/src/locales/es.json index 3a6935edb..af0bb170e 100644 --- a/frontend/src/locales/es.json +++ b/frontend/src/locales/es.json @@ -191,12 +191,15 @@ "no-account": "Aún no tienes una cuenta de Gradido?", "no-redeem": "No puedes canjear tu propio enlace!", "not-copied": "¡Desafortunadamente, su dispositivo no permite copiar! Copie el enlace manualmente!", + "recipientCommunity": "Select your Community to redeem the link-deposit...", "redeem": "Canjear", "redeemed": "¡Canjeado con éxito! Tu cuenta ha sido acreditada con {n} GDD.", "redeemed-at": "El enlace ya se canjeó el {date}.", "redeemed-title": "canjeado", + "switchCommunity": "You have selected a foreign Community...", "to-login": "iniciar sesión", "to-register": "Registre una nueva cuenta.", + "to-switch": "Switch to Community", "validUntil": "Válido hasta", "validUntilDate": "El enlace es válido hasta el {date} ." }, diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 11d9e1efb..91cbd5922 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -197,12 +197,15 @@ "no-account": "Vous n'avez pas encore de compte Gradido?", "no-redeem": "Vous n'êtes pas autorisé à percevoir votre propre lien!", "not-copied": "Malheureusement votre appareil ne permet pas de copier! Veuillez copier le lien manuellement svp!", + "recipientCommunity": "Select your Community to redeem the link-deposit...", "redeem": "Encaisser", "redeemed": "Encaissé avec succès! Votre compte est crédité de {n} GDD.", "redeemed-at": "Le lien a déjà été perçu le {date}.", "redeemed-title": "encaisser", + "switchCommunity": "You have selected a foreign Community...", "to-login": "Connexion", "to-register": "Enregistrer un nouveau compte.", + "to-switch": "Switch to Community", "validUntil": "Valide jusqu'au", "validUntilDate": "Le lien est valide jusqu'au {date}." }, diff --git a/frontend/src/locales/nl.json b/frontend/src/locales/nl.json index 012c89b40..5f0c8d305 100644 --- a/frontend/src/locales/nl.json +++ b/frontend/src/locales/nl.json @@ -191,12 +191,15 @@ "no-account": "Je hebt nog geen Gradido rekening?", "no-redeem": "Je mag je eigen link niet inwisselen!", "not-copied": "Jouw apparaat laat het kopiëren helaas niet toe! Kopieer de link alsjeblieft met de hand!", + "recipientCommunity": "Select your Community to redeem the link-deposit...", "redeem": "Inwisselen", "redeemed": "Succesvol ingewisseld! Op jouw rekening werden {n} GDD bijgeschreven.", "redeemed-at": "De link werd al op {date} ingewisseld.", "redeemed-title": "ingewisseld", + "switchCommunity": "You have selected a foreign Community...", "to-login": "Inloggen", "to-register": "Registreer een nieuwe rekening.", + "to-switch": "Switch to Community", "validUntil": "Geldig tot", "validUntilDate": "De link is geldig tot {date}." }, diff --git a/frontend/src/locales/tr.json b/frontend/src/locales/tr.json index e4c6d3385..bf9898884 100644 --- a/frontend/src/locales/tr.json +++ b/frontend/src/locales/tr.json @@ -176,13 +176,16 @@ "no-account": "Henüz bir Gradido hesabınız yok mu?", "no-redeem": "Kendi linkini kullanarak GDD'lerini paraya dönüştüremezsin!", "not-copied": "Maalesef cihazın kopyalamaya izin vermiyor! Lütfen bağlantıyı elle kopyala!", + "recipientCommunity": "Select your Community to redeem the link-deposit...", "redeem": "Paraya dönüştürme", "redeem-text": "Şu anda paraya dönüştürmek ister misin?", "redeemed": "Başarıyla dönüştürüldü! Hesabına {n} GDD yatırıldı.", "redeemed-at": "Link {date} tarihinde paraya dönüştürüldü.", "redeemed-title": "paraya dönüştürüldü", + "switchCommunity": "You have selected a foreign Community...", "to-login": "Giriş yap", "to-register": "Yeni hesap aç.", + "to-switch": "Switch to Community", "validUntil": "Geçerlilik süresi", "validUntilDate": "Link {date} tarihine kadar geçerli." }, diff --git a/frontend/src/pages/TransactionLink.vue b/frontend/src/pages/TransactionLink.vue index 9b23528a9..4350a0246 100644 --- a/frontend/src/pages/TransactionLink.vue +++ b/frontend/src/pages/TransactionLink.vue @@ -2,8 +2,12 @@