Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hook up challenge badges with on-chain data #1083

Merged
merged 2 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/cadence/scripts/GetChallenges.cdc
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
import "GoldStar"

access(all) struct Challenge {
access(all) let name: String
access(all) let description: String

init(name: String, description: String) {
self.name = name
self.description = description
}
}

access(all)
fun main(): &[{GoldStar.Challenge}] {
return GoldStar.challenges
fun main(): {String: Challenge} {
let challenges: {String: Challenge} = {}
for challengeType in GoldStar.challenges.keys {
challenges[challengeType.identifier] = Challenge(
name: GoldStar.challenges[challengeType]!.name,
description: GoldStar.challenges[challengeType]!.description
)
}

return challenges
}
59 changes: 36 additions & 23 deletions src/components/ProfileModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import Field from '@site/src/ui/design-system/src/lib/Components/Field';
import Modal from '@site/src/ui/design-system/src/lib/Components/Modal';
import RadioGroup from '@site/src/ui/design-system/src/lib/Components/RadioGroup';
import { Button } from '@site/src/ui/design-system/src/lib/Components/Button';
import { ProfileSettings, SocialType } from '../types/gold-star';
import { Challenge, ProfileSettings, SocialType } from '../types/gold-star';
import { useProfile } from '../hooks/use-profile';
import { useCurrentUser } from '../hooks/use-current-user';
import { createProfile, setProfile } from '../utils/gold-star';
import { isEqual } from 'lodash';
import RemovableTag from '@site/src/ui/design-system/src/lib/Components/RemovableTag';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrophy } from '@fortawesome/free-solid-svg-icons';
import { useChallenges } from '../hooks/use-challenges';

interface ProfileModalProps {
isOpen: boolean;
Expand All @@ -27,11 +28,8 @@ const flowSources = [
{ name: 'Other', description: 'Another way not listed above.' },
];

const challenges = [
{ name: 'Learn Flow', description: 'Committed to learning and exploring Flow.' },
];

const ProfileModal: React.FC<ProfileModalProps> = ({ isOpen, onClose }) => {
const { challenges } = useChallenges();
const { user } = useCurrentUser();
const {
profile,
Expand All @@ -50,6 +48,17 @@ const ProfileModal: React.FC<ProfileModalProps> = ({ isOpen, onClose }) => {
const [tags, setTags] = useState<string[]>([]);
const [tagInput, setTagInput] = useState('');

const completedChallenges = Object.keys(profile?.submissions ?? {}).reduce(
(acc, key) => {
const challengeInfo = challenges?.[key];
if (profile?.submissions[key]?.completed && challengeInfo) {
acc.push(challengeInfo);
}
return acc;
},
[] as Challenge[],
);

useEffect(() => {
if (profile && !loaded && !isLoading && !error) {
setSettings({
Expand Down Expand Up @@ -173,25 +182,29 @@ const ProfileModal: React.FC<ProfileModalProps> = ({ isOpen, onClose }) => {
/>
</div>

<div>
<h3 className="text-lg font-bold mb-3">My Challenges</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 gap-6">
{challenges.map((challenge, index) => (
<div
key={index}
className="flex flex-col items-center bg-gray-100 p-6 rounded-lg shadow-md text-center w-full"
>
<FontAwesomeIcon
icon={faTrophy}
size="3x"
className="text-yellow-500 mb-4"
/>
<p className="text-md font-bold mb-2">{challenge.name}</p>
<p className="text-sm text-gray-600">{challenge.description}</p>
</div>
))}
{completedChallenges.length > 0 && (
<div>
<h3 className="text-lg font-bold mb-3">My Challenges</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 gap-6">
{completedChallenges.map((challenge, index) => (
<div
key={index}
className="flex flex-col items-center bg-gray-100 p-6 rounded-lg shadow-md text-center w-full"
>
<FontAwesomeIcon
icon={faTrophy}
size="3x"
className="text-yellow-500 mb-4"
/>
<p className="text-md font-bold mb-2">{challenge.name}</p>
<p className="text-sm text-gray-600">
{challenge.description}
</p>
</div>
))}
</div>
</div>
</div>
)}

<div className="flex flex-col space-y-2">
<Button
Expand Down
17 changes: 17 additions & 0 deletions src/hooks/use-challenges.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import useSWR from 'swr';
import { Challenges } from '../types/gold-star';
import { getChallenges } from '../utils/gold-star';

export function useChallenges() {
const {
data: challenges,
isLoading,
error,
} = useSWR<Challenges>('getChallenges', getChallenges);

return {
challenges,
isLoading,
error,
};
}
21 changes: 13 additions & 8 deletions src/types/gold-star.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,22 @@ export interface ProfileResponse {
referralSource?: string;
}

export interface ChallengeResponse {
id: string;
export type ChallengesResponse = {
[typeIdentifier: string]: {
name: string;
description: string;
};
};

export type Challenges = {
[typeIdentifier: string]: Challenge;
};

export type Challenge = {
name: string;
description: string;
}
};

export enum SocialType {
GITHUB = 'github',
}

export interface Challenge {
contractName: string;
resourceIdentifier: string;
}
6 changes: 4 additions & 2 deletions src/utils/flow.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as fcl from '@onflow/fcl';
import { getContractAddress } from '../config/fcl';
import { Challenge } from '../types/gold-star';

export function typeIdentifier(
address: string,
Expand All @@ -12,7 +11,10 @@ export function typeIdentifier(
}`;
}

export function getChallengeIdentifier(challenge: Challenge): string {
export function getChallengeIdentifier(challenge: {
contractName: string;
resourceIdentifier: string;
}): string {
return typeIdentifier(
getContractAddress(challenge.contractName),
challenge.contractName,
Expand Down
12 changes: 5 additions & 7 deletions src/utils/gold-star.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import Evaluate from '../cadence/transactions/NoopChallenge/Evaluate.cdc';

import * as fcl from '@onflow/fcl';
import {
ChallengeResponse,
Challenges,
ChallengesResponse,
Profile,
ProfileResponse,
ProfileSettings,
Expand All @@ -16,15 +17,12 @@ import {
* Get the list of challenges
* @returns The list of challenges
*/
export const getChallenges = async () => {
export const getChallenges = async (): Promise<Challenges> => {
const resp = (await fcl.query({
cadence: GetChallenges,
})) as ChallengeResponse[];
})) as ChallengesResponse;

return resp.map((c) => ({
name: c.name,
description: c.description,
}));
return resp;
};

/**
Expand Down