custom frontend
This commit is contained in:
parent
783b4a78e6
commit
54dad9b187
|
@ -128,6 +128,28 @@ services:
|
||||||
shared:
|
shared:
|
||||||
ipv4_address: 10.10.10.82
|
ipv4_address: 10.10.10.82
|
||||||
|
|
||||||
|
dashboard:
|
||||||
|
build:
|
||||||
|
context: ./packages/dashboard
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: dashboard
|
||||||
|
restart: unless-stopped
|
||||||
|
logging: *default-logging
|
||||||
|
environment:
|
||||||
|
- SECURITY_MODE=jwks
|
||||||
|
- PROJECT_NAME=Skynet
|
||||||
|
- BASE_URL=/
|
||||||
|
- KRATOS_BROWSER_URL=/.ory/kratos/public
|
||||||
|
- JWKS_URL=http://oathkeeper:4456/.well-known/jwks.json
|
||||||
|
- KRATOS_PUBLIC_URL=http://kratos:4433/
|
||||||
|
- KRATOS_ADMIN_URL=http://kratos:4434/
|
||||||
|
- SQA_OPT_OUT=true
|
||||||
|
networks:
|
||||||
|
shared:
|
||||||
|
ipv4_address: 10.10.10.83
|
||||||
|
expose:
|
||||||
|
- 3000
|
||||||
|
|
||||||
oathkeeper:
|
oathkeeper:
|
||||||
image: oryd/oathkeeper:v0.38
|
image: oryd/oathkeeper:v0.38
|
||||||
container_name: oathkeeper
|
container_name: oathkeeper
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
mutators:
|
mutators:
|
||||||
- handler: noop
|
- handler: noop
|
||||||
|
|
||||||
- id: "ory:kratos-selfservice-ui-node:anonymous"
|
- id: "dashboard:anonymous"
|
||||||
upstream:
|
upstream:
|
||||||
preserve_host: true
|
preserve_host: true
|
||||||
url: "http://kratos-selfservice-ui-node:4435"
|
url: "http://dashboard:3000"
|
||||||
match:
|
match:
|
||||||
url: "http://oathkeeper:4455/<{error,recovery,verify,auth/*,**.css,**.js}{/,}>"
|
url: "http://oathkeeper:4455/<{error,recovery,verify,auth/*,**.css,**.js}{/,}>"
|
||||||
methods:
|
methods:
|
||||||
|
@ -33,12 +33,12 @@
|
||||||
mutators:
|
mutators:
|
||||||
- handler: noop
|
- handler: noop
|
||||||
|
|
||||||
- id: "ory:kratos-selfservice-ui-node:protected"
|
- id: "dashboard:protected"
|
||||||
upstream:
|
upstream:
|
||||||
preserve_host: true
|
preserve_host: true
|
||||||
url: "http://kratos-selfservice-ui-node:4435"
|
url: "http://dashboard:3000"
|
||||||
match:
|
match:
|
||||||
url: "http://oathkeeper:4455/<{,debug,dashboard,settings}>"
|
url: "http://oathkeeper:4455/<{,uploads,downloads,payments}>"
|
||||||
methods:
|
methods:
|
||||||
- GET
|
- GET
|
||||||
authenticators:
|
authenticators:
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
.next
|
|
@ -0,0 +1,3 @@
|
||||||
|
.next
|
||||||
|
package.json
|
||||||
|
package-lock.json
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"printWidth": 120
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
FROM node:14.15.0-alpine
|
||||||
|
|
||||||
|
WORKDIR /usr/app
|
||||||
|
|
||||||
|
COPY pages ./pages
|
||||||
|
COPY public ./public
|
||||||
|
COPY src ./src
|
||||||
|
COPY styles ./styles
|
||||||
|
COPY package.json .
|
||||||
|
COPY postcss.config.js .
|
||||||
|
COPY tailwind.config.js .
|
||||||
|
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
RUN yarn --no-lockfile && yarn build
|
||||||
|
CMD ["yarn", "start"]
|
|
@ -0,0 +1,34 @@
|
||||||
|
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
|
||||||
|
|
||||||
|
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "dashboard",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ory/kratos-client": "^0.5.4-alpha.1",
|
||||||
|
"autoprefixer": "^10.2.4",
|
||||||
|
"next": "^10.0.6",
|
||||||
|
"postcss": "^8.2.4",
|
||||||
|
"prettier": "^2.2.1",
|
||||||
|
"react": "17.0.1",
|
||||||
|
"react-dom": "17.0.1",
|
||||||
|
"swr": "^0.4.1",
|
||||||
|
"tailwindcss": "^2.0.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import "tailwindcss/tailwind.css";
|
||||||
|
|
||||||
|
function MyApp({ Component, pageProps }) {
|
||||||
|
return <Component {...pageProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MyApp;
|
|
@ -0,0 +1,6 @@
|
||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
|
||||||
|
export default (req, res) => {
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.json({ name: "John Doe" });
|
||||||
|
};
|
|
@ -0,0 +1,148 @@
|
||||||
|
import Link from "next/link";
|
||||||
|
import { Configuration, PublicApi } from "@ory/kratos-client";
|
||||||
|
import config from "../../src/config";
|
||||||
|
|
||||||
|
const kratos = new PublicApi(new Configuration({ basePath: config.kratos.public }));
|
||||||
|
|
||||||
|
export async function getServerSideProps(context) {
|
||||||
|
const flow = context.query.flow;
|
||||||
|
|
||||||
|
// The flow is used to identify the login and registration flow and
|
||||||
|
// return data like the csrf_token and so on.
|
||||||
|
if (!flow || typeof flow !== "string") {
|
||||||
|
console.log("No flow ID found in URL, initializing login flow.");
|
||||||
|
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: `${config.kratos.browser}/self-service/login/browser`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, data } = await kratos.getSelfServiceLoginFlow(flow);
|
||||||
|
|
||||||
|
if (status === 200) return { props: { flow: data } };
|
||||||
|
|
||||||
|
throw new Error(`Failed to retrieve flow ${flow} with code ${status}`);
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: `${config.kratos.browser}/self-service/login/browser`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldProps = {
|
||||||
|
identifier: {
|
||||||
|
label: "Email address",
|
||||||
|
autoComplete: "email",
|
||||||
|
position: 0,
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
label: "Password",
|
||||||
|
autoComplete: "current-password",
|
||||||
|
position: 1,
|
||||||
|
},
|
||||||
|
csrf_token: {
|
||||||
|
position: 99,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Login({ flow }) {
|
||||||
|
const fields = flow.methods.password.config.fields
|
||||||
|
.map((field) => ({
|
||||||
|
...field,
|
||||||
|
...fieldProps[field.name],
|
||||||
|
}))
|
||||||
|
.sort((a, b) => (a.position < b.position ? -1 : 1));
|
||||||
|
|
||||||
|
console.log(flow);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||||
|
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="169"
|
||||||
|
height="39"
|
||||||
|
viewBox="0 0 169 39"
|
||||||
|
className="mx-auto h-12 w-auto"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M160.701 31.245c-2.736 0-4.788-.706-6.156-2.118-1.369-1.411-2.053-3.424-2.053-6.039V2.674h7.487v6.33H169v6.15h-9.02v7.355c0 1.753.886 2.63 2.66 2.63h6.134v6.106h-8.073zm-24.942 0c-1.744 0-3.352-.268-4.826-.803-1.473-.535-2.751-1.292-3.833-2.273a10.275 10.275 0 01-2.526-3.521c-.602-1.367-.902-2.882-.902-4.546 0-1.635.3-3.135.902-4.502a10.275 10.275 0 012.526-3.521c1.082-.98 2.36-1.738 3.833-2.273 1.474-.535 3.082-.803 4.826-.803h5.728c1.293 0 2.42.179 3.383.535.962.357 1.767.847 2.413 1.471a5.823 5.823 0 011.443 2.251c.316.877.474 1.835.474 2.875 0 1.961-.571 3.432-1.714 4.412-1.143.981-3.022 1.471-5.638 1.471h-8.028v-4.056h6.45c1.172 0 1.759-.52 1.759-1.56 0-1.1-.572-1.649-1.714-1.649h-4.6c-1.354 0-2.451.46-3.293 1.382-.842.921-1.263 2.228-1.263 3.922s.42 2.964 1.263 3.811c.842.847 1.94 1.27 3.292 1.27h12.268v6.107H135.76zm-22.867 0V19.567c0-2.526-1.308-3.789-3.924-3.789h-7.532v15.467H93.86V9.003h15.29c3.728 0 6.54.922 8.434 2.764 1.894 1.842 2.841 4.457 2.841 7.844v11.634h-7.532zM67.293 39v-6.418h2.39c.963 0 1.715-.193 2.256-.58a4.58 4.58 0 001.308-1.426L62.422 9.003h7.623l6.675 14.263 6.855-14.263h7.668L78.749 33.43c-.48.95-.984 1.775-1.51 2.473a7.85 7.85 0 01-1.782 1.739 7.11 7.11 0 01-2.233 1.025c-.827.223-1.781.334-2.864.334h-3.067zm-14.207-7.755l-7.713-9.316a4.647 4.647 0 01-.54-.914 2.306 2.306 0 01-.181-.913v-.535c0-.654.225-1.278.676-1.872l7.487-8.692h8.48l-9.02 10.787 9.47 11.455h-8.66zm-17.41 0V0h7.532v31.245h-7.532zm-35.315 0v-7.488h21.92c.571 0 1.037-.171 1.398-.513.36-.342.541-.825.541-1.449 0-.624-.18-1.114-.541-1.47-.36-.357-.827-.535-1.398-.535H9.38c-1.353 0-2.608-.216-3.766-.647-1.157-.43-2.15-1.025-2.976-1.782a8.005 8.005 0 01-1.94-2.742C.233 13.55 0 12.361 0 11.054 0 9.746.233 8.55.7 7.466A8.184 8.184 0 012.638 4.68c.826-.773 1.819-1.367 2.976-1.783 1.158-.416 2.413-.624 3.766-.624h22.281v7.444H9.742c-.571 0-1.03.163-1.375.49-.346.327-.52.802-.52 1.426 0 .594.174 1.07.52 1.426.345.357.804.535 1.375.535h12.9c1.383 0 2.646.216 3.788.647a9.097 9.097 0 012.977 1.805 8.038 8.038 0 011.962 2.785c.466 1.085.7 2.266.7 3.544 0 1.337-.234 2.548-.7 3.632a8.267 8.267 0 01-1.962 2.808 8.62 8.62 0 01-2.977 1.806c-1.142.416-2.405.624-3.788.624H.36z"
|
||||||
|
fill="#57B560"
|
||||||
|
fillRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">Sign in to your account</h2>
|
||||||
|
<p className="mt-2 text-center text-sm text-gray-600 max-w">
|
||||||
|
or{" "}
|
||||||
|
<Link href="/auth/registration">
|
||||||
|
<a className="font-medium text-green-600 hover:text-green-500">sign up</a>
|
||||||
|
</Link>{" "}
|
||||||
|
if you don't have one yet
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
|
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||||
|
<form
|
||||||
|
className="space-y-6"
|
||||||
|
action={flow.methods.password.config.action}
|
||||||
|
method={flow.methods.password.config.method}
|
||||||
|
>
|
||||||
|
{fields.map((field) => (
|
||||||
|
<div key={field.name}>
|
||||||
|
{field.type !== "hidden" && (
|
||||||
|
<label htmlFor={field.name} className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
{fieldProps[field.name].label}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
type={field.type}
|
||||||
|
autoComplete={fieldProps[field.name]}
|
||||||
|
required={field.required}
|
||||||
|
value={field.value || undefined}
|
||||||
|
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-green-500 focus:border-green-500 sm:text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
{/* <div className="flex items-center">
|
||||||
|
<input
|
||||||
|
id="remember_me"
|
||||||
|
name="remember_me"
|
||||||
|
type="checkbox"
|
||||||
|
className="h-4 w-4 text-green-600 focus:ring-green-500 border-gray-300 rounded"
|
||||||
|
/>
|
||||||
|
<label htmlFor="remember_me" className="ml-2 block text-sm text-gray-900">
|
||||||
|
Remember me
|
||||||
|
</label>
|
||||||
|
</div> */}
|
||||||
|
<div className="text-sm">
|
||||||
|
<Link href="/auth/recovery">
|
||||||
|
<a className="font-medium text-green-600 hover:text-green-500">Forgot your password?</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
import Link from "next/link";
|
||||||
|
import { Configuration, PublicApi } from "@ory/kratos-client";
|
||||||
|
import config from "../../src/config";
|
||||||
|
|
||||||
|
const kratos = new PublicApi(new Configuration({ basePath: config.kratos.public }));
|
||||||
|
|
||||||
|
export async function getServerSideProps(context) {
|
||||||
|
const flow = context.query.flow;
|
||||||
|
|
||||||
|
// The flow is used to identify the login and registration flow and
|
||||||
|
// return data like the csrf_token and so on.
|
||||||
|
if (!flow || typeof flow !== "string") {
|
||||||
|
console.log("No flow ID found in URL, initializing recovery flow.");
|
||||||
|
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: `${config.kratos.browser}/self-service/recovery/browser`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, data } = await kratos.getSelfServiceRecoveryFlow(flow);
|
||||||
|
|
||||||
|
if (status === 200) return { props: { flow: data } };
|
||||||
|
|
||||||
|
throw new Error(`Failed to retrieve flow ${flow} with code ${status}`);
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: `${config.kratos.browser}/self-service/recovery/browser`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldProps = {
|
||||||
|
email: {
|
||||||
|
label: "Your email",
|
||||||
|
autoComplete: "email",
|
||||||
|
position: 0,
|
||||||
|
},
|
||||||
|
csrf_token: {
|
||||||
|
position: 99,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Registration({ flow }) {
|
||||||
|
const fields = flow.methods.link.config.fields
|
||||||
|
.map((field) => ({
|
||||||
|
...field,
|
||||||
|
...fieldProps[field.name],
|
||||||
|
}))
|
||||||
|
.sort((a, b) => (a.position < b.position ? -1 : 1));
|
||||||
|
|
||||||
|
console.log(flow);
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||||
|
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="169"
|
||||||
|
height="39"
|
||||||
|
viewBox="0 0 169 39"
|
||||||
|
className="mx-auto h-12 w-auto"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M160.701 31.245c-2.736 0-4.788-.706-6.156-2.118-1.369-1.411-2.053-3.424-2.053-6.039V2.674h7.487v6.33H169v6.15h-9.02v7.355c0 1.753.886 2.63 2.66 2.63h6.134v6.106h-8.073zm-24.942 0c-1.744 0-3.352-.268-4.826-.803-1.473-.535-2.751-1.292-3.833-2.273a10.275 10.275 0 01-2.526-3.521c-.602-1.367-.902-2.882-.902-4.546 0-1.635.3-3.135.902-4.502a10.275 10.275 0 012.526-3.521c1.082-.98 2.36-1.738 3.833-2.273 1.474-.535 3.082-.803 4.826-.803h5.728c1.293 0 2.42.179 3.383.535.962.357 1.767.847 2.413 1.471a5.823 5.823 0 011.443 2.251c.316.877.474 1.835.474 2.875 0 1.961-.571 3.432-1.714 4.412-1.143.981-3.022 1.471-5.638 1.471h-8.028v-4.056h6.45c1.172 0 1.759-.52 1.759-1.56 0-1.1-.572-1.649-1.714-1.649h-4.6c-1.354 0-2.451.46-3.293 1.382-.842.921-1.263 2.228-1.263 3.922s.42 2.964 1.263 3.811c.842.847 1.94 1.27 3.292 1.27h12.268v6.107H135.76zm-22.867 0V19.567c0-2.526-1.308-3.789-3.924-3.789h-7.532v15.467H93.86V9.003h15.29c3.728 0 6.54.922 8.434 2.764 1.894 1.842 2.841 4.457 2.841 7.844v11.634h-7.532zM67.293 39v-6.418h2.39c.963 0 1.715-.193 2.256-.58a4.58 4.58 0 001.308-1.426L62.422 9.003h7.623l6.675 14.263 6.855-14.263h7.668L78.749 33.43c-.48.95-.984 1.775-1.51 2.473a7.85 7.85 0 01-1.782 1.739 7.11 7.11 0 01-2.233 1.025c-.827.223-1.781.334-2.864.334h-3.067zm-14.207-7.755l-7.713-9.316a4.647 4.647 0 01-.54-.914 2.306 2.306 0 01-.181-.913v-.535c0-.654.225-1.278.676-1.872l7.487-8.692h8.48l-9.02 10.787 9.47 11.455h-8.66zm-17.41 0V0h7.532v31.245h-7.532zm-35.315 0v-7.488h21.92c.571 0 1.037-.171 1.398-.513.36-.342.541-.825.541-1.449 0-.624-.18-1.114-.541-1.47-.36-.357-.827-.535-1.398-.535H9.38c-1.353 0-2.608-.216-3.766-.647-1.157-.43-2.15-1.025-2.976-1.782a8.005 8.005 0 01-1.94-2.742C.233 13.55 0 12.361 0 11.054 0 9.746.233 8.55.7 7.466A8.184 8.184 0 012.638 4.68c.826-.773 1.819-1.367 2.976-1.783 1.158-.416 2.413-.624 3.766-.624h22.281v7.444H9.742c-.571 0-1.03.163-1.375.49-.346.327-.52.802-.52 1.426 0 .594.174 1.07.52 1.426.345.357.804.535 1.375.535h12.9c1.383 0 2.646.216 3.788.647a9.097 9.097 0 012.977 1.805 8.038 8.038 0 011.962 2.785c.466 1.085.7 2.266.7 3.544 0 1.337-.234 2.548-.7 3.632a8.267 8.267 0 01-1.962 2.808 8.62 8.62 0 01-2.977 1.806c-1.142.416-2.405.624-3.788.624H.36z"
|
||||||
|
fill="#57B560"
|
||||||
|
fillRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">Recover your account</h2>
|
||||||
|
<p className="mt-2 text-center text-sm text-gray-600 max-w">
|
||||||
|
or{" "}
|
||||||
|
<Link href="/auth/login">
|
||||||
|
<a className="font-medium text-green-600 hover:text-green-500">sign in</a>
|
||||||
|
</Link>{" "}
|
||||||
|
if you suddenly remembered your password
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
|
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||||
|
<form className="space-y-6" action={flow.methods.link.config.action} method={flow.methods.link.config.method}>
|
||||||
|
{fields.map((field) => (
|
||||||
|
<div key={field.name}>
|
||||||
|
{field.type !== "hidden" && (
|
||||||
|
<label htmlFor={field.name} className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
{fieldProps[field.name]?.label ?? field.name}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
type={field.type}
|
||||||
|
autoComplete={fieldProps[field.name]}
|
||||||
|
required={field.required}
|
||||||
|
value={field.value || undefined}
|
||||||
|
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-green-500 focus:border-green-500 sm:text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
|
||||||
|
>
|
||||||
|
Send recovery link
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
import Link from "next/link";
|
||||||
|
import { Configuration, PublicApi } from "@ory/kratos-client";
|
||||||
|
import config from "../../src/config";
|
||||||
|
|
||||||
|
const kratos = new PublicApi(new Configuration({ basePath: config.kratos.public }));
|
||||||
|
|
||||||
|
export async function getServerSideProps(context) {
|
||||||
|
const flow = context.query.flow;
|
||||||
|
|
||||||
|
// The flow is used to identify the login and registration flow and
|
||||||
|
// return data like the csrf_token and so on.
|
||||||
|
if (!flow || typeof flow !== "string") {
|
||||||
|
console.log("No flow ID found in URL, initializing registration flow.");
|
||||||
|
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: `${config.kratos.browser}/self-service/registration/browser`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, data } = await kratos.getSelfServiceRegistrationFlow(flow);
|
||||||
|
|
||||||
|
if (status === 200) return { props: { flow: data } };
|
||||||
|
|
||||||
|
throw new Error(`Failed to retrieve flow ${flow} with code ${status}`);
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: `${config.kratos.browser}/self-service/registration/browser`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldProps = {
|
||||||
|
"traits.email": {
|
||||||
|
label: "Email address",
|
||||||
|
autoComplete: "email",
|
||||||
|
position: 0,
|
||||||
|
},
|
||||||
|
"traits.name.first": {
|
||||||
|
label: "First name",
|
||||||
|
autoComplete: "given-name",
|
||||||
|
position: 1,
|
||||||
|
},
|
||||||
|
"traits.name.last": {
|
||||||
|
label: "Last name",
|
||||||
|
autoComplete: "family-name",
|
||||||
|
position: 2,
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
label: "Password",
|
||||||
|
autoComplete: "new-password",
|
||||||
|
position: 4,
|
||||||
|
},
|
||||||
|
csrf_token: {
|
||||||
|
position: 99,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Registration({ flow }) {
|
||||||
|
const fields = flow.methods.password.config.fields
|
||||||
|
.map((field) => ({
|
||||||
|
...field,
|
||||||
|
...fieldProps[field.name],
|
||||||
|
}))
|
||||||
|
.sort((a, b) => (a.position < b.position ? -1 : 1));
|
||||||
|
|
||||||
|
console.log(flow);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||||
|
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="169"
|
||||||
|
height="39"
|
||||||
|
viewBox="0 0 169 39"
|
||||||
|
className="mx-auto h-12 w-auto"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M160.701 31.245c-2.736 0-4.788-.706-6.156-2.118-1.369-1.411-2.053-3.424-2.053-6.039V2.674h7.487v6.33H169v6.15h-9.02v7.355c0 1.753.886 2.63 2.66 2.63h6.134v6.106h-8.073zm-24.942 0c-1.744 0-3.352-.268-4.826-.803-1.473-.535-2.751-1.292-3.833-2.273a10.275 10.275 0 01-2.526-3.521c-.602-1.367-.902-2.882-.902-4.546 0-1.635.3-3.135.902-4.502a10.275 10.275 0 012.526-3.521c1.082-.98 2.36-1.738 3.833-2.273 1.474-.535 3.082-.803 4.826-.803h5.728c1.293 0 2.42.179 3.383.535.962.357 1.767.847 2.413 1.471a5.823 5.823 0 011.443 2.251c.316.877.474 1.835.474 2.875 0 1.961-.571 3.432-1.714 4.412-1.143.981-3.022 1.471-5.638 1.471h-8.028v-4.056h6.45c1.172 0 1.759-.52 1.759-1.56 0-1.1-.572-1.649-1.714-1.649h-4.6c-1.354 0-2.451.46-3.293 1.382-.842.921-1.263 2.228-1.263 3.922s.42 2.964 1.263 3.811c.842.847 1.94 1.27 3.292 1.27h12.268v6.107H135.76zm-22.867 0V19.567c0-2.526-1.308-3.789-3.924-3.789h-7.532v15.467H93.86V9.003h15.29c3.728 0 6.54.922 8.434 2.764 1.894 1.842 2.841 4.457 2.841 7.844v11.634h-7.532zM67.293 39v-6.418h2.39c.963 0 1.715-.193 2.256-.58a4.58 4.58 0 001.308-1.426L62.422 9.003h7.623l6.675 14.263 6.855-14.263h7.668L78.749 33.43c-.48.95-.984 1.775-1.51 2.473a7.85 7.85 0 01-1.782 1.739 7.11 7.11 0 01-2.233 1.025c-.827.223-1.781.334-2.864.334h-3.067zm-14.207-7.755l-7.713-9.316a4.647 4.647 0 01-.54-.914 2.306 2.306 0 01-.181-.913v-.535c0-.654.225-1.278.676-1.872l7.487-8.692h8.48l-9.02 10.787 9.47 11.455h-8.66zm-17.41 0V0h7.532v31.245h-7.532zm-35.315 0v-7.488h21.92c.571 0 1.037-.171 1.398-.513.36-.342.541-.825.541-1.449 0-.624-.18-1.114-.541-1.47-.36-.357-.827-.535-1.398-.535H9.38c-1.353 0-2.608-.216-3.766-.647-1.157-.43-2.15-1.025-2.976-1.782a8.005 8.005 0 01-1.94-2.742C.233 13.55 0 12.361 0 11.054 0 9.746.233 8.55.7 7.466A8.184 8.184 0 012.638 4.68c.826-.773 1.819-1.367 2.976-1.783 1.158-.416 2.413-.624 3.766-.624h22.281v7.444H9.742c-.571 0-1.03.163-1.375.49-.346.327-.52.802-.52 1.426 0 .594.174 1.07.52 1.426.345.357.804.535 1.375.535h12.9c1.383 0 2.646.216 3.788.647a9.097 9.097 0 012.977 1.805 8.038 8.038 0 011.962 2.785c.466 1.085.7 2.266.7 3.544 0 1.337-.234 2.548-.7 3.632a8.267 8.267 0 01-1.962 2.808 8.62 8.62 0 01-2.977 1.806c-1.142.416-2.405.624-3.788.624H.36z"
|
||||||
|
fill="#57B560"
|
||||||
|
fillRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">Sign up for a new account</h2>
|
||||||
|
<p className="mt-2 text-center text-sm text-gray-600 max-w">
|
||||||
|
or{" "}
|
||||||
|
<Link href="/auth/login">
|
||||||
|
<a className="font-medium text-green-600 hover:text-green-500">sign in</a>
|
||||||
|
</Link>{" "}
|
||||||
|
if you already have one
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
|
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||||
|
<form
|
||||||
|
className="space-y-6"
|
||||||
|
action={flow.methods.password.config.action}
|
||||||
|
method={flow.methods.password.config.method}
|
||||||
|
>
|
||||||
|
{fields.map((field) => (
|
||||||
|
<div key={field.name}>
|
||||||
|
{field.type !== "hidden" && (
|
||||||
|
<label htmlFor={field.name} className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
{fieldProps[field.name]?.label ?? field.name}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
type={field.type}
|
||||||
|
autoComplete={fieldProps[field.name]}
|
||||||
|
required={field.required}
|
||||||
|
value={field.value || undefined}
|
||||||
|
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-green-500 focus:border-green-500 sm:text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
|
||||||
|
>
|
||||||
|
Sign up
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import Layout from "../src/components/Layout";
|
||||||
|
import Table from "../src/components/Table";
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{ skylink: "PAL0w4SdA5rFCDGEutgpeQ50Om-YkBabtXVOJAkmedslKw", size: "1 KB", date: "today" },
|
||||||
|
{ skylink: "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg", size: "102 MB", date: "today" },
|
||||||
|
{ skylink: "IADUs8d9CQjUO34LmdaaNPK_STuZo24rpKVfYW3wPPM2uQ", size: "141 KB", date: "today" },
|
||||||
|
{ skylink: "_A2zt5SKoqwnnZU4cBF8uBycSKULXMyeg1c5ZISBr2Q3dA", size: "1 KB", date: "today" },
|
||||||
|
{ skylink: "AAC0uO43g64ULpyrW0zO3bjEknSFbAhm8c-RFP21EQlmSQ", size: "0 KB", date: "today" },
|
||||||
|
{ skylink: "CACqf4NlIMlA0CCCieYGjpViPGyfyJ4v1x3bmuCKZX8FKA", size: "1.3 MB", date: "today" },
|
||||||
|
];
|
||||||
|
const headers = [
|
||||||
|
{ key: "skylink", name: "Skylink" },
|
||||||
|
{ key: "size", name: "Size" },
|
||||||
|
{ key: "date", name: "Access time" },
|
||||||
|
];
|
||||||
|
const actions = [];
|
||||||
|
|
||||||
|
export default function Downloads() {
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout title="Your downloads">
|
||||||
|
<Table data={data} headers={headers} actions={actions} page={page} setPage={setPage} />
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Layout from "../src/components/Layout";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return <Layout title="Dashboard"></Layout>;
|
||||||
|
}
|
|
@ -0,0 +1,401 @@
|
||||||
|
import Layout from "../src/components/Layout";
|
||||||
|
|
||||||
|
export default function Payments() {
|
||||||
|
return (
|
||||||
|
<Layout title="Payments">
|
||||||
|
<div className="bg-white rounded-lg shadow px-5 py-6 sm:px-6">
|
||||||
|
{/* This example requires Tailwind CSS v2.0+ */}
|
||||||
|
<dl className="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||||
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
<div className="px-4 py-5 sm:p-6">
|
||||||
|
<dt className="text-sm font-medium text-gray-500 truncate">Current plan</dt>
|
||||||
|
<dd className="mt-1 text-3xl font-semibold text-gray-900">Free Plan</dd>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
<div className="px-4 py-5 sm:p-6">
|
||||||
|
<dt className="text-sm font-medium text-gray-500 truncate">Next invoice</dt>
|
||||||
|
<dd className="mt-1 text-3xl font-semibold text-gray-900">-</dd>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
<div className="px-4 py-5 sm:p-6">
|
||||||
|
<dt className="text-sm font-medium text-gray-500 truncate">Plan usage this month</dt>
|
||||||
|
<dd className="mt-1 text-3xl font-semibold text-gray-900">24.57%</dd>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
{/* This example requires Tailwind CSS v2.0+ */}
|
||||||
|
<div className="bg-white">
|
||||||
|
<div className="max-w-7xl mx-auto py-24 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="sm:flex sm:flex-col sm:align-center">
|
||||||
|
<h1 className="text-5xl font-extrabold text-gray-900 sm:text-center">Pricing Plans</h1>
|
||||||
|
<p className="mt-5 text-xl text-gray-500 sm:text-center">
|
||||||
|
Start building for free, then add a site plan to go live. Account plans unlock additional features.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-12 space-y-4 sm:mt-16 sm:space-y-0 sm:grid sm:grid-cols-2 sm:gap-6 lg:max-w-4xl lg:mx-auto xl:max-w-none xl:mx-0 xl:grid-cols-4">
|
||||||
|
<div className="border border-gray-200 rounded-lg shadow-sm divide-y divide-gray-200">
|
||||||
|
<div className="p-6">
|
||||||
|
<h2 className="text-lg leading-6 font-medium text-gray-900">Hobby</h2>
|
||||||
|
<p className="mt-4 text-sm text-gray-500">All the basics for starting a new business</p>
|
||||||
|
<p className="mt-8">
|
||||||
|
<span className="text-4xl font-extrabold text-gray-900">$12</span>
|
||||||
|
<span className="text-base font-medium text-gray-500">/mo</span>
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="mt-8 block w-full bg-gray-800 border border-gray-800 rounded-md py-2 text-sm font-semibold text-white text-center hover:bg-gray-900"
|
||||||
|
>
|
||||||
|
Buy Hobby
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="pt-6 pb-8 px-6">
|
||||||
|
<h3 className="text-xs font-medium text-gray-900 tracking-wide uppercase">What's included</h3>
|
||||||
|
<ul className="mt-6 space-y-4">
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Potenti felis, in cras at at ligula nunc.</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Orci neque eget pellentesque.</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border border-gray-200 rounded-lg shadow-sm divide-y divide-gray-200">
|
||||||
|
<div className="p-6">
|
||||||
|
<h2 className="text-lg leading-6 font-medium text-gray-900">Freelancer</h2>
|
||||||
|
<p className="mt-4 text-sm text-gray-500">All the basics for starting a new business</p>
|
||||||
|
<p className="mt-8">
|
||||||
|
<span className="text-4xl font-extrabold text-gray-900">$24</span>
|
||||||
|
<span className="text-base font-medium text-gray-500">/mo</span>
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="mt-8 block w-full bg-gray-800 border border-gray-800 rounded-md py-2 text-sm font-semibold text-white text-center hover:bg-gray-900"
|
||||||
|
>
|
||||||
|
Buy Freelancer
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="pt-6 pb-8 px-6">
|
||||||
|
<h3 className="text-xs font-medium text-gray-900 tracking-wide uppercase">What's included</h3>
|
||||||
|
<ul className="mt-6 space-y-4">
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Potenti felis, in cras at at ligula nunc. </span>
|
||||||
|
</li>
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Orci neque eget pellentesque.</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Donec mauris sit in eu tincidunt etiam.</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border border-gray-200 rounded-lg shadow-sm divide-y divide-gray-200">
|
||||||
|
<div className="p-6">
|
||||||
|
<h2 className="text-lg leading-6 font-medium text-gray-900">Startup</h2>
|
||||||
|
<p className="mt-4 text-sm text-gray-500">All the basics for starting a new business</p>
|
||||||
|
<p className="mt-8">
|
||||||
|
<span className="text-4xl font-extrabold text-gray-900">$32</span>
|
||||||
|
<span className="text-base font-medium text-gray-500">/mo</span>
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="mt-8 block w-full bg-gray-800 border border-gray-800 rounded-md py-2 text-sm font-semibold text-white text-center hover:bg-gray-900"
|
||||||
|
>
|
||||||
|
Buy Startup
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="pt-6 pb-8 px-6">
|
||||||
|
<h3 className="text-xs font-medium text-gray-900 tracking-wide uppercase">What's included</h3>
|
||||||
|
<ul className="mt-6 space-y-4">
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Potenti felis, in cras at at ligula nunc. </span>
|
||||||
|
</li>
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Orci neque eget pellentesque.</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Donec mauris sit in eu tincidunt etiam.</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Faucibus volutpat magna.</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border border-gray-200 rounded-lg shadow-sm divide-y divide-gray-200">
|
||||||
|
<div className="p-6">
|
||||||
|
<h2 className="text-lg leading-6 font-medium text-gray-900">Enterprise</h2>
|
||||||
|
<p className="mt-4 text-sm text-gray-500">All the basics for starting a new business</p>
|
||||||
|
<p className="mt-8">
|
||||||
|
<span className="text-4xl font-extrabold text-gray-900">$48</span>
|
||||||
|
<span className="text-base font-medium text-gray-500">/mo</span>
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="mt-8 block w-full bg-gray-800 border border-gray-800 rounded-md py-2 text-sm font-semibold text-white text-center hover:bg-gray-900"
|
||||||
|
>
|
||||||
|
Buy Enterprise
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="pt-6 pb-8 px-6">
|
||||||
|
<h3 className="text-xs font-medium text-gray-900 tracking-wide uppercase">What's included</h3>
|
||||||
|
<ul className="mt-6 space-y-4">
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Potenti felis, in cras at at ligula nunc. </span>
|
||||||
|
</li>
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Orci neque eget pellentesque.</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Donec mauris sit in eu tincidunt etiam.</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Faucibus volutpat magna.</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Id sed tellus in varius quisque.</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Risus egestas faucibus.</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex space-x-3">
|
||||||
|
{/* Heroicon name: check */}
|
||||||
|
<svg
|
||||||
|
className="flex-shrink-0 h-5 w-5 text-green-500"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-sm text-gray-500">Risus cursus ullamcorper.</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import Layout from "../src/components/Layout";
|
||||||
|
import Table from "../src/components/Table";
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{ skylink: "PAL0w4SdA5rFCDGEutgpeQ50Om-YkBabtXVOJAkmedslKw", size: "1 KB", date: "today" },
|
||||||
|
{ skylink: "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg", size: "102 MB", date: "today" },
|
||||||
|
{ skylink: "IADUs8d9CQjUO34LmdaaNPK_STuZo24rpKVfYW3wPPM2uQ", size: "141 KB", date: "today" },
|
||||||
|
{ skylink: "_A2zt5SKoqwnnZU4cBF8uBycSKULXMyeg1c5ZISBr2Q3dA", size: "1 KB", date: "today" },
|
||||||
|
{ skylink: "AAC0uO43g64ULpyrW0zO3bjEknSFbAhm8c-RFP21EQlmSQ", size: "0 KB", date: "today" },
|
||||||
|
{ skylink: "CACqf4NlIMlA0CCCieYGjpViPGyfyJ4v1x3bmuCKZX8FKA", size: "1.3 MB", date: "today" },
|
||||||
|
];
|
||||||
|
const headers = [
|
||||||
|
{ key: "skylink", name: "Skylink" },
|
||||||
|
{ key: "size", name: "Size" },
|
||||||
|
{ key: "date", name: "Uploaded on" },
|
||||||
|
];
|
||||||
|
const actions = [{ key: "delete", name: "Delete", action: () => undefined }];
|
||||||
|
|
||||||
|
export default function Uploads() {
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout title="Your uploads">
|
||||||
|
<Table data={data} headers={headers} actions={actions} page={page} setPage={setPage} />
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,225 @@
|
||||||
|
import Link from "next/link";
|
||||||
|
import Head from "next/head";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export default function Layout({ title, children }) {
|
||||||
|
const [menuOpen, openMenu] = useState(false);
|
||||||
|
const [avatarDropdownOpen, openAvatarDropdown] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Head>
|
||||||
|
<title>Skynet - {title}</title>
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
<div className="bg-gray-800 pb-32">
|
||||||
|
<nav className="bg-gray-800">
|
||||||
|
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
|
<div className="border-b border-gray-700">
|
||||||
|
<div className="flex items-center justify-between h-16 px-4 sm:px-0">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 66 66"
|
||||||
|
width={33}
|
||||||
|
height={33}
|
||||||
|
className="mx-auto h-12 w-auto"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M52 52V33.287C52 22.784 43.599 14.052 33.096 14 22.544 13.948 13.948 22.543 14 33.096 14.052 43.599 22.784 52 33.287 52H52zM33 1c17.673 0 32 14.326 32 32v32H33C15.326 65 1 50.673 1 33 1 15.326 15.326 1 33 1z"
|
||||||
|
fillRule="nonzero"
|
||||||
|
stroke="green"
|
||||||
|
strokeWidth="2"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="hidden md:block">
|
||||||
|
<div className="ml-10 flex items-baseline space-x-4">
|
||||||
|
{/* Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" */}
|
||||||
|
<Link href="/">
|
||||||
|
<a className="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
||||||
|
Dashboard
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
<Link href="/uploads">
|
||||||
|
<a className="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
||||||
|
Your uploads
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
<Link href="/downloads">
|
||||||
|
<a className="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
||||||
|
Your downloads
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="hidden md:block">
|
||||||
|
<div className="ml-4 flex items-center md:ml-6">
|
||||||
|
{/* Profile dropdown */}
|
||||||
|
<div className="ml-3 relative">
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
className="max-w-xs bg-gray-800 rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white"
|
||||||
|
id="user-menu"
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={() => openAvatarDropdown(!avatarDropdownOpen)}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Open user menu</span>
|
||||||
|
<span className="inline-block h-8 w-8 rounded-full overflow-hidden bg-gray-100">
|
||||||
|
<svg className="h-full w-full text-gray-300" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/*
|
||||||
|
Profile dropdown panel, show/hide based on dropdown state.
|
||||||
|
|
||||||
|
Entering: "transition ease-out duration-100"
|
||||||
|
From: "transform opacity-0 scale-95"
|
||||||
|
To: "transform opacity-100 scale-100"
|
||||||
|
Leaving: "transition ease-in duration-75"
|
||||||
|
From: "transform opacity-100 scale-100"
|
||||||
|
To: "transform opacity-0 scale-95"
|
||||||
|
*/}
|
||||||
|
{avatarDropdownOpen && (
|
||||||
|
<div
|
||||||
|
className="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5"
|
||||||
|
role="menu"
|
||||||
|
aria-orientation="vertical"
|
||||||
|
aria-labelledby="user-menu"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
||||||
|
role="menuitem"
|
||||||
|
>
|
||||||
|
Settings (coming soon)
|
||||||
|
</a>
|
||||||
|
<Link href="/payments">
|
||||||
|
<a className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">
|
||||||
|
Payments
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
||||||
|
role="menuitem"
|
||||||
|
>
|
||||||
|
Sign out
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="-mr-2 flex md:hidden">
|
||||||
|
{/* Mobile menu button */}
|
||||||
|
<button
|
||||||
|
className="bg-gray-800 inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white"
|
||||||
|
onClick={() => openMenu(!menuOpen)}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Open main menu</span>
|
||||||
|
<svg
|
||||||
|
className={`${menuOpen ? "hidden" : "block"} h-6 w-6`}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
className={`${menuOpen ? "block" : "hidden"} h-6 w-6`}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`${menuOpen ? "block" : "hidden"} border-b border-gray-700 md:hidden`}>
|
||||||
|
<div className="px-2 py-3 space-y-1 sm:px-3">
|
||||||
|
{/* Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" */}
|
||||||
|
<a href="#" className="bg-gray-900 text-white block px-3 py-2 rounded-md text-base font-medium">
|
||||||
|
Dashboard
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
|
||||||
|
>
|
||||||
|
Your uploads (coming soon)
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
|
||||||
|
>
|
||||||
|
Your downloads (coming soon)
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="pt-4 pb-3 border-t border-gray-700">
|
||||||
|
<div className="flex items-center px-5">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<span className="inline-block h-10 w-10 rounded-full overflow-hidden bg-gray-100">
|
||||||
|
<svg className="h-full w-full text-gray-300" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3">
|
||||||
|
<div className="text-base font-medium leading-none text-white">John Doe</div>
|
||||||
|
<div className="text-sm font-medium leading-none text-gray-400">john@example.com</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 px-2 space-y-1">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:text-white hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
Settings (coming soon)
|
||||||
|
</a>
|
||||||
|
<Link href="/payments">
|
||||||
|
<a className="block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:text-white hover:bg-gray-700">
|
||||||
|
Payments
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:text-white hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
Sign out
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<header className="py-10">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<h1 className="text-3xl font-bold text-white">{title}</h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
<main className="-mt-32">
|
||||||
|
<div className="max-w-7xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">
|
||||||
|
{/* Replace with your content */}
|
||||||
|
{children || (
|
||||||
|
<div className="bg-white rounded-lg shadow px-5 py-6 sm:px-6">
|
||||||
|
<div className="border-4 border-dashed border-gray-200 rounded-lg h-96" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* /End replace */}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
const rowsPerPage = 10;
|
||||||
|
|
||||||
|
export default function Table({ data, headers, actions, page, setPage }) {
|
||||||
|
const lastPage = Math.ceil(data.length / rowsPerPage);
|
||||||
|
|
||||||
|
if (page > lastPage) setPage(lastPage);
|
||||||
|
|
||||||
|
const dataSlice = data.slice(rowsPerPage * (page - 1), rowsPerPage * (page - 1) + rowsPerPage);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||||
|
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
|
||||||
|
<div className="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
|
||||||
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
{headers.map(({ key, name }) => (
|
||||||
|
<th
|
||||||
|
key={key}
|
||||||
|
scope="col"
|
||||||
|
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
{actions.map(({ key, name }) => (
|
||||||
|
<th key={key} scope="col" className="relative px-6 py-3">
|
||||||
|
<span className="sr-only">{name}</span>
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{dataSlice.map((row, index) => (
|
||||||
|
<tr className={index % 2 ? "bg-white" : "bg-gray-50"} key={index}>
|
||||||
|
{headers.map(({ key, name }) => (
|
||||||
|
<td key={key} className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||||
|
{row[key] || "-"}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
{actions.map(({ key, name, action }) => (
|
||||||
|
<td key={key} className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||||
|
<a href="#" className="text-indigo-600 hover:text-indigo-900" onClick={action}>
|
||||||
|
{name}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{/* This example requires Tailwind CSS v2.0+ */}
|
||||||
|
<nav
|
||||||
|
className="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6"
|
||||||
|
aria-label="Pagination"
|
||||||
|
>
|
||||||
|
<div className="hidden sm:block">
|
||||||
|
<p className="text-sm text-gray-700">
|
||||||
|
Showing <span className="font-medium">{rowsPerPage * (page - 1) + 1}</span> to{" "}
|
||||||
|
<span className="font-medium">
|
||||||
|
{rowsPerPage * page > data.length ? data.length : rowsPerPage * page}
|
||||||
|
</span>{" "}
|
||||||
|
of <span className="font-medium">{data.length}</span> results
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 flex justify-between sm:justify-end">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
||||||
|
onClick={() => (page > 1 ? setPage(page - 1) : undefined)}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
||||||
|
onClick={() => (page < lastPage ? setPage(page + 1) : undefined)}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
export const SECURITY_MODE_STANDALONE = "cookie";
|
||||||
|
export const SECURITY_MODE_JWT = "jwt";
|
||||||
|
|
||||||
|
const baseUrl = process.env.BASE_URL || "/";
|
||||||
|
|
||||||
|
let securityMode = SECURITY_MODE_STANDALONE;
|
||||||
|
let browserUrl = process.env.KRATOS_BROWSER_URL || "https://secure.siasky.xyz/.ory/kratos/public";
|
||||||
|
let publicUrl = process.env.KRATOS_PUBLIC_URL || "https://secure.siasky.xyz/.ory/kratos/public";
|
||||||
|
switch ((process.env.SECURITY_MODE || "").toLowerCase()) {
|
||||||
|
case "jwt":
|
||||||
|
case "oathkeeper":
|
||||||
|
securityMode = SECURITY_MODE_JWT;
|
||||||
|
break;
|
||||||
|
case "cookie":
|
||||||
|
case "standalone":
|
||||||
|
default:
|
||||||
|
securityMode = SECURITY_MODE_STANDALONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
kratos: {
|
||||||
|
browser: browserUrl.replace(/\/+$/, ""),
|
||||||
|
admin: (process.env.KRATOS_ADMIN_URL || "").replace(/\/+$/, ""),
|
||||||
|
public: publicUrl.replace(/\/+$/, ""),
|
||||||
|
},
|
||||||
|
baseUrl,
|
||||||
|
jwksUrl: process.env.JWKS_URL || "/",
|
||||||
|
projectName: process.env.PROJECT_NAME || "SecureApp",
|
||||||
|
|
||||||
|
securityMode,
|
||||||
|
SECURITY_MODE_JWT,
|
||||||
|
SECURITY_MODE_STANDALONE,
|
||||||
|
|
||||||
|
https: {
|
||||||
|
enabled: process.env.hasOwnProperty("TLS_KEY_PATH") && process.env.hasOwnProperty("TLS_CERT_PATH"),
|
||||||
|
certificatePath: process.env.TLS_CERT_PATH || "",
|
||||||
|
keyPath: process.env.TLS_KEY_PATH || "",
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,122 @@
|
||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
padding: 5rem 0;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
|
border-top: 1px solid #eaeaea;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer img {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title a {
|
||||||
|
color: #0070f3;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title a:hover,
|
||||||
|
.title a:focus,
|
||||||
|
.title a:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.15;
|
||||||
|
font-size: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title,
|
||||||
|
.description {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New,
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
max-width: 800px;
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin: 1rem;
|
||||||
|
flex-basis: 45%;
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-align: left;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid #eaeaea;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: color 0.15s ease, border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover,
|
||||||
|
.card:focus,
|
||||||
|
.card:active {
|
||||||
|
color: #0070f3;
|
||||||
|
border-color: #0070f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h3 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.grid {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans,
|
||||||
|
Helvetica Neue, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
module.exports = {
|
||||||
|
purge: ["./pages/**/*.js", "./components/**/*.js"],
|
||||||
|
darkMode: false, // or 'media' or 'class'
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
Reference in New Issue