Merge remote-tracking branch 'origin/main' into feat/wallet

This commit is contained in:
zhigang1992
2023-12-12 22:06:09 +08:00
23 changed files with 529 additions and 353 deletions

View File

@@ -0,0 +1,12 @@
import styled from "styled-components";
import { Button } from "@radix-ui/themes";
export default styled(Button)`
display: inline-flex;
padding: 4px 25px;
flex-direction: column;
align-items: flex-start;
gap: 10px;
border-radius: 10px;
background: #000;
`;

View File

@@ -1,29 +1,45 @@
/// <reference types="vite-plugin-svgr/client" />
import styled from "styled-components";
import {FC} from "react";
import LogonIcon from './_/logo.svg?react'
import { FC, ReactNode } from "react";
export const StyledHeader = styled.div`
//display: flex;
width: 100vw;
background: #A9F868;;
const StyledHeaderName = styled.a`
color: #000;
font-size: 20px;
font-style: normal;
font-weight: 700;
line-height: 30px; /* 150% */
letter-spacing: 3px;
position: fixed;
top: 0;
padding: 29px 34px;
`
top: 40px;
left: 66px;
`;
export const Header: FC = () => {
return (
<StyledHeader>
<LogonIcon/>
</StyledHeader>
)
}
return <StyledHeaderName href={"/"}>STORYTIME</StyledHeaderName>;
};
export const PageContainer = styled.div`
background: #A9F868;
background: #a9f868;
display: flex;
width: 100vw;
min-height: 100vh;
`
padding: 39px 66px;
overflow: hidden;
`;
const StyledContentContainer = styled.div`
flex: 1;
max-width: 1280px;
margin: 0 auto;
`;
export const ContentContainer: FC<{
children: ReactNode;
className?: string;
}> = ({ children, className }) => {
return (
<StyledContentContainer className={className}>
<Header />
{children}
</StyledContentContainer>
);
};

View File

@@ -10,7 +10,7 @@ export function protectedRoute<T extends { user: User }>(component: FC<T>) {
return auth.onAuthStateChanged((user) => setUser(user));
}, []);
if (user === undefined) {
return <div>Loading...</div>;
return null;
}
if (user == null) {
return <Navigate to="/login" />;

View File

@@ -1,3 +1,7 @@
* {
box-sizing: border-box;
}
a {
font-weight: 500;
color: black;
@@ -6,14 +10,8 @@ a {
body {
margin: 0;
display: flex;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
body, div, dl, dt, dd, ul, ol, li, tr, td, th,
h1, h2, h3, h4, h5, h6, hr, br, img, table,

View File

@@ -1,38 +0,0 @@
import { useState } from "react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { Container, Heading, Button } from "@radix-ui/themes";
import { createCharacterType } from "../modules/character/actions";
const CHARACTER_BASE = ["cat", "human", "bear"];
const CharacterBase = () => {
const [characterBase, setCharacterBase] = useState("human");
const navigate = useNavigate();
const dispatch = useDispatch();
return (
<Container>
<Heading>Select Character Base</Heading>
<ul>
{CHARACTER_BASE.map((c) => (
<li key={c} onClick={() => setCharacterBase(c)}>
<img src="" alt={c} />
<Heading as="h4">{c}</Heading>
</li>
))}
</ul>
<Button
onClick={() => {
// update default character type
dispatch(createCharacterType({ characterType: characterBase }));
navigate("/canvas");
}}
>
Select Character
</Button>
</Container>
);
};
export default CharacterBase;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

View File

@@ -0,0 +1,134 @@
import { useState } from "react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import { createCharacterType } from "../../modules/character/actions";
import { Header, PageContainer } from "../../components/Layout/Layout";
import Button from "../../components/Button";
import character1 from "./_/1.png";
import character2 from "./_/2.png";
import character3 from "./_/3.png";
const CHARACTER_BASE = [
{
title: "Bear",
image: character1,
},
{
title: "Human",
image: character2,
},
{
title: "Dog",
image: character3,
},
];
const ContentContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 100%;
height: 100vh;
`;
const StyledTitle = styled.h1`
color: #000;
font-family: Inter Tight;
font-size: 50px;
font-style: normal;
font-weight: 600;
line-height: 60px; /* 120% */
letter-spacing: -1px;
margin-bottom: 76px;
`;
const StyledBaseContainer = styled.div`
display: flex;
justify-content: space-between;
gap: 64px;
padding: 0 20px;
margin-bottom: 64px;
`;
const StyledBaseItem = styled.div<{ $selected: boolean }>`
display: flex;
padding: 6px;
align-items: flex-start;
align-content: flex-start;
gap: 10px;
flex-wrap: wrap;
${(props) =>
props.$selected &&
`
border-radius: 5px;
background: #C9E7B0;
box-shadow: 0px 0px 25px 0px rgba(0, 0, 0, 0.50);
`}
`;
const StyledBaseItemTitle = styled.div`
color: #000;
font-size: 30px;
font-weight: 500;
line-height: 60px;
`;
const StyledBaseItemImage = styled.img`
max-width: 300px;
border-radius: 5px;
`;
const BaseItem: React.FC<{
selected: boolean;
title: string;
image: string;
onClick: () => void;
}> = (props) => {
return (
<div>
<StyledBaseItem $selected={props.selected} onClick={props.onClick}>
<StyledBaseItemImage src={props.image} alt="" />
</StyledBaseItem>
<StyledBaseItemTitle>{props.title}</StyledBaseItemTitle>
</div>
);
};
const CharacterBase = () => {
const [characterBase, setCharacterBase] = useState("Human");
const navigate = useNavigate();
const dispatch = useDispatch();
return (
<PageContainer>
<Header />
<ContentContainer>
<StyledTitle>Select Character Base</StyledTitle>
<StyledBaseContainer>
{CHARACTER_BASE.map((c) => (
<BaseItem
selected={characterBase === c.title}
key={c.title}
title={c.title}
image={c.image}
onClick={() => setCharacterBase(c.title)}
/>
))}
</StyledBaseContainer>
<Button
onClick={() => {
// update default character type
dispatch(createCharacterType({ characterType: characterBase }));
navigate("/canvas");
}}
>
Select Character
</Button>
</ContentContainer>
</PageContainer>
);
};
export default CharacterBase;

View File

@@ -1,55 +0,0 @@
import { db } from "../firebase.ts";
import { useEffect, useState } from "react";
import { Project } from "model";
import {
collection,
onSnapshot,
QueryDocumentSnapshot,
} from "firebase/firestore";
import { Link, useNavigate } from "react-router-dom";
import { User } from "firebase/auth";
import { Container, Button } from "@radix-ui/themes";
import { protectedRoute } from "../components/protectedRoute.tsx";
const DashboardPage = protectedRoute(({ user }: { user: User }) => {
const [projects, setProjects] = useState<QueryDocumentSnapshot<Project>[]>();
const navigate = useNavigate();
useEffect(() => {
onSnapshot(collection(db, `users/${user.uid}/projects`), (snapshot) => {
setProjects(
snapshot.docs.map((x) => x as QueryDocumentSnapshot<Project>)
);
});
}, [user.uid]);
if (projects == null) {
return <div>Loading...</div>;
}
if (projects.length === 0) {
return (
<Container>
Create Your own Adventure
<Button
onClick={() => {
navigate("/name");
}}
>
Get Started
</Button>
</Container>
);
}
return (
<div>
<h1>Dashboard</h1>
{projects.map((project) => (
<Link to={`/projects/${project.id}`}>
<h2>{project.data().name}</h2>
<p>{project.data().title}</p>
</Link>
))}
{projects.length === 0 && <Link to="/canvas">New Project</Link>}
</div>
);
});
export default DashboardPage;

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

View File

@@ -0,0 +1,243 @@
import { useEffect, useState } from "react";
import { Project } from "model";
import {
collection,
onSnapshot,
QueryDocumentSnapshot,
} from "firebase/firestore";
import { User } from "firebase/auth";
import { protectedRoute } from "../../components/protectedRoute";
import { db } from "../../firebase";
import {
ContentContainer,
PageContainer,
} from "../../components/Layout/Layout";
import styled from "styled-components";
import demoImage from "./_/demo.png";
import { Link } from "react-router-dom";
import { Button } from "@radix-ui/themes";
export const StyledContentContainer = styled(ContentContainer)`
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 32px;
`;
const StyledEmptyCard = styled.div`
width: 774px;
height: 420px;
flex-shrink: 0;
border-radius: 15px;
background: #fff;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
gap: 40px;
`;
const StyledEmptyText = styled.div`
color: #000;
text-align: center;
font-size: 50px;
font-weight: 600;
line-height: 60px;
letter-spacing: -1px;
`;
export const StyledCreateButton = styled(Button)`
color: #fff;
width: 149px;
height: 48px;
border-radius: 10px;
background: #000;
text-align: center;
cursor: pointer;
&:hover {
background: #363636;
}
`;
export const StyledSubButton = styled(Button)`
color: #fff;
font-weight: 600;
text-align: center;
border-radius: 10px;
background: #000;
cursor: pointer;
padding: 4px 25px;
&:hover {
background: #363636;
}
`;
export const StyledTitleContainer = styled.div`
width: 774px;
display: flex;
justify-content: space-between;
align-items: center;
`;
export const StyledStoryTitle = styled.p`
text-align: left;
font-size: 28px;
font-weight: 500;
line-height: 29px;
`;
export const StyledStoryContainer = styled.div`
display: flex;
align-items: center;
gap: 48px;
flex-direction: column;
`;
export const StyledStoryItem = styled.div`
padding: 26px;
display: flex;
gap: 34px;
border-radius: 15px;
background: #fff;
width: 774px;
position: relative;
`;
export const StyledStoryImage = styled.img`
width: 365.5px;
height: 365.5px;
`;
const StyledInfo = styled.div`
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 42px;
`;
const StyledMintTag = styled.div`
font-size: 12px;
font-weight: 500;
border-radius: 7.11px;
background: #a9f868;
padding: 7px 11px;
`;
const DateText = styled.span`
color: #000;
font-size: 12.012px;
font-weight: 600;
line-height: 16.18px;
`;
export const StyledStoryItemTitle = styled.p`
color: #000;
font-size: 28px;
font-weight: 500;
line-height: 29px;
width: 186px;
`;
export const StyledDescription = styled.p`
margin-top: 25px;
color: #000;
font-size: 12.012px;
font-weight: 500;
line-height: 16.18px;
`;
//TODO: check margin-top
const StyledLink = styled(Link)`
color: #000;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 14.93px;
text-decoration-line: underline;
margin-top: 80px;
`;
const StyledIndexTag = styled.div`
position: absolute;
padding: 14px 16px;
border-radius: 10px;
background: #fff;
left: -78px;
top: 0;
text-align: center;
font-size: 15px;
font-weight: 600;
line-height: 16.18px;
`;
const DashboardPage = protectedRoute(({ user }: { user: User }) => {
const [projects, setProjects] = useState<QueryDocumentSnapshot<Project>[]>();
useEffect(() => {
onSnapshot(collection(db, `users/${user.uid}/projects`), (snapshot) => {
setProjects(
snapshot.docs.map((x) => x as QueryDocumentSnapshot<Project>),
);
});
}, [user.uid]);
if (projects == null) {
return null;
}
if (projects.length === 0) {
return (
<PageContainer>
<StyledContentContainer>
<StyledTitleContainer>
<StyledStoryTitle>Your Stories</StyledStoryTitle>
</StyledTitleContainer>
<StyledEmptyCard>
<StyledEmptyText>Create your first story</StyledEmptyText>
<Link to="/name">
<StyledCreateButton>Create</StyledCreateButton>
</Link>
</StyledEmptyCard>
</StyledContentContainer>
</PageContainer>
);
}
return (
<PageContainer>
<StyledContentContainer>
<StyledTitleContainer>
<StyledStoryTitle>Your Stories</StyledStoryTitle>
<StyledSubButton>Create New Story</StyledSubButton>
</StyledTitleContainer>
<StyledStoryContainer>
{[
{
name: "The Journey to Dragons Keep",
image: demoImage,
},
{
name: "The Journey to Dragons Keep",
image: demoImage,
},
].map((story, idx) => (
<StyledStoryItem>
<StyledStoryImage src={story.image} alt="" />
<div>
<StyledInfo>
<StyledMintTag>Minted</StyledMintTag>
<DateText>12/12/2023</DateText>
</StyledInfo>
<StyledStoryItemTitle>{story.name}</StyledStoryItemTitle>
<StyledDescription>
A playful and bold collage of colors and shapes, blending a
retro feel with modern design principles to evoke creativity
and the joy of building something unique and impactful.
</StyledDescription>
<StyledLink>Read Story</StyledLink>
</div>
<StyledIndexTag>#{idx + 1}</StyledIndexTag>
</StyledStoryItem>
))}
</StyledStoryContainer>
</StyledContentContainer>
</PageContainer>
);
});
export default DashboardPage;

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

View File

@@ -1,85 +1,62 @@
import { FC, useEffect, useState } from "react";
import { Header, PageContainer } from "../../components/Layout/Layout";
import {
ContentContainer,
PageContainer,
} from "../../components/Layout/Layout";
import styled from "styled-components";
import step1 from "./_/step1.png";
import step2 from "./_/step2.png";
import step3 from "./_/step3.png";
import posterImage from "./_/poster.png";
import { useNavigate } from "react-router-dom";
import { useLocation } from "react-router";
import { upsertSalt, ZKLoginStore } from "../../components/zklogin.store.tsx";
import queryString from "query-string";
import { useNavigate } from "react-router-dom";
import { auth } from "../../firebase.ts";
const StyledTitle = styled.h1`
export const StyledContentContainer = styled(ContentContainer)`
display: flex;
align-items: center;
justify-content: center;
`;
const StyledTitle = styled.div`
color: #000;
font-size: 50px;
font-weight: 600;
line-height: 60px;
width: 604px;
letter-spacing: -1px;
margin-left: 34px;
margin-top: 72px;
`;
const StyledStepContainer = styled.div`
display: flex;
justify-content: space-between;
gap: 20px;
padding: 0 20px;
`;
const StyledStepItem = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
`;
const StyledStepItemTitle = styled.div`
color: #000;
font-size: 30px;
font-weight: 500;
line-height: 60px;
`;
const StyledStepItemImage = styled.img`
width: 100%;
border-radius: 5px;
`;
const StyledStepItemDescription = styled.div`
color: #000;
font-size: 18px;
font-weight: 500;
line-height: 20px;
`;
const StepItem: FC<{
title: string;
description: string;
image: string;
}> = (props) => {
return (
<StyledStepItem>
<StyledStepItemTitle>{props.title}</StyledStepItemTitle>
<StyledStepItemImage src={props.image} alt="" />
<StyledStepItemDescription>{props.description}</StyledStepItemDescription>
</StyledStepItem>
);
};
const StyledStartButton = styled.button`
padding: 4px 25px;
color: #fff;
width: 188px;
height: 48px;
line-height: 48px;
border-radius: 10px;
background: #000;
color: #fff;
text-align: center;
width: fit-content;
cursor: pointer;
position: absolute;
bottom: 40px;
&:hover {
background: #363636;
}
`;
const StyledStartButtonContainer = styled.div`
height: 416px;
display: flex;
margin-top: 140px;
justify-content: center;
background-image: url(${posterImage});
background-repeat: no-repeat;
position: relative;
`;
const StyledBox = styled.div`
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
gap: 40px;
`;
const LoginButton: FC = () => {
@@ -88,6 +65,8 @@ const LoginButton: FC = () => {
const [store, setStore] = useState<ZKLoginStore>(new ZKLoginStore());
const [loading, setLoading] = useState(false);
useEffect(() => {
(async () => {
const oauthParams = queryString.parse(location.hash);
@@ -103,6 +82,7 @@ const LoginButton: FC = () => {
id_token: oauthParams.id_token as string,
}),
);
setLoading(true);
const listener = auth.onAuthStateChanged((x) => {
if (x != null) {
listener();
@@ -123,7 +103,7 @@ const LoginButton: FC = () => {
}
}}
>
Get started
{loading ? "Loading..." : "Continue with Google"}
</StyledStartButton>
);
};
@@ -131,28 +111,17 @@ const LoginButton: FC = () => {
const HomePage: FC = () => {
return (
<PageContainer>
<Header />
<StyledTitle>Create your own Adventure</StyledTitle>
<StyledStepContainer>
<StepItem
title={"1.Create Character"}
image={step1}
description="Youve got two images, you need two words.Start working out that big brain"
/>
<StepItem
title={"2.Generate Story"}
image={step2}
description="Youve got two images, you need two words.Start working out that big brain"
/>
<StepItem
title={"3.Mint Your Story"}
image={step3}
description="Youve got two images, you need two words.Start working out that big brain"
/>
</StyledStepContainer>
<StyledStartButtonContainer>
<LoginButton />
</StyledStartButtonContainer>
<StyledContentContainer>
<StyledBox>
<StyledTitle>
Mint Your Story <br />
Create Your Own Adventure
</StyledTitle>
<StyledStartButtonContainer>
<LoginButton />
</StyledStartButtonContainer>
</StyledBox>
</StyledContentContainer>
</PageContainer>
);
};

View File

@@ -1,110 +0,0 @@
import { FC, useEffect, useState } from "react";
import { Header, PageContainer } from "../../components/Layout/Layout";
import styled from "styled-components";
import {
GoogleAuthProvider,
onAuthStateChanged,
signInWithPopup,
User,
} from "firebase/auth";
import { auth } from "../../firebase.ts";
import { Navigate } from "react-router";
export const StyledPageContainer = styled(PageContainer)`
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
`;
const StyledCard = styled.div`
width: 353px;
height: 433px;
border-radius: 10px;
background: #ddffc2;
display: flex;
flex-direction: column;
padding: 59px 35px;
align-items: center;
`;
const StyledCardTitle = styled.div`
color: #000;
font-family: N27;
font-size: 20px;
line-height: 24px;
text-align: center;
`;
const StyledDescritionTitle = styled.p`
color: #000;
text-align: center;
font-family: Inter Tight;
font-size: 30px;
font-weight: 500;
line-height: 60px;
margin-top: 74px;
`;
const StyledDescription = styled.p`
color: #000;
text-align: center;
font-family: Inter Tight;
font-size: 12px;
font-weight: 500;
line-height: 20px;
width: 237px;
`;
const StyleSubButton = styled.button`
color: #fff;
font-weight: 600;
text-align: center;
width: 283px;
padding: 10px 25px;
gap: 10px;
border-radius: 10px;
background: #000;
margin-top: 80px;
cursor: pointer;
`;
const LoginPage: FC = () => {
const [user, setUser] = useState<User | null>();
useEffect(() => {
onAuthStateChanged(auth, (user) => {
setUser(user);
});
}, []);
if (user === undefined) {
return <div>Loading...</div>;
}
if (user != null) {
return <Navigate to="/dashboard" />;
}
return (
<>
<Header />
<StyledPageContainer>
<StyledCard>
<StyledCardTitle>STORYTIME</StyledCardTitle>
<StyledDescritionTitle>Start Creating!</StyledDescritionTitle>
<StyledDescription>
Youve got two images, you need two words. Start working out that
big brain
</StyledDescription>
<StyleSubButton
onClick={async () => {
const provider = new GoogleAuthProvider();
await signInWithPopup(auth, provider);
}}
>
Continue with google
</StyleSubButton>
</StyledCard>
</StyledPageContainer>
</>
);
};
export default LoginPage;

View File

@@ -1,59 +1,65 @@
import { FC } from "react";
import { Header, PageContainer } from "../../components/Layout/Layout";
import { PageContainer } from "../../components/Layout/Layout";
import storyImg from "./_/story.png";
import styled from "styled-components";
import {
StyledContentContainer,
StyledDescription,
StyledStoryContainer,
StyledStoryImage,
StyledStoryItem,
StyledStoryItemTitle,
StyledStoryTitle,
StyledTitleContainer,
} from "../dashboard";
import { Button } from "@radix-ui/themes";
export const StyledPageContainer = styled(PageContainer)`
const StyledInfoContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 32px;
justify-content: center;
width: 100vw;
`;
const StyledMintTitle = styled.div`
color: #000;
text-align: center;
font-family: Inter;
font-size: 60px;
font-weight: 600;
line-height: 60px;
letter-spacing: -1px;
`;
const StyledMintSubTitle = styled.div`
color: #000;
font-family: Inter;
font-size: 25px;
font-weight: 600;
line-height: 24px;
`
const StyledMintImg = styled.img`
margin-top: 47px;
margin-bottom: 29px;
`
const StyledMintButton = styled.div`
padding: 4px 25px;
const StyledCreateButton = styled(Button)`
color: #fff;
width: 149px;
height: 32px;
border-radius: 10px;
background: #000;
text-align: center;
cursor: pointer;
color: #fff;
margin-top: 29px;
`
&:hover {
background: #363636;
}
`;
const MinStoryPage: FC = () => {
return (
<div>
<Header />
<StyledPageContainer>
<StyledMintTitle>Mint your story</StyledMintTitle>
<StyledMintImg src={storyImg} alt="" />
<StyledMintSubTitle>The Journey to Dragons Keep</StyledMintSubTitle>
<StyledMintButton>MINT STORY</StyledMintButton>
</StyledPageContainer>
</div>
<PageContainer>
<StyledContentContainer>
<StyledTitleContainer>
<StyledStoryTitle>Mint your story</StyledStoryTitle>
</StyledTitleContainer>
<StyledStoryContainer>
<StyledStoryItem>
<StyledStoryImage src={storyImg} alt="" />
<StyledInfoContainer>
<StyledStoryItemTitle>
The Journey to Dragons Keep
</StyledStoryItemTitle>
<StyledDescription>
A playful and bold collage of colors and shapes, blending a
retro feel with modern design principles to evoke creativity and
the joy of building something unique and impactful.
</StyledDescription>
<StyledCreateButton>Mint Story</StyledCreateButton>
</StyledInfoContainer>
</StyledStoryItem>
</StyledStoryContainer>
</StyledContentContainer>
</PageContainer>
);
};

View File

@@ -2,9 +2,10 @@ import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import styled from "styled-components";
import { Heading, TextField, Button } from "@radix-ui/themes";
import { TextField } from "@radix-ui/themes";
import { createCharacterName } from "../modules/character/actions";
import { Header, PageContainer } from "../components/Layout/Layout";
import Button from "../components/Button";
const ContentContainer = styled.div`
display: flex;
@@ -70,7 +71,7 @@ const Name = () => {
<StyledInputRoot>
<StyledInput
value={characterName}
onChange={(e) => setCharacterName(e.target.value)}
onChange={(e: any) => setCharacterName(e.target.value)}
placeholder="ENTER CHARACTER NAME"
/>
</StyledInputRoot>