misc: updates & shit

This commit is contained in:
Théo 2023-07-05 18:48:59 +02:00
parent e22c7dafa1
commit ad66016115
20 changed files with 2087 additions and 1303 deletions

View file

@ -1,4 +1,4 @@
import UserAuthForm from '@/ui/UserAuthForm'; import UserAuthForm from '@/components/ui/UserAuthForm';
export default function Page() { export default function Page() {
return ( return (

View file

@ -1,4 +1,4 @@
import UserAuthForm from '@/ui/UserAuthForm'; import UserAuthForm from '@/components/ui/UserAuthForm';
export default function Page() { export default function Page() {
return ( return (

View file

@ -4,7 +4,7 @@ import { useContext } from 'react';
import { UserContext } from '@/context/user'; import { UserContext } from '@/context/user';
import Badge from '@/ui/Badge'; import Badge from '@/components/ui/Badge';
export default function Page() { export default function Page() {
const { data: me } = useContext(UserContext); const { data: me } = useContext(UserContext);

View file

@ -1,7 +1,7 @@
import { type ReactNode } from 'react'; import { type ReactNode } from 'react';
import { UserProvider } from '@/context/user'; import { UserProvider } from '@/context/user';
import Wrapper from '@/ui/dashboard/Wrapper'; import Wrapper from '@/components/ui/dashboard/Wrapper';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
export default async function Layout({ children }: { children: ReactNode }) { export default async function Layout({ children }: { children: ReactNode }) {

View file

@ -1,4 +1,4 @@
import Leaderboard from '@/ui/Leaderboard'; import Leaderboard from '@/components/ui/Leaderboard';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
export const metadata = { export const metadata = {

View file

@ -2,8 +2,8 @@
import { useContext } from 'react'; import { useContext } from 'react';
import Card from '@/components/ui/Card';
import { UserContext } from '@/context/user'; import { UserContext } from '@/context/user';
import Card from '@/ui/Card';
export default function Page() { export default function Page() {
const { data: me, isLoading } = useContext(UserContext); const { data: me, isLoading } = useContext(UserContext);

View file

@ -1,6 +1,6 @@
import { getPuzzle } from '@/lib/puzzles'; import { getPuzzle } from '@/lib/puzzles';
import Puzzle from '@/ui/Puzzle'; import Puzzle from '@/components/ui/Puzzle';
import SWRFallback from '@/ui/SWRFallback'; import SWRFallback from '@/components/ui/SWRFallback';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';

View file

@ -1,7 +1,7 @@
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
import Puzzles from '@/ui/Puzzles'; import Puzzles from '@/components/ui/Puzzles';
import SWRFallback from '@/ui/SWRFallback'; import SWRFallback from '@/components/ui/SWRFallback';
import { getPuzzles } from '@/lib/puzzles'; import { getPuzzles } from '@/lib/puzzles';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';

View file

@ -1,5 +1,4 @@
import '@/styles/globals.css'; import '@/styles/global.css';
import 'remixicon/fonts/remixicon.css';
import { type Metadata } from 'next'; import { type Metadata } from 'next';
import { Fira_Code } from 'next/font/google'; import { Fira_Code } from 'next/font/google';
@ -8,10 +7,13 @@ import { type ReactNode } from 'react';
import { cn, getURL } from '@/lib/utils'; import { cn, getURL } from '@/lib/utils';
import { ThemeProvider } from '@/components/ThemeProvider';
const sans = localFont({ const sans = localFont({
variable: '--font-sans', variable: '--font-sans',
src: './fonts/Karrik.woff2', src: './fonts/Karrik.woff2',
weight: 'variable' weight: 'variable',
display: 'swap'
}); });
const code = Fira_Code({ const code = Fira_Code({
@ -28,26 +30,6 @@ export const metadata: Metadata = {
}, },
description: "Apprendre la programmation et la cybersécurité en s'amusant.", description: "Apprendre la programmation et la cybersécurité en s'amusant.",
// manifest: getURL('/favicon/site.webmanifest'), // manifest: getURL('/favicon/site.webmanifest'),
openGraph: {
title: {
default: 'Peer-at Code',
template: `%s - Peer-at Code`
},
description: "Apprendre la programmation et la cybersécurité en s'amusant.",
url: getURL(),
siteName: 'Peer-at Code',
// images: getURL('/assets/social.jpg'),
type: 'website'
},
twitter: {
card: 'summary_large_image',
title: {
default: 'Peer-at Code',
template: `%s - Peer-at Code`
},
description: "Apprendre la programmation et la cybersécurité en s'amusant."
// images: getURL('/assets/social.jpg'),
},
alternates: { alternates: {
canonical: getURL() canonical: getURL()
}, },
@ -74,14 +56,16 @@ export default function RootLayout({ children }: { children: ReactNode }) {
lang="fr" lang="fr"
dir="ltr" dir="ltr"
className={cn( className={cn(
'scroll-smooth bg-gradient-to-b from-primary-800 to-primary-900 [color-scheme:dark]', 'scroll-smooth bg-gradient-to-b from-primary-800 to-primary-900',
sans.variable, sans.variable,
code.variable code.variable
)} )}
> >
<head /> <head />
<body className="relative min-h-screen"> <body className="relative min-h-screen">
<main>{children}</main> <ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
<main>{children}</main>
</ThemeProvider>
</body> </body>
</html> </html>
); );

View file

@ -1,5 +1,5 @@
import AppLink from '@/ui/AppLink'; import AppLink from '@/components/ui/AppLink';
import Console from '@/ui/Console'; import Console from '@/components/ui/Console';
import Image from 'next/image'; import Image from 'next/image';
export default function Page() { export default function Page() {

15
components.json Normal file
View file

@ -0,0 +1,15 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "styles/global.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

187
lib/hooks/use-toast.ts Normal file
View file

@ -0,0 +1,187 @@
// Inspired by react-hot-toast library
import * as React from 'react';
import type { ToastActionElement, ToastProps } from '@/components/ui/Toast';
const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast = ToastProps & {
id: string;
title?: React.ReactNode;
description?: React.ReactNode;
action?: ToastActionElement;
};
const actionTypes = {
ADD_TOAST: 'ADD_TOAST',
UPDATE_TOAST: 'UPDATE_TOAST',
DISMISS_TOAST: 'DISMISS_TOAST',
REMOVE_TOAST: 'REMOVE_TOAST'
} as const;
let count = 0;
function genId() {
count = (count + 1) % Number.MAX_VALUE;
return count.toString();
}
type ActionType = typeof actionTypes;
type Action =
| {
type: ActionType['ADD_TOAST'];
toast: ToasterToast;
}
| {
type: ActionType['UPDATE_TOAST'];
toast: Partial<ToasterToast>;
}
| {
type: ActionType['DISMISS_TOAST'];
toastId?: ToasterToast['id'];
}
| {
type: ActionType['REMOVE_TOAST'];
toastId?: ToasterToast['id'];
};
interface State {
toasts: ToasterToast[];
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return;
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId);
dispatch({
type: 'REMOVE_TOAST',
toastId: toastId
});
}, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout);
};
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'ADD_TOAST':
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT)
};
case 'UPDATE_TOAST':
return {
...state,
toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t))
};
case 'DISMISS_TOAST': {
const { toastId } = action;
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId);
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id);
});
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false
}
: t
)
};
}
case 'REMOVE_TOAST':
if (action.toastId === undefined) {
return {
...state,
toasts: []
};
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId)
};
}
};
const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [] };
function dispatch(action: Action) {
memoryState = reducer(memoryState, action);
listeners.forEach((listener) => {
listener(memoryState);
});
}
type Toast = Omit<ToasterToast, 'id'>;
function toast({ ...props }: Toast) {
const id = genId();
const update = (props: ToasterToast) =>
dispatch({
type: 'UPDATE_TOAST',
toast: { ...props, id }
});
const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id });
dispatch({
type: 'ADD_TOAST',
toast: {
...props,
id,
open: true,
onOpenChange: (open: any) => {
if (!open) dismiss();
}
}
});
return {
id: id,
dismiss,
update
};
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState);
React.useEffect(() => {
listeners.push(setState);
return () => {
const index = listeners.indexOf(setState);
if (index > -1) {
listeners.splice(index, 1);
}
};
}, [state]);
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId })
};
}
export { useToast, toast };

View file

@ -1,3 +1,6 @@
import { Icons, type Icon } from '@/components/ui/Icon';
import { ReactNode } from 'react';
/** /**
* A navigation item. * A navigation item.
* *

View file

@ -29,7 +29,7 @@ export const getPlayer = async ({
export type Player = { export type Player = {
email: string; email: string;
pseudo: string; pseudo: string;
firstnames: string; firstname: string;
lastname: string; lastname: string;
description: string; description: string;
avatar: string; avatar: string;

View file

@ -2,9 +2,6 @@
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
swcMinify: true, swcMinify: true,
experimental: {
scrollRestoration: true
},
redirects: async () => { redirects: async () => {
return [ return [
{ {

View file

@ -20,25 +20,34 @@
}, },
"homepage": "https://github.com/Peer-at-Code/peer-at-code#readme", "homepage": "https://github.com/Peer-at-Code/peer-at-code#readme",
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.1.0",
"@radix-ui/react-dialog": "^1.0.3", "@radix-ui/react-dialog": "^1.0.3",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.5", "@radix-ui/react-popover": "^1.0.5",
"@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.4",
"boring-avatars": "^1.7.0", "boring-avatars": "^1.7.0",
"class-variance-authority": "^0.6.0",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"edge-csrf": "^1.0.3", "edge-csrf": "^1.0.3",
"framer-motion": "^10.12.4", "framer-motion": "^10.12.4",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"next": "13.4.0", "lucide-react": "^0.252.0",
"next": "13.4.8",
"next-themes": "^0.2.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "^7.43.1", "react-hook-form": "^7.44.2",
"react-markdown": "^8.0.5", "react-markdown": "^8.0.5",
"remark-breaks": "^3.0.2", "remark-breaks": "^3.0.2",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"remixicon": "^2.5.0", "remixicon": "^3.3.0",
"sharp": "^0.32.1", "sharp": "^0.32.1",
"swr": "^2.0.3", "swr": "^2.0.3",
"tailwind-merge": "^1.9.0", "tailwind-merge": "^1.12.0",
"zod": "^3.20.2" "tailwindcss-animate": "^1.0.5",
"zod": "^3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",

2918
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

93
styles/global.css Normal file
View file

@ -0,0 +1,93 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--ring: 215 20.2% 65.1%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--ring: 217.2 32.6% 17.5%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply text-foreground;
}
}
@layer components {
.console {
@apply relative top-0.5 inline-block;
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:focus {
-webkit-box-shadow: 0 0 0px 1000px hsl(258deg 15% 17%) inset;
transition: background-color 5000s ease-in-out 0s;
}
}

View file

@ -1,18 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.console {
@apply relative top-0.5 inline-block;
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:focus {
-webkit-box-shadow: 0 0 0px 1000px hsl(258deg 15% 17%) inset;
transition: background-color 5000s ease-in-out 0s;
}
}

View file

@ -3,19 +3,60 @@ const defaultTheme = require('tailwindcss/defaultTheme');
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
mode: 'jit', darkMode: ['class'],
content: ['./app/**/*.{js,ts,jsx,tsx}', './ui/**/*.{js,ts,jsx,tsx}'], content: [
future: { './pages/**/*.{ts,tsx}',
hoverOnlyWhenSupported: true './components/**/*.{ts,tsx}',
}, './app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}'
],
theme: { theme: {
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px'
}
},
extend: { extend: {
fontFamily: { fontFamily: {
sans: ['var(--font-sans)', ...defaultTheme.fontFamily.sans], sans: ['var(--font-sans)', ...defaultTheme.fontFamily.sans],
code: ['var(--font-code)', ...defaultTheme.fontFamily.serif] code: ['var(--font-code)', ...defaultTheme.fontFamily.serif]
}, },
colors: { colors: {
...require('tailwindcss/colors'), border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
primary: { primary: {
900: 'hsl(258deg 15% 7%)', 900: 'hsl(258deg 15% 7%)',
800: 'hsl(258deg 15% 11%)', 800: 'hsl(258deg 15% 11%)',
@ -83,8 +124,27 @@ module.exports = {
tertiary: 'hsl(258deg 8% 65%)', tertiary: 'hsl(258deg 8% 65%)',
secondaryAccent: '#e2e8f0', secondaryAccent: '#e2e8f0',
muted: 'hsl(258deg 7% 46%)' muted: 'hsl(258deg 7% 46%)'
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
keyframes: {
'accordion-down': {
from: { height: 0 },
to: { height: 'var(--radix-accordion-content-height)' }
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: 0 }
}
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
} }
} }
}, },
plugins: [require('@tailwindcss/forms')] plugins: [require('tailwindcss-animate'), require('tailwindcss'), require('autoprefixer')]
}; };