Skip to content
Draft
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
108 changes: 85 additions & 23 deletions app/src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { Anchor, Box, Container, Group, SimpleGrid, Stack, Text } from '@mantine/core';
import type { CountryId } from '@/api/report';
import FooterSubscribe from '@/components/FooterSubscribe';
import { colors, spacing, typography } from '@/designTokens';
import { spacing, typography } from '@/designTokens';
import { useCurrentCountry } from '@/hooks/useCurrentCountry';

const PolicyEngineLogo = '/assets/logos/policyengine/white.svg';
Expand All @@ -20,8 +20,6 @@ const getContactLinks = (countryId: CountryId) => ({
donate: `/${countryId}/donate`,
privacy: `/${countryId}/privacy`,
terms: `/${countryId}/terms`,
// TODO: Add developer-tools page once it's built out
// developerTools: `/${countryId}/developer-tools`,
});

const SOCIAL_LINKS = [
Expand All @@ -37,13 +35,34 @@ const SOCIAL_LINKS = [
export default function Footer() {
const countryId = useCurrentCountry();
const CONTACT_LINKS = getContactLinks(countryId);

return (
<Box
component="footer"
w="100%"
style={{ backgroundColor: colors.primary[900], padding: '3rem 4rem' }}
style={{
background: 'linear-gradient(180deg, #0d2b2a 0%, #0a1f1e 100%)',
padding: '3rem 4rem',
position: 'relative',
overflow: 'hidden',
}}
>
<Container size="2xl">
{/* Decorative gradient orb */}
<Box
style={{
position: 'absolute',
top: '-50%',
right: '-10%',
width: '400px',
height: '400px',
background: 'radial-gradient(circle, rgba(79, 209, 197, 0.08) 0%, transparent 70%)',
borderRadius: '50%',
filter: 'blur(60px)',
pointerEvents: 'none',
}}
/>

<Container size="2xl" style={{ position: 'relative', zIndex: 1 }}>
<img
src={PolicyEngineLogo}
alt="PolicyEngine"
Expand All @@ -58,62 +77,105 @@ export default function Footer() {
<Stack gap="xs">
<Anchor
href={CONTACT_LINKS.about}
c={colors.white}
fz="md"
underline="never"
ff={typography.fontFamily.primary}
style={{
color: 'rgba(255, 255, 255, 0.8)',
transition: 'color 0.2s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = '#4FD1C5';
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.8)';
}}
>
About us
</Anchor>
<Anchor
href={CONTACT_LINKS.donate}
c={colors.white}
fz="md"
underline="never"
ff={typography.fontFamily.primary}
style={{
color: 'rgba(255, 255, 255, 0.8)',
transition: 'color 0.2s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = '#4FD1C5';
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.8)';
}}
>
Donate
</Anchor>
<Anchor
href={CONTACT_LINKS.privacy}
c={colors.white}
fz="md"
underline="never"
ff={typography.fontFamily.primary}
style={{
color: 'rgba(255, 255, 255, 0.8)',
transition: 'color 0.2s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = '#4FD1C5';
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.8)';
}}
>
Privacy policy
</Anchor>
<Anchor
href={CONTACT_LINKS.terms}
c={colors.white}
fz="md"
underline="never"
ff={typography.fontFamily.primary}
style={{
color: 'rgba(255, 255, 255, 0.8)',
transition: 'color 0.2s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = '#4FD1C5';
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.8)';
}}
>
Terms and conditions
</Anchor>
{/* TODO: Uncomment when developer-tools page is built
<Anchor
href={CONTACT_LINKS.developerTools}
c={colors.white}
fz="md"
underline="never"
ff={typography.fontFamily.primary}
>
Developer tools
</Anchor>
*/}
</Stack>

<Stack gap="md">
<Group gap="md">
{SOCIAL_LINKS.map(({ icon: Icon, href }, index) => (
<Anchor key={index} href={href} target="_blank">
<Icon size={24} color={colors.white} />
<Anchor
key={index}
href={href}
target="_blank"
style={{
color: 'rgba(255, 255, 255, 0.6)',
transition: 'color 0.2s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = '#4FD1C5';
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = 'rgba(255, 255, 255, 0.6)';
}}
>
<Icon size={24} />
</Anchor>
))}
</Group>
<Text fz="xs" c={colors.white} ff={typography.fontFamily.primary}>
<Text
fz="xs"
ff={typography.fontFamily.primary}
style={{ color: 'rgba(255, 255, 255, 0.5)' }}
>
© {new Date().getFullYear()} PolicyEngine. All rights reserved.
</Text>
</Stack>
Expand Down
41 changes: 37 additions & 4 deletions app/src/components/FooterSubscribe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,19 @@ export default function FooterSubscribe() {

return (
<Stack gap="xs" pl="2xl">
<Text fw={600} fz="h2" c={colors.white} ff={typography.fontFamily.primary}>
<Text
fw={600}
fz="h2"
ff={typography.fontFamily.primary}
style={{ color: 'rgba(255, 255, 255, 0.95)' }}
>
Subscribe to PolicyEngine
</Text>
<Text fz="h5" c={colors.white} ff={typography.fontFamily.primary}>
<Text
fz="h5"
ff={typography.fontFamily.primary}
style={{ color: 'rgba(255, 255, 255, 0.7)' }}
>
Get the latest posts delivered right to your inbox.
</Text>
<Stack gap="sm" w="80%" mt="20px">
Expand All @@ -54,17 +63,41 @@ export default function FooterSubscribe() {
value={email}
onChange={(event) => setEmail(event.currentTarget.value)}
styles={{
input: { backgroundColor: colors.white, flex: 1 },
input: {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
border: '1px solid rgba(79, 209, 197, 0.3)',
color: '#ffffff',
'&::placeholder': {
color: 'rgba(255, 255, 255, 0.5)',
},
'&:focus': {
borderColor: '#4FD1C5',
},
},
}}
disabled={status === 'loading'}
/>
<Button
color={colors.primary[500]}
size="md"
ff={typography.fontFamily.primary}
onClick={handleSubscribe}
loading={status === 'loading'}
disabled={status === 'loading'}
style={{
background: 'linear-gradient(135deg, #4FD1C5 0%, #38B2AC 100%)',
color: '#0d2b2a',
fontWeight: 600,
border: 'none',
transition: 'all 0.3s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-1px)';
e.currentTarget.style.boxShadow = '0 4px 20px rgba(79, 209, 197, 0.3)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = 'none';
}}
>
SUBSCRIBE
</Button>
Expand Down
17 changes: 12 additions & 5 deletions app/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { useLocation, useNavigate } from 'react-router-dom';
import { Box, Button, Stack } from '@mantine/core';
import { useCurrentCountry } from '@/hooks/useCurrentCountry';
import { colors, spacing, typography } from '../designTokens';
import { spacing, typography } from '../designTokens';
import SidebarDivider from './sidebar/SidebarDivider';
import SidebarNavItem from './sidebar/SidebarNavItem';
import SidebarSection from './sidebar/SidebarSection';
Expand Down Expand Up @@ -81,9 +81,9 @@ export default function Sidebar({ isOpen = true }: SidebarProps) {
return (
<Stack
h="100vh"
bg="white"
style={{
borderRight: `1px solid ${colors.border.light}`,
background: 'linear-gradient(180deg, #0d2b2a 0%, #0a1f1e 100%)',
borderRight: '1px solid rgba(79, 209, 197, 0.1)',
width: parseInt(spacing.appShell.navbar.width, 10),
left: 0,
top: 0,
Expand All @@ -101,9 +101,16 @@ export default function Sidebar({ isOpen = true }: SidebarProps) {
h={36}
styles={{
root: {
backgroundColor: colors.primary[600],
background: 'linear-gradient(135deg, #4FD1C5 0%, #38B2AC 100%)',
color: '#0d2b2a',
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.medium,
fontWeight: typography.fontWeight.semibold,
border: 'none',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-1px)',
boxShadow: '0 4px 20px rgba(79, 209, 197, 0.3)',
},
},
}}
onClick={() => navigate(`/${countryId}/reports/create`)}
Expand Down
27 changes: 22 additions & 5 deletions app/src/components/blog/BlogPostCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,24 @@ export function BlogPostCard({ item, countryId }: BlogPostCardProps) {
const cardContent = (
<Box
style={{
border: `1px solid ${colors.gray[300]}`,
border: '1px solid rgba(79, 209, 197, 0.2)',
borderRadius: spacing.radius.md,
overflow: 'hidden',
backgroundColor: colors.white,
height: '100%',
display: 'flex',
flexDirection: 'column',
transition: 'box-shadow 0.2s ease',
transition: 'all 0.3s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.boxShadow = `0 4px 12px ${colors.gray[300]}`;
e.currentTarget.style.boxShadow = '0 8px 30px rgba(79, 209, 197, 0.15)';
e.currentTarget.style.transform = 'translateY(-4px)';
e.currentTarget.style.borderColor = 'rgba(79, 209, 197, 0.4)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.boxShadow = 'none';
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.borderColor = 'rgba(79, 209, 197, 0.2)';
}}
>
{/* Image */}
Expand All @@ -83,6 +87,7 @@ export function BlogPostCard({ item, countryId }: BlogPostCardProps) {
width: '100%',
height: '100%',
objectFit: 'cover',
transition: 'transform 0.3s ease',
}}
onError={(e) => {
// Hide broken images
Expand All @@ -105,7 +110,18 @@ export function BlogPostCard({ item, countryId }: BlogPostCardProps) {
<Group justify="space-between" mb="xs">
<Group gap="xs">
{displayTags.map((tag) => (
<Badge key={tag} size="xs" variant="light" color={item.isApp ? 'teal' : 'blue'}>
<Badge
key={tag}
size="xs"
variant="light"
style={{
backgroundColor: item.isApp
? 'rgba(79, 209, 197, 0.1)'
: 'rgba(79, 209, 197, 0.1)',
color: '#0d9488',
border: '1px solid rgba(79, 209, 197, 0.2)',
}}
>
{tag}
</Badge>
))}
Expand All @@ -130,8 +146,9 @@ export function BlogPostCard({ item, countryId }: BlogPostCardProps) {
size="sm"
mt="sm"
style={{
color: colors.primary[600],
color: '#0d9488',
textAlign: 'right',
fontWeight: 500,
}}
>
{item.isApp ? 'Open →' : 'Read →'}
Expand Down
23 changes: 19 additions & 4 deletions app/src/components/common/EmptyState.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Stack, Text } from '@mantine/core';
import { Box, Stack, Text } from '@mantine/core';

interface EmptyStateProps {
ingredient: string; // e.g., "Policy"
Expand All @@ -8,11 +8,26 @@ interface EmptyStateProps {
export default function EmptyState({ ingredient }: EmptyStateProps) {
return (
<Stack align="center" justify="center" p="xl">
<Text size="lg" c="dimmed">
<Box
style={{
width: '80px',
height: '80px',
borderRadius: '50%',
background: 'linear-gradient(135deg, rgba(79, 209, 197, 0.1) 0%, rgba(79, 209, 197, 0.05) 100%)',
border: '1px solid rgba(79, 209, 197, 0.2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginBottom: '16px',
}}
>
<Text size="xl" style={{ color: '#4FD1C5' }}>
?
</Text>
</Box>
<Text size="lg" style={{ color: 'rgba(13, 43, 42, 0.5)' }}>
No {ingredient.toLowerCase()} found.
{/* <br/>Please create {ingredient} to get started. */}
</Text>
{/* <Button onClick={onAction}>Create {ingredient}</Button> */}
</Stack>
);
}
Loading
Loading