diff --git a/.storybook/main.ts b/.storybook/main.ts
index 48d59b0..c6268fd 100644
--- a/.storybook/main.ts
+++ b/.storybook/main.ts
@@ -14,6 +14,7 @@ const config: StorybookConfig = {
name: '@storybook/nextjs',
options: {},
},
+ staticDirs: ['../public'],
};
export default config;
diff --git a/app/layout.tsx b/app/layout.tsx
index 76c7667..2420930 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from 'next';
import type { PropsWithChildren } from 'react';
+import { AppLayout } from '@/components/layout/app-layout';
import { ThemeProvider } from '@/components/theme-provider';
import '@/components/theme-provider/styles.css';
@@ -13,7 +14,9 @@ export default function RootLayout({ children }: PropsWithChildren) {
return (
- {children}
+
+ {children}
+
);
diff --git a/components/layout/app-content.tsx b/components/layout/app-content.tsx
new file mode 100644
index 0000000..e48a2a3
--- /dev/null
+++ b/components/layout/app-content.tsx
@@ -0,0 +1,17 @@
+import type { PropsWithChildren } from 'react';
+
+import { Container } from '@radix-ui/themes';
+
+export function AppContent(props: PropsWithChildren) {
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/components/layout/app-footer.stories.tsx b/components/layout/app-footer.stories.tsx
new file mode 100644
index 0000000..f9bb2d9
--- /dev/null
+++ b/components/layout/app-footer.stories.tsx
@@ -0,0 +1,13 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { AppFooter } from './app-footer';
+
+export const Default: StoryObj = {
+ render: () => ,
+};
+
+const meta: Meta = {
+ component: AppFooter,
+};
+
+export default meta;
diff --git a/components/layout/app-footer.tsx b/components/layout/app-footer.tsx
new file mode 100644
index 0000000..bb90d84
--- /dev/null
+++ b/components/layout/app-footer.tsx
@@ -0,0 +1,82 @@
+import { GitHubLogoIcon, TwitterLogoIcon } from '@radix-ui/react-icons';
+import { Flex, Link, Text } from '@radix-ui/themes';
+import React from 'react';
+
+import { AppContent } from './app-content';
+
+// @TODO: replace with real links: SUPA-27
+const FOOTER_SERVICE_LINKS = [
+ {
+ href: 'https://experiment.st',
+ label: 'Privacy policy',
+ },
+ {
+ href: 'https://experiment.st',
+ label: 'Terms of service',
+ },
+] as const;
+
+const FOOTER_SOCIAL_LINKS = [
+ {
+ href: 'https://github.com/experiment-station/beecast',
+ icon: ,
+ title: 'GitHub',
+ },
+ {
+ href: 'https://twitter.com/beecast_ai',
+ icon: ,
+ title: 'Twitter',
+ },
+] as const;
+
+export function AppFooter() {
+ return (
+
+
+
+
+ Built with ☕️ by your folks at the{' '}
+
+ Experiment Station
+
+
+
+
+ {FOOTER_SERVICE_LINKS.map((link) => (
+
+
+ {link.label}
+
+
+ {link !==
+ FOOTER_SERVICE_LINKS[FOOTER_SERVICE_LINKS.length - 1] ? (
+ ·
+ ) : null}
+
+ ))}
+
+
+
+
+
+ {FOOTER_SOCIAL_LINKS.map((link) => (
+
+ {link.icon}
+
+ ))}
+
+
+
+ ©2023 Experiment Station
+
+
+
+
+ );
+}
diff --git a/components/layout/app-header.stories.tsx b/components/layout/app-header.stories.tsx
new file mode 100644
index 0000000..b3d1379
--- /dev/null
+++ b/components/layout/app-header.stories.tsx
@@ -0,0 +1,26 @@
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { AppHeader } from './app-header';
+
+export const Guest: StoryObj = {
+ render: () => ,
+};
+
+export const Authenticated: StoryObj = {
+ render: () => (
+
+ ),
+};
+
+const meta: Meta = {
+ component: AppHeader,
+};
+
+export default meta;
diff --git a/components/layout/app-header.tsx b/components/layout/app-header.tsx
new file mode 100644
index 0000000..0d15cb8
--- /dev/null
+++ b/components/layout/app-header.tsx
@@ -0,0 +1,117 @@
+import { ArrowRightIcon, PersonIcon } from '@radix-ui/react-icons';
+import {
+ Avatar,
+ Button,
+ DropdownMenu,
+ Flex,
+ Heading,
+ Text,
+} from '@radix-ui/themes';
+import Image from 'next/image';
+import Link from 'next/link';
+
+import { AppContent } from './app-content';
+
+type Props =
+ | {
+ user: {
+ avatarURL?: string;
+ credits: number;
+ username: string;
+ };
+ variant: 'authenticated';
+ }
+ | {
+ variant: 'guest';
+ };
+
+function AppHeaderActionsAuthenticated(
+ props: Pick, 'user'>,
+) {
+ return (
+
+
+
+ {props.user.username}
+
+
+
+ {props.user.credits === 0 ? 'No' : props.user.credits} credits
+ remaining
+
+
+
+
+
+ }
+ radius="full"
+ src={props.user.avatarURL}
+ />
+
+
+
+ Buy credits
+ Settings
+
+ Sign out
+
+
+
+ );
+}
+
+function AppHeaderActionsGuest() {
+ return (
+
+
+
+
+
+ );
+}
+
+export function AppHeader(props: Props) {
+ return (
+
+
+
+
+
+
+
+
+ Beecast
+
+
+
+
+ {props.variant === 'authenticated' ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
diff --git a/components/layout/app-layout.tsx b/components/layout/app-layout.tsx
new file mode 100644
index 0000000..a66eaa4
--- /dev/null
+++ b/components/layout/app-layout.tsx
@@ -0,0 +1,32 @@
+import type { PropsWithChildren } from 'react';
+
+import { Box, Flex } from '@radix-ui/themes';
+
+import { AppContent } from './app-content';
+import { AppFooter } from './app-footer';
+import { AppHeader } from './app-header';
+
+export function AppLayout({ children }: PropsWithChildren) {
+ return (
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
diff --git a/components/theme-provider/radix-themes.provider.tsx b/components/theme-provider/radix-themes.provider.tsx
index b1072b6..e136bbe 100644
--- a/components/theme-provider/radix-themes.provider.tsx
+++ b/components/theme-provider/radix-themes.provider.tsx
@@ -1,12 +1,11 @@
import type { PropsWithChildren } from 'react';
-import { Theme, ThemePanel } from '@radix-ui/themes';
+import { Theme } from '@radix-ui/themes';
export function RadixThemesProvider({ children }: PropsWithChildren) {
return (
-
+
{children}
-
);
}
diff --git a/components/theme-provider/styles.css b/components/theme-provider/styles.css
index e544efd..a3de752 100644
--- a/components/theme-provider/styles.css
+++ b/components/theme-provider/styles.css
@@ -1 +1,5 @@
@import '@radix-ui/themes/styles.css';
+
+body {
+ margin: 0;
+}
diff --git a/package.json b/package.json
index 0c21a88..e6eef42 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
},
"prettier": "@vercel/style-guide/prettier",
"dependencies": {
+ "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/themes": "^2.0.2",
"next": "14.0.4",
"next-themes": "1.0.0-beta.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dc65a2d..caa3180 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false
dependencies:
+ '@radix-ui/react-icons':
+ specifier: ^1.3.0
+ version: 1.3.0(react@18.2.0)
'@radix-ui/themes':
specifier: ^2.0.2
version: 2.0.2(@types/react-dom@18.2.17)(@types/react@18.2.43)(react-dom@18.2.0)(react@18.2.0)
@@ -2709,6 +2712,14 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-icons@1.3.0(react@18.2.0):
+ resolution: {integrity: sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==}
+ peerDependencies:
+ react: ^16.x || ^17.x || ^18.x
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/@radix-ui/react-id@1.0.1(@types/react@18.2.43)(react@18.2.0):
resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==}
peerDependencies:
diff --git a/public/beecast.png b/public/beecast.png
new file mode 100644
index 0000000..d15e237
Binary files /dev/null and b/public/beecast.png differ
diff --git a/public/next.svg b/public/next.svg
deleted file mode 100644
index 5174b28..0000000
--- a/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/vercel.svg b/public/vercel.svg
deleted file mode 100644
index d2f8422..0000000
--- a/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file