Skip to content

Commit

Permalink
Add functionality to edit or create profile (#1071)
Browse files Browse the repository at this point in the history
  • Loading branch information
jribbink authored Jan 6, 2025
1 parent 4a4344f commit 0ffbc1c
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 32 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"@jest/globals": "^29.7.0",
"@tsconfig/docusaurus": "^2.0.3",
"@types/jest": "^29.5.14",
"@types/lodash": "^4.17.14",
"@typescript-eslint/eslint-plugin": "^8.11.0",
"concurrently": "^8.2.1",
"eslint": "^8.0.1",
Expand Down
2 changes: 1 addition & 1 deletion src/cadence/contracts/GoldStar.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ contract GoldStar {

access(all)
resource Profile {
access(all)
access(mapping Identity)
var handle: ProfileHandle

access(all)
Expand Down
8 changes: 6 additions & 2 deletions src/cadence/transactions/GoldStar/UpdateProfile.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ transaction(
deployedContracts: {Address: [String]},
socials: {String: String}
) {
let profile: auth(GoldStar.UpdateSocials, GoldStar.UpdateDeployedContracts, GoldStar.UpdateReferralSource) &GoldStar.Profile
let profile: auth(GoldStar.UpdateHandle, GoldStar.UpdateSocials, GoldStar.UpdateDeployedContracts, GoldStar.UpdateReferralSource) &GoldStar.Profile

prepare(signer: auth(BorrowValue) &Account) {
self.profile = signer.storage
.borrow<auth(GoldStar.UpdateSocials, GoldStar.UpdateDeployedContracts, GoldStar.UpdateReferralSource) &GoldStar.Profile>(from: GoldStar.profileStoragePath)
.borrow<auth(GoldStar.UpdateHandle, GoldStar.UpdateSocials, GoldStar.UpdateDeployedContracts, GoldStar.UpdateReferralSource) &GoldStar.Profile>(from: GoldStar.profileStoragePath)
?? panic("missing profile")
}

execute {
// Update the handle
self.profile.handle.update(newHandle: handle)

// Update the referral source
if let referralSource = referralSource {
self.profile.updateReferralSource(source: referralSource)
}
Expand Down
16 changes: 13 additions & 3 deletions src/components/ConnectButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,20 @@ const ConnectButton: React.FC = () => {
return (
<>
<Dropdown buttonLabel={displayAddress} items={dropdownItems} />
<ProgressModal isOpen={isProgressModalOpen} onClose={handleCloseProgressModal} />
<ProfileModal isOpen={isProfileModalOpen} onClose={handleCloseProfileModal} />
<ProgressModal
isOpen={isProgressModalOpen}
onClose={handleCloseProgressModal}
onOpenProfileModal={() => {
handleCloseProgressModal();
handleOpenProfileModal();
}}
/>
<ProfileModal
isOpen={isProfileModalOpen}
onClose={handleCloseProfileModal}
/>
</>
)
);
};

export default ConnectButton;
108 changes: 97 additions & 11 deletions src/components/ProfileModal.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import Input from '@site/src/ui/design-system/src/lib/Components/Input';
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 { 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';

interface ProfileModalProps {
Expand All @@ -21,10 +26,36 @@ const flowSources = [
];

const ProfileModal: React.FC<ProfileModalProps> = ({ isOpen, onClose }) => {
const [selectedSource, setSelectedSource] = useState(flowSources[0].name);
const { user } = useCurrentUser();
const {
profile,
isLoading,
error,
mutate: mutateProfile,
} = useProfile(user.addr);
const [settings, setSettings] = useState<ProfileSettings>({
handle: '',
socials: {},
referralSource: '',
deployedContracts: {},
});
const [loaded, setLoaded] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [tags, setTags] = useState<string[]>([]);
const [tagInput, setTagInput] = useState('');

useEffect(() => {
if (profile && !loaded && !isLoading && !error) {
setSettings({
handle: profile.handle,
socials: profile.socials,
referralSource: profile.referralSource,
deployedContracts: profile.deployedContracts,
});
setLoaded(true);
}
}, [profile, settings, loaded, isLoading, error]);

const handleAddTag = () => {
if (tagInput.trim() && !tags.includes(tagInput.trim())) {
setTags([...tags, tagInput.trim()]);
Expand All @@ -36,19 +67,66 @@ const ProfileModal: React.FC<ProfileModalProps> = ({ isOpen, onClose }) => {
setTags(tags.filter((tag) => tag !== tagToRemove));
};

async function handleSave() {
if (!settings) return;

setIsSaving(true);
try {
if (profile) {
await setProfile(settings);
} else {
await createProfile(settings);
}
} catch (e) {
console.error(e);
} finally {
setIsSaving(false);
mutateProfile();
}
}

function hasChanges() {
return (
!isEqual(profile?.handle, settings?.handle) ||
!isEqual(profile?.socials, settings?.socials) ||
!isEqual(profile?.referralSource, settings?.referralSource) ||
!isEqual(profile?.deployedContracts, settings?.deployedContracts)
);
}

return (
<Modal isOpen={isOpen} onClose={onClose} title="Profile">
<div className="space-y-6">
<div className="space-y-4">
<Field label="Username" description="What should we call you?">
<Input name="username" placeholder="johndoe" />
<Input
name="username"
placeholder="johndoe"
value={settings?.handle || ''}
onChange={(e) =>
setSettings({ ...settings, handle: e.target.value })
}
/>
</Field>
<Field label="Github Handle" description="What's your Github handle?">
<Input name="profile_handle" placeholder="joedoecodes" />
<Input
name="profile_handle"
placeholder="joedoecodes"
value={settings?.socials?.[SocialType.GITHUB] || ''}
onChange={(e) =>
setSettings({
...settings,
socials: { [SocialType.GITHUB]: e.target.value },
})
}
/>
</Field>
</div>

<Field label="Contracts Deployed" description="Add your contracts in the canonical format (A.0x123.Foobar).">
<Field
label="Contracts Deployed"
description="Add your contracts in the canonical format (A.0x123.Foobar)."
>
<div className="space-y-2">
<div className="flex items-center gap-2">
<Input
Expand Down Expand Up @@ -77,18 +155,26 @@ const ProfileModal: React.FC<ProfileModalProps> = ({ isOpen, onClose }) => {
<div className="max-w-sm mx-auto">
<RadioGroup
options={flowSources.map((source) => source.name)}
value={selectedSource}
onChange={setSelectedSource}
value={settings?.referralSource || ''}
onChange={(value) =>
setSettings({ ...settings, referralSource: value })
}
label="How did you find Flow?"
getDescription={(option) =>
flowSources.find((source) => source.name === option)?.description || ''
flowSources.find((source) => source.name === option)
?.description || ''
}
/>
</div>

<div className="flex justify-center">
<Button size="sm" className="w-full max-w-md" onClick={onClose}>
Close
<div className="flex flex-col space-y-2">
<Button
size="sm"
className="w-full max-w-md"
onClick={handleSave}
disabled={!hasChanges() || !settings || isSaving}
>
Save
</Button>
</div>
</div>
Expand Down
13 changes: 7 additions & 6 deletions src/components/ProgressModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ import { SocialType } from '../types/gold-star';
interface ProgressModalProps {
isOpen: boolean;
onClose: () => void;
onOpenProfileModal: () => void;
}

const ProgressModal: React.FC<ProgressModalProps> = ({ isOpen, onClose }) => {
const ProgressModal: React.FC<ProgressModalProps> = ({
isOpen,
onClose,
onOpenProfileModal,
}) => {
const user = useCurrentUser();
const { profile } = useProfile(user.user.addr);

Expand All @@ -38,10 +43,6 @@ const ProgressModal: React.FC<ProgressModalProps> = ({ isOpen, onClose }) => {
{ label: 'Complete first challenge', completed: true },
];

const onProfileAction = () => {
console.log('TODO: Profile action');
};

const onChallengeAction = () => {
console.log('TODO: Challenge action');
};
Expand All @@ -56,7 +57,7 @@ const ProgressModal: React.FC<ProgressModalProps> = ({ isOpen, onClose }) => {
<Button
size="sm"
className="w-full max-w-md"
onClick={onProfileAction}
onClick={onOpenProfileModal}
>
Update Profile to Complete Items
</Button>
Expand Down
34 changes: 30 additions & 4 deletions src/utils/gold-star.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,22 @@ export const createProfile = async (profile: ProfileSettings) => {
args: (arg, t) => [
arg(profile.handle, t.String),
arg(profile.referralSource, t.Optional(t.String)),
arg(profile.socials, t.Dictionary(t.String, t.String)),
arg(
profile.deployedContracts,
profile.socials
? Object.entries(profile.socials).map(([key, value]) => ({
key,
value,
}))
: [],
t.Dictionary(t.Address, t.String),
),
arg(
profile.deployedContracts
? Object.entries(profile.deployedContracts).map(([key, value]) => ({
key,
value,
}))
: [],
t.Dictionary(t.Address, t.Array(t.String)),
),
],
Expand All @@ -77,9 +90,22 @@ export const setProfile = async (profile: ProfileSettings) => {
args: (arg, t) => [
arg(profile.handle, t.String),
arg(profile.referralSource, t.Optional(t.String)),
arg(profile.socials, t.Dictionary(t.String, t.String)),
arg(
profile.deployedContracts,
profile.socials
? Object.entries(profile.socials).map(([key, value]) => ({
key,
value,
}))
: [],
t.Dictionary(t.Address, t.String),
),
arg(
profile.deployedContracts
? Object.entries(profile.deployedContracts).map(([key, value]) => ({
key,
value,
}))
: [],
t.Dictionary(t.Address, t.Array(t.String)),
),
],
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5113,6 +5113,11 @@
resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.11.1.tgz#34de04477dcf79e2ef6c8d23b41a3d81f9ebeaf5"
integrity sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg==

"@types/lodash@^4.17.14":
version "4.17.14"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.14.tgz#bafc053533f4cdc5fcc9635af46a963c1f3deaff"
integrity sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==

"@types/mdast@^3.0.0":
version "3.0.15"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.15.tgz#49c524a263f30ffa28b71ae282f813ed000ab9f5"
Expand Down Expand Up @@ -17086,11 +17091,6 @@ use-sync-external-store@^1.2.0, use-sync-external-store@^1.4.0:
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc"
integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==

use-sync-external-store@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc"
integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==

util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
Expand Down

0 comments on commit 0ffbc1c

Please sign in to comment.